Add styling for Container, implement auth trust prompt, misc additions

This commit is contained in:
Imbris 2020-06-03 01:23:51 -04:00
parent 20a46eb526
commit 73982637b2
11 changed files with 257 additions and 64 deletions

View File

@ -65,6 +65,7 @@
"common.back": "Back",
"common.create": "Create",
"common.okay": "Okay",
"common.add": "Add",
"common.accept": "Accept",
"common.decline": "Decline",
"common.disclaimer": "Disclaimer",

View File

@ -1,20 +1,19 @@
use super::{IcedImgs as Imgs, Message};
use super::{ConnectionState, IcedImgs as Imgs, Message};
use crate::{
i18n::Localization,
ui::{
fonts::IcedFonts as Fonts,
ice::{
component::neat_button,
widget::{image, BackgroundContainer, Image},
ButtonStyle, Element,
},
ice::{component::neat_button, style, widget::image, Element},
},
};
use iced::{button, Color, Column, Container, HorizontalAlignment, Length, Row, Space, Text};
use iced::{
button, Align, Color, Column, Container, HorizontalAlignment, Length, Row, Space, Text,
};
/// Connecting screen for the main menu
pub struct Screen {
cancel_button: button::State,
add_button: button::State,
}
// TODO: move to super and unify with identical login consts
@ -25,6 +24,7 @@ impl Screen {
pub fn new() -> Self {
Self {
cancel_button: Default::default(),
add_button: Default::default(),
}
}
@ -34,13 +34,13 @@ impl Screen {
imgs: &Imgs,
bg_img: image::Handle,
start: &std::time::Instant,
status_text: &str,
connection_state: &ConnectionState,
version: &str,
time: f32,
i18n: &Localization,
) -> Element<Message> {
let fade_msg = (time * 2.0).sin() * 0.5 + 0.51;
let button_style = ButtonStyle::new(imgs.button)
let button_style = style::button::Style::new(imgs.button)
.hover_image(imgs.button_hover)
.press_image(imgs.button_press)
.text_color(TEXT_COLOR)
@ -49,10 +49,12 @@ impl Screen {
let version = Text::new(version)
.size(fonts.cyri.scale(15)) // move version text size to const
.width(Length::Fill)
.height(Length::Fill)
.height(if matches!(connection_state, ConnectionState::InProgress {..}){Length::Fill}else{Length::Shrink})
.horizontal_alignment(HorizontalAlignment::Right);
let status = Text::new(status_text)
let (middle, bottom) = match connection_state {
ConnectionState::InProgress { status } => {
let status = Text::new(status)
.size(fonts.alkhemi.scale(80))
.font(fonts.alkhemi.id)
.color(Color::from_rgba(1.0, 1.0, 1.0, fade_msg))
@ -77,13 +79,69 @@ impl Screen {
.center_x()
.padding(3);
let content = Column::with_children(vec![version.into(), status.into(), cancel.into()])
(status.into(), cancel.into())
},
ConnectionState::AuthTrustPrompt { msg, .. } => {
let text = Text::new(msg).size(fonts.cyri.scale(25));
let cancel = neat_button(
&mut self.cancel_button,
i18n.get("common.cancel"),
0.7,
button_style,
Some(Message::TrustPromptCancel),
);
let add = neat_button(
&mut self.add_button,
i18n.get("common.add"),
0.7,
button_style,
Some(Message::TrustPromptAdd),
);
let content = Column::with_children(vec![
text.into(),
Container::new(
Row::with_children(vec![cancel.into(), add.into()])
.spacing(20)
.height(Length::Units(25)),
)
.align_x(Align::End)
.width(Length::Fill)
.into(),
])
.spacing(4)
.max_width(500)
.width(Length::Fill)
.height(Length::Fill);
let prompt_window = Container::new(content)
// TODO: add borders
.style(style::container::Style::Color((10, 10, 0, 255).into()))
.padding(10);
let container = Container::new(prompt_window)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.center_y();
(
container.into(),
Space::new(Length::Fill, Length::Units(fonts.cyri.scale(15))).into(),
)
},
};
let content = Column::with_children(vec![version.into(), middle, bottom])
.width(Length::Fill)
.height(Length::Fill)
.padding(3);
// Note: could replace this with styling on iced's container since we aren't
// using fixed aspect ratio
BackgroundContainer::new(Image::new(bg_img), content).into()
Container::new(content)
.style(style::container::Style::image(bg_img))
.into()
}
}

View File

@ -5,11 +5,12 @@ use crate::{
fonts::IcedFonts as Fonts,
ice::{
component::neat_button,
style,
widget::{
compound_graphic::{CompoundGraphic, Graphic},
BackgroundContainer, Image, Padding,
},
ButtonStyle, Element,
Element,
},
},
};
@ -52,11 +53,12 @@ impl Screen {
imgs: &Imgs,
login_info: &LoginInfo,
info: &Info,
error: Option<&str>,
version: &str,
show_servers: bool,
i18n: &Localization,
) -> Element<Message> {
let button_style = ButtonStyle::new(imgs.button)
let button_style = style::button::Style::new(imgs.button)
.hover_image(imgs.button_hover)
.press_image(imgs.button_press)
.text_color(TEXT_COLOR)
@ -146,7 +148,9 @@ impl Screen {
.spacing(10)
.padding(3);
BackgroundContainer::new(Image::new(imgs.bg), content).into()
Container::new(content)
.style(style::container::Style::image(imgs.bg))
.into()
}
}
@ -179,7 +183,7 @@ impl Banner {
imgs: &Imgs,
login_info: &LoginInfo,
i18n: &Localization,
button_style: ButtonStyle,
button_style: style::button::Style,
) -> Element<Message> {
let input_text_size = fonts.cyri.scale(INPUT_TEXT_SIZE);

View File

@ -119,15 +119,35 @@ enum Info {
Intro,
}
enum ConnectionState {
InProgress {
status: String,
},
AuthTrustPrompt {
auth_server: String,
msg: String,
// To remember when we switch back
status: String,
},
}
impl ConnectionState {
fn take_status_string(&mut self) -> String {
std::mem::take(match self {
Self::InProgress { status } => status,
Self::AuthTrustPrompt { status, .. } => status,
})
}
}
enum Screen {
Login {
screen: login::Screen,
// Error to display in a box
error: Option<String>,
},
Connecting {
screen: connecting::Screen,
// TODO: why instant?
start: std::time::Instant,
status_text: String,
connection_state: ConnectionState,
},
}
@ -142,8 +162,6 @@ struct IcedState {
login_info: LoginInfo,
// TODO: not sure if this should be used for connecting
popup: Option<PopupData>,
show_servers: bool,
info: Info,
time: f32,
@ -163,6 +181,10 @@ enum Message {
Server(String),
FocusPassword,
CancelConnect,
TrustPromptAdd,
TrustPromptCancel,
CloseError,
CloseDisclaimer,
}
impl IcedState {
@ -198,13 +220,13 @@ impl IcedState {
server: String::new(),
},
popup: None,
show_servers: false,
info,
time: 0.0,
screen: Screen::Login {
screen: login::Screen::new(),
error: None,
},
}
}
@ -212,12 +234,14 @@ impl IcedState {
fn view(&mut self, dt: f32) -> Element<Message> {
self.time = self.time + dt;
// TODO: make disclaimer it's own screen?
match &mut self.screen {
Screen::Login { screen } => screen.view(
Screen::Login { screen, error } => screen.view(
&self.fonts,
&self.imgs,
&self.login_info,
&self.info,
error.as_deref(),
&self.version,
self.show_servers,
&self.i18n,
@ -225,13 +249,13 @@ impl IcedState {
Screen::Connecting {
screen,
start,
status_text,
connection_state,
} => screen.view(
&self.fonts,
&self.imgs,
self.bg_img,
&start,
&status_text,
&connection_state,
&self.version,
self.time,
&self.i18n,
@ -248,7 +272,9 @@ impl IcedState {
self.screen = Screen::Connecting {
screen: connecting::Screen::new(),
start: std::time::Instant::now(),
status_text: [self.i18n.get("main.creating_world"), "..."].concat(),
connection_state: ConnectionState::InProgress {
status: [self.i18n.get("main.creating_world"), "..."].concat(),
},
};
events.push(Event::StartSingleplayer);
@ -257,7 +283,9 @@ impl IcedState {
self.screen = Screen::Connecting {
screen: connecting::Screen::new(),
start: std::time::Instant::now(),
status_text: [self.i18n.get("main.connecting"), "..."].concat(),
connection_state: ConnectionState::InProgress {
status: [self.i18n.get("main.connecting"), "..."].concat(),
},
};
events.push(Event::LoginAttempt {
@ -279,6 +307,34 @@ impl IcedState {
self.cancel_connection();
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,
status,
..
} = connection_state
{
let auth_server = std::mem::take(auth_server);
let status = std::mem::take(status);
let added = matches!(msg, Message::TrustPromptAdd);
*connection_state = ConnectionState::InProgress { status };
events.push(Event::AuthServerTrust(auth_server, added));
}
}
},
Message::CloseError => {
if let Screen::Login { error, .. } = &mut self.screen {
*error = None;
}
},
Message::CloseDisclaimer => {
events.push(Event::DisclaimerClosed);
},
}
}
@ -286,6 +342,37 @@ impl IcedState {
if matches!(&self.screen, Screen::Connecting {..}) {
self.screen = Screen::Login {
screen: 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,
status: connection_state.take_status_string(),
};
}
}
fn connection_error(&mut self, error: String) {
if matches!(&self.screen, Screen::Connecting {..}) {
self.screen = Screen::Login {
screen: login::Screen::new(),
error: Some(error),
}
}
}
@ -1061,6 +1148,7 @@ impl<'a> MainMenuUi {
}
pub fn auth_trust_prompt(&mut self, auth_server: String) {
self.ice_state.auth_trust_prompt(auth_server.clone());
self.popup = Some(PopupData {
msg: format!(
"Warning: The server you are trying to connect to has provided this \
@ -1074,6 +1162,8 @@ impl<'a> MainMenuUi {
}
pub fn show_info(&mut self, msg: String) {
self.ice_state.connection_error(msg.clone());
self.popup = Some(PopupData {
msg,
popup_type: PopupType::Error,

View File

@ -1,15 +1,15 @@
use crate::ui::ice as ui;
use iced::{button::State, Button, Element, Length};
use ui::{
style::button::Style,
widget::{AspectRatioContainer, FillText},
ButtonStyle,
};
pub fn neat_button<M: Clone + 'static>(
state: &mut State,
label: impl Into<String>,
fill_fraction: f32,
button_style: ButtonStyle,
button_style: Style,
message: Option<M>,
) -> Element<M, ui::IcedRenderer> {
let button = Button::new(state, FillText::new(label).fill_fraction(fill_fraction))

View File

@ -9,7 +9,7 @@ mod winit_conversion;
pub use cache::{Font, FontId, RawFont};
pub use graphic::{Id, Rotation};
pub use iced::Event;
pub use renderer::{ButtonStyle, IcedRenderer};
pub use renderer::{style, IcedRenderer};
pub use winit_conversion::window_event;
use super::{

View File

@ -1,10 +1,9 @@
mod defaults;
mod primitive;
mod style;
pub mod style;
mod widget;
pub use defaults::Defaults;
pub use style::ButtonStyle;
pub(self) use primitive::Primitive;

View File

@ -0,0 +1,18 @@
use super::super::super::widget::image;
use vek::Rgba;
/// Background of the container
pub enum Style {
Image(image::Handle, Rgba<u8>),
Color(Rgba<u8>),
None,
}
impl Style {
/// Shorthand for common case where the color of the image is not modified
pub fn image(image: image::Handle) -> Self { Self::Image(image, Rgba::broadcast(255)) }
}
impl Default for Style {
fn default() -> Self { Self::None }
}

View File

@ -1,3 +1,2 @@
mod button;
pub use button::Style as ButtonStyle;
pub mod button;
pub mod container;

View File

@ -1,10 +1,10 @@
use super::super::{super::Rotation, Defaults, IcedRenderer, Primitive};
use super::super::{super::Rotation, style, Defaults, IcedRenderer, Primitive};
use iced::{button, mouse, Element, Layout, Point, Rectangle};
use vek::Rgba;
impl button::Renderer for IcedRenderer {
// TODO: what if this gets large enough to not be copied around?
type Style = super::super::style::ButtonStyle;
type Style = style::button::Style;
const DEFAULT_PADDING: u16 = 0;

View File

@ -1,23 +1,47 @@
use super::super::IcedRenderer;
use super::super::{super::Rotation, style, IcedRenderer, Primitive};
use common::util::srgba_to_linear;
use iced::{container, Element, Layout, Point, Rectangle};
impl container::Renderer for IcedRenderer {
type Style = ();
type Style = style::container::Style;
fn draw<M>(
&mut self,
defaults: &Self::Defaults,
_bounds: Rectangle,
bounds: Rectangle,
cursor_position: Point,
_style_sheet: &Self::Style,
style_sheet: &Self::Style,
content: &Element<'_, M, Self>,
content_layout: Layout<'_>,
) -> Self::Output {
let (content, mouse_interaction) =
content.draw(self, defaults, content_layout, cursor_position);
// We may have more stuff here if styles are used
let prim = match style_sheet {
Self::Style::Image(handle, color) => {
let background = Primitive::Image {
handle: (*handle, Rotation::None),
bounds,
color: *color,
};
(content, mouse_interaction)
Primitive::Group {
primitives: vec![background, content],
}
},
Self::Style::Color(color) => {
let background = Primitive::Rectangle {
bounds,
linear_color: srgba_to_linear(color.map(|e| e as f32 / 255.0)),
};
Primitive::Group {
primitives: vec![background, content],
}
},
Self::Style::None => content,
};
(prim, mouse_interaction)
}
}