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