diff --git a/common/src/comp/body/humanoid.rs b/common/src/comp/body/humanoid.rs index ccbb8f73c4..2658672127 100644 --- a/common/src/comp/body/humanoid.rs +++ b/common/src/comp/body/humanoid.rs @@ -58,7 +58,7 @@ impl Body { self.hair_color = self.hair_color.min(self.species.num_hair_colors() - 1); self.skin = self.skin.min(self.species.num_skin_colors() - 1); self.eyes = self.eyes.min(self.species.num_eyes(self.body_type) - 1); - self.eye_color = self.hair_style.min(self.species.num_eye_colors() - 1); + self.eye_color = self.eye_color.min(self.species.num_eye_colors() - 1); self.accessory = self .accessory .min(self.species.num_accessories(self.body_type) - 1); diff --git a/voxygen/src/menu/char_selection/ui/mod.rs b/voxygen/src/menu/char_selection/ui/mod.rs index 62129cedd7..c338207aab 100644 --- a/voxygen/src/menu/char_selection/ui/mod.rs +++ b/voxygen/src/menu/char_selection/ui/mod.rs @@ -32,8 +32,8 @@ use crate::settings::Settings; //use std::time::Duration; //use ui::ice::widget; use iced::{ - button, scrollable, text_input, Align, Button, Column, Container, HorizontalAlignment, Length, - Row, Scrollable, Space, Text, TextInput, + button, scrollable, slider, text_input, Align, Button, Column, Container, HorizontalAlignment, + Length, Row, Scrollable, Slider, Space, Text, TextInput, }; use vek::Rgba; @@ -44,6 +44,7 @@ const FILL_FRAC_ONE: f32 = 0.77; const FILL_FRAC_TWO: f32 = 0.60; const TOOLTIP_HOVER_DUR: std::time::Duration = std::time::Duration::from_millis(150); const TOOLTIP_FADE_DUR: std::time::Duration = std::time::Duration::from_millis(350); +const BANNER_ALPHA: u8 = 210; const STARTER_HAMMER: &str = "common.items.weapons.hammer.starter_hammer"; const STARTER_BOW: &str = "common.items.weapons.bow.starter_bow"; @@ -51,14 +52,17 @@ const STARTER_AXE: &str = "common.items.weapons.axe.starter_axe"; const STARTER_STAFF: &str = "common.items.weapons.staff.starter_staff"; const STARTER_SWORD: &str = "common.items.weapons.sword.starter_sword"; const STARTER_SCEPTRE: &str = "common.items.weapons.sceptre.starter_sceptre"; +// TODO: what does this comment mean? // // Use in future MR to make this a starter weapon -// TODO: look into what was using this in old ui +// TODO: use for info popup frame/background and for char list scrollbar const UI_MAIN: iced::Color = iced::Color::from_rgba(0.61, 0.70, 0.70, 1.0); // Greenish Blue image_ids_ice! { struct Imgs { + frame_bottom: "voxygen.element.frames.banner_bot", + slider_range: "voxygen.element.slider.track", slider_indicator: "voxygen.element.slider.indicator", @@ -139,7 +143,6 @@ enum Mode { new_character_button: button::State, logout_button: button::State, enter_world_button: button::State, - change_server_button: button::State, yes_button: button::State, no_button: button::State, }, @@ -152,6 +155,7 @@ enum Mode { body_type_buttons: [button::State; 2], species_buttons: [button::State; 6], tool_buttons: [button::State; 6], + sliders: Sliders, scroll: scrollable::State, name_input: text_input::State, back_button: button::State, @@ -170,7 +174,6 @@ impl Mode { new_character_button: Default::default(), logout_button: Default::default(), enter_world_button: Default::default(), - change_server_button: Default::default(), yes_button: Default::default(), no_button: Default::default(), } @@ -193,6 +196,7 @@ impl Mode { body_type_buttons: Default::default(), species_buttons: Default::default(), tool_buttons: Default::default(), + sliders: Default::default(), scroll: Default::default(), name_input: Default::default(), back_button: Default::default(), @@ -248,7 +252,6 @@ enum Message { EnterWorld, Select(usize), Delete(usize), - ChangeServer, NewCharacter, CreateCharacter, Name(String), @@ -258,6 +261,16 @@ enum Message { RandomizeCharacter, CancelDeletion, ConfirmDeletion, + HairStyle(u8), + HairColor(u8), + Skin(u8), + Eyes(u8), + EyeColor(u8), + Accessory(u8), + Beard(u8), + // Workaround for widgets that require a message but we don't want them to actually do + // anything + DoNothing, } impl Controls { @@ -334,7 +347,6 @@ impl Controls { ref mut new_character_button, ref mut logout_button, ref mut enter_world_button, - ref mut change_server_button, ref mut yes_button, ref mut no_button, } => { @@ -343,24 +355,11 @@ impl Controls { Text::new(&client.server_info.name) .size(fonts.cyri.scale(25)) .into(), - Container::new(neat_button( - change_server_button, - i18n.get("char_selection.change_server"), - FILL_FRAC_TWO, - button_style, - Some(Message::ChangeServer), - )) - .height(Length::Units(35)) - .into(), ]) .spacing(5) .align_items(Align::Center), ) - .style(style::container::Style::color_with_image_border( - Rgba::new(0, 0, 0, 217), - imgs.gray_corner, - imgs.gray_edge, - )) + .style(style::container::Style::color(Rgba::new(0, 0, 0, 217))) .padding(12) .center_x() .width(Length::Fill); @@ -490,23 +489,32 @@ impl Controls { // TODO: could replace column with scrollable completely if it had a with // children method - let characters = Container::new( - Scrollable::new(characters_scroll) - .push(Column::with_children(characters).spacing(4)), - ) - .style(style::container::Style::color_with_image_border( - Rgba::new(0, 0, 0, 217), - imgs.gray_corner, - imgs.gray_edge, - )) - .padding(9) - .width(Length::Fill) + let characters = Column::with_children(vec![ + Container::new( + Scrollable::new(characters_scroll) + .push(Column::with_children(characters).spacing(4)) + .width(Length::Fill), + ) + .padding(5) + .style(style::container::Style::color(Rgba::from_translucent( + 0, + BANNER_ALPHA, + ))) + .width(Length::Units(320)) + .height(Length::Fill) + .center_x() + .into(), + Image::new(imgs.frame_bottom) + .height(Length::Units(40)) + .width(Length::Units(320)) + .color(Rgba::from_translucent(0, BANNER_ALPHA)) + .into(), + ]) .height(Length::Fill); let right_column = Column::with_children(vec![server.into(), characters.into()]) - .padding(15) .spacing(10) - .width(Length::Units(360)) // TODO: see if we can get iced to work with settings below + .width(Length::Units(320)) // TODO: see if we can get iced to work with settings below //.max_width(360) //.width(Length::Fill) .height(Length::Fill); @@ -622,6 +630,7 @@ impl Controls { ref mut body_type_buttons, ref mut species_buttons, ref mut tool_buttons, + ref mut sliders, ref mut name_input, ref mut back_button, ref mut create_button, @@ -639,7 +648,7 @@ impl Controls { Container::new( Button::<_, IcedRenderer>::new( button, - Space::new(Length::Units(70), Length::Units(70)), + Space::new(Length::Units(60), Length::Units(60)), ) .style(if selected { selected_style @@ -830,42 +839,183 @@ impl Controls { ]) .spacing(1); - let column_content = vec![body_type.into(), species.into(), tool.into()]; + const SLIDER_TEXT_SIZE: u16 = 20; + const SLIDER_CURSOR_SIZE: (u16, u16) = (9, 21); + const SLIDER_BAR_HEIGHT: u16 = 9; + const SLIDER_BAR_PAD: u16 = 5; - let right_column = Container::new( + fn char_slider<'a>( + text: &str, + state: &'a mut slider::State, + max: u8, + selected_val: u8, + on_change: impl 'static + Fn(u8) -> Message, + (fonts, imgs): (&Fonts, &Imgs), + ) -> Element<'a, Message> { Column::with_children(vec![ - Text::new(i18n.get("char_selection.character_creation")) - .size(fonts.cyri.scale(26)) + Text::new(text) + .size(fonts.cyri.scale(SLIDER_TEXT_SIZE)) .into(), - Scrollable::new(scroll) - .push( - Column::with_children(column_content) - .align_items(Align::Center) - .spacing(16), - ) + Slider::new(state, 0..=max, selected_val, on_change) + .style(style::slider::Style::images( + imgs.slider_indicator, + imgs.slider_range, + SLIDER_BAR_PAD, + SLIDER_CURSOR_SIZE, + SLIDER_BAR_HEIGHT, + )) .into(), ]) - .spacing(20) - .padding(10) - .width(Length::Fill) - .align_items(Align::Center), + .align_items(Align::Center) + .into() + }; + fn char_slider_greyable<'a>( + active: bool, + text: &str, + state: &'a mut slider::State, + max: u8, + selected_val: u8, + on_change: impl 'static + Fn(u8) -> Message, + (fonts, imgs): (&Fonts, &Imgs), + ) -> Element<'a, Message> { + if active { + char_slider(text, state, max, selected_val, on_change, (fonts, imgs)) + } else { + Column::with_children(vec![ + Text::new(text) + .size(fonts.cyri.scale(SLIDER_TEXT_SIZE)) + .color(DISABLED_TEXT_COLOR) + .into(), + // "Disabled" slider + // TODO: add iced support for disabled sliders (like buttons) + Slider::new(state, 0..=max, selected_val, |_| Message::DoNothing) + .style(style::slider::Style { + cursor: style::slider::Cursor::Color(Rgba::zero()), + bar: style::slider::Bar::Image( + imgs.slider_range, + Rgba::from_translucent(255, 51), + SLIDER_BAR_PAD, + ), + labels: false, + ..Default::default() + }) + .into(), + ]) + .align_items(Align::Center) + .into() + } + }; + + let slider_options = Column::with_children(vec![ + char_slider( + i18n.get("char_selection.hair_style"), + &mut sliders.hair_style, + body.species.num_hair_styles(body.body_type) - 1, + body.hair_style, + Message::HairStyle, + (fonts, imgs), + ), + char_slider( + i18n.get("char_selection.hair_color"), + &mut sliders.hair_color, + body.species.num_hair_colors() - 1, + body.hair_color, + Message::HairColor, + (fonts, imgs), + ), + char_slider( + i18n.get("char_selection.skin"), + &mut sliders.skin, + body.species.num_skin_colors() - 1, + body.skin, + Message::Skin, + (fonts, imgs), + ), + char_slider( + i18n.get("char_selection.eyeshape"), + &mut sliders.eyes, + body.species.num_eyes(body.body_type) - 1, + body.eyes, + Message::Eyes, + (fonts, imgs), + ), + char_slider( + i18n.get("char_selection.eye_color"), + &mut sliders.eye_color, + body.species.num_eye_colors() - 1, + body.eye_color, + Message::EyeColor, + (fonts, imgs), + ), + char_slider_greyable( + body.species.num_accessories(body.body_type) > 1, + i18n.get("char_selection.accessories"), + &mut sliders.accessory, + body.species.num_accessories(body.body_type) - 1, + body.accessory, + Message::Accessory, + (fonts, imgs), + ), + char_slider_greyable( + body.species.num_beards(body.body_type) > 1, + i18n.get("char_selection.beard"), + &mut sliders.beard, + body.species.num_beards(body.body_type) - 1, + body.beard, + Message::Beard, + (fonts, imgs), + ), + ]) + .max_width(200) + .spacing(3) + .padding(5); + + let column_content = vec![ + body_type.into(), + species.into(), + tool.into(), + slider_options.into(), + ]; + + let right_column = Container::new( + Scrollable::new(scroll) + .push( + Column::with_children(column_content) + .align_items(Align::Center) + .width(Length::Fill) + .spacing(5), + ) + .padding(5) + .width(Length::Fill) + .align_items(Align::Center), ) - .style(style::container::Style::color_with_image_border( - Rgba::new(0, 0, 0, 217), - imgs.gray_corner, - imgs.gray_edge, - )) - .padding(10) - .width(Length::Units(360)) // TODO: see if we can get iced to work with settings below + .width(Length::Units(320)) // TODO: see if we can get iced to work with settings below //.max_width(360) //.width(Length::Fill) .height(Length::Fill); + let right_column = Column::with_children(vec![ + Container::new(right_column) + .style(style::container::Style::color(Rgba::from_translucent( + 0, + BANNER_ALPHA, + ))) + .width(Length::Units(320)) + .center_x() + .into(), + Image::new(imgs.frame_bottom) + .height(Length::Units(40)) + .width(Length::Units(320)) + .color(Rgba::from_translucent(0, BANNER_ALPHA)) + .into(), + ]) + .height(Length::Fill); + let top = Row::with_children(vec![ right_column.into(), MouseDetector::new(&mut self.mouse_detector, Length::Fill, Length::Fill).into(), ]) - .padding(15) + .padding(10) .width(Length::Fill) .height(Length::Fill); @@ -1001,9 +1151,6 @@ impl Controls { *info_content = Some(InfoContent::Deletion(idx)); } }, - Message::ChangeServer => { - events.push(Event::Logout); - }, Message::NewCharacter => { if matches!(&self.mode, Mode::Select { .. }) { self.mode = Mode::create(String::new()); @@ -1069,6 +1216,49 @@ impl Controls { } } }, + Message::HairStyle(value) => { + if let Mode::Create { body, .. } = &mut self.mode { + body.hair_style = value; + body.validate(); + } + }, + Message::HairColor(value) => { + if let Mode::Create { body, .. } = &mut self.mode { + body.hair_color = value; + body.validate(); + } + }, + Message::Skin(value) => { + if let Mode::Create { body, .. } = &mut self.mode { + body.skin = value; + body.validate(); + } + }, + Message::Eyes(value) => { + if let Mode::Create { body, .. } = &mut self.mode { + body.eyes = value; + body.validate(); + } + }, + Message::EyeColor(value) => { + if let Mode::Create { body, .. } = &mut self.mode { + body.eye_color = value; + body.validate(); + } + }, + Message::Accessory(value) => { + if let Mode::Create { body, .. } = &mut self.mode { + body.accessory = value; + body.validate(); + } + }, + Message::Beard(value) => { + if let Mode::Create { body, .. } = &mut self.mode { + body.beard = value; + body.validate(); + } + }, + Message::DoNothing => {}, } } @@ -1184,3 +1374,14 @@ impl CharSelectionUi { // TODO: do we need globals? pub fn render(&self, renderer: &mut Renderer) { self.ui.render(renderer); } } + +#[derive(Default)] +struct Sliders { + hair_style: slider::State, + hair_color: slider::State, + skin: slider::State, + eyes: slider::State, + eye_color: slider::State, + accessory: slider::State, + beard: slider::State, +} diff --git a/voxygen/src/menu/main/ui/servers.rs b/voxygen/src/menu/main/ui/servers.rs index e84ed5fe27..b07804ee63 100644 --- a/voxygen/src/menu/main/ui/servers.rs +++ b/voxygen/src/menu/main/ui/servers.rs @@ -3,12 +3,7 @@ use crate::{ i18n::Localization, ui::{ fonts::IcedFonts as Fonts, - ice::{ - component::neat_button, - style, - widget::background_container::{BackgroundContainer, Padding}, - Element, - }, + ice::{component::neat_button, style, Element}, }, }; use iced::{ diff --git a/voxygen/src/ui/ice/renderer/style/slider.rs b/voxygen/src/ui/ice/renderer/style/slider.rs index 6c6da652ac..08a12f8913 100644 --- a/voxygen/src/ui/ice/renderer/style/slider.rs +++ b/voxygen/src/ui/ice/renderer/style/slider.rs @@ -6,6 +6,8 @@ pub struct Style { pub cursor: Cursor, pub bar: Bar, pub labels: bool, + pub cursor_size: (u16, u16), + pub bar_height: u16, } impl Default for Style { @@ -14,6 +16,8 @@ impl Default for Style { cursor: Cursor::Color(Rgba::new(0.5, 0.5, 0.5, 1.0)), bar: Bar::Color(Rgba::new(0.5, 0.5, 0.5, 1.0)), labels: false, + cursor_size: (8, 16), + bar_height: 6, } } } @@ -27,6 +31,23 @@ pub enum Cursor { #[derive(Clone, Copy)] pub enum Bar { Color(Rgba), - Image(image::Handle, Rgba), + Image(image::Handle, Rgba, u16), } +impl Style { + pub fn images( + cursor: image::Handle, + bar: image::Handle, + bar_pad: u16, + cursor_size: (u16, u16), + bar_height: u16, + ) -> Self { + Self { + cursor: Cursor::Image(cursor, Rgba::white()), + bar: Bar::Image(bar, Rgba::white(), bar_pad), + labels: false, + cursor_size, + bar_height, + } + } +} diff --git a/voxygen/src/ui/ice/renderer/widget/overlay.rs b/voxygen/src/ui/ice/renderer/widget/overlay.rs index 8fac3ed675..7a602917ec 100644 --- a/voxygen/src/ui/ice/renderer/widget/overlay.rs +++ b/voxygen/src/ui/ice/renderer/widget/overlay.rs @@ -1,8 +1,6 @@ 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, diff --git a/voxygen/src/ui/ice/renderer/widget/scrollable.rs b/voxygen/src/ui/ice/renderer/widget/scrollable.rs index 014ccd12e3..6c176fb7d0 100644 --- a/voxygen/src/ui/ice/renderer/widget/scrollable.rs +++ b/voxygen/src/ui/ice/renderer/widget/scrollable.rs @@ -3,9 +3,9 @@ use common::util::srgba_to_linear; use iced::{mouse, scrollable, Rectangle}; use style::scrollable::{Scroller, Track}; -const SCROLLBAR_WIDTH: u16 = 10; +const SCROLLBAR_WIDTH: u16 = 6; const SCROLLBAR_MIN_HEIGHT: u16 = 6; -const SCROLLBAR_MARGIN: u16 = 2; +const SCROLLBAR_MARGIN: u16 = 1; impl scrollable::Renderer for IcedRenderer { type Style = style::scrollable::Style; diff --git a/voxygen/src/ui/ice/renderer/widget/slider.rs b/voxygen/src/ui/ice/renderer/widget/slider.rs index e58c6acc80..bdb8321f9b 100644 --- a/voxygen/src/ui/ice/renderer/widget/slider.rs +++ b/voxygen/src/ui/ice/renderer/widget/slider.rs @@ -4,14 +4,12 @@ use core::ops::RangeInclusive; use iced::{mouse, slider, Point, Rectangle}; use style::slider::{Bar, Cursor, Style}; -const CURSOR_WIDTH: f32 = 10.0; -const CURSOR_HEIGHT: f32 = 16.0; -const BAR_HEIGHT: f32 = 18.0; +const CURSOR_DRAG_SHIFT: f32 = 0.7; impl slider::Renderer for IcedRenderer { type Style = Style; - const DEFAULT_HEIGHT: u16 = 20; + const DEFAULT_HEIGHT: u16 = 25; fn draw( &mut self, @@ -23,7 +21,8 @@ impl slider::Renderer for IcedRenderer { style: &Self::Style, ) -> Self::Output { let bar_bounds = Rectangle { - height: BAR_HEIGHT, + height: style.bar_height as f32, + y: bounds.y + (bounds.height - style.bar_height as f32) / 2.0, ..bounds }; let bar = match style.bar { @@ -31,20 +30,30 @@ impl slider::Renderer for IcedRenderer { bounds: bar_bounds, linear_color: srgba_to_linear(color), }, - Bar::Image(handle, color) => Primitive::Image { + // Note: bar_pad adds to the size of the bar currently since the dragging logic wouldn't + // account for shrinking the area that the cursor is shown in + Bar::Image(handle, color, bar_pad) => Primitive::Image { handle: (handle, Rotation::None), - bounds: bar_bounds, + bounds: Rectangle { + x: bar_bounds.x - bar_pad as f32, + width: bar_bounds.width + bar_pad as f32 * 2.0, + ..bar_bounds + }, color, }, }; - let (max, min) = range.into_inner(); - let offset = bounds.width as f32 * (max - min) / (value - min); + let (cursor_width, cursor_height) = style.cursor_size; + let (cursor_width, cursor_height) = (f32::from(cursor_width), f32::from(cursor_height)); + let (min, max) = range.into_inner(); + let offset = bounds.width as f32 * (value - min) / (max - min); let cursor_bounds = Rectangle { - x: bounds.x + offset - CURSOR_WIDTH / 2.0, - y: bounds.y + if is_dragging { 2.0 } else { 0.0 }, - width: CURSOR_WIDTH, - height: CURSOR_HEIGHT, + x: bounds.x + offset - cursor_width / 2.0, + y: bounds.y + + if is_dragging { CURSOR_DRAG_SHIFT } else { 0.0 } + + (bounds.height - cursor_height) / 2.0, + width: cursor_width, + height: cursor_height, }; let cursor = match style.cursor { Cursor::Color(color) => Primitive::Rectangle {