use std::{ cell::{RefCell, RefMut}, collections::VecDeque, rc::Rc, }; use crate::{ animation::Animations, components::{Component, InitData}, event::{self, EventAlterables, EventListenerCollection}, globals::WguiGlobals, transform_stack::Transform, widget::{self, EventParams, WidgetObj, WidgetState, div::WidgetDiv}, }; use glam::{Vec2, vec2}; use slotmap::{HopSlotMap, SecondaryMap, new_key_type}; use taffy::{TaffyTree, TraversePartialTree}; new_key_type! { pub struct WidgetID; } #[derive(Clone)] pub struct Widget(Rc>); impl Widget { pub fn new(widget_state: WidgetState) -> Self { Self(Rc::new(RefCell::new(widget_state))) } // panics on failure // TODO: panic-less alternative pub fn get_as_mut(&self) -> RefMut { RefMut::map(self.0.borrow_mut(), |w| w.obj.get_as_mut::()) } pub fn state(&self) -> RefMut { self.0.borrow_mut() } } pub struct WidgetMap(HopSlotMap); pub type WidgetNodeMap = SecondaryMap; impl WidgetMap { fn new() -> Self { Self(HopSlotMap::with_key()) } pub fn get_as(&self, handle: WidgetID) -> Option> { Some(self.0.get(handle)?.get_as_mut::()) } pub fn get(&self, handle: WidgetID) -> Option<&Widget> { self.0.get(handle) } pub fn insert(&mut self, obj: Widget) -> WidgetID { self.0.insert(obj) } // cast to specific widget type, does nothing if widget ID is expired // panics in case if the widget type is wrong // TODO: panic-less alternative pub fn call(&self, widget_id: WidgetID, func: FUNC) where WIDGET: WidgetObj, FUNC: FnOnce(&mut WIDGET), { let Some(widget) = self.get(widget_id) else { debug_assert!(false); return; }; func(&mut widget.get_as_mut::()); } } pub struct LayoutState { pub globals: WguiGlobals, pub widgets: WidgetMap, pub nodes: WidgetNodeMap, pub tree: taffy::tree::TaffyTree, } pub struct Layout { pub state: LayoutState, pub components_to_init: VecDeque, pub root_widget: WidgetID, pub root_node: taffy::NodeId, pub prev_size: Vec2, pub content_size: Vec2, pub needs_redraw: bool, pub haptics_triggered: bool, pub animations: Animations, } fn add_child_internal( tree: &mut taffy::TaffyTree, widgets: &mut WidgetMap, nodes: &mut WidgetNodeMap, parent_node: Option, widget_state: WidgetState, style: taffy::Style, ) -> anyhow::Result<(WidgetID, taffy::NodeId)> { #[allow(clippy::arc_with_non_send_sync)] let child_id = widgets.insert(Widget::new(widget_state)); let child_node = tree.new_leaf_with_context(style, child_id)?; if let Some(parent_node) = parent_node { tree.add_child(parent_node, child_node)?; } nodes.insert(child_id, child_node); Ok((child_id, child_node)) } impl Layout { pub fn add_child( &mut self, parent_widget_id: WidgetID, widget: WidgetState, style: taffy::Style, ) -> anyhow::Result<(WidgetID, taffy::NodeId)> { let parent_node = *self.state.nodes.get(parent_widget_id).unwrap(); self.needs_redraw = true; add_child_internal( &mut self.state.tree, &mut self.state.widgets, &mut self.state.nodes, Some(parent_node), widget, style, ) } fn process_pending_components(&mut self) -> anyhow::Result<()> { let mut alterables = EventAlterables::default(); while let Some(c) = self.components_to_init.pop_front() { c.0.init(&mut InitData { state: &self.state, alterables: &mut alterables, }); } self.process_alterables(alterables)?; Ok(()) } pub fn defer_component_init(&mut self, component: Component) { self.components_to_init.push_back(component); } fn push_event_children( &self, listeners: &EventListenerCollection, parent_node_id: taffy::NodeId, event: &event::Event, alterables: &mut EventAlterables, user_data: &mut (&mut U1, &mut U2), ) -> anyhow::Result<()> { for child_id in self.state.tree.child_ids(parent_node_id) { self.push_event_widget(listeners, child_id, event, alterables, user_data)?; } Ok(()) } fn push_event_widget( &self, listeners: &EventListenerCollection, node_id: taffy::NodeId, event: &event::Event, alterables: &mut EventAlterables, user_data: &mut (&mut U1, &mut U2), ) -> anyhow::Result<()> { let l = self.state.tree.layout(node_id)?; let Some(widget_id) = self.state.tree.get_node_context(node_id).copied() else { anyhow::bail!("invalid widget ID"); }; let style = self.state.tree.style(node_id)?; let Some(widget) = self.state.widgets.get(widget_id) else { debug_assert!(false); anyhow::bail!("invalid widget"); }; let transform = Transform { pos: Vec2::new(l.location.x, l.location.y), dim: Vec2::new(l.size.width, l.size.height), transform: glam::Mat4::IDENTITY, // TODO: event transformations? Not needed for now }; alterables.transform_stack.push(transform); let mut iter_children = true; let mut params = EventParams { state: &self.state, layout: l, alterables, node_id, style, }; let listeners_vec = listeners.get(widget_id); let mut widget = widget.0.borrow_mut(); match widget.process_event( widget_id, listeners_vec, node_id, event, user_data, &mut params, )? { widget::EventResult::Pass => { // go on } widget::EventResult::Consumed | widget::EventResult::Outside | widget::EventResult::Unused => { iter_children = false; } } drop(widget); // free mutex if iter_children { self.push_event_children(listeners, node_id, event, alterables, user_data)?; } alterables.transform_stack.pop(); Ok(()) } pub const fn check_toggle_needs_redraw(&mut self) -> bool { if self.needs_redraw { self.needs_redraw = false; true } else { false } } pub const fn check_toggle_haptics_triggered(&mut self) -> bool { if self.haptics_triggered { self.haptics_triggered = false; true } else { false } } pub fn push_event( &mut self, listeners: &mut EventListenerCollection, event: &event::Event, mut user_data: (&mut U1, &mut U2), ) -> anyhow::Result<()> { let mut alterables = EventAlterables::default(); self.push_event_widget( listeners, self.root_node, event, &mut alterables, &mut user_data, )?; self.process_alterables(alterables)?; listeners.gc(); Ok(()) } pub fn new(globals: WguiGlobals) -> anyhow::Result { let mut state = LayoutState { tree: TaffyTree::new(), widgets: WidgetMap::new(), nodes: WidgetNodeMap::default(), globals, }; let (root_widget, root_node) = add_child_internal( &mut state.tree, &mut state.widgets, &mut state.nodes, None, // no parent WidgetDiv::create(), taffy::Style { size: taffy::Size::auto(), ..Default::default() }, )?; Ok(Self { state, prev_size: Vec2::default(), content_size: Vec2::default(), root_node, root_widget, needs_redraw: true, haptics_triggered: false, animations: Animations::default(), components_to_init: VecDeque::new(), }) } pub fn update(&mut self, size: Vec2, timestep_alpha: f32) -> anyhow::Result<()> { let mut alterables = EventAlterables::default(); self .animations .process(&self.state, &mut alterables, timestep_alpha); self.process_alterables(alterables)?; if self.state.tree.dirty(self.root_node)? || self.prev_size != size { self.needs_redraw = true; log::debug!("re-computing layout, size {}x{}", size.x, size.y); self.prev_size = size; self.state.tree.compute_layout_with_measure( self.root_node, taffy::Size { width: taffy::AvailableSpace::Definite(size.x), height: taffy::AvailableSpace::Definite(size.y), }, |known_dimensions, available_space, _node_id, node_context, _style| { if let taffy::Size { width: Some(width), height: Some(height), } = known_dimensions { return taffy::Size { width, height }; } match node_context { None => taffy::Size::ZERO, Some(h) => { if let Some(w) = self.state.widgets.get(*h) { w.0 .borrow_mut() .obj .measure(known_dimensions, available_space) } else { taffy::Size::ZERO } } } }, )?; let root_size = self.state.tree.layout(self.root_node).unwrap().size; if self.content_size.x != root_size.width || self.content_size.y != root_size.height { log::debug!( "content size changed: {:.0}x{:.0} → {:.0}x{:.0}", self.content_size.x, self.content_size.y, root_size.width, root_size.height ); } self.content_size = vec2(root_size.width, root_size.height); } Ok(()) } 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_alterables(alterables)?; Ok(()) } fn process_alterables(&mut self, alterables: EventAlterables) -> anyhow::Result<()> { for node in alterables.dirty_nodes { self.state.tree.mark_dirty(node)?; } if alterables.needs_redraw { self.needs_redraw = true; } if alterables.trigger_haptics { self.haptics_triggered = true; } if !alterables.animations.is_empty() { self.needs_redraw = true; for anim in alterables.animations { self.animations.add(anim); } } 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 ); } } Ok(()) } }