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::{AssetExt, Image, Ron};
27use rand::{rng, seq::IndexedRandom};
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 = Ron::<Credits>::load_expect_cloned("credits").into_inner();
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                    && let ConnectionState::AuthTrustPrompt { auth_server, .. } = connection_state
593                {
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            Message::CloseError => {
602                if let Screen::Login { error, .. } = &mut self.screen {
603                    *error = None;
604                }
605            },
606            Message::DeleteServer => {
607                if let Some(server_index) = self.selected_server_index {
608                    events.push(Event::DeleteServer { server_index });
609                    self.selected_server_index = None;
610                }
611            },
612            /* Note: Keeping in case we re-add the disclaimer */
613            /*Message::AcceptDisclaimer => {
614                if let Screen::Disclaimer { .. } = &self.screen {
615                    events.push(Event::DisclaimerAccepted);
616                    self.screen = Screen::Login {
617                        screen: login::Screen::default(),
618                        error: None,
619                    };
620                }
621            },*/
622        }
623    }
624
625    // Connection successful of failed
626    fn exit_connect_screen(&mut self) {
627        if matches!(&self.screen, Screen::Connecting { .. }) {
628            self.screen = Screen::Login {
629                screen: Box::default(),
630                error: None,
631            }
632        }
633    }
634
635    fn auth_trust_prompt(&mut self, auth_server: String) {
636        if let Screen::Connecting {
637            connection_state, ..
638        } = &mut self.screen
639        {
640            let msg = format!(
641                "Warning: The server you are trying to connect to has provided this \
642                 authentication server address:\n\n{}\n\nbut it is not in your list of trusted \
643                 authentication servers.\n\nMake sure that you trust this site and owner to not \
644                 try and bruteforce your password!",
645                &auth_server
646            );
647
648            *connection_state = ConnectionState::AuthTrustPrompt { auth_server, msg };
649        }
650    }
651
652    fn connection_error(&mut self, error: String) {
653        if matches!(&self.screen, Screen::Connecting { .. })
654            || matches!(&self.screen, Screen::Login { .. })
655        {
656            self.screen = Screen::Login {
657                screen: Box::default(),
658                error: Some(error),
659            }
660        } else {
661            warn!("connection_error invoked on unhandled screen!");
662        }
663    }
664
665    fn update_init_stage(&mut self, stage: DetailedInitializationStage) {
666        if let Screen::Connecting { init_stage, .. } = &mut self.screen {
667            *init_stage = stage
668        }
669    }
670
671    fn tab(&mut self) {
672        if let Screen::Login { screen, .. } = &mut self.screen {
673            // TODO: add select all function in iced
674            if screen.banner.username.is_focused() {
675                screen.banner.username = text_input::State::new();
676                screen.banner.password = text_input::State::focused();
677                screen.banner.password.move_cursor_to_end();
678            } else if screen.banner.password.is_focused() {
679                screen.banner.password = text_input::State::new();
680                // Skip focusing server field if it isn't editable!
681                if self.server_field_locked {
682                    screen.banner.username = text_input::State::focused();
683                } else {
684                    screen.banner.server = text_input::State::focused();
685                }
686                screen.banner.server.move_cursor_to_end();
687            } else if screen.banner.server.is_focused() {
688                screen.banner.server = text_input::State::new();
689                screen.banner.username = text_input::State::focused();
690                screen.banner.username.move_cursor_to_end();
691            } else {
692                screen.banner.username = text_input::State::focused();
693                screen.banner.username.move_cursor_to_end();
694            }
695        }
696    }
697}
698
699pub struct MainMenuUi {
700    ui: Ui,
701    // TODO: re add this
702    // tip_no: u16,
703    controls: Controls,
704    bg_img_spec: &'static str,
705}
706
707impl MainMenuUi {
708    pub fn new(global_state: &mut GlobalState) -> Self {
709        // Load language
710        let i18n = &global_state.i18n.read();
711        // TODO: don't add default font twice
712        let font = load_font(&i18n.fonts().get("cyri").unwrap().asset_key);
713
714        let mut ui = Ui::new(
715            &mut global_state.window,
716            font,
717            global_state.settings.interface.ui_scale,
718        )
719        .unwrap();
720
721        let fonts = Fonts::load(i18n.fonts(), &mut ui).expect("Impossible to load fonts");
722
723        let bg_img_spec = rand_bg_image_spec();
724
725        let bg_img = Image::load_expect(bg_img_spec).read().to_image();
726        let controls = Controls::new(
727            fonts,
728            Imgs::load(&mut ui).expect("Failed to load images"),
729            ui.add_graphic(Graphic::Image(bg_img, None)),
730            global_state.i18n,
731            &global_state.settings,
732            global_state.args.server.clone(),
733        );
734
735        Self {
736            ui,
737            controls,
738            bg_img_spec,
739        }
740    }
741
742    pub fn bg_img_spec(&self) -> &'static str { self.bg_img_spec }
743
744    pub fn update_language(&mut self, i18n: LocalizationHandle, settings: &Settings) {
745        self.controls.i18n = i18n;
746        let i18n = &i18n.read();
747        let font = load_font(&i18n.fonts().get("cyri").unwrap().asset_key);
748        self.ui.clear_fonts(font);
749        self.controls.fonts =
750            Fonts::load(i18n.fonts(), &mut self.ui).expect("Impossible to load fonts!");
751        let language_metadatas = i18n::list_localizations();
752        self.controls.selected_language_index = language_metadatas
753            .iter()
754            .position(|f| f.language_identifier == settings.language.selected_language);
755    }
756
757    pub fn auth_trust_prompt(&mut self, auth_server: String) {
758        self.controls.auth_trust_prompt(auth_server);
759    }
760
761    pub fn show_info(&mut self, msg: String) { self.controls.connection_error(msg); }
762
763    pub fn update_stage(&mut self, stage: DetailedInitializationStage) {
764        tracing::trace!(?stage, "Updating stage");
765        self.controls.update_init_stage(stage);
766    }
767
768    pub fn connected(&mut self) { self.controls.exit_connect_screen(); }
769
770    pub fn cancel_connection(&mut self) { self.controls.exit_connect_screen(); }
771
772    pub fn handle_event(&mut self, event: window::Event) -> bool {
773        match event {
774            // Pass events to ui.
775            window::Event::IcedUi(event) => {
776                self.handle_ui_event(event);
777                true
778            },
779            window::Event::ScaleFactorChanged(s) => {
780                self.ui.scale_factor_changed(s);
781                false
782            },
783            _ => false,
784        }
785    }
786
787    pub fn handle_ui_event(&mut self, event: ui::ice::Event) {
788        // Tab for input fields
789        use iced::keyboard;
790        if matches!(
791            &event,
792            iced::Event::Keyboard(keyboard::Event::KeyPressed {
793                key_code: keyboard::KeyCode::Tab,
794                ..
795            })
796        ) {
797            self.controls.tab();
798        }
799
800        self.ui.handle_event(event);
801    }
802
803    pub fn set_scale_mode(&mut self, scale_mode: ui::ScaleMode) {
804        self.ui.set_scaling_mode(scale_mode);
805    }
806
807    pub fn maintain(&mut self, global_state: &mut GlobalState, dt: Duration) -> Vec<Event> {
808        let mut events = Vec::new();
809
810        #[cfg(feature = "singleplayer")]
811        let worlds_default = crate::singleplayer::SingleplayerWorlds::default();
812        #[cfg(feature = "singleplayer")]
813        let worlds = global_state
814            .singleplayer
815            .as_init()
816            .unwrap_or(&worlds_default);
817
818        let (messages, _) = self.ui.maintain(
819            self.controls.view(
820                &global_state.settings,
821                dt.as_secs_f32(),
822                #[cfg(feature = "singleplayer")]
823                worlds,
824            ),
825            global_state.window.renderer_mut(),
826            None,
827            &mut global_state.clipboard,
828        );
829
830        messages.into_iter().for_each(|message| {
831            self.controls
832                .update(message, &mut events, &global_state.settings, &mut self.ui)
833        });
834
835        events
836    }
837
838    pub fn render<'a>(&'a self, drawer: &mut UiDrawer<'_, 'a>) { self.ui.render(drawer); }
839}
840
841pub fn rand_bg_image_spec() -> &'static str { BG_IMGS.choose(&mut rng()).unwrap() }