1mod connecting;
2mod 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};
27use 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
36pub 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
74const 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 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 Credits {
205 screen: credits::Screen,
206 },
207 Login {
208 screen: Box<login::Screen>, 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 version: String,
249 credits: Credits,
250
251 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 }
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 let screen = Screen::Login {
322 screen: Box::default(),
323 error: None,
324 };
325 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 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 let content = match &mut self.screen {
406 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 }
671 }
672
673 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 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 controls: Controls,
747 bg_img_spec: &'static str,
748}
749
750impl MainMenuUi {
751 pub fn new(global_state: &mut GlobalState) -> Self {
752 let i18n = &global_state.i18n.read();
754 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 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 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}