mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
607 lines
20 KiB
Rust
607 lines
20 KiB
Rust
mod connecting;
|
|
// Note: Keeping in case we re-add the disclaimer
|
|
//mod disclaimer;
|
|
mod credits;
|
|
mod login;
|
|
mod servers;
|
|
|
|
use crate::{
|
|
credits::Credits,
|
|
render::UiDrawer,
|
|
ui::{
|
|
self,
|
|
fonts::IcedFonts as Fonts,
|
|
ice::{load_font, style, widget, Element, IcedUi as Ui},
|
|
img_ids::ImageGraphic,
|
|
Graphic,
|
|
},
|
|
window, GlobalState,
|
|
};
|
|
use i18n::{LanguageMetadata, LocalizationHandle};
|
|
use iced::{text_input, Column, Container, HorizontalAlignment, Length, Row, Space};
|
|
//ImageFrame, Tooltip,
|
|
use crate::settings::Settings;
|
|
use common::assets::{self, AssetExt};
|
|
use rand::{seq::SliceRandom, thread_rng};
|
|
use std::time::Duration;
|
|
use tracing::warn;
|
|
|
|
// TODO: what is this? (showed up in rebase)
|
|
//const COL1: Color = Color::Rgba(0.07, 0.1, 0.1, 0.9);
|
|
|
|
pub const TEXT_COLOR: iced::Color = iced::Color::from_rgb(1.0, 1.0, 1.0);
|
|
pub const DISABLED_TEXT_COLOR: iced::Color = iced::Color::from_rgba(1.0, 1.0, 1.0, 0.2);
|
|
|
|
pub const FILL_FRAC_ONE: f32 = 0.77;
|
|
pub const FILL_FRAC_TWO: f32 = 0.53;
|
|
|
|
image_ids_ice! {
|
|
struct Imgs {
|
|
<ImageGraphic>
|
|
v_logo: "voxygen.element.v_logo",
|
|
bg: "voxygen.background.bg_main",
|
|
banner_top: "voxygen.element.ui.generic.frames.banner_top",
|
|
banner_gradient_bottom: "voxygen.element.ui.generic.frames.banner_gradient_bottom",
|
|
button: "voxygen.element.ui.generic.buttons.button",
|
|
button_hover: "voxygen.element.ui.generic.buttons.button_hover",
|
|
button_press: "voxygen.element.ui.generic.buttons.button_press",
|
|
input_bg: "voxygen.element.ui.generic.textbox",
|
|
loading_art: "voxygen.element.ui.generic.frames.loading_screen.loading_bg",
|
|
loading_art_l: "voxygen.element.ui.generic.frames.loading_screen.loading_bg_l",
|
|
loading_art_r: "voxygen.element.ui.generic.frames.loading_screen.loading_bg_r",
|
|
selection: "voxygen.element.ui.generic.frames.selection",
|
|
selection_hover: "voxygen.element.ui.generic.frames.selection_hover",
|
|
selection_press: "voxygen.element.ui.generic.frames.selection_press",
|
|
}
|
|
}
|
|
|
|
// Randomly loaded background images
|
|
const BG_IMGS: [&str; 13] = [
|
|
"voxygen.background.bg_1",
|
|
"voxygen.background.bg_2",
|
|
"voxygen.background.bg_3",
|
|
"voxygen.background.bg_4",
|
|
"voxygen.background.bg_5",
|
|
"voxygen.background.bg_6",
|
|
"voxygen.background.bg_7",
|
|
"voxygen.background.bg_8",
|
|
"voxygen.background.bg_9",
|
|
"voxygen.background.bg_10",
|
|
"voxygen.background.bg_11",
|
|
"voxygen.background.bg_12",
|
|
"voxygen.background.bg_13",
|
|
];
|
|
|
|
pub enum Event {
|
|
LoginAttempt {
|
|
username: String,
|
|
password: String,
|
|
server_address: String,
|
|
},
|
|
CancelLoginAttempt,
|
|
ChangeLanguage(LanguageMetadata),
|
|
#[cfg(feature = "singleplayer")]
|
|
StartSingleplayer,
|
|
Quit,
|
|
// Note: Keeping in case we re-add the disclaimer
|
|
//DisclaimerAccepted,
|
|
AuthServerTrust(String, bool),
|
|
}
|
|
|
|
pub struct LoginInfo {
|
|
pub username: String,
|
|
pub password: String,
|
|
pub server: String,
|
|
}
|
|
|
|
enum ConnectionState {
|
|
InProgress,
|
|
AuthTrustPrompt { auth_server: String, msg: String },
|
|
}
|
|
|
|
enum Screen {
|
|
// Note: Keeping in case we re-add the disclaimer
|
|
/*Disclaimer {
|
|
screen: disclaimer::Screen,
|
|
},*/
|
|
Credits {
|
|
screen: credits::Screen,
|
|
},
|
|
Login {
|
|
screen: Box<login::Screen>, // boxed to avoid large variant
|
|
// Error to display in a box
|
|
error: Option<String>,
|
|
},
|
|
Servers {
|
|
screen: servers::Screen,
|
|
},
|
|
Connecting {
|
|
screen: connecting::Screen,
|
|
connection_state: ConnectionState,
|
|
},
|
|
}
|
|
|
|
struct Controls {
|
|
fonts: Fonts,
|
|
imgs: Imgs,
|
|
bg_img: widget::image::Handle,
|
|
i18n: LocalizationHandle,
|
|
// Voxygen version
|
|
version: String,
|
|
// Alpha disclaimer
|
|
alpha: String,
|
|
credits: Credits,
|
|
|
|
selected_server_index: Option<usize>,
|
|
login_info: LoginInfo,
|
|
|
|
is_selecting_language: bool,
|
|
selected_language_index: Option<usize>,
|
|
|
|
time: f64,
|
|
|
|
screen: Screen,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
enum Message {
|
|
Quit,
|
|
Back,
|
|
ShowServers,
|
|
ShowCredits,
|
|
#[cfg(feature = "singleplayer")]
|
|
Singleplayer,
|
|
Multiplayer,
|
|
LanguageChanged(usize),
|
|
OpenLanguageMenu,
|
|
Username(String),
|
|
Password(String),
|
|
Server(String),
|
|
ServerChanged(usize),
|
|
FocusPassword,
|
|
CancelConnect,
|
|
TrustPromptAdd,
|
|
TrustPromptCancel,
|
|
CloseError,
|
|
/* Note: Keeping in case we re-add the disclaimer
|
|
*AcceptDisclaimer, */
|
|
}
|
|
|
|
impl Controls {
|
|
fn new(
|
|
fonts: Fonts,
|
|
imgs: Imgs,
|
|
bg_img: widget::image::Handle,
|
|
i18n: LocalizationHandle,
|
|
settings: &Settings,
|
|
) -> Self {
|
|
let version = common::util::DISPLAY_VERSION_LONG.clone();
|
|
let alpha = format!("Veloren {}", common::util::DISPLAY_VERSION.as_str());
|
|
|
|
let credits = Credits::load_expect_cloned("common.credits");
|
|
|
|
// Note: Keeping in case we re-add the disclaimer
|
|
let screen = /* if settings.show_disclaimer {
|
|
Screen::Disclaimer {
|
|
screen: disclaimer::Screen::new(),
|
|
}
|
|
} else { */
|
|
Screen::Login {
|
|
screen: Box::new(login::Screen::new()),
|
|
error: None,
|
|
};
|
|
//};
|
|
|
|
let login_info = LoginInfo {
|
|
username: settings.networking.username.clone(),
|
|
password: String::new(),
|
|
server: settings.networking.default_server.clone(),
|
|
};
|
|
let selected_server_index = settings
|
|
.networking
|
|
.servers
|
|
.iter()
|
|
.position(|f| f == &login_info.server);
|
|
|
|
let language_metadatas = i18n::list_localizations();
|
|
let selected_language_index = language_metadatas
|
|
.iter()
|
|
.position(|f| f.language_identifier == settings.language.selected_language);
|
|
|
|
Self {
|
|
fonts,
|
|
imgs,
|
|
bg_img,
|
|
i18n,
|
|
version,
|
|
alpha,
|
|
credits,
|
|
|
|
selected_server_index,
|
|
login_info,
|
|
|
|
is_selecting_language: false,
|
|
selected_language_index,
|
|
|
|
time: 0.0,
|
|
|
|
screen,
|
|
}
|
|
}
|
|
|
|
fn view(&mut self, settings: &Settings, dt: f32) -> Element<Message> {
|
|
self.time += dt as f64;
|
|
|
|
// TODO: consider setting this as the default in the renderer
|
|
let button_style = style::button::Style::new(self.imgs.button)
|
|
.hover_image(self.imgs.button_hover)
|
|
.press_image(self.imgs.button_press)
|
|
.text_color(TEXT_COLOR)
|
|
.disabled_text_color(DISABLED_TEXT_COLOR);
|
|
|
|
let alpha = iced::Text::new(&self.alpha)
|
|
.size(self.fonts.cyri.scale(12))
|
|
.width(Length::Fill)
|
|
.horizontal_alignment(HorizontalAlignment::Center);
|
|
|
|
let top_text = Row::with_children(vec![
|
|
Space::new(Length::Fill, Length::Shrink).into(),
|
|
alpha.into(),
|
|
if matches!(&self.screen, Screen::Login { .. }) {
|
|
// Login screen shows the Velroen logo over the version
|
|
Space::new(Length::Fill, Length::Shrink).into()
|
|
} else {
|
|
iced::Text::new(&self.version)
|
|
.size(self.fonts.cyri.scale(15))
|
|
.width(Length::Fill)
|
|
.horizontal_alignment(HorizontalAlignment::Right)
|
|
.into()
|
|
},
|
|
])
|
|
.padding(3)
|
|
.width(Length::Fill);
|
|
|
|
let bg_img = if matches!(&self.screen, Screen::Connecting { .. }) {
|
|
self.bg_img
|
|
} else {
|
|
self.imgs.bg
|
|
};
|
|
|
|
let language_metadatas = 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 {
|
|
// Note: Keeping in case we re-add the disclaimer
|
|
//Screen::Disclaimer { screen } => screen.view(&self.fonts, &self.i18n, button_style),
|
|
Screen::Credits { screen } => {
|
|
screen.view(&self.fonts, &self.i18n.read(), &self.credits, button_style)
|
|
},
|
|
Screen::Login { screen, error } => screen.view(
|
|
&self.fonts,
|
|
&self.imgs,
|
|
&self.login_info,
|
|
error.as_deref(),
|
|
&self.i18n.read(),
|
|
self.is_selecting_language,
|
|
self.selected_language_index,
|
|
&language_metadatas,
|
|
button_style,
|
|
&self.version,
|
|
),
|
|
Screen::Servers { screen } => screen.view(
|
|
&self.fonts,
|
|
&self.imgs,
|
|
&settings.networking.servers,
|
|
self.selected_server_index,
|
|
&self.i18n.read(),
|
|
button_style,
|
|
),
|
|
Screen::Connecting {
|
|
screen,
|
|
connection_state,
|
|
} => screen.view(
|
|
&self.fonts,
|
|
&self.imgs,
|
|
connection_state,
|
|
self.time,
|
|
&self.i18n.read(),
|
|
button_style,
|
|
settings.interface.loading_tips,
|
|
&settings.controls,
|
|
),
|
|
};
|
|
|
|
Container::new(
|
|
Column::with_children(vec![top_text.into(), content])
|
|
.spacing(3)
|
|
.width(Length::Fill)
|
|
.height(Length::Fill),
|
|
)
|
|
.style(style::container::Style::image(bg_img))
|
|
.into()
|
|
}
|
|
|
|
fn update(
|
|
&mut self,
|
|
message: Message,
|
|
events: &mut Vec<Event>,
|
|
settings: &Settings,
|
|
ui: &mut Ui,
|
|
) {
|
|
let servers = &settings.networking.servers;
|
|
let mut language_metadatas = i18n::list_localizations();
|
|
|
|
match message {
|
|
Message::Quit => events.push(Event::Quit),
|
|
Message::Back => {
|
|
self.screen = Screen::Login {
|
|
screen: Box::new(login::Screen::new()),
|
|
error: None,
|
|
};
|
|
},
|
|
Message::ShowServers => {
|
|
if matches!(&self.screen, Screen::Login { .. }) {
|
|
self.selected_server_index =
|
|
servers.iter().position(|f| f == &self.login_info.server);
|
|
self.screen = Screen::Servers {
|
|
screen: servers::Screen::new(),
|
|
};
|
|
}
|
|
},
|
|
Message::ShowCredits => {
|
|
self.screen = Screen::Credits {
|
|
screen: credits::Screen::new(),
|
|
};
|
|
},
|
|
#[cfg(feature = "singleplayer")]
|
|
Message::Singleplayer => {
|
|
self.screen = Screen::Connecting {
|
|
screen: connecting::Screen::new(ui),
|
|
connection_state: ConnectionState::InProgress,
|
|
};
|
|
events.push(Event::StartSingleplayer);
|
|
},
|
|
Message::Multiplayer => {
|
|
self.screen = Screen::Connecting {
|
|
screen: connecting::Screen::new(ui),
|
|
connection_state: ConnectionState::InProgress,
|
|
};
|
|
|
|
events.push(Event::LoginAttempt {
|
|
username: self.login_info.username.trim().to_string(),
|
|
password: self.login_info.password.clone(),
|
|
server_address: self.login_info.server.clone(),
|
|
});
|
|
},
|
|
Message::Username(new_value) => self.login_info.username = new_value,
|
|
Message::LanguageChanged(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;
|
|
},
|
|
Message::ServerChanged(new_value) => {
|
|
self.selected_server_index = Some(new_value);
|
|
self.login_info.server = servers[new_value].clone();
|
|
},
|
|
Message::FocusPassword => {
|
|
if let Screen::Login { screen, .. } = &mut self.screen {
|
|
screen.banner.password = text_input::State::focused();
|
|
screen.banner.username = text_input::State::new();
|
|
}
|
|
},
|
|
Message::CancelConnect => {
|
|
self.exit_connect_screen();
|
|
events.push(Event::CancelLoginAttempt);
|
|
},
|
|
msg @ Message::TrustPromptAdd | msg @ Message::TrustPromptCancel => {
|
|
if let Screen::Connecting {
|
|
connection_state, ..
|
|
} = &mut self.screen
|
|
{
|
|
if let ConnectionState::AuthTrustPrompt { auth_server, .. } = connection_state {
|
|
let auth_server = std::mem::take(auth_server);
|
|
let added = matches!(msg, Message::TrustPromptAdd);
|
|
|
|
*connection_state = ConnectionState::InProgress;
|
|
events.push(Event::AuthServerTrust(auth_server, added));
|
|
}
|
|
}
|
|
},
|
|
Message::CloseError => {
|
|
if let Screen::Login { error, .. } = &mut self.screen {
|
|
*error = None;
|
|
}
|
|
},
|
|
/* Note: Keeping in case we re-add the disclaimer */
|
|
/*Message::AcceptDisclaimer => {
|
|
if let Screen::Disclaimer { .. } = &self.screen {
|
|
events.push(Event::DisclaimerAccepted);
|
|
self.screen = Screen::Login {
|
|
screen: login::Screen::new(),
|
|
error: None,
|
|
};
|
|
}
|
|
},*/
|
|
}
|
|
}
|
|
|
|
// Connection successful of failed
|
|
fn exit_connect_screen(&mut self) {
|
|
if matches!(&self.screen, Screen::Connecting { .. }) {
|
|
self.screen = Screen::Login {
|
|
screen: Box::new(login::Screen::new()),
|
|
error: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn auth_trust_prompt(&mut self, auth_server: String) {
|
|
if let Screen::Connecting {
|
|
connection_state, ..
|
|
} = &mut self.screen
|
|
{
|
|
let msg = format!(
|
|
"Warning: The server you are trying to connect to has provided this \
|
|
authentication server address:\n\n{}\n\nbut it is not in your list of trusted \
|
|
authentication servers.\n\nMake sure that you trust this site and owner to not \
|
|
try and bruteforce your password!",
|
|
&auth_server
|
|
);
|
|
|
|
*connection_state = ConnectionState::AuthTrustPrompt { auth_server, msg };
|
|
}
|
|
}
|
|
|
|
fn connection_error(&mut self, error: String) {
|
|
if matches!(&self.screen, Screen::Connecting { .. })
|
|
|| matches!(&self.screen, Screen::Login { .. })
|
|
{
|
|
self.screen = Screen::Login {
|
|
screen: Box::new(login::Screen::new()),
|
|
error: Some(error),
|
|
}
|
|
} else {
|
|
warn!("connection_error invoked on unhandled screen!");
|
|
}
|
|
}
|
|
|
|
fn tab(&mut self) {
|
|
if let Screen::Login { screen, .. } = &mut self.screen {
|
|
// TODO: add select all function in iced
|
|
if screen.banner.username.is_focused() {
|
|
screen.banner.username = iced::text_input::State::new();
|
|
screen.banner.password = iced::text_input::State::focused();
|
|
screen.banner.password.move_cursor_to_end();
|
|
} else if screen.banner.password.is_focused() {
|
|
screen.banner.password = iced::text_input::State::new();
|
|
screen.banner.server = iced::text_input::State::focused();
|
|
screen.banner.server.move_cursor_to_end();
|
|
} else if screen.banner.server.is_focused() {
|
|
screen.banner.server = iced::text_input::State::new();
|
|
screen.banner.username = iced::text_input::State::focused();
|
|
screen.banner.username.move_cursor_to_end();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct MainMenuUi {
|
|
ui: Ui,
|
|
// TODO: re add this
|
|
// tip_no: u16,
|
|
controls: Controls,
|
|
}
|
|
|
|
impl MainMenuUi {
|
|
pub fn new(global_state: &mut GlobalState) -> Self {
|
|
// Load language
|
|
let i18n = &global_state.i18n.read();
|
|
// TODO: don't add default font twice
|
|
let font = load_font(&i18n.fonts().get("cyri").unwrap().asset_key);
|
|
|
|
let mut ui = Ui::new(
|
|
&mut global_state.window,
|
|
font,
|
|
global_state.settings.interface.ui_scale,
|
|
)
|
|
.unwrap();
|
|
|
|
let fonts = Fonts::load(i18n.fonts(), &mut ui).expect("Impossible to load fonts");
|
|
|
|
let bg_img_spec = BG_IMGS.choose(&mut thread_rng()).unwrap();
|
|
|
|
let bg_img = assets::Image::load_expect(bg_img_spec).read().to_image();
|
|
let controls = Controls::new(
|
|
fonts,
|
|
Imgs::load(&mut ui).expect("Failed to load images"),
|
|
ui.add_graphic(Graphic::Image(bg_img, None)),
|
|
global_state.i18n,
|
|
&global_state.settings,
|
|
);
|
|
|
|
Self { ui, controls }
|
|
}
|
|
|
|
pub fn update_language(&mut self, i18n: LocalizationHandle, settings: &Settings) {
|
|
self.controls.i18n = i18n;
|
|
let i18n = &i18n.read();
|
|
let font = load_font(&i18n.fonts().get("cyri").unwrap().asset_key);
|
|
self.ui.clear_fonts(font);
|
|
self.controls.fonts =
|
|
Fonts::load(i18n.fonts(), &mut self.ui).expect("Impossible to load fonts!");
|
|
let language_metadatas = i18n::list_localizations();
|
|
self.controls.selected_language_index = language_metadatas
|
|
.iter()
|
|
.position(|f| f.language_identifier == settings.language.selected_language);
|
|
}
|
|
|
|
pub fn auth_trust_prompt(&mut self, auth_server: String) {
|
|
self.controls.auth_trust_prompt(auth_server);
|
|
}
|
|
|
|
pub fn show_info(&mut self, msg: String) { self.controls.connection_error(msg); }
|
|
|
|
pub fn connected(&mut self) { self.controls.exit_connect_screen(); }
|
|
|
|
pub fn cancel_connection(&mut self) { self.controls.exit_connect_screen(); }
|
|
|
|
pub fn handle_event(&mut self, event: window::Event) -> bool {
|
|
match event {
|
|
// Pass events to ui.
|
|
window::Event::IcedUi(event) => {
|
|
self.handle_ui_event(event);
|
|
true
|
|
},
|
|
window::Event::ScaleFactorChanged(s) => {
|
|
self.ui.scale_factor_changed(s);
|
|
false
|
|
},
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
pub fn handle_ui_event(&mut self, event: ui::ice::Event) {
|
|
// Tab for input fields
|
|
use iced::keyboard;
|
|
if matches!(
|
|
&event,
|
|
iced::Event::Keyboard(keyboard::Event::KeyPressed {
|
|
key_code: keyboard::KeyCode::Tab,
|
|
..
|
|
})
|
|
) {
|
|
self.controls.tab();
|
|
}
|
|
|
|
self.ui.handle_event(event);
|
|
}
|
|
|
|
pub fn set_scale_mode(&mut self, scale_mode: ui::ScaleMode) {
|
|
self.ui.set_scaling_mode(scale_mode);
|
|
}
|
|
|
|
pub fn maintain(&mut self, global_state: &mut GlobalState, dt: Duration) -> Vec<Event> {
|
|
let mut events = Vec::new();
|
|
|
|
let (messages, _) = self.ui.maintain(
|
|
self.controls.view(&global_state.settings, dt.as_secs_f32()),
|
|
global_state.window.renderer_mut(),
|
|
None,
|
|
&mut global_state.clipboard,
|
|
);
|
|
|
|
messages.into_iter().for_each(|message| {
|
|
self.controls
|
|
.update(message, &mut events, &global_state.settings, &mut self.ui)
|
|
});
|
|
|
|
events
|
|
}
|
|
|
|
pub fn render<'a>(&'a self, drawer: &mut UiDrawer<'_, 'a>) { self.ui.render(drawer); }
|
|
}
|