From b6954837326b765d13cf1cd0e03636a1b017d5c9 Mon Sep 17 00:00:00 2001 From: Joshua Park Date: Thu, 22 Jan 2026 21:55:35 -0500 Subject: [PATCH 01/12] feat: mouse event trait --- src/events.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/events.rs b/src/events.rs index 2bf422d..87be0a1 100644 --- a/src/events.rs +++ b/src/events.rs @@ -4,6 +4,10 @@ use crate::input::{KeyCode, MouseButton, MouseScrollDirection}; pub trait Event: Send + 'static {} +pub trait MouseEvent: Event { + fn coords(&self) -> (u16, u16); +} + pub struct Message { event_type_id: TypeId, event: Box, @@ -44,6 +48,12 @@ pub struct MouseDown { impl Event for MouseDown {} +impl MouseEvent for MouseDown { + fn coords(&self) -> (u16, u16) { + (self.x, self.y) + } +} + pub struct MouseUp { pub x: u16, pub y: u16, @@ -52,6 +62,12 @@ pub struct MouseUp { impl Event for MouseUp {} +impl MouseEvent for MouseUp { + fn coords(&self) -> (u16, u16) { + (self.x, self.y) + } +} + pub struct MouseHover { pub x: u16, pub y: u16, @@ -59,6 +75,12 @@ pub struct MouseHover { impl Event for MouseHover {} +impl MouseEvent for MouseHover { + fn coords(&self) -> (u16, u16) { + (self.x, self.y) + } +} + pub struct MouseDrag { pub x: u16, pub y: u16, @@ -67,6 +89,12 @@ pub struct MouseDrag { impl Event for MouseDrag {} +impl MouseEvent for MouseDrag { + fn coords(&self) -> (u16, u16) { + (self.x, self.y) + } +} + pub struct MouseScroll { pub x: u16, pub y: u16, @@ -75,6 +103,12 @@ pub struct MouseScroll { impl Event for MouseScroll {} +impl MouseEvent for MouseScroll { + fn coords(&self) -> (u16, u16) { + (self.x, self.y) + } +} + pub struct KeyPress { pub key: KeyCode, } From 0b88d3eb7169c6d53a18b10f226c688c43144ce7 Mon Sep 17 00:00:00 2001 From: Joshua Park Date: Fri, 23 Jan 2026 02:59:55 -0500 Subject: [PATCH 02/12] feat: scaffold EventContext::is_mouse_target --- src/context.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/context.rs b/src/context.rs index 43ed445..97b712d 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,6 +1,6 @@ use std::ops::Deref; -use crate::events::Event; +use crate::events::{Event, MouseEvent}; pub struct EventContext<'rt, E: Event> { event: &'rt E, @@ -25,6 +25,12 @@ impl<'rt, E: Event> EventContext<'rt, E> { } } +impl<'rt, E: MouseEvent> EventContext<'rt, E> { + pub fn is_mouse_target(&self) -> bool { + false + } +} + #[derive(Default)] pub(crate) struct Context { pub shutdown_requested: bool, From 1263cde9846996605802b4465ba2a26c01398234 Mon Sep 17 00:00:00 2001 From: Joshua Park Date: Sat, 24 Jan 2026 02:21:08 -0500 Subject: [PATCH 03/12] refactor: make render and dispatch purely private within arena --- src/arena.rs | 25 +++++++++++-------------- src/runtime.rs | 20 ++++---------------- 2 files changed, 15 insertions(+), 30 deletions(-) diff --git a/src/arena.rs b/src/arena.rs index d360acd..e65afd8 100644 --- a/src/arena.rs +++ b/src/arena.rs @@ -1,4 +1,4 @@ -use ratatui::buffer::Buffer; +use ratatui::{Frame, buffer::Buffer}; use slotmap::{SlotMap, new_key_type}; use crate::{ @@ -24,16 +24,13 @@ impl Arena { arena } - pub fn update_for_each(&mut self, mut update_fn: F) - where - F: FnMut(&mut ArenaNode), - { + pub fn dispatch(&mut self, msg: &Message, ctx: &mut Context) { let mut stack = vec![(self.root, false)]; while let Some((id, visited)) = stack.pop() { if visited { let node = &mut self.inner[id]; - update_fn(node); + node.dispatch(msg, ctx); } else { stack.push((id, true)); for &(child, _) in self.inner[id].children.iter().rev() { @@ -43,17 +40,17 @@ impl Arena { } } - pub fn draw_for_each(&mut self, rect: LogicalRect, mut draw_fn: F) - where - F: FnMut(&ArenaNode), - { + pub fn render(&mut self, frame: &mut Frame) { + let rect = frame.area().into(); + let buf = frame.buffer_mut(); + let mut stack = vec![self.root]; self.compute_layout(rect); while let Some(id) = stack.pop() { let node = &self.inner[id]; - draw_fn(node); + node.render(buf); let mut children = self.inner[id] .children @@ -129,7 +126,7 @@ impl Arena { } } -pub(crate) struct ArenaNode { +struct ArenaNode { inner: Node, parent: Option, children: Vec<(NodeId, Measure)>, @@ -137,14 +134,14 @@ pub(crate) struct ArenaNode { } impl ArenaNode { - pub(crate) fn render(&self, buffer: &mut Buffer) { + fn render(&self, buffer: &mut Buffer) { if let Some(renderer) = &self.inner.get_renderer() { let mut canvas = Canvas::new(self.rect, buffer); renderer(&mut canvas); } } - pub(crate) fn dispatch(&mut self, msg: &Message, ctx: &mut Context) { + fn dispatch(&mut self, msg: &Message, ctx: &mut Context) { if let Some(listeners) = self.inner.get_listeners(msg) { listeners.dispatch(msg, ctx); } diff --git a/src/runtime.rs b/src/runtime.rs index 9edba93..2611e57 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -39,12 +39,7 @@ impl Runtime { let terminal = driver.terminal(); terminal.draw(|f| { - let rect = f.area().into(); - let buffer = f.buffer_mut(); - - self.arena.draw_for_each(rect, |node| { - node.render(buffer); - }); + self.arena.render(f); })?; Ok(()) @@ -53,14 +48,15 @@ impl Runtime { pub fn update(&mut self) { let deadline = Instant::now() + Duration::from_millis(16); let msg = self.source.recv(); + let ctx = &mut self.context; - self.dispatch(&msg); + self.arena.dispatch(&msg, ctx); while Instant::now() < deadline { let msg = self.source.recv_timeout(deadline - Instant::now()); if let Some(msg) = msg { - self.dispatch(&msg); + self.arena.dispatch(&msg, ctx); } } } @@ -69,11 +65,3 @@ impl Runtime { self.context.shutdown_requested } } - -impl Runtime { - fn dispatch(&mut self, msg: &Message) { - self.arena.update_for_each(|node| { - node.dispatch(msg, &mut self.context); - }) - } -} From 5c2a8d6421966c03e672f8aace5cee23389fc98c Mon Sep 17 00:00:00 2001 From: Joshua Park Date: Sat, 24 Jan 2026 19:55:45 -0500 Subject: [PATCH 04/12] feat: cache traversal order, make update order reverse of draw order --- src/arena.rs | 83 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 29 deletions(-) diff --git a/src/arena.rs b/src/arena.rs index e65afd8..ae6b4a9 100644 --- a/src/arena.rs +++ b/src/arena.rs @@ -11,58 +11,56 @@ use crate::{ new_key_type! { struct NodeId; } -#[derive(Default)] pub(crate) struct Arena { root: NodeId, inner: SlotMap, + traversal: Option>, } impl Arena { pub fn new(root: Node) -> Self { - let mut arena = Self::default(); + let mut arena = Self { + root: NodeId::default(), + inner: SlotMap::default(), + traversal: None, + }; + arena.root = arena.push(root); + arena.compute_traversal(); + arena } pub fn dispatch(&mut self, msg: &Message, ctx: &mut Context) { - let mut stack = vec![(self.root, false)]; - - while let Some((id, visited)) = stack.pop() { - if visited { - let node = &mut self.inner[id]; - node.dispatch(msg, ctx); - } else { - stack.push((id, true)); - for &(child, _) in self.inner[id].children.iter().rev() { - stack.push((child, false)); - } - } + if self.traversal.is_none() { + self.compute_traversal(); + } + + let order = self.traversal + .as_ref() + .unwrap() + .iter() + .rev(); + + for &id in order { + let node = &mut self.inner[id]; + node.dispatch(msg, ctx); } } pub fn render(&mut self, frame: &mut Frame) { + if self.traversal.is_none() { + self.compute_traversal(); + } + let rect = frame.area().into(); let buf = frame.buffer_mut(); - let mut stack = vec![self.root]; - self.compute_layout(rect); - while let Some(id) = stack.pop() { + for &id in self.traversal.as_ref().unwrap() { let node = &self.inner[id]; node.render(buf); - - let mut children = self.inner[id] - .children - .iter() - .map(|(c, _)| *c) - .collect::>(); - - children.sort_by_key(|&child_id| self.inner[child_id].inner.get_layer()); - - for &child in children.iter().rev() { - stack.push(child); - } } } } @@ -96,6 +94,31 @@ impl Arena { visit(self, root); } + fn compute_traversal(&mut self) { + let root = self.root; + + let mut order = Vec::new(); + let mut stack = vec![root]; + + while let Some(id) = stack.pop() { + order.push(id); + + let mut children = self.inner[id] + .children + .iter() + .map(|(c, _)| *c) + .collect::>(); + + children.sort_by_key(|&child_id| self.inner[child_id].inner.get_layer()); + + for &child in children.iter().rev() { + stack.push(child); + } + } + + self.traversal = Some(order); + } + fn push(&mut self, node: Node) -> NodeId { let id = self.inner.insert(ArenaNode { inner: node, @@ -122,6 +145,8 @@ impl Arena { self.inner[id].children.push((child_id, measure)); } + self.traversal = None; + id } } From 634beee4c397c27cfb4e1998fca435856b14fb40 Mon Sep 17 00:00:00 2001 From: Joshua Park Date: Sat, 24 Jan 2026 19:56:51 -0500 Subject: [PATCH 05/12] chore: run formatter --- src/arena.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/arena.rs b/src/arena.rs index ae6b4a9..51b3189 100644 --- a/src/arena.rs +++ b/src/arena.rs @@ -36,11 +36,7 @@ impl Arena { self.compute_traversal(); } - let order = self.traversal - .as_ref() - .unwrap() - .iter() - .rev(); + let order = self.traversal.as_ref().unwrap().iter().rev(); for &id in order { let node = &mut self.inner[id]; From eaf4c6007487278c400011e326af5afb9d482894 Mon Sep 17 00:00:00 2001 From: Joshua Park Date: Sun, 25 Jan 2026 01:40:47 -0500 Subject: [PATCH 06/12] feat: implement EventContext::on_mouse_hit --- src/arena.rs | 39 ++++++++++++++++---------------- src/context.rs | 59 +++++++++++++++++++++++++++++++++++++----------- src/listeners.rs | 8 +++---- 3 files changed, 70 insertions(+), 36 deletions(-) diff --git a/src/arena.rs b/src/arena.rs index 51b3189..dcdd14c 100644 --- a/src/arena.rs +++ b/src/arena.rs @@ -4,7 +4,7 @@ use slotmap::{SlotMap, new_key_type}; use crate::{ canvas::{Canvas, LogicalRect}, component::{Child, Node}, - context::Context, + context::{Context, UpdatePass, UpdateState}, events::Message, layout::Measure, }; @@ -13,7 +13,7 @@ new_key_type! { struct NodeId; } pub(crate) struct Arena { root: NodeId, - inner: SlotMap, + nodes: SlotMap, traversal: Option>, } @@ -21,7 +21,7 @@ impl Arena { pub fn new(root: Node) -> Self { let mut arena = Self { root: NodeId::default(), - inner: SlotMap::default(), + nodes: SlotMap::default(), traversal: None, }; @@ -37,10 +37,12 @@ impl Arena { } let order = self.traversal.as_ref().unwrap().iter().rev(); + let mut state = UpdateState::default(); for &id in order { - let node = &mut self.inner[id]; - node.dispatch(msg, ctx); + let node = &mut self.nodes[id]; + let pass = UpdatePass::new(ctx, &mut state, node.rect); + node.dispatch(msg, pass); } } @@ -55,7 +57,7 @@ impl Arena { self.compute_layout(rect); for &id in self.traversal.as_ref().unwrap() { - let node = &self.inner[id]; + let node = &self.nodes[id]; node.render(buf); } } @@ -64,10 +66,10 @@ impl Arena { impl Arena { fn compute_layout(&mut self, rect: LogicalRect) { let root = self.root; - self.inner[root].rect = rect; + self.nodes[root].rect = rect; fn visit(arena: &mut Arena, id: NodeId) { - let node = &arena.inner[id]; + let node = &arena.nodes[id]; let rect = node.rect; let composition = node.inner.composition(); @@ -82,7 +84,7 @@ impl Arena { debug_assert_eq!(rects.len(), children.len()); for ((child_id, _), rect) in children.iter().zip(rects) { - arena.inner[*child_id].rect = rect; + arena.nodes[*child_id].rect = rect; visit(arena, *child_id); } } @@ -99,13 +101,13 @@ impl Arena { while let Some(id) = stack.pop() { order.push(id); - let mut children = self.inner[id] + let mut children = self.nodes[id] .children .iter() .map(|(c, _)| *c) .collect::>(); - children.sort_by_key(|&child_id| self.inner[child_id].inner.get_layer()); + children.sort_by_key(|&child_id| self.nodes[child_id].inner.get_layer()); for &child in children.iter().rev() { stack.push(child); @@ -116,14 +118,13 @@ impl Arena { } fn push(&mut self, node: Node) -> NodeId { - let id = self.inner.insert(ArenaNode { + let id = self.nodes.insert(ArenaNode { inner: node, - parent: None, children: Vec::new(), rect: LogicalRect::new(0, 0, 0, 0), }); - let children = self.inner[id] + let children = self.nodes[id] .inner .composition() .children() @@ -137,8 +138,9 @@ impl Arena { for (child, measure) in children { let child_id = self.push(child); - self.inner[child_id].parent = Some(id); - self.inner[id].children.push((child_id, measure)); + let parent = &mut self.nodes[id]; + + parent.children.push((child_id, measure)); } self.traversal = None; @@ -149,7 +151,6 @@ impl Arena { struct ArenaNode { inner: Node, - parent: Option, children: Vec<(NodeId, Measure)>, rect: LogicalRect, } @@ -162,9 +163,9 @@ impl ArenaNode { } } - fn dispatch(&mut self, msg: &Message, ctx: &mut Context) { + fn dispatch(&mut self, msg: &Message, pass: UpdatePass<'_>) { if let Some(listeners) = self.inner.get_listeners(msg) { - listeners.dispatch(msg, ctx); + listeners.dispatch(msg, pass); } } } diff --git a/src/context.rs b/src/context.rs index 97b712d..4c7f9a1 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,33 +1,45 @@ use std::ops::Deref; -use crate::events::{Event, MouseEvent}; +use crate::{ + canvas::LogicalRect, + events::{Event, MouseEvent}, +}; -pub struct EventContext<'rt, E: Event> { - event: &'rt E, - context: &'rt mut Context, +pub struct EventContext<'e, E: Event> { + event: &'e E, + pass: UpdatePass<'e>, } -impl<'rt, E: Event> Deref for EventContext<'rt, E> { +impl<'e, E: Event> Deref for EventContext<'e, E> { type Target = E; - fn deref(&self) -> &'rt Self::Target { + fn deref(&self) -> &'e Self::Target { self.event } } -impl<'rt, E: Event> EventContext<'rt, E> { - pub(crate) fn new(event: &'rt E, context: &'rt mut Context) -> Self { - Self { event, context } +impl<'e, E: Event> EventContext<'e, E> { + pub(crate) fn new(event: &'e E, pass: UpdatePass<'e>) -> Self { + Self { event, pass } } pub fn request_shutdown(&mut self) { - self.context.shutdown_requested = true; + self.pass.context.shutdown_requested = true; } } -impl<'rt, E: MouseEvent> EventContext<'rt, E> { - pub fn is_mouse_target(&self) -> bool { - false +impl<'e, E: MouseEvent> EventContext<'e, E> { + pub fn on_mouse_hit(&mut self, callback: F) + where + F: FnOnce(&mut EventContext<'e, E>), + { + let (x, y) = self.coords(); + let cursor = LogicalRect::new(x as i32, y as i32, 1, 1); + + if self.pass.rect.intersects(cursor) && !self.pass.state.mouse_hit_handled { + self.pass.state.mouse_hit_handled = true; + callback(self); + } } } @@ -35,3 +47,24 @@ impl<'rt, E: MouseEvent> EventContext<'rt, E> { pub(crate) struct Context { pub shutdown_requested: bool, } + +pub(crate) struct UpdatePass<'e> { + context: &'e mut Context, + state: &'e mut UpdateState, + rect: LogicalRect, +} + +impl<'e> UpdatePass<'e> { + pub fn new(context: &'e mut Context, state: &'e mut UpdateState, rect: LogicalRect) -> Self { + Self { + context, + state, + rect, + } + } +} + +#[derive(Default)] +pub(crate) struct UpdateState { + pub mouse_hit_handled: bool, +} diff --git a/src/listeners.rs b/src/listeners.rs index 1116f61..7500a16 100644 --- a/src/listeners.rs +++ b/src/listeners.rs @@ -5,7 +5,7 @@ use std::{ use crate::{ canvas::Canvas, - context::{Context, EventContext}, + context::{EventContext, UpdatePass}, events::{Event, Message}, }; @@ -13,7 +13,7 @@ pub(crate) type DrawListener = Box; pub(crate) type Listener = Box)>; pub(crate) trait ErasedListenerBucket { - fn dispatch(&mut self, msg: &Message, ctx: &mut Context); + fn dispatch(&mut self, msg: &Message, pass: UpdatePass<'_>); fn as_any_mut(&mut self) -> &mut dyn Any; } @@ -56,9 +56,9 @@ impl ListenerBucket { } impl ErasedListenerBucket for ListenerBucket { - fn dispatch(&mut self, msg: &Message, ctx: &mut Context) { + fn dispatch<'a>(&mut self, msg: &Message, pass: UpdatePass<'_>) { let event = msg.downcast_ref::().expect("TypeId mismatch"); - let mut ctx = EventContext::new(event, ctx); + let mut ctx = EventContext::new(event, pass); for listener in &mut self.inner { listener(&mut ctx); From 5b8f294da44b274da4da0df6a3c0f1f07a726c2c Mon Sep 17 00:00:00 2001 From: Joshua Park Date: Sun, 25 Jan 2026 01:48:17 -0500 Subject: [PATCH 07/12] feat: use Vec::with_capacity for Arena::compute_traversal --- src/arena.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/arena.rs b/src/arena.rs index dcdd14c..ad6195c 100644 --- a/src/arena.rs +++ b/src/arena.rs @@ -95,7 +95,7 @@ impl Arena { fn compute_traversal(&mut self) { let root = self.root; - let mut order = Vec::new(); + let mut order = Vec::with_capacity(self.nodes.len()); let mut stack = vec![root]; while let Some(id) = stack.pop() { From 93e0874469769c00172f2cc0f327f9f8e36edff2 Mon Sep 17 00:00:00 2001 From: Joshua Park Date: Sun, 25 Jan 2026 01:49:11 -0500 Subject: [PATCH 08/12] refactor: remove unused imports --- macros/src/vtui.rs | 1 - src/runtime.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/macros/src/vtui.rs b/macros/src/vtui.rs index fb15c50..a1e49c4 100644 --- a/macros/src/vtui.rs +++ b/macros/src/vtui.rs @@ -2,7 +2,6 @@ use quote::{ToTokens, quote}; use syn::{ Token, braced, parse::{Parse, ParseStream, discouraged::Speculative}, - punctuated::Punctuated, spanned::Spanned, }; diff --git a/src/runtime.rs b/src/runtime.rs index 2611e57..16d98ce 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -8,7 +8,6 @@ use crate::{ context::Context, drivers::Driver, error::RuntimeError, - events::Message, transport::EventSource, }; From 33f67e06b52c866ef2036c5b629eceb66fec80f0 Mon Sep 17 00:00:00 2001 From: Joshua Park Date: Sun, 25 Jan 2026 14:52:37 -0500 Subject: [PATCH 09/12] refactor: move Message to transport module --- src/arena.rs | 3 +-- src/component.rs | 4 ++-- src/events.rs | 30 ------------------------------ src/input.rs | 18 ++++++++++++------ src/listeners.rs | 2 +- src/transport.rs | 34 ++++++++++++++++++++++++++++++---- 6 files changed, 46 insertions(+), 45 deletions(-) diff --git a/src/arena.rs b/src/arena.rs index ad6195c..e4cdd2e 100644 --- a/src/arena.rs +++ b/src/arena.rs @@ -5,8 +5,7 @@ use crate::{ canvas::{Canvas, LogicalRect}, component::{Child, Node}, context::{Context, UpdatePass, UpdateState}, - events::Message, - layout::Measure, + layout::Measure, transport::Message, }; new_key_type! { struct NodeId; } diff --git a/src/component.rs b/src/component.rs index 5882337..68d25ef 100644 --- a/src/component.rs +++ b/src/component.rs @@ -3,10 +3,10 @@ use std::cell::{RefCell, RefMut}; use crate::{ canvas::{Canvas, LogicalRect}, context::EventContext, - events::{Event, Message}, + events::Event, layout::{Flow, Layer, Measure, compute_split}, listeners::{DrawListener, ErasedListenerBucket, ListenerStore}, - state::{State, StateOwner}, + state::{State, StateOwner}, transport::Message, }; pub type FactoryFn

= fn(Component, P) -> Node; diff --git a/src/events.rs b/src/events.rs index 87be0a1..df1fcbc 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,5 +1,3 @@ -use std::any::{Any, TypeId}; - use crate::input::{KeyCode, MouseButton, MouseScrollDirection}; pub trait Event: Send + 'static {} @@ -8,34 +6,6 @@ pub trait MouseEvent: Event { fn coords(&self) -> (u16, u16); } -pub struct Message { - event_type_id: TypeId, - event: Box, -} - -impl From for Message { - fn from(value: E) -> Self { - Self { - event_type_id: TypeId::of::(), - event: Box::new(value), - } - } -} - -impl Message { - pub fn new(event: E) -> Self { - Self::from(event) - } - - pub(crate) fn event_type_id(&self) -> TypeId { - self.event_type_id - } - - pub(crate) fn downcast_ref(&self) -> Option<&E> { - self.event.downcast_ref::() - } -} - pub struct Tick {} impl Event for Tick {} diff --git a/src/input.rs b/src/input.rs index c93301e..98b7970 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,7 +1,7 @@ -use crate::events::{ - KeyPress, KeyRelease, KeyRepeat, Message, MouseDown, MouseDrag, MouseHover, MouseScroll, +use crate::{events::{ + KeyPress, KeyRelease, KeyRepeat, MouseDown, MouseDrag, MouseHover, MouseScroll, MouseUp, Resize, -}; +}, transport::Message}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ModifierKeyCode { @@ -122,9 +122,9 @@ pub enum Input { }, } -impl Input { - pub fn to_message(self) -> Message { - match self { +impl From for Message { + fn from(value: Input) -> Self { + match value { Input::MouseDown { x, y, button } => Message::new(MouseDown { x, y, button }), Input::MouseUp { x, y, button } => Message::new(MouseUp { x, y, button }), Input::MouseHover { x, y } => Message::new(MouseHover { x, y }), @@ -137,3 +137,9 @@ impl Input { } } } + +impl Input { + pub fn to_message(self) -> Message { + Message::from(self) + } +} diff --git a/src/listeners.rs b/src/listeners.rs index 7500a16..2d377a5 100644 --- a/src/listeners.rs +++ b/src/listeners.rs @@ -6,7 +6,7 @@ use std::{ use crate::{ canvas::Canvas, context::{EventContext, UpdatePass}, - events::{Event, Message}, + events::Event, transport::Message, }; pub(crate) type DrawListener = Box; diff --git a/src/transport.rs b/src/transport.rs index 40436fb..cc4f85c 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -1,10 +1,36 @@ use std::{ - sync::mpsc::{Receiver, Sender}, - thread::JoinHandle, - time::Duration, + any::{Any, TypeId}, sync::mpsc::{Receiver, Sender}, thread::JoinHandle, time::Duration }; -use crate::{error::SendError, events::Message}; +use crate::{error::SendError, events::Event}; + +pub struct Message { + event_type_id: TypeId, + event: Box, +} + +impl From for Message { + fn from(value: E) -> Self { + Self { + event_type_id: TypeId::of::(), + event: Box::new(value), + } + } +} + +impl Message { + pub fn new(event: E) -> Self { + Self::from(event) + } + + pub(crate) fn event_type_id(&self) -> TypeId { + self.event_type_id + } + + pub(crate) fn downcast_ref(&self) -> Option<&E> { + self.event.downcast_ref::() + } +} pub struct EventSource { tx: Sender, From 3fd2a929a67f86696a603f415e9302a8f057abef Mon Sep 17 00:00:00 2001 From: Joshua Park Date: Sun, 25 Jan 2026 19:24:28 -0500 Subject: [PATCH 10/12] chore: run formatter --- src/arena.rs | 3 ++- src/component.rs | 3 ++- src/input.rs | 11 +++++++---- src/listeners.rs | 3 ++- src/transport.rs | 5 ++++- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/arena.rs b/src/arena.rs index e4cdd2e..4ff962e 100644 --- a/src/arena.rs +++ b/src/arena.rs @@ -5,7 +5,8 @@ use crate::{ canvas::{Canvas, LogicalRect}, component::{Child, Node}, context::{Context, UpdatePass, UpdateState}, - layout::Measure, transport::Message, + layout::Measure, + transport::Message, }; new_key_type! { struct NodeId; } diff --git a/src/component.rs b/src/component.rs index 68d25ef..4de3b6f 100644 --- a/src/component.rs +++ b/src/component.rs @@ -6,7 +6,8 @@ use crate::{ events::Event, layout::{Flow, Layer, Measure, compute_split}, listeners::{DrawListener, ErasedListenerBucket, ListenerStore}, - state::{State, StateOwner}, transport::Message, + state::{State, StateOwner}, + transport::Message, }; pub type FactoryFn

= fn(Component, P) -> Node; diff --git a/src/input.rs b/src/input.rs index 98b7970..ffe985b 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,7 +1,10 @@ -use crate::{events::{ - KeyPress, KeyRelease, KeyRepeat, MouseDown, MouseDrag, MouseHover, MouseScroll, - MouseUp, Resize, -}, transport::Message}; +use crate::{ + events::{ + KeyPress, KeyRelease, KeyRepeat, MouseDown, MouseDrag, MouseHover, MouseScroll, MouseUp, + Resize, + }, + transport::Message, +}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ModifierKeyCode { diff --git a/src/listeners.rs b/src/listeners.rs index 2d377a5..c820858 100644 --- a/src/listeners.rs +++ b/src/listeners.rs @@ -6,7 +6,8 @@ use std::{ use crate::{ canvas::Canvas, context::{EventContext, UpdatePass}, - events::Event, transport::Message, + events::Event, + transport::Message, }; pub(crate) type DrawListener = Box; diff --git a/src/transport.rs b/src/transport.rs index cc4f85c..8736c79 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -1,5 +1,8 @@ use std::{ - any::{Any, TypeId}, sync::mpsc::{Receiver, Sender}, thread::JoinHandle, time::Duration + any::{Any, TypeId}, + sync::mpsc::{Receiver, Sender}, + thread::JoinHandle, + time::Duration, }; use crate::{error::SendError, events::Event}; From 0613c9ce214cb535f5c2c7eb8f70dc1819460f2e Mon Sep 17 00:00:00 2001 From: Joshua Park Date: Mon, 26 Jan 2026 01:59:55 -0500 Subject: [PATCH 11/12] feat: implement EventContext::is_mouse_hit --- src/context.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/context.rs b/src/context.rs index 4c7f9a1..9f3c9bc 100644 --- a/src/context.rs +++ b/src/context.rs @@ -29,17 +29,11 @@ impl<'e, E: Event> EventContext<'e, E> { } impl<'e, E: MouseEvent> EventContext<'e, E> { - pub fn on_mouse_hit(&mut self, callback: F) - where - F: FnOnce(&mut EventContext<'e, E>), - { + pub fn is_mouse_hit(&self) -> bool { let (x, y) = self.coords(); let cursor = LogicalRect::new(x as i32, y as i32, 1, 1); - if self.pass.rect.intersects(cursor) && !self.pass.state.mouse_hit_handled { - self.pass.state.mouse_hit_handled = true; - callback(self); - } + self.pass.rect.intersects(cursor) } } @@ -65,6 +59,4 @@ impl<'e> UpdatePass<'e> { } #[derive(Default)] -pub(crate) struct UpdateState { - pub mouse_hit_handled: bool, -} +pub(crate) struct UpdateState {} From 50cca5ed6a3eb8c42e73429ed9061df4b3fa4b71 Mon Sep 17 00:00:00 2001 From: Joshua Park Date: Mon, 26 Jan 2026 12:39:05 -0500 Subject: [PATCH 12/12] refactor: make order variable --- src/arena.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/arena.rs b/src/arena.rs index 4ff962e..e05cc8f 100644 --- a/src/arena.rs +++ b/src/arena.rs @@ -56,7 +56,9 @@ impl Arena { self.compute_layout(rect); - for &id in self.traversal.as_ref().unwrap() { + let order = self.traversal.as_ref().unwrap(); + + for &id in order { let node = &self.nodes[id]; node.render(buf); }