From 545f3bd44b4cd56403c2e325535e2c3e946c8abf Mon Sep 17 00:00:00 2001 From: Imbris Date: Sat, 27 Jun 2020 01:09:04 -0400 Subject: [PATCH] Start rewrite of character selection with iced --- assets/voxygen/element/buttons/x_red.png | 3 + assets/voxygen/element/buttons/x_red.vox | 3 - .../voxygen/element/buttons/x_red_hover.png | 3 + .../voxygen/element/buttons/x_red_hover.vox | 3 - .../voxygen/element/buttons/x_red_press.png | 3 + .../voxygen/element/buttons/x_red_press.vox | 3 - voxygen/src/menu/char_selection/mod.rs | 32 +- .../menu/char_selection/{ui.rs => old_ui.rs} | 0 voxygen/src/menu/char_selection/ui/mod.rs | 513 ++++++++++++++++++ voxygen/src/menu/main/ui/login.rs | 2 - voxygen/src/menu/main/ui/mod.rs | 16 +- voxygen/src/ui/ice/renderer/style/button.rs | 11 + voxygen/src/ui/ice/renderer/widget/mod.rs | 1 + voxygen/src/ui/ice/renderer/widget/overlay.rs | 36 ++ voxygen/src/ui/ice/widget/mod.rs | 2 + voxygen/src/ui/ice/widget/overlay.rs | 222 ++++++++ 16 files changed, 811 insertions(+), 42 deletions(-) create mode 100644 assets/voxygen/element/buttons/x_red.png delete mode 100644 assets/voxygen/element/buttons/x_red.vox create mode 100644 assets/voxygen/element/buttons/x_red_hover.png delete mode 100644 assets/voxygen/element/buttons/x_red_hover.vox create mode 100644 assets/voxygen/element/buttons/x_red_press.png delete mode 100644 assets/voxygen/element/buttons/x_red_press.vox rename voxygen/src/menu/char_selection/{ui.rs => old_ui.rs} (100%) create mode 100644 voxygen/src/menu/char_selection/ui/mod.rs create mode 100644 voxygen/src/ui/ice/renderer/widget/overlay.rs create mode 100644 voxygen/src/ui/ice/widget/overlay.rs diff --git a/assets/voxygen/element/buttons/x_red.png b/assets/voxygen/element/buttons/x_red.png new file mode 100644 index 0000000000..d5861b8d14 --- /dev/null +++ b/assets/voxygen/element/buttons/x_red.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9084fd6e5c84ad1e1f0d7c93fea09f34a2d4c40d6bfd0aa94c113a919e3e6b9 +size 9714 diff --git a/assets/voxygen/element/buttons/x_red.vox b/assets/voxygen/element/buttons/x_red.vox deleted file mode 100644 index de6f58812e..0000000000 --- a/assets/voxygen/element/buttons/x_red.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6f0f9767a05cc881686698e1a54c2bcee8d6908220c52874804b12792e22f5da -size 58652 diff --git a/assets/voxygen/element/buttons/x_red_hover.png b/assets/voxygen/element/buttons/x_red_hover.png new file mode 100644 index 0000000000..bf12dd51d4 --- /dev/null +++ b/assets/voxygen/element/buttons/x_red_hover.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f339fe623c1c1cfb97ecbbf81854cb6504b6168b8aff558c6a98aab4dcb8912 +size 10485 diff --git a/assets/voxygen/element/buttons/x_red_hover.vox b/assets/voxygen/element/buttons/x_red_hover.vox deleted file mode 100644 index a114431a3e..0000000000 --- a/assets/voxygen/element/buttons/x_red_hover.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:936fdad33796e5908b44181a4ac12bc733482873bd797dd3f1c959635afa4978 -size 58652 diff --git a/assets/voxygen/element/buttons/x_red_press.png b/assets/voxygen/element/buttons/x_red_press.png new file mode 100644 index 0000000000..0561755e16 --- /dev/null +++ b/assets/voxygen/element/buttons/x_red_press.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de84c990b9a2c8c45f9503a64e8d7f61ae9bd2da93b4ea59aea64310db5d2ff0 +size 8668 diff --git a/assets/voxygen/element/buttons/x_red_press.vox b/assets/voxygen/element/buttons/x_red_press.vox deleted file mode 100644 index 7f546cbf13..0000000000 --- a/assets/voxygen/element/buttons/x_red_press.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b5a872ee983ed3ec1729ef251ae0179b74acab50184d72b7e517dd3ab24def01 -size 59020 diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index 4cf356e5f8..91dbabd584 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -39,16 +39,10 @@ impl CharSelectionState { fn get_humanoid_body(&self) -> Option { self.char_selection_ui - .get_character_list() - .and_then(|data| { - if let Some(character) = data.get(self.char_selection_ui.selected_character) { - match character.body { - comp::Body::Humanoid(body) => Some(body), - _ => None, - } - } else { - None - } + .selected_character() + .and_then(|character| match character.body { + comp::Body::Humanoid(body) => Some(body), + _ => None, }) } } @@ -98,18 +92,10 @@ impl PlayState for CharSelectionState { ui::Event::DeleteCharacter(character_id) => { self.client.borrow_mut().delete_character(character_id); }, - ui::Event::Play => { - let char_data = self - .char_selection_ui - .get_character_list() - .expect("Character data is required to play"); - - if let Some(selected_character) = - char_data.get(self.char_selection_ui.selected_character) - { - if let Some(character_id) = selected_character.character.id { - self.client.borrow_mut().request_character(character_id); - } + ui::Event::Play(character) => { + // TODO: eliminate option in character id + if let Some(character_id) = character.character.id { + self.client.borrow_mut().request_character(character_id); } return PlayStateResult::Switch(Box::new(SessionState::new( @@ -212,6 +198,6 @@ impl PlayState for CharSelectionState { // Draw the UI to the screen. self.char_selection_ui - .render(renderer, self.scene.globals()); + .render(global_state.window.renderer_mut()); } } diff --git a/voxygen/src/menu/char_selection/ui.rs b/voxygen/src/menu/char_selection/old_ui.rs similarity index 100% rename from voxygen/src/menu/char_selection/ui.rs rename to voxygen/src/menu/char_selection/old_ui.rs diff --git a/voxygen/src/menu/char_selection/ui/mod.rs b/voxygen/src/menu/char_selection/ui/mod.rs new file mode 100644 index 0000000000..42bf044b2a --- /dev/null +++ b/voxygen/src/menu/char_selection/ui/mod.rs @@ -0,0 +1,513 @@ +use crate::{ + i18n::{i18n_asset_key, Localization}, + render::Renderer, + ui::{ + self, + fonts::IcedFonts as Fonts, + ice::{component::neat_button, style, widget::Overlay, Element, IcedUi as Ui}, + img_ids::{ImageGraphic, VoxelGraphic}, + }, + window, GlobalState, +}; +use client::Client; +use common::{ + character::{CharacterItem, MAX_CHARACTERS_PER_PLAYER}, + comp, + comp::humanoid, +}; +//ImageFrame, Tooltip, +use crate::settings::Settings; +use common::assets::load_expect; +//use std::time::Duration; +//use ui::ice::widget; +use iced::{ + button, text_input, Align, Button, Column, Container, HorizontalAlignment, Length, Row, Space, + Text, +}; + +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); +const FILL_FRAC_ONE: f32 = 0.77; +const FILL_FRAC_TWO: f32 = 0.53; + +image_ids_ice! { + struct Imgs { + + // TODO: convert large frames into borders + charlist_frame: "voxygen.element.frames.window_4", + server_frame: "voxygen.element.frames.server_frame", + slider_range: "voxygen.element.slider.track", + slider_indicator: "voxygen.element.slider.indicator", + + + selection: "voxygen.element.frames.selection", + selection_hover: "voxygen.element.frames.selection_hover", + selection_press: "voxygen.element.frames.selection_press", + + delete_button: "voxygen.element.buttons.x_red", + delete_button_hover: "voxygen.element.buttons.x_red_hover", + delete_button_press: "voxygen.element.buttons.x_red_press", + + + name_input: "voxygen.element.misc_bg.textbox_mid", + + // Tool Icons + daggers: "voxygen.element.icons.daggers", + sword: "voxygen.element.icons.sword", + axe: "voxygen.element.icons.axe", + hammer: "voxygen.element.icons.hammer", + bow: "voxygen.element.icons.bow", + staff: "voxygen.element.icons.staff", + + // Species Icons + male: "voxygen.element.icons.male", + female: "voxygen.element.icons.female", + human_m: "voxygen.element.icons.human_m", + human_f: "voxygen.element.icons.human_f", + orc_m: "voxygen.element.icons.orc_m", + orc_f: "voxygen.element.icons.orc_f", + dwarf_m: "voxygen.element.icons.dwarf_m", + dwarf_f: "voxygen.element.icons.dwarf_f", + undead_m: "voxygen.element.icons.ud_m", + undead_f: "voxygen.element.icons.ud_f", + elf_m: "voxygen.element.icons.elf_m", + elf_f: "voxygen.element.icons.elf_f", + danari_m: "voxygen.element.icons.danari_m", + danari_f: "voxygen.element.icons.danari_f", + // Icon Borders + icon_border: "voxygen.element.buttons.border", + icon_border_mo: "voxygen.element.buttons.border_mo", + icon_border_press: "voxygen.element.buttons.border_press", + icon_border_pressed: "voxygen.element.buttons.border_pressed", + + + button: "voxygen.element.buttons.button", + button_hover: "voxygen.element.buttons.button_hover", + button_press: "voxygen.element.buttons.button_press", + } +} + +// TODO: do rotation in widget renderer +/*rotation_image_ids! { + pub struct ImgsRot { + + + // Tooltip Test + tt_side: "voxygen/element/frames/tt_test_edge", + tt_corner: "voxygen/element/frames/tt_test_corner_tr", + } +}*/ + +pub enum Event { + Logout, + Play(CharacterItem), + AddCharacter { + alias: String, + tool: Option, + body: comp::Body, + }, + DeleteCharacter(i32), +} + +struct CharacterList { + characters: Vec, + selected_character: usize, +} + +enum Mode { + Select { + list: Option, + character_buttons: Vec, + new_character_button: button::State, + + logout_button: button::State, + enter_world_button: button::State, + change_server_button: button::State, + }, + Create { + name: String, // TODO: default to username + body: humanoid::Body, + loadout: comp::Loadout, + tool: Option<&'static str>, + + name_input: text_input::State, + back_button: button::State, + create_button: button::State, + }, +} + +#[derive(PartialEq)] +enum InfoContent { + Deletion(usize), + LoadingCharacters, + CreatingCharacter, + DeletingCharacter, + CharacterError, +} + +/* +impl InfoContent { + pub fn has_content(&self, character_list_loading: &bool) -> bool { + match self { + Self::None => false, + Self::CreatingCharacter | Self::DeletingCharacter | Self::LoadingCharacters => { + *character_list_loading + }, + _ => true, + } + } +} +*/ + +struct Controls { + fonts: Fonts, + imgs: Imgs, + i18n: std::sync::Arc, + // Voxygen version + version: String, + + info_content: Option, + // enter: bool, + mode: Mode, +} + +#[derive(Clone)] +enum Message { + Back, + Logout, + EnterWorld, + Delete(usize), + ChangeServer, + NewCharacter, + CreateCharacter, + Name(String), +} + +impl Controls { + fn new(fonts: Fonts, imgs: Imgs, i18n: std::sync::Arc) -> Self { + let version = format!( + "{}-{}", + env!("CARGO_PKG_VERSION"), + common::util::GIT_VERSION.to_string() + ); + + Self { + fonts, + imgs, + i18n, + version, + + info_content: None, + mode: Mode::Select { + list: None, + character_buttons: Vec::new(), + new_character_button: Default::default(), + logout_button: Default::default(), + enter_world_button: Default::default(), + change_server_button: Default::default(), + }, + } + } + + fn view(&mut self, settings: &Settings) -> Element { + // TODO: if enter key pressed and character is selected then enter the world + // TODO: tooltip widget + + let imgs = &self.imgs; + let i18n = &self.i18n; + + let button_style = style::button::Style::new(imgs.button) + .hover_image(imgs.button_hover) + .press_image(imgs.button_press) + .text_color(TEXT_COLOR) + .disabled_text_color(DISABLED_TEXT_COLOR); + + let version = iced::Text::new(&self.version) + .size(self.fonts.cyri.scale(15)) + .width(Length::Fill) + .horizontal_alignment(HorizontalAlignment::Right); + + let content = match &mut self.mode { + Mode::Select { + list, + ref mut character_buttons, + ref mut new_character_button, + ref mut logout_button, + ref mut enter_world_button, + ref mut change_server_button, + } => { + // TODO: impl delete prompt as overlay + let change_server = Space::new(Length::Units(100), Length::Units(40)); + let characters = if let Some(list) = list { + let num = list.characters.len(); + // Ensure we have enough button states + character_buttons.resize_with(num * 2, Default::default); + + let mut characters = list + .characters + .iter() + .zip(character_buttons.chunks_exact_mut(2)) + .map(|(character, buttons)| { + let mut buttons = buttons.iter_mut(); + ( + character, + (buttons.next().unwrap(), buttons.next().unwrap()), + ) + }) + .enumerate() + .map(|(i, (character, (select_button, delete_button)))| { + Overlay::new( + Button::new(select_button, Space::new(Length::Fill, Length::Fill)) + .width(Length::Units(20)) + .height(Length::Units(20)) + .style( + style::button::Style::new(imgs.delete_button) + .hover_image(imgs.delete_button_hover) + .press_image(imgs.delete_button_press), + ), + Button::new( + delete_button, + Column::with_children(vec![ + Text::new("Hi").into(), + Text::new("Hi").into(), + Text::new("Hi").into(), + ]), + ) + .style( + style::button::Style::new(imgs.selection) + .hover_image(imgs.selection_hover) + .press_image(imgs.selection_press), + ), + ) + .into() + }) + .collect::>(); + + // Add create new character button + let color = if num >= MAX_CHARACTERS_PER_PLAYER { + iced::Color::from_rgb8(97, 97, 25) + } else { + iced::Color::from_rgb8(97, 255, 18) + }; + characters.push( + Button::new( + new_character_button, + Text::new(i18n.get("char_selection.create_new_character")), + ) + .style( + style::button::Style::new(imgs.selection) + .hover_image(imgs.selection_hover) + .press_image(imgs.selection_press) + .image_color(color) + .text_color(color), + ) + .into(), + ); + characters + } else { + Vec::new() + }; + + let characters = Column::with_children(characters); + + let right_column = + Column::with_children(vec![change_server.into(), characters.into()]) + .spacing(10) + .width(Length::Fill) + .height(Length::Fill) + .max_width(300); + + let top = Container::new(right_column) + .width(Length::Fill) + .height(Length::Fill); + + let logout = neat_button( + logout_button, + i18n.get("char_selection.logout"), + FILL_FRAC_ONE, + button_style, + Some(Message::Logout), + ); + + let enter_world = neat_button( + enter_world_button, + i18n.get("char_selection.enter_world"), + FILL_FRAC_ONE, + button_style, + Some(Message::EnterWorld), + ); + + let bottom = Row::with_children(vec![ + Container::new(logout) + .width(Length::Fill) + .height(Length::Units(40)) + .align_y(Align::End) + .into(), + Container::new(enter_world) + .width(Length::Fill) + .height(Length::Units(60)) + .center_x() + .align_y(Align::End) + .into(), + Space::new(Length::Fill, Length::Shrink).into(), + ]); + + Column::with_children(vec![top.into(), bottom.into()]) + .width(Length::Fill) + .height(Length::Fill) + }, + Mode::Create { + name, + body, + loadout, + tool, + name_input, + back_button, + create_button, + } => { + let top_row = Row::with_children(vec![]); + let bottom_row = Row::with_children(vec![]); + + Column::with_children(vec![top_row.into(), bottom_row.into()]) + .width(Length::Fill) + .height(Length::Fill) + }, + }; + + Container::new( + Column::with_children(vec![version.into(), content.into()]) + .spacing(3) + .width(Length::Fill) + .height(Length::Fill), + ) + .padding(3) + .into() + } + + fn update(&mut self, message: Message, events: &mut Vec, settings: &Settings) { + let servers = &settings.networking.servers; + + //match message { } + } + + pub fn selected_character(&self) -> Option<&CharacterItem> { + match self.mode { + // TODO + Mode::Select { .. } => None, + // TODO + Mode::Create { .. } => None, + } + } +} + +pub struct CharSelectionUi { + ui: Ui, + controls: Controls, +} + +impl CharSelectionUi { + pub fn new(global_state: &mut GlobalState) -> Self { + // Load language + let i18n = load_expect::(&i18n_asset_key( + &global_state.settings.language.selected_language, + )); + + // TODO: don't add default font twice + let font = { + use std::io::Read; + let mut buf = Vec::new(); + common::assets::load_file("voxygen.font.haxrcorp_4089_cyrillic_altgr_extended", &[ + "ttf", + ]) + .unwrap() + .read_to_end(&mut buf) + .unwrap(); + ui::ice::Font::try_from_vec(buf).unwrap() + }; + + let mut ui = Ui::new(&mut global_state.window, font).unwrap(); + + let fonts = Fonts::load(&i18n.fonts, &mut ui).expect("Impossible to load fonts"); + + let controls = Controls::new( + fonts, + Imgs::load(&mut ui).expect("Failed to load images"), + i18n, + ); + + Self { ui, controls } + } + + pub fn selected_character(&self) -> Option<&CharacterItem> { + self.controls.selected_character() + } + + // TODO + pub fn get_loadout(&mut self) -> Option { + // TODO: don't clone + /*match &mut self.mode { + Mode::Select(character_list) => { + if let Some(data) = character_list { + data.get(self.selected_character).map(|c| c.loadout.clone()) + } else { + None + } + }, + Mode::Create { loadout, tool, .. } => { + loadout.active_item = tool.map(|tool| comp::ItemConfig { + item: (*load_expect::(tool)).clone(), + ability1: None, + ability2: None, + ability3: None, + block_ability: None, + dodge_ability: None, + }); + loadout.chest = Some(assets::load_expect_cloned( + "common.items.armor.starter.rugged_chest", + )); + loadout.pants = Some(assets::load_expect_cloned( + "common.items.armor.starter.rugged_pants", + )); + loadout.foot = Some(assets::load_expect_cloned( + "common.items.armor.starter.sandals_0", + )); + Some(loadout.clone()) + }, + }*/ + None + } + + pub fn handle_event(&mut self, event: window::Event) -> bool { + match event { + window::Event::IcedUi(event) => { + self.ui.handle_event(event); + true + }, + window::Event::MouseButton(_, window::PressState::Pressed) => { + // TODO: implement this with iced + // !self.ui.no_widget_capturing_mouse() + false + }, + _ => false, + } + } + + pub fn maintain(&mut self, global_state: &mut GlobalState, client: &mut Client) -> Vec { + let mut events = Vec::new(); + + let (messages, _) = self.ui.maintain( + self.controls.view(&global_state.settings), + global_state.window.renderer_mut(), + ); + + messages.into_iter().for_each(|message| { + self.controls + .update(message, &mut events, &global_state.settings) + }); + + events + } + + // TODO: do we need globals + pub fn render(&self, renderer: &mut Renderer) { self.ui.render(renderer); } +} diff --git a/voxygen/src/menu/main/ui/login.rs b/voxygen/src/menu/main/ui/login.rs index a55ac6f5cb..acd374119a 100644 --- a/voxygen/src/menu/main/ui/login.rs +++ b/voxygen/src/menu/main/ui/login.rs @@ -17,8 +17,6 @@ use crate::{ use iced::{button, text_input, Align, Column, Container, Length, Row, Space, Text, TextInput}; use vek::*; -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); const FILL_FRAC_ONE: f32 = 0.77; const FILL_FRAC_TWO: f32 = 0.53; const INPUT_WIDTH: u16 = 250; diff --git a/voxygen/src/menu/main/ui/mod.rs b/voxygen/src/menu/main/ui/mod.rs index bca5c332fc..629803e53c 100644 --- a/voxygen/src/menu/main/ui/mod.rs +++ b/voxygen/src/menu/main/ui/mod.rs @@ -9,19 +9,19 @@ use crate::{ ui::{ self, fonts::IcedFonts as Fonts, - ice::{Element, IcedUi as Ui}, + ice::{style, widget, Element, Font, IcedUi as Ui}, img_ids::{ImageGraphic, VoxelGraphic}, Graphic, }, GlobalState, }; +use iced::{text_input, Column, Container, HorizontalAlignment, Length}; //ImageFrame, Tooltip, use crate::settings::Settings; use common::assets::Asset; use image::DynamicImage; use rand::{seq::SliceRandom, thread_rng, Rng}; use std::time::Duration; -use ui::ice::widget; // TODO: what is this? (showed up in rebase) //const COL1: Color = Color::Rgba(0.07, 0.1, 0.1, 0.9); @@ -30,8 +30,9 @@ use ui::ice::widget; /*const UI_MAIN: Color = Color::Rgba(0.61, 0.70, 0.70, 1.0); // Greenish Blue const UI_HIGHLIGHT_0: Color = Color::Rgba(0.79, 1.09, 1.09, 1.0);*/ -use iced::{text_input, Column, Container, HorizontalAlignment, Length}; -use ui::ice::style; +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); + image_ids_ice! { struct Imgs { @@ -227,8 +228,8 @@ impl Controls { let button_style = style::button::Style::new(self.imgs.button) .hover_image(self.imgs.button_hover) .press_image(self.imgs.button_press) - .text_color(login::TEXT_COLOR) - .disabled_text_color(login::DISABLED_TEXT_COLOR); + .text_color(TEXT_COLOR) + .disabled_text_color(DISABLED_TEXT_COLOR); let version = iced::Text::new(&self.version) .size(self.fonts.cyri.scale(15)) @@ -432,7 +433,6 @@ pub struct MainMenuUi { ui: Ui, // TODO: re add this // tip_no: u16, - pub show_iced: bool, controls: Controls, } @@ -453,7 +453,7 @@ impl<'a> MainMenuUi { .unwrap() .read_to_end(&mut buf) .unwrap(); - ui::ice::Font::try_from_vec(buf).unwrap() + Font::try_from_vec(buf).unwrap() }; let mut ui = Ui::new(&mut global_state.window, font).unwrap(); diff --git a/voxygen/src/ui/ice/renderer/style/button.rs b/voxygen/src/ui/ice/renderer/style/button.rs index 20f02a5264..4c4de0e58b 100644 --- a/voxygen/src/ui/ice/renderer/style/button.rs +++ b/voxygen/src/ui/ice/renderer/style/button.rs @@ -6,6 +6,7 @@ struct Background { default: image::Handle, hover: image::Handle, press: image::Handle, + color: Color, } impl Background { @@ -14,6 +15,7 @@ impl Background { default: image, hover: image, press: image, + color: Color::WHITE, } } } @@ -58,6 +60,15 @@ impl Style { self } + // TODO: this needs to be refactored since the color isn't used if there is no + // background + pub fn image_color(mut self, color: Color) -> Self { + if let Some(background) = &mut self.background { + background.color = color; + } + self + } + pub fn text_color(mut self, color: Color) -> Self { self.enabled_text = color; self diff --git a/voxygen/src/ui/ice/renderer/widget/mod.rs b/voxygen/src/ui/ice/renderer/widget/mod.rs index 9cdb7b5e81..3cd501dcf3 100644 --- a/voxygen/src/ui/ice/renderer/widget/mod.rs +++ b/voxygen/src/ui/ice/renderer/widget/mod.rs @@ -5,6 +5,7 @@ mod column; mod compound_graphic; mod container; mod image; +mod overlay; mod row; mod scrollable; mod slider; diff --git a/voxygen/src/ui/ice/renderer/widget/overlay.rs b/voxygen/src/ui/ice/renderer/widget/overlay.rs new file mode 100644 index 0000000000..8fac3ed675 --- /dev/null +++ b/voxygen/src/ui/ice/renderer/widget/overlay.rs @@ -0,0 +1,36 @@ +use super::super::{super::widget::overlay, IcedRenderer, Primitive}; +use iced::{mouse::Interaction, Element, Layout, Point, Rectangle}; + +const BORDER_SIZE: u16 = 8; + +impl overlay::Renderer for IcedRenderer { + fn draw( + &mut self, + defaults: &Self::Defaults, + _bounds: Rectangle, + cursor_position: Point, + over: &Element<'_, M, Self>, + over_layout: Layout<'_>, + under: &Element<'_, M, Self>, + under_layout: Layout<'_>, + ) -> Self::Output { + let (under, under_mouse_interaction) = + under.draw(self, defaults, under_layout, cursor_position); + + let (over, over_mouse_interaction) = + over.draw(self, defaults, over_layout, cursor_position); + + // TODO: this isn't perfect but should be obselete when iced gets layer support + let mouse_interaction = if over_mouse_interaction == Interaction::Idle { + under_mouse_interaction + } else { + over_mouse_interaction + }; + + let prim = Primitive::Group { + primitives: vec![under, over], + }; + + (prim, mouse_interaction) + } +} diff --git a/voxygen/src/ui/ice/widget/mod.rs b/voxygen/src/ui/ice/widget/mod.rs index 18034b58ec..b7db600fea 100644 --- a/voxygen/src/ui/ice/widget/mod.rs +++ b/voxygen/src/ui/ice/widget/mod.rs @@ -3,6 +3,7 @@ pub mod background_container; pub mod compound_graphic; pub mod fill_text; pub mod image; +pub mod overlay; pub mod stack; pub use self::{ @@ -10,4 +11,5 @@ pub use self::{ background_container::{BackgroundContainer, Padding}, fill_text::FillText, image::Image, + overlay::Overlay, }; diff --git a/voxygen/src/ui/ice/widget/overlay.rs b/voxygen/src/ui/ice/widget/overlay.rs new file mode 100644 index 0000000000..5cc0cc96ba --- /dev/null +++ b/voxygen/src/ui/ice/widget/overlay.rs @@ -0,0 +1,222 @@ +use iced::{ + layout, mouse, Align, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, + Size, Widget, +}; +use std::hash::Hash; + +/// A widget used to overlay one widget on top of another +/// Layout behaves similar to the iced::Container widget +/// Manages filtering out mouse input for the back widget if the mouse is over +/// the front widget +/// Alignment and padding is used for the front widget +pub struct Overlay<'a, M, R: self::Renderer> { + padding: u16, + width: Length, + height: Length, + max_width: u32, + max_height: u32, + horizontal_alignment: Align, + vertical_alignment: Align, + over: Element<'a, M, R>, + under: Element<'a, M, R>, + // add style etc as needed +} + +impl<'a, M, R> Overlay<'a, M, R> +where + R: self::Renderer, +{ + pub fn new(over: O, under: U) -> Self + where + O: Into>, + U: Into>, + { + Self { + padding: 0, + width: Length::Shrink, + height: Length::Shrink, + max_width: u32::MAX, + max_height: u32::MAX, + horizontal_alignment: Align::Start, + vertical_alignment: Align::Start, + over: over.into(), + under: under.into(), + } + } + + pub fn padding(mut self, pad: u16) -> Self { + self.padding = pad; + self + } + + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + pub fn max_width(mut self, max_width: u32) -> Self { + self.max_width = max_width; + self + } + + pub fn max_height(mut self, max_height: u32) -> Self { + self.max_height = max_height; + self + } + + pub fn align_x(mut self, align_x: Align) -> Self { + self.horizontal_alignment = align_x; + self + } + + pub fn align_y(mut self, align_y: Align) -> Self { + self.vertical_alignment = align_y; + self + } + + pub fn center_x(mut self) -> Self { + self.horizontal_alignment = Align::Center; + self + } + + pub fn center_y(mut self) -> Self { + self.vertical_alignment = Align::Center; + self + } +} + +impl<'a, M, R> Widget for Overlay<'a, M, R> +where + R: self::Renderer, +{ + fn width(&self) -> Length { self.width } + + fn height(&self) -> Length { self.height } + + fn layout(&self, renderer: &R, limits: &layout::Limits) -> layout::Node { + let padding = self.padding as f32; + + let limits = limits + .loose() + .max_width(self.max_width) + .max_height(self.max_height) + .width(self.width) + .height(self.height); + + let under = self.under.layout(renderer, &limits.loose()); + let under_size = under.size(); + + let limits = limits.pad(padding); + let mut over = self.under.layout(renderer, &limits.loose()); + let over_size = over.size(); + + let size = limits.resolve(Size { + width: under_size.width.max(over_size.width), + height: under_size.width.max(over_size.width), + }); + + over.move_to(Point::new(padding, padding)); + over.align(self.horizontal_alignment, self.vertical_alignment, size); + + layout::Node::with_children(size.pad(padding), vec![over, under]) + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + renderer: &R, + clipboard: Option<&dyn Clipboard>, + ) { + self.over.on_event( + event.clone(), + layout, + cursor_position, + messages, + renderer, + clipboard, + ); + + // If mouse press check if over the overlay widget before sending to under + // widget + if !matches!(&event, Event::Mouse(mouse::Event::ButtonPressed(_))) + || !layout + .children() + .next() + .unwrap() + .bounds() + .contains(cursor_position) + { + self.under.on_event( + event, + layout, + cursor_position, + messages, + renderer, + clipboard, + ); + } + } + + fn draw( + &self, + renderer: &mut R, + defaults: &R::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> R::Output { + let mut children = layout.children(); + renderer.draw( + defaults, + layout.bounds(), + cursor_position, + &self.over, + children.next().unwrap(), + &self.under, + children.next().unwrap(), + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + struct Marker; + std::any::TypeId::of::().hash(state); + + self.padding.hash(state); + self.width.hash(state); + self.height.hash(state); + self.max_width.hash(state); + self.max_height.hash(state); + + self.over.hash_layout(state); + self.under.hash_layout(state); + } +} + +pub trait Renderer: iced::Renderer { + fn draw( + &mut self, + defaults: &Self::Defaults, + bounds: Rectangle, + cursor_position: Point, + //style: &self::Style, + over: &Element<'_, M, Self>, + over_layout: Layout<'_>, + under: &Element<'_, M, Self>, + under_layout: Layout<'_>, + ) -> Self::Output; +} + +impl<'a, M, R> From> for Element<'a, M, R> +where + R: 'a + self::Renderer, + M: 'a, +{ + fn from(overlay: Overlay<'a, M, R>) -> Element<'a, M, R> { Element::new(overlay) } +}