From 9bbc7b2d22e01896ce81bcb41a5d83d7b91cb7ad Mon Sep 17 00:00:00 2001 From: Aleksander Date: Sat, 28 Jun 2025 23:11:37 +0200 Subject: [PATCH] slider animations, ui tweaks --- wgui/src/components/button.rs | 5 +- wgui/src/components/slider.rs | 113 ++++++++++++++++++++++++---- wgui/src/drawing.rs | 11 ++- wgui/src/parser/component_button.rs | 17 ++++- wgui/src/parser/style.rs | 8 ++ 5 files changed, 133 insertions(+), 21 deletions(-) diff --git a/wgui/src/components/button.rs b/wgui/src/components/button.rs index 76543ae..fbc82a6 100644 --- a/wgui/src/components/button.rs +++ b/wgui/src/components/button.rs @@ -115,10 +115,13 @@ pub fn construct( parent, Rectangle::create(RectangleParams { color: params.color, + color2: params + .color + .lerp(&Color::new(0.0, 0.0, 0.0, params.color.a), 0.3), + gradient: drawing::GradientMode::Vertical, round: params.round, border_color: params.border_color, border: 2.0, - ..Default::default() })?, style, )?; diff --git a/wgui/src/components/slider.rs b/wgui/src/components/slider.rs index db9ad3d..f0c46b9 100644 --- a/wgui/src/components/slider.rs +++ b/wgui/src/components/slider.rs @@ -1,13 +1,17 @@ use std::sync::Arc; +use glam::{Mat4, Vec2, Vec3}; use taffy::prelude::{length, percent}; use crate::{ + animation::{Animation, AnimationEasing}, components::Component, drawing::{self}, - event::{EventListenerCollection, EventListenerKind, WidgetCallback}, + event::{self, EventListenerCollection, EventListenerKind, WidgetCallback}, layout::{Layout, WidgetID}, + renderer_vk::util, widget::{ + div::Div, rectangle::{Rectangle, RectangleParams}, util::WLength, }, @@ -54,6 +58,57 @@ impl Slider { } } +const BODY_COLOR: drawing::Color = drawing::Color::new(0.6, 0.65, 0.7, 1.0); +const BODY_BORDER_COLOR: drawing::Color = drawing::Color::new(0.4, 0.45, 0.5, 1.0); +const HANDLE_BORDER_COLOR: drawing::Color = drawing::Color::new(0.85, 0.85, 0.85, 1.0); +const HANDLE_BORDER_COLOR_HOVERED: drawing::Color = drawing::Color::new(0.0, 0.0, 0.0, 1.0); +const HANDLE_COLOR: drawing::Color = drawing::Color::new(1.0, 1.0, 1.0, 1.0); +const HANDLE_COLOR_HOVERED: drawing::Color = drawing::Color::new(0.9, 0.9, 0.9, 1.0); + +const SLIDER_HOVER_SCALE: f32 = 0.25; +fn get_anim_transform(pos: f32, widget_size: Vec2) -> Mat4 { + util::centered_matrix( + widget_size, + &Mat4::from_scale(Vec3::splat(SLIDER_HOVER_SCALE.mul_add(pos, 1.0))), + ) +} + +fn anim_rect(rect: &mut Rectangle, pos: f32) { + rect.params.color = drawing::Color::lerp(&HANDLE_COLOR, &HANDLE_COLOR_HOVERED, pos); + rect.params.border_color = + drawing::Color::lerp(&HANDLE_BORDER_COLOR, &HANDLE_BORDER_COLOR_HOVERED, pos); +} + +fn on_enter_anim(data: &mut event::CallbackData, handle_id: WidgetID) { + data.animations.push(Animation::new( + handle_id, + 5, + AnimationEasing::OutQuad, + Box::new(move |data| { + let rect = data.obj.get_as_mut::(); + data.data.transform = get_anim_transform(data.pos, data.widget_size); + anim_rect(rect, data.pos); + data.needs_redraw = true; + }), + )); +} + +fn on_leave_anim(data: &mut event::CallbackData, handle_id: WidgetID) { + data.animations.push(Animation::new( + handle_id, + 10, + AnimationEasing::OutQuad, + Box::new(move |data| { + let rect = data.obj.get_as_mut::(); + data.data.transform = get_anim_transform(1.0 - data.pos, data.widget_size); + anim_rect(rect, 1.0 - data.pos); + data.needs_redraw = true; + }), + )); +} + +const PAD_PERCENT: f32 = 0.75; + pub fn construct( layout: &mut Layout, listeners: &mut EventListenerCollection, @@ -64,35 +119,57 @@ pub fn construct( style.position = taffy::Position::Relative; style.min_size = style.size; style.max_size = style.size; + style.align_items = Some(taffy::AlignItems::Center); + style.justify_content = Some(taffy::JustifyContent::Center); - let body_color = drawing::Color::new(0.2, 0.3, 0.4, 1.0); - let body_border_color = drawing::Color::new(0.1, 0.2, 0.3, 1.0); - let handle_color = drawing::Color::new(1.0, 1.0, 1.0, 1.0); + let (body_id, _) = layout.add_child(parent, Div::create()?, style)?; - let (body_id, _) = layout.add_child( - parent, + let (_background_id, _) = layout.add_child( + body_id, Rectangle::create(RectangleParams { - color: body_color, + color: BODY_COLOR, round: WLength::Percent(1.0), - border_color: body_border_color, + border_color: BODY_BORDER_COLOR, border: 2.0, ..Default::default() })?, - style, + taffy::Style { + size: taffy::Size { + width: percent(1.0), + height: percent(PAD_PERCENT), + }, + position: taffy::Position::Absolute, + ..Default::default() + }, )?; let mut handle_style = taffy::Style::default(); handle_style.size.width = length(32.0); handle_style.size.height = percent(1.0); + handle_style.position = taffy::Position::Absolute; + handle_style.align_items = Some(taffy::AlignItems::Center); + handle_style.justify_content = Some(taffy::JustifyContent::Center); - let (slider_handle_id, slider_handle_node) = layout.add_child( - body_id, + // invisible outer handle body + let (slider_handle_id, slider_handle_node) = + layout.add_child(body_id, Div::create()?, handle_style)?; + + let (slider_handle_rect_id, _) = layout.add_child( + slider_handle_id, Rectangle::create(RectangleParams { - color: handle_color, + color: HANDLE_COLOR, + border_color: HANDLE_BORDER_COLOR, + border: 2.0, round: WLength::Percent(1.0), ..Default::default() })?, - handle_style, + taffy::Style { + size: taffy::Size { + width: percent(PAD_PERCENT), + height: percent(PAD_PERCENT), + }, + ..Default::default() + }, )?; let slider = Arc::new(Slider { @@ -106,7 +183,10 @@ pub fn construct( listeners.add( body_id, EventListenerKind::MouseEnter, - Box::new(move |_data, _, _| {}), + Box::new(move |data, _, _| { + data.trigger_haptics = true; + on_enter_anim(data, slider_handle_rect_id); + }), ); listeners.add( @@ -118,7 +198,10 @@ pub fn construct( listeners.add( body_id, EventListenerKind::MouseLeave, - Box::new(move |_data, _, _| {}), + Box::new(move |data, _, _| { + data.trigger_haptics = true; + on_leave_anim(data, slider_handle_rect_id); + }), ); Ok(slider) diff --git a/wgui/src/drawing.rs b/wgui/src/drawing.rs index 9f4d1cb..e99cc33 100644 --- a/wgui/src/drawing.rs +++ b/wgui/src/drawing.rs @@ -47,9 +47,18 @@ pub struct Color { } impl Color { - pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self { + pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self { Self { r, g, b, a } } + + pub fn lerp(&self, other: &Color, n: f32) -> Color { + Color { + r: self.r * (1.0 - n) + other.r * n, + g: self.g * (1.0 - n) + other.g * n, + b: self.b * (1.0 - n) + other.b * n, + a: self.a * (1.0 - n) + other.a * n, + } + } } impl Default for Color { diff --git a/wgui/src/parser/component_button.rs b/wgui/src/parser/component_button.rs index 2766e49..582f5ce 100644 --- a/wgui/src/parser/component_button.rs +++ b/wgui/src/parser/component_button.rs @@ -4,7 +4,7 @@ use crate::{ layout::WidgetID, parser::{ ParserContext, ParserFile, iter_attribs, - style::{parse_color, parse_round, parse_style, parse_text_style}, + style::{parse_color, parse_color_opt, parse_round, parse_style, parse_text_style}, }, widget::util::WLength, }; @@ -16,7 +16,7 @@ pub fn parse_component_button<'a, U1, U2>( parent_id: WidgetID, ) -> anyhow::Result<()> { let mut color = Color::new(1.0, 1.0, 1.0, 1.0); - let mut border_color = Color::new(0.0, 0.0, 0.0, 1.0); + let mut border_color: Option = None; let mut round = WLength::Units(4.0); let mut text = String::default(); @@ -37,19 +37,28 @@ pub fn parse_component_button<'a, U1, U2>( parse_color(&value, &mut color); } "border_color" => { - parse_color(&value, &mut border_color); + parse_color_opt(&value, &mut border_color); } _ => {} } } + // slight border outlines by default + if border_color.is_none() { + border_color = Some(Color::lerp( + &color, + &Color::new(0.0, 0.0, 0.0, color.a), + 0.3, + )); + } + let _button = button::construct( ctx.layout, ctx.listeners, parent_id, button::Params { color, - border_color, + border_color: border_color.unwrap(), text: &text, style, text_style, diff --git a/wgui/src/parser/style.rs b/wgui/src/parser/style.rs index cb39040..8736a94 100644 --- a/wgui/src/parser/style.rs +++ b/wgui/src/parser/style.rs @@ -37,6 +37,14 @@ pub fn parse_color(value: &str, color: &mut drawing::Color) { } } +pub fn parse_color_opt(value: &str, color: &mut Option) { + if let Some(res_color) = parse_color_hex(value) { + *color = Some(res_color); + } else { + print_invalid_value(value); + } +} + pub fn parse_text_style(attribs: &[(Rc, Rc)]) -> TextStyle { let mut style = TextStyle::default();