diff --git a/dash-frontend/assets/gui/tab/apps.xml b/dash-frontend/assets/gui/tab/apps.xml index 0f36747b..fd8bf484 100644 --- a/dash-frontend/assets/gui/tab/apps.xml +++ b/dash-frontend/assets/gui/tab/apps.xml @@ -34,7 +34,7 @@ id="app_list_parent" flex_direction="row" flex_wrap="wrap" - justify_content="stretch" + justify_content="center" gap="4" overflow_y="scroll" /> diff --git a/uidev/src/main.rs b/uidev/src/main.rs index 02ff8f5b..66afc7eb 100644 --- a/uidev/src/main.rs +++ b/uidev/src/main.rs @@ -335,12 +335,16 @@ fn main() -> Result<(), Box> { .begin_rendering(tgt, WGfxClearMode::Clear([0.0, 0.0, 0.0, 0.1])) .unwrap(); - let draw_params = wgui::drawing::DrawParams { - layout: &testbed.layout().borrow_mut(), + let mut layout = testbed.layout().borrow_mut(); + let mut draw_params = wgui::drawing::DrawParams { + layout: &mut layout, debug_draw: debug_draw_enabled, + alpha: timestep.alpha, }; - let primitives = wgui::drawing::draw(&draw_params).unwrap(); + let primitives = wgui::drawing::draw(&mut draw_params).unwrap(); + drop(layout); + let draw_result = render_context .draw(&mut shared_context, &mut cmd_buf, &primitives) .unwrap(); diff --git a/wgui/src/drawing.rs b/wgui/src/drawing.rs index 0ee45e21..4f96334f 100644 --- a/wgui/src/drawing.rs +++ b/wgui/src/drawing.rs @@ -6,10 +6,11 @@ use taffy::TraversePartialTree; use crate::{ drawing, + event::EventAlterables, layout::Widget, renderer_vk::text::{TextShadow, custom_glyph::CustomGlyph}, stack::{self, ScissorBoundary, ScissorStack, TransformStack}, - widget::{self}, + widget::{self, ScrollbarInfo, WidgetState}, }; use super::{layout::Layout, widget::DrawState}; @@ -34,8 +35,8 @@ impl Boundary { let transform = transform_stack.get(); Self { - pos: Vec2::new(transform.abs_pos.x, transform.abs_pos.y), - size: Vec2::new(transform.dim.x, transform.dim.y), + pos: transform.abs_pos, + size: transform.raw_dim, } } @@ -45,7 +46,7 @@ impl Boundary { Self { pos: Vec2::ZERO, - size: Vec2::new(transform.dim.x, transform.dim.y), + size: transform.raw_dim, } } @@ -152,8 +153,9 @@ pub enum RenderPrimitive { } pub struct DrawParams<'a> { - pub layout: &'a Layout, + pub layout: &'a mut Layout, pub debug_draw: bool, + pub alpha: f32, // timestep alpha, 0.0 - 1.0, used for motion interpolation if rendering above tick rate: smoother animations or scrolling } pub fn has_overflow_clip(style: &taffy::Style) -> bool { @@ -175,6 +177,45 @@ fn primitive_debug_rect(boundary: &Boundary, transform: &Mat4, color: drawing::C ) } +pub fn push_transform_stack( + transform_stack: &mut TransformStack, + l: &taffy::Layout, + scroll_shift: Vec2, + widget_state: &WidgetState, +) { + let raw_dim = Vec2::new(l.size.width, l.size.height); + let visual_dim = raw_dim + scroll_shift; + + transform_stack.push(stack::Transform { + rel_pos: Vec2::new(l.location.x, l.location.y) - scroll_shift, + transform: widget_state.data.transform, + raw_dim, + visual_dim, + abs_pos: Default::default(), + transform_rel: Default::default(), + }); +} + +/// returns true if scissor has been pushed +pub fn push_scissor_stack( + transform_stack: &mut TransformStack, + scissor_stack: &mut ScissorStack, + scroll_shift: Vec2, + info: &Option, + style: &taffy::Style, +) -> bool { + let scissor_pushed = info.is_some() && has_overflow_clip(style); + if !scissor_pushed { + return false; + } + + let mut boundary_absolute = drawing::Boundary::construct_absolute(transform_stack); + boundary_absolute.pos += scroll_shift; + scissor_stack.push(ScissorBoundary(boundary_absolute)); + + true +} + fn draw_widget( params: &DrawParams, state: &mut DrawState, @@ -189,17 +230,16 @@ fn draw_widget( let mut widget_state = widget.state(); - let (scroll_shift, info) = match widget::get_scrollbar_info(l) { - Some(info) => (widget_state.get_scroll_shift(&info, l), Some(info)), - None => (Vec2::default(), None), + let (scroll_shift, wants_redraw, info) = match widget::get_scrollbar_info(l) { + Some(info) => { + let (scrolling, wants_redraw) = widget_state.get_scroll_shift_smooth(&info, l, params.alpha); + (scrolling, wants_redraw, Some(info)) + } + None => (Vec2::default(), false, None), }; - state.transform_stack.push(stack::Transform { - rel_pos: Vec2::new(l.location.x, l.location.y) - scroll_shift, - transform: widget_state.data.transform, - dim: Vec2::new(l.size.width, l.size.height), - ..Default::default() - }); + // see layout.rs push_event_widget too + push_transform_stack(state.transform_stack, l, scroll_shift, &widget_state); if params.debug_draw { let boundary = drawing::Boundary::construct_relative(state.transform_stack); @@ -210,13 +250,9 @@ fn draw_widget( )); } - let scissor_pushed = info.is_some() && has_overflow_clip(style); + let scissor_pushed = push_scissor_stack(state.transform_stack, state.scissor_stack, scroll_shift, &info, style); if scissor_pushed { - let mut boundary_absolute = drawing::Boundary::construct_absolute(state.transform_stack); - boundary_absolute.pos += scroll_shift; - state.scissor_stack.push(ScissorBoundary(boundary_absolute)); - if params.debug_draw { let mut boundary_relative = drawing::Boundary::construct_relative(state.transform_stack); boundary_relative.pos += scroll_shift; @@ -254,6 +290,10 @@ fn draw_widget( if let Some(info) = &info { widget_state.draw_scrollbars(state, &draw_params, info); } + + if wants_redraw { + state.alterables.mark_redraw(); + } } fn draw_children(params: &DrawParams, state: &mut DrawState, parent_node_id: taffy::NodeId) { @@ -279,7 +319,7 @@ fn draw_children(params: &DrawParams, state: &mut DrawState, parent_node_id: taf } } -pub fn draw(params: &DrawParams) -> anyhow::Result> { +pub fn draw(params: &mut DrawParams) -> anyhow::Result> { let mut primitives = Vec::::new(); let mut transform_stack = TransformStack::new(); let mut scissor_stack = ScissorStack::new(); @@ -292,14 +332,18 @@ pub fn draw(params: &DrawParams) -> anyhow::Result> { panic!(); }; + let mut alterables = EventAlterables::default(); + let mut state = DrawState { primitives: &mut primitives, transform_stack: &mut transform_stack, scissor_stack: &mut scissor_stack, layout: params.layout, + alterables: &mut alterables, }; draw_widget(params, &mut state, params.layout.root_node, style, root_widget); + params.layout.process_alterables(alterables)?; Ok(primitives) } diff --git a/wgui/src/event.rs b/wgui/src/event.rs index 64944a34..ccbb84d2 100644 --- a/wgui/src/event.rs +++ b/wgui/src/event.rs @@ -1,5 +1,6 @@ use std::{ cell::{RefCell, RefMut}, + collections::HashSet, rc::Rc, }; @@ -75,9 +76,9 @@ pub enum Event { impl Event { fn test_transform_pos(transform: &Transform, pos: Vec2) -> bool { pos.x >= transform.abs_pos.x - && pos.x < transform.abs_pos.x + transform.dim.x + && pos.x < transform.abs_pos.x + transform.visual_dim.x && pos.y >= transform.abs_pos.y - && pos.y < transform.abs_pos.y + transform.dim.y + && pos.y < transform.abs_pos.y + transform.visual_dim.y } pub fn test_mouse_within_transform(&self, transform: &Transform) -> bool { @@ -96,6 +97,7 @@ pub struct EventAlterables { pub dirty_nodes: Vec, pub style_set_requests: Vec<(taffy::NodeId, taffy::Style)>, pub animations: Vec, + pub widgets_to_tick: HashSet, // widgets which needs to be ticked in the next `Layout::update()` fn pub transform_stack: TransformStack, pub scissor_stack: ScissorStack, pub needs_redraw: bool, @@ -115,6 +117,10 @@ impl EventAlterables { self.dirty_nodes.push(node_id); } + pub fn mark_tick(&mut self, widget_id: WidgetID) { + self.widgets_to_tick.insert(widget_id); + } + pub const fn trigger_haptics(&mut self) { self.trigger_haptics = true; } diff --git a/wgui/src/layout.rs b/wgui/src/layout.rs index 072997d7..fa041db9 100644 --- a/wgui/src/layout.rs +++ b/wgui/src/layout.rs @@ -7,15 +7,15 @@ use std::{ use crate::{ animation::Animations, components::{Component, InitData}, - drawing::{self, has_overflow_clip, Boundary}, + drawing::{self, Boundary, has_overflow_clip, push_scissor_stack, push_transform_stack}, event::{self, CallbackDataCommon, EventAlterables, EventListenerCollection}, globals::WguiGlobals, stack::{self, ScissorBoundary}, - widget::{self, div::WidgetDiv, EventParams, WidgetObj, WidgetState}, + widget::{self, EventParams, WidgetObj, WidgetState, div::WidgetDiv}, }; -use glam::{vec2, Vec2}; -use slotmap::{new_key_type, HopSlotMap, SecondaryMap}; +use glam::{Vec2, vec2}; +use slotmap::{HopSlotMap, SecondaryMap, new_key_type}; use taffy::{NodeId, TaffyTree, TraversePartialTree}; new_key_type! { @@ -112,7 +112,8 @@ pub struct LayoutState { pub struct Layout { pub state: LayoutState, - pub components_to_init: VecDeque, + pub components_to_init: Vec, + pub widgets_to_tick: Vec, pub root_widget: WidgetID, pub root_node: taffy::NodeId, @@ -218,25 +219,32 @@ impl Layout { self.needs_redraw = true; } - fn process_pending_components(&mut self) -> anyhow::Result<()> { - let mut alterables = EventAlterables::default(); - - while let Some(c) = self.components_to_init.pop_front() { + fn process_pending_components(&mut self, alterables: &mut EventAlterables) -> anyhow::Result<()> { + for comp in &self.components_to_init { let mut common = CallbackDataCommon { state: &self.state, - alterables: &mut alterables, + alterables, }; - c.0.init(&mut InitData { common: &mut common }); + comp.0.init(&mut InitData { common: &mut common }); } - - self.process_alterables(alterables)?; - + self.components_to_init.clear(); Ok(()) } + fn process_pending_widget_ticks(&mut self, alterables: &mut EventAlterables) { + for widget_id in &self.widgets_to_tick { + let Some(widget) = self.state.widgets.get(*widget_id) else { + continue; + }; + + widget.state().tick(*widget_id, alterables); + } + self.widgets_to_tick.clear(); + } + pub fn defer_component_init(&mut self, component: Component) { - self.components_to_init.push_back(component); + self.components_to_init.push(component); } fn push_event_children( @@ -276,24 +284,20 @@ impl Layout { let mut widget = widget.0.borrow_mut(); let (scroll_shift, info) = match widget::get_scrollbar_info(l) { - Some(info) => (widget.get_scroll_shift(&info, l), Some(info)), + Some(info) => (widget.get_scroll_shift_raw(&info, l), Some(info)), None => (Vec2::default(), None), }; - alterables.transform_stack.push(stack::Transform { - rel_pos: Vec2::new(l.location.x, l.location.y) - scroll_shift, - transform: widget.data.transform, - dim: Vec2::new(l.size.width, l.size.height), - ..Default::default() - }); + // see drawing.rs draw_widget too + push_transform_stack(&mut alterables.transform_stack, l, scroll_shift, &widget); - // see drawing.rs too - let scissor_pushed = info.is_some() && has_overflow_clip(style); - if scissor_pushed { - let mut boundary_absolute = drawing::Boundary::construct_absolute(&alterables.transform_stack); - boundary_absolute.pos += scroll_shift; - alterables.scissor_stack.push(ScissorBoundary(boundary_absolute)); - } + let scissor_pushed = push_scissor_stack( + &mut alterables.transform_stack, + &mut alterables.scissor_stack, + scroll_shift, + &info, + style, + ); let mut iter_children = true; @@ -399,7 +403,8 @@ impl Layout { needs_redraw: true, haptics_triggered: false, animations: Animations::default(), - components_to_init: VecDeque::new(), + components_to_init: Vec::new(), + widgets_to_tick: Vec::new(), }) } @@ -467,9 +472,9 @@ impl Layout { pub fn tick(&mut self) -> anyhow::Result<()> { let mut alterables = EventAlterables::default(); self.animations.tick(&self.state, &mut alterables); - self.process_pending_components()?; + self.process_pending_components(&mut alterables)?; + self.process_pending_widget_ticks(&mut alterables); self.process_alterables(alterables)?; - Ok(()) } @@ -493,6 +498,12 @@ impl Layout { } } + if !alterables.widgets_to_tick.is_empty() { + for widget_id in &alterables.widgets_to_tick { + self.widgets_to_tick.push(*widget_id); + } + } + for request in alterables.style_set_requests { if let Err(e) = self.state.tree.set_style(request.0, request.1) { log::error!("failed to set style for taffy widget ID {:?}: {:?}", request.0, e); diff --git a/wgui/src/stack.rs b/wgui/src/stack.rs index 28f77a9d..c41bec2d 100644 --- a/wgui/src/stack.rs +++ b/wgui/src/stack.rs @@ -53,8 +53,9 @@ impl, const STACK_MAX: usize> Default for GenericStack { pub primitives: &'a mut Vec, pub transform_stack: &'a mut TransformStack, pub scissor_stack: &'a mut ScissorStack, + pub alterables: &'a mut EventAlterables, } // per-widget draw params @@ -116,6 +121,7 @@ pub trait WidgetObj: AnyTrait { fn set_id(&mut self, id: WidgetID); // always set at insertion fn draw(&mut self, state: &mut DrawState, params: &DrawParams); + fn measure( &mut self, _known_dimensions: taffy::Size>, @@ -133,12 +139,6 @@ pub struct EventParams<'a> { pub layout: &'a taffy::Layout, } -impl EventParams<'_> { - pub const fn mark_redraw(&mut self) { - self.alterables.needs_redraw = true; - } -} - pub enum EventResult { Pass, // widget acknowledged it and allows the event to pass to the children Consumed, // widget triggered an action, do not pass to children @@ -212,10 +212,27 @@ macro_rules! call_event { } impl WidgetState { - pub fn get_scroll_shift(&self, info: &ScrollbarInfo, l: &taffy::Layout) -> Vec2 { + pub fn get_scroll_shift_smooth(&self, info: &ScrollbarInfo, l: &taffy::Layout, timestep_alpha: f32) -> (Vec2, bool) { + let currently_animating = self.data.scrolling_cur != self.data.scrolling_cur_prev; + + let scrolling = self + .data + .scrolling_cur_prev + .lerp(self.data.scrolling_cur, timestep_alpha); + + ( + Vec2::new( + (info.content_size.x - l.content_box_width()) * scrolling.x, + (info.content_size.y - l.content_box_height()) * scrolling.y, + ), + currently_animating, + ) + } + + pub fn get_scroll_shift_raw(&self, info: &ScrollbarInfo, l: &taffy::Layout) -> Vec2 { Vec2::new( - (info.content_size.x - l.content_box_width()) * self.data.scrolling.x, - (info.content_size.y - l.content_box_height()) * self.data.scrolling.y, + (info.content_size.x - l.content_box_width()) * self.data.scrolling_target.x, + (info.content_size.y - l.content_box_height()) * self.data.scrolling_target.y, ) } @@ -223,6 +240,31 @@ impl WidgetState { self.obj.draw(state, params); } + pub fn tick(&mut self, this_widget_id: WidgetID, alterables: &mut EventAlterables) { + let scrolling_cur = &mut self.data.scrolling_cur; + let scrolling_cur_prev = &mut self.data.scrolling_cur_prev; + let scrolling_target = &mut self.data.scrolling_target; + + *scrolling_cur_prev = *scrolling_cur; + + if scrolling_cur != scrolling_target { + // the magic part + *scrolling_cur = scrolling_cur.lerp(*scrolling_target, 0.2); + + // trigger tick request again + alterables.mark_tick(this_widget_id); + alterables.mark_redraw(); + + let epsilon = 0.00001; + if (scrolling_cur.x - scrolling_target.x).abs() < epsilon + && (scrolling_cur.y - scrolling_target.y).abs() < epsilon + { + log::info!("stopped animating"); + *scrolling_cur = *scrolling_target; + } + } + } + pub fn draw_scrollbars(&mut self, state: &mut DrawState, params: &DrawParams, info: &ScrollbarInfo) { let (enabled_horiz, enabled_vert) = get_scroll_enabled(params.style); if !enabled_horiz && !enabled_vert { @@ -248,10 +290,10 @@ impl WidgetState { PrimitiveExtent { boundary: drawing::Boundary::from_pos_size( Vec2::new( - transform.abs_pos.x + transform.dim.x * (1.0 - info.handle_size.x) * self.data.scrolling.x, - transform.abs_pos.y + transform.dim.y - thickness - margin, + transform.abs_pos.x + transform.raw_dim.x * (1.0 - info.handle_size.x) * self.data.scrolling_cur.x, + transform.abs_pos.y + transform.raw_dim.y - thickness - margin, ), - Vec2::new(transform.dim.x * info.handle_size.x, thickness), + Vec2::new(transform.raw_dim.x * info.handle_size.x, thickness), ), transform: transform.transform, }, @@ -265,10 +307,10 @@ impl WidgetState { PrimitiveExtent { boundary: drawing::Boundary::from_pos_size( Vec2::new( - transform.abs_pos.x + transform.dim.x - thickness - margin, - transform.abs_pos.y + transform.dim.y * (1.0 - info.handle_size.y) * self.data.scrolling.y, + transform.abs_pos.x + transform.raw_dim.x - thickness - margin, + transform.abs_pos.y + transform.raw_dim.y * (1.0 - info.handle_size.y) * self.data.scrolling_cur.y, ), - Vec2::new(thickness, transform.dim.y * info.handle_size.y), + Vec2::new(thickness, transform.raw_dim.y * info.handle_size.y), ), transform: transform.transform, }, @@ -293,25 +335,25 @@ impl WidgetState { return false; }; - let step_pixels = 32.0; + let step_pixels = 64.0; if info.handle_size.x < 1.0 && wheel.pos.x != 0.0 { // Horizontal scrolling let mult = (1.0 / (l.content_box_width() - info.content_size.x)) * step_pixels; - let new_scroll = (self.data.scrolling.x + wheel.shift.x * mult).clamp(0.0, 1.0); - if self.data.scrolling.x != new_scroll { - self.data.scrolling.x = new_scroll; - params.mark_redraw(); + let new_scroll = (self.data.scrolling_target.x + wheel.shift.x * mult).clamp(0.0, 1.0); + if self.data.scrolling_target.x != new_scroll { + self.data.scrolling_target.x = new_scroll; + params.alterables.mark_tick(self.obj.get_id()); } } if info.handle_size.y < 1.0 && wheel.pos.y != 0.0 { // Vertical scrolling let mult = (1.0 / (l.content_box_height() - info.content_size.y)) * step_pixels; - let new_scroll = (self.data.scrolling.y + wheel.shift.y * mult).clamp(0.0, 1.0); - if self.data.scrolling.y != new_scroll { - self.data.scrolling.y = new_scroll; - params.mark_redraw(); + let new_scroll = (self.data.scrolling_target.y + wheel.shift.y * mult).clamp(0.0, 1.0); + if self.data.scrolling_target.y != new_scroll { + self.data.scrolling_target.y = new_scroll; + params.alterables.mark_tick(self.obj.get_id()); } }