1mod connecting;
2mod 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;
25use 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
34pub 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
72const 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 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 Credits {
173 screen: credits::Screen,
174 },
175 Login {
176 screen: Box<login::Screen>, 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 version: String,
217 alpha: String,
219 credits: Credits,
220
221 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 }
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 let screen = Screen::Login {
291 screen: Box::default(),
292 error: None,
293 };
294 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 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 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 let content = match &mut self.screen {
384 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 }
600 }
601
602 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 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 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 controls: Controls,
681 bg_img_spec: &'static str,
682}
683
684impl MainMenuUi {
685 pub fn new(global_state: &mut GlobalState) -> Self {
686 let i18n = &global_state.i18n.read();
688 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 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 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() }