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    credits: Credits,
244
245    // If a server address was provided via cli argument we hide the server list button and replace
246    // the server field with a plain label (with a button to exit this mode and freely edit the
247    // field).
248    server_field_locked: bool,
249    selected_server_index: Option<usize>,
250    login_info: LoginInfo,
251
252    show: Showing,
253    selected_language_index: Option<usize>,
254
255    time: f64,
256
257    screen: Screen,
258}
259
260#[derive(Clone)]
261enum Message {
262    Quit,
263    Back,
264    ShowServers,
265    ShowCredits,
266    #[cfg(feature = "singleplayer")]
267    Singleplayer,
268    #[cfg(feature = "singleplayer")]
269    SingleplayerPlay,
270    #[cfg(feature = "singleplayer")]
271    WorldChanged(WorldsChange),
272    #[cfg(feature = "singleplayer")]
273    WorldCancelConfirmation,
274    #[cfg(feature = "singleplayer")]
275    WorldConfirmation(world_selector::Confirmation),
276    Multiplayer,
277    UnlockServerField,
278    LanguageChanged(usize),
279    OpenLanguageMenu,
280    Username(String),
281    Password(String),
282    Server(String),
283    ServerChanged(usize),
284    FocusPassword,
285    CancelConnect,
286    TrustPromptAdd,
287    TrustPromptCancel,
288    CloseError,
289    DeleteServer,
290    /* Note: Keeping in case we re-add the disclaimer
291     *AcceptDisclaimer, */
292}
293
294impl Controls {
295    fn new(
296        fonts: Fonts,
297        imgs: Imgs,
298        bg_img: widget::image::Handle,
299        i18n: LocalizationHandle,
300        settings: &Settings,
301        server: Option<String>,
302    ) -> Self {
303        let version = format!("Veloren {}", *common::util::DISPLAY_VERSION);
304
305        let credits = Ron::<Credits>::load_expect_cloned("credits").into_inner();
306
307        // Note: Keeping in case we re-add the disclaimer
308        let screen = /* if settings.show_disclaimer {
309            Screen::Disclaimer {
310                screen: disclaimer::Screen::new(),
311            }
312        } else { */
313            Screen::Login {
314                screen: Box::default(),
315                error: None,
316            };
317        //};
318
319        let server_field_locked = server.is_some();
320        let login_info = LoginInfo {
321            username: settings.networking.username.clone(),
322            password: String::new(),
323            server: server.unwrap_or_else(|| settings.networking.default_server.clone()),
324        };
325        let selected_server_index = settings
326            .networking
327            .servers
328            .iter()
329            .position(|f| f == &login_info.server);
330
331        let language_metadatas = i18n::list_localizations();
332        let selected_language_index = language_metadatas
333            .iter()
334            .position(|f| f.language_identifier == settings.language.selected_language);
335
336        Self {
337            fonts,
338            imgs,
339            bg_img,
340            i18n,
341            version,
342            credits,
343
344            server_field_locked,
345            selected_server_index,
346            login_info,
347
348            show: Showing::Login,
349            selected_language_index,
350
351            time: 0.0,
352
353            screen,
354        }
355    }
356
357    fn view(
358        &mut self,
359        settings: &Settings,
360        dt: f32,
361        #[cfg(feature = "singleplayer")] worlds: &crate::singleplayer::SingleplayerWorlds,
362    ) -> Element<'_, Message> {
363        self.time += dt as f64;
364
365        // TODO: consider setting this as the default in the renderer
366        let button_style = style::button::Style::new(self.imgs.button)
367            .hover_image(self.imgs.button_hover)
368            .press_image(self.imgs.button_press)
369            .text_color(TEXT_COLOR)
370            .disabled_text_color(DISABLED_TEXT_COLOR);
371
372        let version = iced::Text::new(&self.version)
373            .size(self.fonts.cyri.scale(12))
374            .width(Length::Fill)
375            .horizontal_alignment(HorizontalAlignment::Center);
376
377        let top_text = Row::with_children(vec![
378            Space::new(Length::Fill, Length::Shrink).into(),
379            version.into(),
380            Space::new(Length::Fill, Length::Shrink).into(),
381        ])
382        .padding(3)
383        .width(Length::Fill);
384
385        let bg_img = if matches!(&self.screen, Screen::Connecting { .. }) {
386            self.bg_img
387        } else {
388            self.imgs.bg
389        };
390
391        let language_metadatas = i18n::list_localizations();
392
393        // TODO: make any large text blocks scrollable so that if the area is to
394        // small they can still be read
395        let content = match &mut self.screen {
396            // Note: Keeping in case we re-add the disclaimer
397            //Screen::Disclaimer { screen } => screen.view(&self.fonts, &self.i18n, button_style),
398            Screen::Credits { screen } => {
399                screen.view(&self.fonts, &self.i18n.read(), &self.credits, button_style)
400            },
401            Screen::Login { screen, error } => screen.view(
402                &self.fonts,
403                &self.imgs,
404                self.server_field_locked,
405                &self.login_info,
406                error.as_deref(),
407                &self.i18n.read(),
408                &self.show,
409                self.selected_language_index,
410                &language_metadatas,
411                button_style,
412            ),
413            Screen::Servers { screen } => screen.view(
414                &self.fonts,
415                &self.imgs,
416                &settings.networking.servers,
417                self.selected_server_index,
418                &self.i18n.read(),
419                button_style,
420            ),
421            Screen::Connecting {
422                screen,
423                connection_state,
424                init_stage,
425            } => screen.view(
426                &self.fonts,
427                &self.imgs,
428                connection_state,
429                init_stage,
430                self.time,
431                &self.i18n.read(),
432                button_style,
433                settings.interface.loading_tips,
434                &settings.controls,
435            ),
436            #[cfg(feature = "singleplayer")]
437            Screen::WorldSelector { screen } => screen.view(
438                &self.fonts,
439                &self.imgs,
440                worlds,
441                &self.i18n.read(),
442                button_style,
443            ),
444        };
445
446        Container::new(
447            Column::with_children(vec![top_text.into(), content])
448                .spacing(3)
449                .width(Length::Fill)
450                .height(Length::Fill),
451        )
452        .style(style::container::Style::image(bg_img))
453        .into()
454    }
455
456    fn update(
457        &mut self,
458        message: Message,
459        events: &mut Vec<Event>,
460        settings: &Settings,
461        ui: &mut Ui,
462    ) {
463        let servers = &settings.networking.servers;
464        let mut language_metadatas = i18n::list_localizations();
465
466        match message {
467            Message::Quit => events.push(Event::Quit),
468            Message::Back => {
469                self.screen = Screen::Login {
470                    screen: Box::default(),
471                    error: None,
472                };
473            },
474            Message::ShowServers => {
475                if matches!(&self.screen, Screen::Login { .. }) {
476                    self.selected_server_index =
477                        servers.iter().position(|f| f == &self.login_info.server);
478                    self.screen = Screen::Servers {
479                        screen: servers::Screen::new(),
480                    };
481                }
482            },
483            Message::ShowCredits => {
484                self.screen = Screen::Credits {
485                    screen: credits::Screen::new(),
486                };
487            },
488            #[cfg(feature = "singleplayer")]
489            Message::Singleplayer => {
490                self.screen = Screen::WorldSelector {
491                    screen: world_selector::Screen::default(),
492                };
493                events.push(Event::InitSingleplayer);
494            },
495            #[cfg(feature = "singleplayer")]
496            Message::SingleplayerPlay => {
497                self.screen = Screen::Connecting {
498                    screen: connecting::Screen::new(ui),
499                    connection_state: ConnectionState::InProgress,
500                    init_stage: DetailedInitializationStage::Singleplayer,
501                };
502                events.push(Event::StartSingleplayer);
503            },
504            #[cfg(feature = "singleplayer")]
505            Message::WorldChanged(change) => {
506                match change {
507                    WorldsChange::Delete(_) | WorldsChange::Regenerate(_) => {
508                        if let Screen::WorldSelector {
509                            screen: world_selector::Screen { confirmation, .. },
510                        } = &mut self.screen
511                        {
512                            *confirmation = None;
513                        }
514                    },
515                    _ => {},
516                }
517                events.push(Event::SinglePlayerChange(change))
518            },
519            #[cfg(feature = "singleplayer")]
520            Message::WorldCancelConfirmation => {
521                if let Screen::WorldSelector {
522                    screen: world_selector::Screen { confirmation, .. },
523                } = &mut self.screen
524                {
525                    *confirmation = None;
526                }
527            },
528            #[cfg(feature = "singleplayer")]
529            Message::WorldConfirmation(new_confirmation) => {
530                if let Screen::WorldSelector {
531                    screen: world_selector::Screen { confirmation, .. },
532                } = &mut self.screen
533                {
534                    *confirmation = Some(new_confirmation);
535                }
536            },
537            Message::Multiplayer => {
538                self.screen = Screen::Connecting {
539                    screen: connecting::Screen::new(ui),
540                    connection_state: ConnectionState::InProgress,
541                    init_stage: DetailedInitializationStage::StartingMultiplayer,
542                };
543
544                events.push(Event::LoginAttempt {
545                    username: self.login_info.username.trim().to_string(),
546                    password: self.login_info.password.clone(),
547                    server_address: self.login_info.server.trim().to_string(),
548                });
549            },
550            Message::UnlockServerField => self.server_field_locked = false,
551            Message::Username(new_value) => self.login_info.username = new_value,
552            Message::LanguageChanged(new_value) => {
553                events.push(Event::ChangeLanguage(language_metadatas.remove(new_value)));
554            },
555            Message::OpenLanguageMenu => self.show.toggle(Showing::Languages),
556            Message::Password(new_value) => self.login_info.password = new_value,
557            Message::Server(new_value) => {
558                self.login_info.server = new_value;
559            },
560            Message::ServerChanged(new_value) => {
561                self.selected_server_index = Some(new_value);
562                self.login_info.server.clone_from(&servers[new_value]);
563            },
564            Message::FocusPassword => {
565                if let Screen::Login { screen, .. } = &mut self.screen {
566                    screen.banner.password = text_input::State::focused();
567                    screen.banner.username = text_input::State::new();
568                }
569            },
570            Message::CancelConnect => {
571                self.exit_connect_screen();
572                events.push(Event::CancelLoginAttempt);
573            },
574            msg @ Message::TrustPromptAdd | msg @ Message::TrustPromptCancel => {
575                if let Screen::Connecting {
576                    connection_state, ..
577                } = &mut self.screen
578                    && let ConnectionState::AuthTrustPrompt { auth_server, .. } = connection_state
579                {
580                    let auth_server = std::mem::take(auth_server);
581                    let added = matches!(msg, Message::TrustPromptAdd);
582
583                    *connection_state = ConnectionState::InProgress;
584                    events.push(Event::AuthServerTrust(auth_server, added));
585                }
586            },
587            Message::CloseError => {
588                if let Screen::Login { error, .. } = &mut self.screen {
589                    *error = None;
590                }
591            },
592            Message::DeleteServer => {
593                if let Some(server_index) = self.selected_server_index {
594                    events.push(Event::DeleteServer { server_index });
595                    self.selected_server_index = None;
596                }
597            },
598            /* Note: Keeping in case we re-add the disclaimer */
599            /*Message::AcceptDisclaimer => {
600                if let Screen::Disclaimer { .. } = &self.screen {
601                    events.push(Event::DisclaimerAccepted);
602                    self.screen = Screen::Login {
603                        screen: login::Screen::default(),
604                        error: None,
605                    };
606                }
607            },*/
608        }
609    }
610
611    // Connection successful of failed
612    fn exit_connect_screen(&mut self) {
613        if matches!(&self.screen, Screen::Connecting { .. }) {
614            self.screen = Screen::Login {
615                screen: Box::default(),
616                error: None,
617            }
618        }
619    }
620
621    fn auth_trust_prompt(&mut self, auth_server: String) {
622        if let Screen::Connecting {
623            connection_state, ..
624        } = &mut self.screen
625        {
626            let msg = format!(
627                "Warning: The server you are trying to connect to has provided this \
628                 authentication server address:\n\n{}\n\nbut it is not in your list of trusted \
629                 authentication servers.\n\nMake sure that you trust this site and owner to not \
630                 try and bruteforce your password!",
631                &auth_server
632            );
633
634            *connection_state = ConnectionState::AuthTrustPrompt { auth_server, msg };
635        }
636    }
637
638    fn connection_error(&mut self, error: String) {
639        if matches!(&self.screen, Screen::Connecting { .. })
640            || matches!(&self.screen, Screen::Login { .. })
641        {
642            self.screen = Screen::Login {
643                screen: Box::default(),
644                error: Some(error),
645            }
646        } else {
647            warn!("connection_error invoked on unhandled screen!");
648        }
649    }
650
651    fn update_init_stage(&mut self, stage: DetailedInitializationStage) {
652        if let Screen::Connecting { init_stage, .. } = &mut self.screen {
653            *init_stage = stage
654        }
655    }
656
657    fn tab(&mut self) {
658        if let Screen::Login { screen, .. } = &mut self.screen {
659            // TODO: add select all function in iced
660            if screen.banner.username.is_focused() {
661                screen.banner.username = text_input::State::new();
662                screen.banner.password = text_input::State::focused();
663                screen.banner.password.move_cursor_to_end();
664            } else if screen.banner.password.is_focused() {
665                screen.banner.password = text_input::State::new();
666                // Skip focusing server field if it isn't editable!
667                if self.server_field_locked {
668                    screen.banner.username = text_input::State::focused();
669                } else {
670                    screen.banner.server = text_input::State::focused();
671                }
672                screen.banner.server.move_cursor_to_end();
673            } else if screen.banner.server.is_focused() {
674                screen.banner.server = text_input::State::new();
675                screen.banner.username = text_input::State::focused();
676                screen.banner.username.move_cursor_to_end();
677            } else {
678                screen.banner.username = text_input::State::focused();
679                screen.banner.username.move_cursor_to_end();
680            }
681        }
682    }
683}
684
685pub struct MainMenuUi {
686    ui: Ui,
687    // TODO: re add this
688    // tip_no: u16,
689    controls: Controls,
690    bg_img_spec: &'static str,
691}
692
693impl MainMenuUi {
694    pub fn new(global_state: &mut GlobalState) -> Self {
695        // Load language
696        let i18n = &global_state.i18n.read();
697        // TODO: don't add default font twice
698        let font = load_font(&i18n.fonts().get("cyri").unwrap().asset_key);
699
700        let mut ui = Ui::new(
701            &mut global_state.window,
702            font,
703            global_state.settings.interface.ui_scale,
704        )
705        .unwrap();
706
707        let fonts = Fonts::load(i18n.fonts(), &mut ui).expect("Impossible to load fonts");
708
709        let bg_img_spec = rand_bg_image_spec();
710
711        let bg_img = Image::load_expect(bg_img_spec).read().to_image();
712        let controls = Controls::new(
713            fonts,
714            Imgs::load(&mut ui).expect("Failed to load images"),
715            ui.add_graphic(Graphic::Image(bg_img, None)),
716            global_state.i18n,
717            &global_state.settings,
718            global_state.args.server.clone(),
719        );
720
721        Self {
722            ui,
723            controls,
724            bg_img_spec,
725        }
726    }
727
728    pub fn bg_img_spec(&self) -> &'static str { self.bg_img_spec }
729
730    pub fn update_language(&mut self, i18n: LocalizationHandle, settings: &Settings) {
731        self.controls.i18n = i18n;
732        let i18n = &i18n.read();
733        let font = load_font(&i18n.fonts().get("cyri").unwrap().asset_key);
734        self.ui.clear_fonts(font);
735        self.controls.fonts =
736            Fonts::load(i18n.fonts(), &mut self.ui).expect("Impossible to load fonts!");
737        let language_metadatas = i18n::list_localizations();
738        self.controls.selected_language_index = language_metadatas
739            .iter()
740            .position(|f| f.language_identifier == settings.language.selected_language);
741    }
742
743    pub fn auth_trust_prompt(&mut self, auth_server: String) {
744        self.controls.auth_trust_prompt(auth_server);
745    }
746
747    pub fn show_info(&mut self, msg: String) { self.controls.connection_error(msg); }
748
749    pub fn update_stage(&mut self, stage: DetailedInitializationStage) {
750        tracing::trace!(?stage, "Updating stage");
751        self.controls.update_init_stage(stage);
752    }
753
754    pub fn connected(&mut self) { self.controls.exit_connect_screen(); }
755
756    pub fn cancel_connection(&mut self) { self.controls.exit_connect_screen(); }
757
758    pub fn handle_event(&mut self, event: window::Event) -> bool {
759        match event {
760            // Pass events to ui.
761            window::Event::IcedUi(event) => {
762                self.handle_ui_event(event);
763                true
764            },
765            window::Event::ScaleFactorChanged(s) => {
766                self.ui.scale_factor_changed(s);
767                false
768            },
769            _ => false,
770        }
771    }
772
773    pub fn handle_ui_event(&mut self, event: ui::ice::Event) {
774        // Tab for input fields
775        use iced::keyboard;
776        if matches!(
777            &event,
778            iced::Event::Keyboard(keyboard::Event::KeyPressed {
779                key_code: keyboard::KeyCode::Tab,
780                ..
781            })
782        ) {
783            self.controls.tab();
784        }
785
786        self.ui.handle_event(event);
787    }
788
789    pub fn set_scale_mode(&mut self, scale_mode: ui::ScaleMode) {
790        self.ui.set_scaling_mode(scale_mode);
791    }
792
793    pub fn maintain(&mut self, global_state: &mut GlobalState, dt: Duration) -> Vec<Event> {
794        let mut events = Vec::new();
795
796        #[cfg(feature = "singleplayer")]
797        let worlds_default = crate::singleplayer::SingleplayerWorlds::default();
798        #[cfg(feature = "singleplayer")]
799        let worlds = global_state
800            .singleplayer
801            .as_init()
802            .unwrap_or(&worlds_default);
803
804        let (messages, _) = self.ui.maintain(
805            self.controls.view(
806                &global_state.settings,
807                dt.as_secs_f32(),
808                #[cfg(feature = "singleplayer")]
809                worlds,
810            ),
811            global_state.window.renderer_mut(),
812            None,
813            &mut global_state.clipboard,
814        );
815
816        messages.into_iter().for_each(|message| {
817            self.controls
818                .update(message, &mut events, &global_state.settings, &mut self.ui)
819        });
820
821        events
822    }
823
824    pub fn render<'a>(&'a self, drawer: &mut UiDrawer<'_, 'a>) { self.ui.render(drawer); }
825}
826
827pub fn rand_bg_image_spec() -> &'static str { BG_IMGS.choose(&mut rng()).unwrap() }