veloren_voxygen/menu/main/ui/
mod.rs

1mod connecting;
2// Note: Keeping in case we re-add the disclaimer
3//mod disclaimer;
4mod credits;
5mod login;
6mod servers;
7#[cfg(feature = "singleplayer")]
8mod world_selector;
9
10use crate::{
11    GlobalState,
12    credits::Credits,
13    render::UiDrawer,
14    ui::{
15        self, Graphic,
16        fonts::IcedFonts as Fonts,
17        ice::{Element, IcedUi as Ui, load_font, style, widget},
18        img_ids::ImageGraphic,
19    },
20    window,
21};
22use i18n::{LanguageMetadata, LocalizationHandle};
23use iced::{Column, Container, HorizontalAlignment, Length, Row, Space, text_input};
24//ImageFrame, Tooltip,
25use crate::settings::Settings;
26use common::assets::{self, AssetExt};
27use rand::{seq::SliceRandom, thread_rng};
28use std::time::Duration;
29use tracing::warn;
30
31use super::DetailedInitializationStage;
32
33// TODO: what is this? (showed up in rebase)
34//const COL1: Color = Color::Rgba(0.07, 0.1, 0.1, 0.9);
35
36pub const TEXT_COLOR: iced::Color = iced::Color::from_rgb(1.0, 1.0, 1.0);
37pub const DISABLED_TEXT_COLOR: iced::Color = iced::Color::from_rgba(1.0, 1.0, 1.0, 0.2);
38
39pub const FILL_FRAC_ONE: f32 = 0.67;
40pub const FILL_FRAC_TWO: f32 = 0.53;
41
42image_ids_ice! {
43    struct Imgs {
44        <ImageGraphic>
45        v_logo: "voxygen.element.v_logo",
46        bg: "voxygen.background.bg_main",
47        banner_top: "voxygen.element.ui.generic.frames.banner_top",
48        banner_gradient_bottom: "voxygen.element.ui.generic.frames.banner_gradient_bottom",
49        button: "voxygen.element.ui.generic.buttons.button",
50        button_hover: "voxygen.element.ui.generic.buttons.button_hover",
51        button_press: "voxygen.element.ui.generic.buttons.button_press",
52        input_bg: "voxygen.element.ui.generic.textbox",
53        loading_art: "voxygen.element.ui.generic.frames.loading_screen.loading_bg",
54        loading_art_l: "voxygen.element.ui.generic.frames.loading_screen.loading_bg_l",
55        loading_art_r: "voxygen.element.ui.generic.frames.loading_screen.loading_bg_r",
56        selection: "voxygen.element.ui.generic.frames.selection",
57        selection_hover: "voxygen.element.ui.generic.frames.selection_hover",
58        selection_press: "voxygen.element.ui.generic.frames.selection_press",
59
60        #[cfg(feature = "singleplayer")]
61        slider_range: "voxygen.element.ui.generic.slider.track",
62        #[cfg(feature = "singleplayer")]
63        slider_indicator: "voxygen.element.ui.generic.slider.indicator",
64
65        unlock: "voxygen.element.ui.generic.buttons.unlock",
66        unlock_hover: "voxygen.element.ui.generic.buttons.unlock_hover",
67        unlock_press: "voxygen.element.ui.generic.buttons.unlock_press",
68    }
69}
70
71// Randomly loaded background images
72const BG_IMGS: [&str; 41] = [
73    "voxygen.background.bg_1",
74    "voxygen.background.bg_2",
75    "voxygen.background.bg_3",
76    "voxygen.background.bg_4",
77    "voxygen.background.bg_5",
78    "voxygen.background.bg_6",
79    "voxygen.background.bg_7",
80    "voxygen.background.bg_8",
81    "voxygen.background.bg_9",
82    "voxygen.background.bg_10",
83    "voxygen.background.bg_11",
84    "voxygen.background.bg_12",
85    "voxygen.background.bg_13",
86    "voxygen.background.bg_14",
87    "voxygen.background.bg_15",
88    "voxygen.background.bg_16",
89    "voxygen.background.bg_17",
90    "voxygen.background.bg_18",
91    "voxygen.background.bg_19",
92    "voxygen.background.bg_20",
93    "voxygen.background.bg_21",
94    "voxygen.background.bg_22",
95    "voxygen.background.bg_23",
96    "voxygen.background.bg_24",
97    "voxygen.background.bg_25",
98    "voxygen.background.bg_26",
99    "voxygen.background.bg_27",
100    "voxygen.background.bg_28",
101    "voxygen.background.bg_29",
102    "voxygen.background.bg_30",
103    "voxygen.background.bg_31",
104    "voxygen.background.bg_32",
105    "voxygen.background.bg_33",
106    "voxygen.background.bg_34",
107    "voxygen.background.bg_35",
108    "voxygen.background.bg_36",
109    "voxygen.background.bg_37",
110    "voxygen.background.bg_38",
111    "voxygen.background.bg_39",
112    "voxygen.background.bg_40",
113    "voxygen.background.bg_41",
114];
115
116#[cfg(feature = "singleplayer")]
117#[derive(Clone)]
118pub enum WorldChange {
119    Name(String),
120    Seed(u32),
121    DayLength(f64),
122    SizeX(u32),
123    SizeY(u32),
124    Scale(f64),
125    MapKind(common::resources::MapKind),
126    ErosionQuality(f32),
127    DefaultGenOps,
128}
129
130#[cfg(feature = "singleplayer")]
131impl WorldChange {
132    pub fn apply(self, world: &mut crate::singleplayer::SingleplayerWorld) {
133        let mut def = Default::default();
134        let gen_opts = world.gen_opts.as_mut().unwrap_or(&mut def);
135        match self {
136            WorldChange::Name(name) => world.name = name,
137            WorldChange::Seed(seed) => world.seed = seed,
138            WorldChange::DayLength(d) => world.day_length = d,
139            WorldChange::SizeX(s) => gen_opts.x_lg = s,
140            WorldChange::SizeY(s) => gen_opts.y_lg = s,
141            WorldChange::Scale(scale) => gen_opts.scale = scale,
142            WorldChange::MapKind(kind) => gen_opts.map_kind = kind,
143            WorldChange::ErosionQuality(q) => gen_opts.erosion_quality = q,
144            WorldChange::DefaultGenOps => world.gen_opts = Some(Default::default()),
145        }
146    }
147}
148
149#[cfg(feature = "singleplayer")]
150#[derive(Clone)]
151pub enum WorldsChange {
152    SetActive(Option<usize>),
153    Delete(usize),
154    Regenerate(usize),
155    AddNew,
156    CurrentWorldChange(WorldChange),
157}
158
159pub enum Event {
160    LoginAttempt {
161        username: String,
162        password: String,
163        server_address: String,
164    },
165    CancelLoginAttempt,
166    ChangeLanguage(LanguageMetadata),
167    #[cfg(feature = "singleplayer")]
168    StartSingleplayer,
169    #[cfg(feature = "singleplayer")]
170    InitSingleplayer,
171    #[cfg(feature = "singleplayer")]
172    SinglePlayerChange(WorldsChange),
173    Quit,
174    // Note: Keeping in case we re-add the disclaimer
175    //DisclaimerAccepted,
176    AuthServerTrust(String, bool),
177    DeleteServer {
178        server_index: usize,
179    },
180}
181
182pub struct LoginInfo {
183    pub username: String,
184    pub password: String,
185    pub server: String,
186}
187
188enum ConnectionState {
189    InProgress,
190    AuthTrustPrompt { auth_server: String, msg: String },
191}
192
193enum Screen {
194    // Note: Keeping in case we re-add the disclaimer
195    /*Disclaimer {
196        screen: disclaimer::Screen,
197    },*/
198    Credits {
199        screen: credits::Screen,
200    },
201    Login {
202        screen: Box<login::Screen>, // boxed to avoid large variant
203        // Error to display in a box
204        error: Option<String>,
205    },
206    Servers {
207        screen: servers::Screen,
208    },
209    Connecting {
210        screen: connecting::Screen,
211        connection_state: ConnectionState,
212        init_stage: DetailedInitializationStage,
213    },
214    #[cfg(feature = "singleplayer")]
215    WorldSelector {
216        screen: world_selector::Screen,
217    },
218}
219
220#[derive(PartialEq, Eq)]
221enum Showing {
222    Login,
223    Languages,
224}
225
226impl Showing {
227    fn toggle(&mut self, other: Showing) {
228        if *self == other {
229            *self = Showing::Login;
230        } else {
231            *self = other;
232        }
233    }
234}
235
236pub struct Controls {
237    fonts: Fonts,
238    imgs: Imgs,
239    bg_img: widget::image::Handle,
240    i18n: LocalizationHandle,
241    // Voxygen version
242    version: String,
243    // Alpha disclaimer
244    alpha: String,
245    credits: Credits,
246
247    // If a server address was provided via cli argument we hide the server list button and replace
248    // the server field with a plain label (with a button to exit this mode and freely edit the
249    // field).
250    server_field_locked: bool,
251    selected_server_index: Option<usize>,
252    login_info: LoginInfo,
253
254    show: Showing,
255    selected_language_index: Option<usize>,
256
257    time: f64,
258
259    screen: Screen,
260}
261
262#[derive(Clone)]
263enum Message {
264    Quit,
265    Back,
266    ShowServers,
267    ShowCredits,
268    #[cfg(feature = "singleplayer")]
269    Singleplayer,
270    #[cfg(feature = "singleplayer")]
271    SingleplayerPlay,
272    #[cfg(feature = "singleplayer")]
273    WorldChanged(WorldsChange),
274    #[cfg(feature = "singleplayer")]
275    WorldCancelConfirmation,
276    #[cfg(feature = "singleplayer")]
277    WorldConfirmation(world_selector::Confirmation),
278    Multiplayer,
279    UnlockServerField,
280    LanguageChanged(usize),
281    OpenLanguageMenu,
282    Username(String),
283    Password(String),
284    Server(String),
285    ServerChanged(usize),
286    FocusPassword,
287    CancelConnect,
288    TrustPromptAdd,
289    TrustPromptCancel,
290    CloseError,
291    DeleteServer,
292    /* Note: Keeping in case we re-add the disclaimer
293     *AcceptDisclaimer, */
294}
295
296impl Controls {
297    fn new(
298        fonts: Fonts,
299        imgs: Imgs,
300        bg_img: widget::image::Handle,
301        i18n: LocalizationHandle,
302        settings: &Settings,
303        server: Option<String>,
304    ) -> Self {
305        let version = common::util::DISPLAY_VERSION_LONG.clone();
306        let alpha = format!("Veloren {}", common::util::DISPLAY_VERSION.as_str());
307
308        let credits = Credits::load_expect_cloned("credits");
309
310        // Note: Keeping in case we re-add the disclaimer
311        let screen = /* if settings.show_disclaimer {
312            Screen::Disclaimer {
313                screen: disclaimer::Screen::new(),
314            }
315        } else { */
316            Screen::Login {
317                screen: Box::default(),
318                error: None,
319            };
320        //};
321
322        let server_field_locked = server.is_some();
323        let login_info = LoginInfo {
324            username: settings.networking.username.clone(),
325            password: String::new(),
326            server: server.unwrap_or_else(|| settings.networking.default_server.clone()),
327        };
328        let selected_server_index = settings
329            .networking
330            .servers
331            .iter()
332            .position(|f| f == &login_info.server);
333
334        let language_metadatas = i18n::list_localizations();
335        let selected_language_index = language_metadatas
336            .iter()
337            .position(|f| f.language_identifier == settings.language.selected_language);
338
339        Self {
340            fonts,
341            imgs,
342            bg_img,
343            i18n,
344            version,
345            alpha,
346            credits,
347
348            server_field_locked,
349            selected_server_index,
350            login_info,
351
352            show: Showing::Login,
353            selected_language_index,
354
355            time: 0.0,
356
357            screen,
358        }
359    }
360
361    fn view(
362        &mut self,
363        settings: &Settings,
364        dt: f32,
365        #[cfg(feature = "singleplayer")] worlds: &crate::singleplayer::SingleplayerWorlds,
366    ) -> Element<Message> {
367        self.time += dt as f64;
368
369        // TODO: consider setting this as the default in the renderer
370        let button_style = style::button::Style::new(self.imgs.button)
371            .hover_image(self.imgs.button_hover)
372            .press_image(self.imgs.button_press)
373            .text_color(TEXT_COLOR)
374            .disabled_text_color(DISABLED_TEXT_COLOR);
375
376        let alpha = iced::Text::new(&self.alpha)
377            .size(self.fonts.cyri.scale(12))
378            .width(Length::Fill)
379            .horizontal_alignment(HorizontalAlignment::Center);
380
381        let top_text = Row::with_children(vec![
382            Space::new(Length::Fill, Length::Shrink).into(),
383            alpha.into(),
384            if matches!(&self.screen, Screen::Login { .. }) {
385                // Login screen shows the Velroen logo over the version
386                Space::new(Length::Fill, Length::Shrink).into()
387            } else {
388                iced::Text::new(&self.version)
389                    .size(self.fonts.cyri.scale(15))
390                    .width(Length::Fill)
391                    .horizontal_alignment(HorizontalAlignment::Right)
392                    .into()
393            },
394        ])
395        .padding(3)
396        .width(Length::Fill);
397
398        let bg_img = if matches!(&self.screen, Screen::Connecting { .. }) {
399            self.bg_img
400        } else {
401            self.imgs.bg
402        };
403
404        let language_metadatas = i18n::list_localizations();
405
406        // TODO: make any large text blocks scrollable so that if the area is to
407        // small they can still be read
408        let content = match &mut self.screen {
409            // Note: Keeping in case we re-add the disclaimer
410            //Screen::Disclaimer { screen } => screen.view(&self.fonts, &self.i18n, button_style),
411            Screen::Credits { screen } => {
412                screen.view(&self.fonts, &self.i18n.read(), &self.credits, button_style)
413            },
414            Screen::Login { screen, error } => screen.view(
415                &self.fonts,
416                &self.imgs,
417                self.server_field_locked,
418                &self.login_info,
419                error.as_deref(),
420                &self.i18n.read(),
421                &self.show,
422                self.selected_language_index,
423                &language_metadatas,
424                button_style,
425                &self.version,
426            ),
427            Screen::Servers { screen } => screen.view(
428                &self.fonts,
429                &self.imgs,
430                &settings.networking.servers,
431                self.selected_server_index,
432                &self.i18n.read(),
433                button_style,
434            ),
435            Screen::Connecting {
436                screen,
437                connection_state,
438                init_stage,
439            } => screen.view(
440                &self.fonts,
441                &self.imgs,
442                connection_state,
443                init_stage,
444                self.time,
445                &self.i18n.read(),
446                button_style,
447                settings.interface.loading_tips,
448                &settings.controls,
449            ),
450            #[cfg(feature = "singleplayer")]
451            Screen::WorldSelector { screen } => screen.view(
452                &self.fonts,
453                &self.imgs,
454                worlds,
455                &self.i18n.read(),
456                button_style,
457            ),
458        };
459
460        Container::new(
461            Column::with_children(vec![top_text.into(), content])
462                .spacing(3)
463                .width(Length::Fill)
464                .height(Length::Fill),
465        )
466        .style(style::container::Style::image(bg_img))
467        .into()
468    }
469
470    fn update(
471        &mut self,
472        message: Message,
473        events: &mut Vec<Event>,
474        settings: &Settings,
475        ui: &mut Ui,
476    ) {
477        let servers = &settings.networking.servers;
478        let mut language_metadatas = i18n::list_localizations();
479
480        match message {
481            Message::Quit => events.push(Event::Quit),
482            Message::Back => {
483                self.screen = Screen::Login {
484                    screen: Box::default(),
485                    error: None,
486                };
487            },
488            Message::ShowServers => {
489                if matches!(&self.screen, Screen::Login { .. }) {
490                    self.selected_server_index =
491                        servers.iter().position(|f| f == &self.login_info.server);
492                    self.screen = Screen::Servers {
493                        screen: servers::Screen::new(),
494                    };
495                }
496            },
497            Message::ShowCredits => {
498                self.screen = Screen::Credits {
499                    screen: credits::Screen::new(),
500                };
501            },
502            #[cfg(feature = "singleplayer")]
503            Message::Singleplayer => {
504                self.screen = Screen::WorldSelector {
505                    screen: world_selector::Screen::default(),
506                };
507                events.push(Event::InitSingleplayer);
508            },
509            #[cfg(feature = "singleplayer")]
510            Message::SingleplayerPlay => {
511                self.screen = Screen::Connecting {
512                    screen: connecting::Screen::new(ui),
513                    connection_state: ConnectionState::InProgress,
514                    init_stage: DetailedInitializationStage::Singleplayer,
515                };
516                events.push(Event::StartSingleplayer);
517            },
518            #[cfg(feature = "singleplayer")]
519            Message::WorldChanged(change) => {
520                match change {
521                    WorldsChange::Delete(_) | WorldsChange::Regenerate(_) => {
522                        if let Screen::WorldSelector {
523                            screen: world_selector::Screen { confirmation, .. },
524                        } = &mut self.screen
525                        {
526                            *confirmation = None;
527                        }
528                    },
529                    _ => {},
530                }
531                events.push(Event::SinglePlayerChange(change))
532            },
533            #[cfg(feature = "singleplayer")]
534            Message::WorldCancelConfirmation => {
535                if let Screen::WorldSelector {
536                    screen: world_selector::Screen { confirmation, .. },
537                } = &mut self.screen
538                {
539                    *confirmation = None;
540                }
541            },
542            #[cfg(feature = "singleplayer")]
543            Message::WorldConfirmation(new_confirmation) => {
544                if let Screen::WorldSelector {
545                    screen: world_selector::Screen { confirmation, .. },
546                } = &mut self.screen
547                {
548                    *confirmation = Some(new_confirmation);
549                }
550            },
551            Message::Multiplayer => {
552                self.screen = Screen::Connecting {
553                    screen: connecting::Screen::new(ui),
554                    connection_state: ConnectionState::InProgress,
555                    init_stage: DetailedInitializationStage::StartingMultiplayer,
556                };
557
558                events.push(Event::LoginAttempt {
559                    username: self.login_info.username.trim().to_string(),
560                    password: self.login_info.password.clone(),
561                    server_address: self.login_info.server.trim().to_string(),
562                });
563            },
564            Message::UnlockServerField => self.server_field_locked = false,
565            Message::Username(new_value) => self.login_info.username = new_value,
566            Message::LanguageChanged(new_value) => {
567                events.push(Event::ChangeLanguage(language_metadatas.remove(new_value)));
568            },
569            Message::OpenLanguageMenu => self.show.toggle(Showing::Languages),
570            Message::Password(new_value) => self.login_info.password = new_value,
571            Message::Server(new_value) => {
572                self.login_info.server = new_value;
573            },
574            Message::ServerChanged(new_value) => {
575                self.selected_server_index = Some(new_value);
576                self.login_info.server.clone_from(&servers[new_value]);
577            },
578            Message::FocusPassword => {
579                if let Screen::Login { screen, .. } = &mut self.screen {
580                    screen.banner.password = text_input::State::focused();
581                    screen.banner.username = text_input::State::new();
582                }
583            },
584            Message::CancelConnect => {
585                self.exit_connect_screen();
586                events.push(Event::CancelLoginAttempt);
587            },
588            msg @ Message::TrustPromptAdd | msg @ Message::TrustPromptCancel => {
589                if let Screen::Connecting {
590                    connection_state, ..
591                } = &mut self.screen
592                {
593                    if let ConnectionState::AuthTrustPrompt { auth_server, .. } = connection_state {
594                        let auth_server = std::mem::take(auth_server);
595                        let added = matches!(msg, Message::TrustPromptAdd);
596
597                        *connection_state = ConnectionState::InProgress;
598                        events.push(Event::AuthServerTrust(auth_server, added));
599                    }
600                }
601            },
602            Message::CloseError => {
603                if let Screen::Login { error, .. } = &mut self.screen {
604                    *error = None;
605                }
606            },
607            Message::DeleteServer => {
608                if let Some(server_index) = self.selected_server_index {
609                    events.push(Event::DeleteServer { server_index });
610                    self.selected_server_index = None;
611                }
612            },
613            /* Note: Keeping in case we re-add the disclaimer */
614            /*Message::AcceptDisclaimer => {
615                if let Screen::Disclaimer { .. } = &self.screen {
616                    events.push(Event::DisclaimerAccepted);
617                    self.screen = Screen::Login {
618                        screen: login::Screen::default(),
619                        error: None,
620                    };
621                }
622            },*/
623        }
624    }
625
626    // Connection successful of failed
627    fn exit_connect_screen(&mut self) {
628        if matches!(&self.screen, Screen::Connecting { .. }) {
629            self.screen = Screen::Login {
630                screen: Box::default(),
631                error: None,
632            }
633        }
634    }
635
636    fn auth_trust_prompt(&mut self, auth_server: String) {
637        if let Screen::Connecting {
638            connection_state, ..
639        } = &mut self.screen
640        {
641            let msg = format!(
642                "Warning: The server you are trying to connect to has provided this \
643                 authentication server address:\n\n{}\n\nbut it is not in your list of trusted \
644                 authentication servers.\n\nMake sure that you trust this site and owner to not \
645                 try and bruteforce your password!",
646                &auth_server
647            );
648
649            *connection_state = ConnectionState::AuthTrustPrompt { auth_server, msg };
650        }
651    }
652
653    fn connection_error(&mut self, error: String) {
654        if matches!(&self.screen, Screen::Connecting { .. })
655            || matches!(&self.screen, Screen::Login { .. })
656        {
657            self.screen = Screen::Login {
658                screen: Box::default(),
659                error: Some(error),
660            }
661        } else {
662            warn!("connection_error invoked on unhandled screen!");
663        }
664    }
665
666    fn update_init_stage(&mut self, stage: DetailedInitializationStage) {
667        if let Screen::Connecting { init_stage, .. } = &mut self.screen {
668            *init_stage = stage
669        }
670    }
671
672    fn tab(&mut self) {
673        if let Screen::Login { screen, .. } = &mut self.screen {
674            // TODO: add select all function in iced
675            if screen.banner.username.is_focused() {
676                screen.banner.username = text_input::State::new();
677                screen.banner.password = text_input::State::focused();
678                screen.banner.password.move_cursor_to_end();
679            } else if screen.banner.password.is_focused() {
680                screen.banner.password = text_input::State::new();
681                // Skip focusing server field if it isn't editable!
682                if self.server_field_locked {
683                    screen.banner.username = text_input::State::focused();
684                } else {
685                    screen.banner.server = text_input::State::focused();
686                }
687                screen.banner.server.move_cursor_to_end();
688            } else if screen.banner.server.is_focused() {
689                screen.banner.server = text_input::State::new();
690                screen.banner.username = text_input::State::focused();
691                screen.banner.username.move_cursor_to_end();
692            } else {
693                screen.banner.username = text_input::State::focused();
694                screen.banner.username.move_cursor_to_end();
695            }
696        }
697    }
698}
699
700pub struct MainMenuUi {
701    ui: Ui,
702    // TODO: re add this
703    // tip_no: u16,
704    controls: Controls,
705    bg_img_spec: &'static str,
706}
707
708impl MainMenuUi {
709    pub fn new(global_state: &mut GlobalState) -> Self {
710        // Load language
711        let i18n = &global_state.i18n.read();
712        // TODO: don't add default font twice
713        let font = load_font(&i18n.fonts().get("cyri").unwrap().asset_key);
714
715        let mut ui = Ui::new(
716            &mut global_state.window,
717            font,
718            global_state.settings.interface.ui_scale,
719        )
720        .unwrap();
721
722        let fonts = Fonts::load(i18n.fonts(), &mut ui).expect("Impossible to load fonts");
723
724        let bg_img_spec = rand_bg_image_spec();
725
726        let bg_img = assets::Image::load_expect(bg_img_spec).read().to_image();
727        let controls = Controls::new(
728            fonts,
729            Imgs::load(&mut ui).expect("Failed to load images"),
730            ui.add_graphic(Graphic::Image(bg_img, None)),
731            global_state.i18n,
732            &global_state.settings,
733            global_state.args.server.clone(),
734        );
735
736        Self {
737            ui,
738            controls,
739            bg_img_spec,
740        }
741    }
742
743    pub fn bg_img_spec(&self) -> &'static str { self.bg_img_spec }
744
745    pub fn update_language(&mut self, i18n: LocalizationHandle, settings: &Settings) {
746        self.controls.i18n = i18n;
747        let i18n = &i18n.read();
748        let font = load_font(&i18n.fonts().get("cyri").unwrap().asset_key);
749        self.ui.clear_fonts(font);
750        self.controls.fonts =
751            Fonts::load(i18n.fonts(), &mut self.ui).expect("Impossible to load fonts!");
752        let language_metadatas = i18n::list_localizations();
753        self.controls.selected_language_index = language_metadatas
754            .iter()
755            .position(|f| f.language_identifier == settings.language.selected_language);
756    }
757
758    pub fn auth_trust_prompt(&mut self, auth_server: String) {
759        self.controls.auth_trust_prompt(auth_server);
760    }
761
762    pub fn show_info(&mut self, msg: String) { self.controls.connection_error(msg); }
763
764    pub fn update_stage(&mut self, stage: DetailedInitializationStage) {
765        tracing::trace!(?stage, "Updating stage");
766        self.controls.update_init_stage(stage);
767    }
768
769    pub fn connected(&mut self) { self.controls.exit_connect_screen(); }
770
771    pub fn cancel_connection(&mut self) { self.controls.exit_connect_screen(); }
772
773    pub fn handle_event(&mut self, event: window::Event) -> bool {
774        match event {
775            // Pass events to ui.
776            window::Event::IcedUi(event) => {
777                self.handle_ui_event(event);
778                true
779            },
780            window::Event::ScaleFactorChanged(s) => {
781                self.ui.scale_factor_changed(s);
782                false
783            },
784            _ => false,
785        }
786    }
787
788    pub fn handle_ui_event(&mut self, event: ui::ice::Event) {
789        // Tab for input fields
790        use iced::keyboard;
791        if matches!(
792            &event,
793            iced::Event::Keyboard(keyboard::Event::KeyPressed {
794                key_code: keyboard::KeyCode::Tab,
795                ..
796            })
797        ) {
798            self.controls.tab();
799        }
800
801        self.ui.handle_event(event);
802    }
803
804    pub fn set_scale_mode(&mut self, scale_mode: ui::ScaleMode) {
805        self.ui.set_scaling_mode(scale_mode);
806    }
807
808    pub fn maintain(&mut self, global_state: &mut GlobalState, dt: Duration) -> Vec<Event> {
809        let mut events = Vec::new();
810
811        #[cfg(feature = "singleplayer")]
812        let worlds_default = crate::singleplayer::SingleplayerWorlds::default();
813        #[cfg(feature = "singleplayer")]
814        let worlds = global_state
815            .singleplayer
816            .as_init()
817            .unwrap_or(&worlds_default);
818
819        let (messages, _) = self.ui.maintain(
820            self.controls.view(
821                &global_state.settings,
822                dt.as_secs_f32(),
823                #[cfg(feature = "singleplayer")]
824                worlds,
825            ),
826            global_state.window.renderer_mut(),
827            None,
828            &mut global_state.clipboard,
829        );
830
831        messages.into_iter().for_each(|message| {
832            self.controls
833                .update(message, &mut events, &global_state.settings, &mut self.ui)
834        });
835
836        events
837    }
838
839    pub fn render<'a>(&'a self, drawer: &mut UiDrawer<'_, 'a>) { self.ui.render(drawer); }
840}
841
842pub fn rand_bg_image_spec() -> &'static str { BG_IMGS.choose(&mut thread_rng()).unwrap() }