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