Skip to main content

veloren_voxygen/menu/main/ui/
mod.rs

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