From de7c9a9858279f241b96cdb2f4a14e744b47ab8d Mon Sep 17 00:00:00 2001 From: Yusuf Bera Ertan Date: Mon, 20 Jul 2020 17:07:08 +0300 Subject: [PATCH] add language selection menu to main menu screen --- assets/voxygen/i18n/en.ron | 1 + voxygen/src/menu/main/mod.rs | 18 +++- voxygen/src/menu/main/ui/login.rs | 140 ++++++++++++++++++++++++++++-- voxygen/src/menu/main/ui/mod.rs | 33 ++++++- 4 files changed, 181 insertions(+), 11 deletions(-) diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index 2c9c5e3f2d..6501b36463 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -114,6 +114,7 @@ Is the client up to date?"#, "main.connecting": "Connecting", "main.creating_world": "Creating world", "main.tip": "Tip:", + "main.select_language": "Select a language", // Welcome notice that appears the first time Veloren is started "main.notice": r#"Welcome to the alpha version of Veloren! diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index ed94928f19..b26213a3cb 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -5,8 +5,11 @@ use super::char_selection::CharSelectionState; #[cfg(feature = "singleplayer")] use crate::singleplayer::Singleplayer; use crate::{ - render::Renderer, settings::Settings, window::Event, Direction, GlobalState, PlayState, - PlayStateResult, + i18n::{i18n_asset_key, Localization}, + render::Renderer, + settings::Settings, + window::Event, + Direction, GlobalState, PlayState, PlayStateResult, }; use client_init::{ClientInit, Error as InitError, Msg as InitMsg}; use common::{assets::Asset, comp, span}; @@ -47,7 +50,7 @@ impl PlayState for MainMenuState { fn tick(&mut self, global_state: &mut GlobalState, events: Vec) -> PlayStateResult { span!(_guard, "tick", "::tick"); - let localized_strings = crate::i18n::Localization::load_expect( + let mut localized_strings = crate::i18n::Localization::load_expect( &crate::i18n::i18n_asset_key(&global_state.settings.language.selected_language), ); @@ -248,6 +251,15 @@ impl PlayState for MainMenuState { self.client_init = None; self.main_menu_ui.cancel_connection(); }, + MainMenuEvent::ChangeLanguage(new_language) => { + global_state.settings.language.selected_language = + new_language.language_identifier; + localized_strings = Localization::load_expect(&i18n_asset_key( + &global_state.settings.language.selected_language, + )); + localized_strings.log_missing_entries(); + self.main_menu_ui.update_language(localized_strings.clone()); + }, #[cfg(feature = "singleplayer")] MainMenuEvent::StartSingleplayer => { let singleplayer = Singleplayer::new(None); // TODO: Make client and server use the same thread pool diff --git a/voxygen/src/menu/main/ui/login.rs b/voxygen/src/menu/main/ui/login.rs index b64e3bfd8f..494e53561e 100644 --- a/voxygen/src/menu/main/ui/login.rs +++ b/voxygen/src/menu/main/ui/login.rs @@ -14,7 +14,10 @@ use crate::{ }, }, }; -use iced::{button, text_input, Align, Column, Container, Length, Row, Space, Text, TextInput}; +use iced::{ + button, scrollable, text_input, Align, Button, Column, Container, Length, Row, Scrollable, + Space, Text, TextInput, +}; use vek::*; const FILL_FRAC_ONE: f32 = 0.77; @@ -27,10 +30,12 @@ pub struct Screen { quit_button: button::State, settings_button: button::State, servers_button: button::State, + language_select_button: button::State, error_okay_button: button::State, - pub banner: Banner, + pub banner: LoginBanner, + language_selection: LanguageSelectBanner, } impl Screen { @@ -39,10 +44,12 @@ impl Screen { servers_button: Default::default(), settings_button: Default::default(), quit_button: Default::default(), + language_select_button: Default::default(), error_okay_button: Default::default(), - banner: Banner::new(), + banner: LoginBanner::new(), + language_selection: LanguageSelectBanner::new(), } } @@ -53,6 +60,9 @@ impl Screen { login_info: &LoginInfo, error: Option<&str>, i18n: &Localization, + is_selecting_language: bool, + selected_language_index: Option, + language_metadatas: &[crate::i18n::LanguageMetadata], button_style: style::button::Style, ) -> Element { let buttons = Column::with_children(vec![ @@ -77,6 +87,13 @@ impl Screen { button_style, Some(Message::Quit), ), + neat_button( + &mut self.language_select_button, + i18n.get("common.languages"), + FILL_FRAC_ONE, + button_style, + Some(Message::OpenLanguageMenu), + ), ]) .width(Length::Fill) .max_width(200) @@ -140,8 +157,19 @@ impl Screen { .padding(20) .into() } else { - self.banner - .view(fonts, imgs, login_info, i18n, button_style) + if is_selecting_language { + self.language_selection.view( + fonts, + imgs, + i18n, + language_metadatas, + selected_language_index, + button_style, + ) + } else { + self.banner + .view(fonts, imgs, login_info, i18n, button_style) + } }; let central_column = Container::new(central_content) @@ -164,7 +192,105 @@ impl Screen { } } -pub struct Banner { +pub struct LanguageSelectBanner { + okay_button: button::State, + language_buttons: Vec, + + selection_list: scrollable::State, +} + +impl LanguageSelectBanner { + fn new() -> Self { + Self { + okay_button: Default::default(), + language_buttons: Default::default(), + selection_list: Default::default(), + } + } + + fn view( + &mut self, + fonts: &Fonts, + imgs: &Imgs, + i18n: &Localization, + language_metadatas: &[crate::i18n::LanguageMetadata], + selected_language_index: Option, + button_style: style::button::Style, + ) -> Element { + let title = + Container::new(Text::new(i18n.get("main.select_language")).size(fonts.cyri.scale(35))) + .center_x(); + + let mut list = Scrollable::new(&mut self.selection_list) + .height(Length::Fill) + .width(Length::Fill) + .align_items(Align::Start); + + if self.language_buttons.len() != language_metadatas.len() { + self.language_buttons = vec![Default::default(); language_metadatas.len()]; + } + + for (i, (state, lang)) in self + .language_buttons + .iter_mut() + .zip(language_metadatas) + .enumerate() + { + let text = format!( + "{}{}", + if Some(i) == selected_language_index { + "-> " + } else { + " " + }, + lang.language_name, + ); + let button = Button::new( + state, + Container::new(Text::new(text).size(fonts.cyri.scale(25))) + .padding(5) + .center_y(), + ) + .width(Length::Fill) + .on_press(Message::LangaugeChanged(i)); + list = list.push(button); + } + + let okay_button = Container::new(neat_button( + &mut self.okay_button, + i18n.get("common.okay"), + FILL_FRAC_TWO, + button_style, + Some(Message::OpenLanguageMenu), + )) + .center_x() + .max_width(200); + + let content = Column::with_children(vec![title.into(), list.into(), okay_button.into()]) + .spacing(8) + .width(Length::Fill) + .height(Length::FillPortion(38)) + .align_items(Align::Center); + + let selection_menu = BackgroundContainer::new( + CompoundGraphic::from_graphics(vec![ + Graphic::image(imgs.banner_top, [138, 17], [0, 0]), + Graphic::rect(Rgba::new(0, 0, 0, 230), [130, 165], [4, 17]), + // TODO: use non image gradient + Graphic::gradient(Rgba::new(0, 0, 0, 230), Rgba::zero(), [130, 50], [4, 182]), + ]) + .fix_aspect_ratio() + .height(Length::Fill), + content, + ) + .padding(Padding::new().horizontal(8).vertical(15).bottom(50)) + .max_width(350); + + selection_menu.into() + } +} + +pub struct LoginBanner { pub username: text_input::State, pub password: text_input::State, pub server: text_input::State, @@ -174,7 +300,7 @@ pub struct Banner { singleplayer_button: button::State, } -impl Banner { +impl LoginBanner { fn new() -> Self { Self { username: Default::default(), diff --git a/voxygen/src/menu/main/ui/mod.rs b/voxygen/src/menu/main/ui/mod.rs index 0cb9960e76..2b04d69469 100644 --- a/voxygen/src/menu/main/ui/mod.rs +++ b/voxygen/src/menu/main/ui/mod.rs @@ -4,7 +4,7 @@ mod login; mod servers; use crate::{ - i18n::{i18n_asset_key, Localization}, + i18n::{i18n_asset_key, LanguageMetadata, Localization}, render::Renderer, ui::{ self, @@ -78,6 +78,7 @@ pub enum Event { server_address: String, }, CancelLoginAttempt, + ChangeLanguage(LanguageMetadata), #[cfg(feature = "singleplayer")] StartSingleplayer, Quit, @@ -143,6 +144,9 @@ struct Controls { selected_server_index: Option, login_info: LoginInfo, + is_selecting_language: bool, + selected_language_index: Option, + time: f32, screen: Screen, @@ -156,6 +160,8 @@ enum Message { #[cfg(feature = "singleplayer")] Singleplayer, Multiplayer, + LangaugeChanged(usize), + OpenLanguageMenu, Username(String), Password(String), Server(String), @@ -206,6 +212,11 @@ impl Controls { .iter() .position(|f| f == &login_info.server); + let language_metadatas = crate::i18n::list_localizations(); + let selected_language_index = language_metadatas + .iter() + .position(|f| &f.language_identifier == &settings.language.selected_language); + Self { fonts, imgs, @@ -217,6 +228,9 @@ impl Controls { selected_server_index, login_info, + is_selecting_language: false, + selected_language_index, + time: 0.0, screen, @@ -256,6 +270,8 @@ impl Controls { self.imgs.bg }; + let language_metadatas = crate::i18n::list_localizations(); + // TODO: make any large text blocks scrollable so that if the area is to // small they can still be read let content = match &mut self.screen { @@ -266,6 +282,9 @@ impl Controls { &self.login_info, error.as_deref(), &self.i18n, + self.is_selecting_language, + self.selected_language_index, + &language_metadatas, button_style, ), Screen::Servers { screen } => screen.view( @@ -300,6 +319,7 @@ impl Controls { fn update(&mut self, message: Message, events: &mut Vec, settings: &Settings) { let servers = &settings.networking.servers; + let mut language_metadatas = crate::i18n::list_localizations(); match message { Message::Quit => events.push(Event::Quit), @@ -344,6 +364,11 @@ impl Controls { }); }, Message::Username(new_value) => self.login_info.username = new_value, + Message::LangaugeChanged(new_value) => { + self.selected_language_index = Some(new_value); + events.push(Event::ChangeLanguage(language_metadatas.remove(new_value))); + }, + Message::OpenLanguageMenu => self.is_selecting_language = !self.is_selecting_language, Message::Password(new_value) => self.login_info.password = new_value, Message::Server(new_value) => { self.login_info.server = new_value; @@ -506,6 +531,12 @@ impl<'a> MainMenuUi { Self { ui, controls } } + pub fn update_language(&mut self, i18n: std::sync::Arc) { + self.controls.i18n = i18n; + self.controls.fonts = Fonts::load(&self.controls.i18n.fonts, &mut self.ui) + .expect("Impossible to load fonts!"); + } + pub fn auth_trust_prompt(&mut self, auth_server: String) { self.controls.auth_trust_prompt(auth_server); }