diff --git a/mangades/src/main.rs b/mangades/src/main.rs index 98a0a9f..362c39e 100644 --- a/mangades/src/main.rs +++ b/mangades/src/main.rs @@ -18,7 +18,8 @@ fn main() { } }, fill: Paint::color(Color::rgb(255, 0, 0)), - radius: 10. + radius: 10., + events: Default::default() }))); root.children.push(Arc::new(RwLock::new(Layout { style: Style { @@ -48,7 +49,8 @@ fn main() { } }, fill: Paint::color(Color::rgb(0, 255, 0)), - radius: 5. + radius: 5., + events: Default::default() })), Arc::new(RwLock::new(nodes::primitives::Rectangle { style: Style { @@ -62,12 +64,13 @@ fn main() { } }, fill: Paint::color(Color::rgb(0, 255, 255)), - radius: 5. + radius: 5., + events: Default::default() })) - ] - + ], + events: Default::default() }))); - root.children.push(Arc::new(RwLock::new(nodes::primitives::Rectangle { + let right_node = Arc::new(RwLock::new(nodes::primitives::Rectangle { style: Style { overflow: nodes::Overflow::Visible, layout: TaffyStyle { @@ -79,8 +82,21 @@ fn main() { } }, fill: Paint::color(Color::rgb(0, 0, 255)), - radius: 0. - }))); + radius: 0., + events: Default::default() + })); + root.children.push(right_node.clone()); + right_node.clone().write().unwrap().events.add_handler(Box::new(move |event| { + match event.event { + mangui::events::InnerEvent::MouseDown(_) => { + right_node.write().unwrap().fill = Paint::color(Color::rgb(255, 0, 255)); + }, + mangui::events::InnerEvent::MouseUp(_) => { + right_node.write().unwrap().fill = Paint::color(Color::rgb(0, 0, 255)); + }, + _ => {} + } + })); let groot: SharedNode = Arc::new(RwLock::new(root)); mangui::run_event_loop(groot); diff --git a/ui/src/events/handler.rs b/ui/src/events/handler.rs new file mode 100644 index 0000000..da8d2a4 --- /dev/null +++ b/ui/src/events/handler.rs @@ -0,0 +1,52 @@ +use std::{collections::HashMap, fmt::Debug, sync::{Arc, Mutex}}; +// use crate::nodes::Node; +use super::NodeEvent; + +/// A node event handler +pub type EventHandler = dyn FnMut(&NodeEvent); + +pub type InnerEventHandlerDataset = Arc>>>>>; + +/// An event handler database that allows adding and removing event handlers and running them all. +/// **IMPORTANT**: handlers are locked during event execution, so you can't access handlers from within an event handler. +/// Debug output of EventHandlerDatabase is changed as to prevent deadlocks - handlers are not printed. +/// Although Arc is used, you may be able to delay changing handlers by using thread/some async runtime. I didn't check it though :) +#[derive(Default)] +pub struct EventHandlerDatabase { + pub handlers: InnerEventHandlerDataset, + next_token: usize, +} + +impl Debug for EventHandlerDatabase { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("EventHandlerDatabase") + .field("handlers", &"Disabled as it's too easy to get a deadlock :(") + .field("next_token", &self.next_token) + .finish() + } +} + +impl EventHandlerDatabase { + /// Creates a new event handler database with the given handlers. + /// If you don't need to add or remove handlers, you can use [EventHandlerDatabase::default] instead. + pub fn new(handlers: Vec>) -> Self { + let mut db = Self::default(); + for handler in handlers { + db.add_handler(handler); + } + db + } + + /// Adds an event handler to the database and returns a token that can be used to remove it. + pub fn add_handler(&mut self, handler: Box) -> usize { + let token = self.next_token; + self.next_token += 1; + self.handlers.lock().unwrap().insert(token, Arc::new(Mutex::new(handler))); + token + } + + /// Removes an event handler from the database using the token returned by [EventHandlerDatabase::add_handler]. + pub fn remove_handler(&mut self, token: usize) { + self.handlers.lock().unwrap().remove(&token); + } +} diff --git a/ui/src/events/mod.rs b/ui/src/events/mod.rs index 3982a8a..6fba8b5 100644 --- a/ui/src/events/mod.rs +++ b/ui/src/events/mod.rs @@ -5,6 +5,7 @@ use winit::event::ElementState; pub use winit::event::{TouchPhase, MouseScrollDelta, DeviceId, ModifiersState, VirtualKeyCode, ScanCode, MouseButton}; use crate::SharedNode; +pub mod handler; #[derive(Clone, Debug)] pub struct NodeEvent { diff --git a/ui/src/lib.rs b/ui/src/lib.rs index ff2125c..211abd2 100644 --- a/ui/src/lib.rs +++ b/ui/src/lib.rs @@ -9,7 +9,7 @@ use femtovg::{Canvas, Color}; use glutin::surface::Surface; use glutin::{context::PossiblyCurrentContext, display::Display}; use glutin_winit::DisplayBuilder; -use nodes::get_element_at; +use nodes::{get_element_at, run_event_handlers, run_single_event_handlers}; use raw_window_handle::HasRawWindowHandle; use winit::event::{Event, WindowEvent, ModifiersState, DeviceId}; use winit::event_loop::{ControlFlow, EventLoop}; @@ -126,9 +126,8 @@ pub fn run_event_loop(root_node: SharedNode) -> ! { }) }; - for node in path.iter().rev() { - node.write().unwrap().on_event(&event); - } + run_event_handlers(path, event); + window.request_redraw(); } }, WindowEvent::DroppedFile(path) => {}, @@ -147,7 +146,8 @@ pub fn run_event_loop(root_node: SharedNode) -> ! { path: strong_focus_path.clone(), event: if focused { events::InnerEvent::Focus } else { events::InnerEvent::Blur } }; - strong_focus_path.last().unwrap().write().unwrap().on_event(&focus_event); + // strong_focus_path.last().unwrap().write().unwrap().on_event(&focus_event); + run_single_event_handlers(strong_focus_path.last().unwrap().clone(), focus_event); let focus_event = NodeEvent { target: strong_focus_path.last().unwrap().clone(), @@ -155,9 +155,8 @@ pub fn run_event_loop(root_node: SharedNode) -> ! { event: if focused { events::InnerEvent::FocusIn } else { events::InnerEvent::FocusOut } }; - for node in strong_focus_path.iter().rev() { - node.write().unwrap().on_event(&focus_event); - } + run_event_handlers(strong_focus_path, focus_event); + window.request_redraw(); }, None => {} }; @@ -201,9 +200,8 @@ pub fn run_event_loop(root_node: SharedNode) -> ! { } }; - for node in path.iter().rev() { - node.write().unwrap().on_event(&event); - } + window.request_redraw(); + run_event_handlers(path, event); }, None => {} } diff --git a/ui/src/nodes/layout.rs b/ui/src/nodes/layout.rs index 82516f0..c1b6956 100644 --- a/ui/src/nodes/layout.rs +++ b/ui/src/nodes/layout.rs @@ -1,19 +1,21 @@ use std::fmt::{Debug, Formatter}; -use crate::nodes::{Node, NodeChildren, Style}; +use crate::{nodes::{Node, NodeChildren, Style}, events::handler::EventHandlerDatabase}; use taffy::style::Dimension; /// A simple layout node which contains children. -#[derive(Clone, Default)] +#[derive(Default)] pub struct Layout { pub style: Style, - pub children: NodeChildren + pub children: NodeChildren, + pub events: EventHandlerDatabase } impl Layout { pub fn new(children: NodeChildren) -> Layout { Layout { style: Style::default(), - children + children, + events: EventHandlerDatabase::default() } } } @@ -39,5 +41,11 @@ impl Node for Layout { self.style.layout.size.height = Dimension::Points(height); } - fn on_event(&mut self, event: &crate::events::NodeEvent) {} + // fn on_event(&mut self, event: &crate::events::NodeEvent) { + // // dbg!("layout", self.children.len(), &event.event); + // } + + fn event_handlers(&self) -> Option { + Some(self.events.handlers.clone()) + } } \ No newline at end of file diff --git a/ui/src/nodes/mod.rs b/ui/src/nodes/mod.rs index 2fcfebb..e7ebe2e 100644 --- a/ui/src/nodes/mod.rs +++ b/ui/src/nodes/mod.rs @@ -8,9 +8,8 @@ use taffy::layout::Layout; pub use taffy::style::Style as TaffyStyle; use taffy::Taffy; use crate::events::Location; -use crate::{NodeLayoutMap, NodePtr, CurrentRenderer}; - -type SharedTNode = Arc>; +use crate::events::handler::InnerEventHandlerDataset; +use crate::{NodeLayoutMap, NodePtr, CurrentRenderer, SharedNode}; pub struct RenderContext { pub canvas: Canvas, @@ -51,7 +50,7 @@ pub struct Style { pub overflow: Overflow } -type NodeChildren = Vec; +type NodeChildren = Vec; pub trait Node: Debug { /// Return style. Usually, you just want self.style. @@ -67,8 +66,19 @@ pub trait Node: Debug { /// Called when an event happens on the node. This is called after the children have been called. /// Beware! Events include a path and target with [Arc>]s, but you already have a write lock for this node! /// Remember to check if the node is the same as self, and if it is, use self instead of the node in the path to prevent deadlocks! - fn on_event(&mut self, _event: &crate::events::NodeEvent) {} + // fn on_event(&mut self, _event: &crate::events::NodeEvent) {} + /// Returns the event handlers of the node. If the node has no event handlers, return None. + /// Use [EventHandlerDatabase] to manage this, and return it's handlers property. + /// Example code: + /// ```rust + /// fn event_handlers(&self) -> Option { + /// Some(self.events.handlers.clone()) + /// } + /// ``` + fn event_handlers(&self) -> Option { + None + } /// Called when the size of window changes on the root node. Layouts do implement this. /// Is an optional function instead of another trait because of missing support for trait upcasting @@ -76,7 +86,32 @@ pub trait Node: Debug { fn resize(&mut self, _width: f32, _height: f32) {} } -pub fn get_element_at(node: &SharedTNode, context: &RenderContext, location: Location) -> Option> { +/// Runs event handlers for the given path +/// The target element should be the last one in path (event handlers are ran in reverse order) +pub fn run_event_handlers(path: Vec, event: crate::events::NodeEvent) { + for node in path.iter().rev() { + let mut node = node.read().unwrap(); + if let Some(handlers) = node.event_handlers() { + drop(node); + for handler in handlers.lock().unwrap().values_mut() { + handler.lock().unwrap()(&event); + } + } + } +} + +pub fn run_single_event_handlers(node: SharedNode, event: crate::events::NodeEvent) { + let mut node = node.read().unwrap(); + if let Some(handlers) = node.event_handlers() { + drop(node); + for handler in handlers.lock().unwrap().values_mut() { + handler.lock().unwrap()(&event); + } + } +} + +/// Attempts to get path to the element at the target location. Assumes elements are always inside their parents. +pub fn get_element_at(node: &SharedNode, context: &RenderContext, location: Location) -> Option> { let node_borrowed = node.read().unwrap(); let children = node_borrowed.children(); let taffy_node = context.node_layout.get(node); @@ -106,7 +141,7 @@ pub fn get_element_at(node: &SharedTNode, context: &RenderContext, location: Loc } } -pub fn layout_recursively(node: &SharedTNode, context: &mut RenderContext) -> taffy::node::Node { +pub fn layout_recursively(node: &SharedNode, context: &mut RenderContext) -> taffy::node::Node { let taffy_node = context.node_layout.get(node); let taffy_node = match taffy_node { Some(taffy_node) => taffy_node, @@ -133,7 +168,7 @@ pub fn layout_recursively(node: &SharedTNode, context: &mut RenderContext) -> ta taffy_node } -pub fn render_recursively(node: &SharedTNode, context: &mut RenderContext) { +pub fn render_recursively(node: &SharedNode, context: &mut RenderContext) { let read_node = node.read().unwrap(); let styles = read_node.style(); let taffy_node = context.node_layout.get(node).unwrap(); diff --git a/ui/src/nodes/primitives.rs b/ui/src/nodes/primitives.rs index 2760445..248009b 100644 --- a/ui/src/nodes/primitives.rs +++ b/ui/src/nodes/primitives.rs @@ -1,12 +1,13 @@ -use femtovg::{Color, Paint, Path, Renderer}; +use femtovg::{Color, Paint, Path}; use taffy::layout::Layout; -use crate::nodes::{Node, NodeChildren, RenderContext, Style}; +use crate::{nodes::{Node, NodeChildren, RenderContext, Style}, events::handler::EventHandlerDatabase}; -#[derive(Clone, Default, Debug)] +#[derive(Default, Debug)] pub struct Rectangle { pub style: Style, pub fill: Paint, - pub radius: f32 + pub radius: f32, + pub events: EventHandlerDatabase } impl Rectangle { @@ -14,7 +15,8 @@ impl Rectangle { Rectangle { style: Style::default(), fill: Paint::color(Color::rgb(0, 0, 0)), - radius: 0. + radius: 0., + events: EventHandlerDatabase::default() } } } @@ -41,5 +43,14 @@ impl Node for Rectangle { ); } - fn on_event(&mut self, event: &crate::events::NodeEvent) {} + // fn on_event(&mut self, event: &crate::events::NodeEvent) { + // // dbg!("rect", &self.fill, &event.event); + // self.events.handlers.lock().unwrap().values_mut().for_each(|handler| { + // handler.lock().unwrap()(event, self); + // }); + // } + + fn event_handlers(&self) -> Option { + Some(self.events.handlers.clone()) + } } \ No newline at end of file