rlvgl_core/object.rs
1//! LVGL-parity object metadata and tree helpers.
2//!
3//! This module is the additive object substrate for the LPAR initiative. It
4//! deliberately leaves [`Widget`](crate::widget::Widget) and
5//! [`WidgetNode`](crate::WidgetNode) untouched while providing a richer node
6//! carrier for LVGL-like flags, states, ordering, detach semantics, hidden-aware
7//! drawing, hit testing, and the LPAR-04 event/focus/lifecycle runtime.
8//!
9//! # LPAR-04 additions
10//!
11//! - [`ObjectEvent`] — the object-semantic event vocabulary.
12//! - [`GestureDir`] — directional swipe summary payload for
13//! [`ObjectEvent::Gesture`].
14//! - [`ObjectFlags::EVENT_BUBBLE`] — opt-in per-object bubbling flag.
15//! - Per-node handler lists (trickle, target, bubble phase).
16//! - [`dispatch_object_event`] — the routing entry point.
17//! - [`DispatchInput`] and [`Disposition`] — dispatch input/output types.
18//! - [`EventContext`] — dispatch context exposed to handlers.
19//! - Lifecycle emission from mutation helpers
20//! ([`append_child`](ObjectNode::append_child),
21//! [`insert_child`](ObjectNode::insert_child),
22//! [`detach_child`](ObjectNode::detach_child)).
23
24use alloc::boxed::Box;
25use alloc::rc::Rc;
26use alloc::vec::Vec;
27use core::cell::RefCell;
28
29use crate::WidgetNode;
30use crate::event::{Event, Key};
31use crate::layout::LayoutState;
32use crate::renderer::{ClipRenderer, Renderer};
33use crate::widget::{Rect, Widget};
34
35// ---------------------------------------------------------------------------
36// ObjectEvent — the §5.3 v1 code set
37// ---------------------------------------------------------------------------
38
39/// Directional swipe summary delivered as the payload of
40/// [`ObjectEvent::Gesture`].
41///
42/// Derived from `DragStart`/`DragMove`/`DragEnd` displacement at the dispatch
43/// layer (LPAR-04 §9.6). Scroll begin/end/throw semantics belong to LPAR-05
44/// and are **not** modelled here.
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum GestureDir {
47 /// Swipe toward the top of the screen.
48 Up,
49 /// Swipe toward the bottom of the screen.
50 Down,
51 /// Swipe toward the left edge of the screen.
52 Left,
53 /// Swipe toward the right edge of the screen.
54 Right,
55}
56
57/// Object-semantic event vocabulary for the LPAR-04 dispatch model.
58///
59/// This is the **object-level** event type, distinct from the device/recognizer
60/// stream vocabulary in [`crate::event::Event`]. Stream events flow through
61/// pipelines and legacy dispatch; `ObjectEvent` codes are delivered only
62/// through [`dispatch_object_event`].
63///
64/// This enum is `#[non_exhaustive]`: consumers **must** include a wildcard
65/// arm. New codes will be added in later LPAR phases under the Specification
66/// Required policy.
67///
68/// # LVGL analogue table (LPAR-04 §5.3 + LPAR-05 §6)
69///
70/// | Variant | Trigger | LVGL analogue |
71/// |---|---|---|
72/// | [`Pressed`](Self::Pressed) | Stream `PressDown` at target | `LV_EVENT_PRESSED` |
73/// | [`Released`](Self::Released) | Contact ended over target | `LV_EVENT_RELEASED` |
74/// | [`Clicked`](Self::Clicked) | Stream `PressRelease` at target | `LV_EVENT_CLICKED` |
75/// | [`DoubleClicked`](Self::DoubleClicked) | Stream `DoubleTap` at target | `LV_EVENT_DOUBLE_CLICKED` |
76/// | [`LongPressed`](Self::LongPressed) | Long-press recognizer output at target | `LV_EVENT_LONG_PRESSED` |
77/// | [`LongPressedRepeat`](Self::LongPressedRepeat) | Long-press repeat output | `LV_EVENT_LONG_PRESSED_REPEAT` |
78/// | [`Key`](Self::Key) | Key delivery to focused object | `LV_EVENT_KEY` |
79/// | [`Rotary`](Self::Rotary) | Encoder diff in editing mode | `LV_EVENT_ROTARY` |
80/// | [`Focused`](Self::Focused) | Object gained focus | `LV_EVENT_FOCUSED` |
81/// | [`Defocused`](Self::Defocused) | Object lost focus | `LV_EVENT_DEFOCUSED` |
82/// | [`Gesture`](Self::Gesture) | Directional swipe summary | `LV_EVENT_GESTURE` |
83/// | [`Attached`](Self::Attached) | Subtree root attached via `append_child`/`insert_child` | `LV_EVENT_CHILD_CREATED` (inverted) |
84/// | [`Detached`](Self::Detached) | Subtree root detached via `detach_child` | `LV_EVENT_DELETE` (narrowed) |
85/// | [`ChildChanged`](Self::ChildChanged) | Parent notified after child add/remove/reorder | `LV_EVENT_CHILD_CHANGED` |
86/// | [`ScrollBegin`](Self::ScrollBegin) | Scroll session began on this container | `LV_EVENT_SCROLL_BEGIN` |
87/// | [`Scroll`](Self::Scroll) | Scroll offset changed | `LV_EVENT_SCROLL` |
88/// | [`ScrollEnd`](Self::ScrollEnd) | Scroll session ended and offset settled | `LV_EVENT_SCROLL_END` |
89/// | [`ScrollThrow`](Self::ScrollThrow) | Throw/momentum phase initiated | `LV_EVENT_SCROLL_THROW_BEGIN` |
90#[non_exhaustive]
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub enum ObjectEvent {
93 /// A stable contact began at the given coordinates.
94 ///
95 /// Derived from stream [`Event::PressDown`] resolved at this target.
96 /// Use for visual press feedback (e.g. button highlight).
97 Pressed {
98 /// Horizontal coordinate of the contact.
99 x: i32,
100 /// Vertical coordinate of the contact.
101 y: i32,
102 },
103 /// A contact ended over this object (any cause).
104 ///
105 /// Always paired with a preceding [`Pressed`](Self::Pressed).
106 Released {
107 /// Horizontal coordinate at release.
108 x: i32,
109 /// Vertical coordinate at release.
110 y: i32,
111 },
112 /// A short clean tap was completed on this object.
113 ///
114 /// Derived from stream [`Event::PressRelease`]. Contacts that crossed
115 /// the drag threshold produce no `Clicked` (INPUT-00 §6 click-vs-drag
116 /// suppression).
117 Clicked {
118 /// Horizontal coordinate of the tap.
119 x: i32,
120 /// Vertical coordinate of the tap.
121 y: i32,
122 },
123 /// Two consecutive short taps were detected on this object.
124 ///
125 /// Derived from stream [`Event::DoubleTap`].
126 DoubleClicked {
127 /// Horizontal coordinate of the second tap.
128 x: i32,
129 /// Vertical coordinate of the second tap.
130 y: i32,
131 },
132 /// A held contact reached the long-press threshold.
133 ///
134 /// Emitted once per contact by the long-press recognizer (LPAR-04 §9).
135 /// Disarmed by `DragStart` before emission.
136 LongPressed {
137 /// Horizontal coordinate of the held contact.
138 x: i32,
139 /// Vertical coordinate of the held contact.
140 y: i32,
141 },
142 /// A long-pressed contact crossed another repeat interval.
143 ///
144 /// Emitted every repeat period after the initial [`LongPressed`](Self::LongPressed).
145 LongPressedRepeat {
146 /// Horizontal coordinate of the held contact.
147 x: i32,
148 /// Vertical coordinate of the held contact.
149 y: i32,
150 },
151 /// A key event was delivered to this focused object.
152 ///
153 /// Delivered when this object has focus and a keypad/encoder key event
154 /// is received. In navigate mode, encoder presses map to
155 /// [`Key::Enter`]; in editing mode, all keys route here.
156 Key(
157 /// The key that was pressed or repeated.
158 Key,
159 ),
160 /// Encoder rotation delta delivered to this focused object in editing mode.
161 ///
162 /// In navigate mode, rotation drives `focus_next`/`focus_prev` instead.
163 Rotary {
164 /// Net rotation steps (positive = clockwise).
165 diff: i32,
166 },
167 /// This object gained keyboard focus.
168 ///
169 /// The [`ObjectStates::FOCUSED`] bit is set before this event is emitted.
170 Focused,
171 /// This object lost keyboard focus.
172 ///
173 /// The [`ObjectStates::FOCUSED`] bit is cleared before this event is emitted.
174 Defocused,
175 /// A directional swipe was recognized over this object.
176 ///
177 /// Derived from drag displacement (LPAR-04 §9.6). Scroll semantics are
178 /// LPAR-05 non-goals.
179 Gesture(
180 /// Direction of the swipe.
181 GestureDir,
182 ),
183 /// This node was attached to a live tree via
184 /// [`append_child`](ObjectNode::append_child) or
185 /// [`insert_child`](ObjectNode::insert_child).
186 ///
187 /// Delivered synchronously to the attached subtree root after the
188 /// structural change.
189 Attached,
190 /// This node was detached from its parent via
191 /// [`detach_child`](ObjectNode::detach_child).
192 ///
193 /// Delivered synchronously to the detached subtree root after the
194 /// structural change. This is the lifecycle event name reserved by
195 /// LPAR-02 §6.10.
196 Detached,
197 /// A child was added to or removed from this node.
198 ///
199 /// Delivered synchronously to the **parent** after
200 /// [`append_child`](ObjectNode::append_child),
201 /// [`insert_child`](ObjectNode::insert_child), or
202 /// [`detach_child`](ObjectNode::detach_child).
203 ChildChanged,
204 /// Scroll session began on this container.
205 ///
206 /// Emitted once per scroll session when a drag gesture transitions the
207 /// container into active scrolling. The offset is unchanged at this point;
208 /// the first [`Scroll`](Self::Scroll) event carries the new offset.
209 ///
210 /// LVGL analogue: `LV_EVENT_SCROLL_BEGIN`.
211 ScrollBegin,
212 /// Scroll offset changed.
213 ///
214 /// Emitted each tick/frame that the offset advances — during active drag
215 /// scrolling and during throw deceleration. The payload carries the new
216 /// logical scroll offset after the change.
217 ///
218 /// LVGL analogue: `LV_EVENT_SCROLL`.
219 Scroll {
220 /// New horizontal scroll offset in logical pixels.
221 scroll_x: i32,
222 /// New vertical scroll offset in logical pixels.
223 scroll_y: i32,
224 },
225 /// Scroll session ended and the offset has settled.
226 ///
227 /// Emitted once per scroll session, after the final [`Scroll`](Self::Scroll)
228 /// event (after throw deceleration or snap settle completes, or after a
229 /// drag ended with zero residual velocity).
230 ///
231 /// LVGL analogue: `LV_EVENT_SCROLL_END`.
232 ScrollEnd,
233 /// Throw/momentum phase initiated.
234 ///
235 /// Emitted once when the finger lifted with enough residual velocity to
236 /// initiate throw/momentum. Emitted between the last drag-driven
237 /// [`Scroll`](Self::Scroll) and the first momentum-driven
238 /// [`Scroll`](Self::Scroll).
239 ///
240 /// LVGL analogue: `LV_EVENT_SCROLL_THROW_BEGIN`.
241 ScrollThrow {
242 /// Estimated horizontal throw velocity in logical pixels per tick.
243 vel_x: i32,
244 /// Estimated throw velocity in logical pixels per tick.
245 vel_y: i32,
246 },
247
248 // -----------------------------------------------------------------------
249 // LPAR-10 layout events (§5.F — registered via Specification Required)
250 // -----------------------------------------------------------------------
251 /// This node's effective bounds changed as a result of a layout pass.
252 ///
253 /// Delivered to the node whose computed rect was updated. Does **not**
254 /// bubble by default. Widgets that maintain internal layout caches (e.g.
255 /// a label that wraps text at its container width) SHOULD listen for this
256 /// and invalidate their caches.
257 ///
258 /// LVGL analogue: `LV_EVENT_SIZE_CHANGED`.
259 SizeChanged,
260
261 /// All children of this layout container have been re-placed.
262 ///
263 /// Delivered to the container node after [`run_layout`](crate::layout::run_layout)
264 /// completes placing all children for this container. Does **not** bubble
265 /// by default.
266 ///
267 /// LVGL analogue: `LV_EVENT_LAYOUT_CHANGED`.
268 LayoutChanged,
269}
270
271// ---------------------------------------------------------------------------
272// EventContext — dispatch context exposed to handlers
273// ---------------------------------------------------------------------------
274
275/// Dispatch context passed to every event handler during propagation.
276///
277/// `target_tag` and `current_tag` are the test-automation tags of the dispatch
278/// target and the node currently running its handlers, respectively. Tags are
279/// optional; a `None` value means the node carries no tag.
280#[derive(Debug, Clone, Copy)]
281pub struct EventContext {
282 /// Test-automation tag of the ultimate dispatch target.
283 pub target_tag: Option<&'static str>,
284 /// Test-automation tag of the node whose handlers are currently executing.
285 pub current_tag: Option<&'static str>,
286}
287
288// ---------------------------------------------------------------------------
289// Handler type alias
290// ---------------------------------------------------------------------------
291
292/// Signature for an event handler stored on an [`ObjectNode`].
293///
294/// Receives the [`ObjectEvent`] and the current [`EventContext`]. Returns
295/// `true` to consume the event and stop propagation.
296pub type ObjectHandler = Box<dyn FnMut(&ObjectEvent, EventContext) -> bool>;
297
298// ---------------------------------------------------------------------------
299// ObjectFlags
300// ---------------------------------------------------------------------------
301
302/// LVGL-like object behavior flags.
303///
304/// Flags are stored as a compact bitset. Use the associated constants with
305/// [`contains`](Self::contains), [`insert`](Self::insert), and
306/// [`remove`](Self::remove).
307#[derive(Debug, Clone, Copy, PartialEq, Eq)]
308pub struct ObjectFlags(u32);
309
310impl ObjectFlags {
311 /// No flags set.
312 pub const EMPTY: Self = Self(0);
313 /// Skip drawing and targeting for the object and its subtree.
314 pub const HIDDEN: Self = Self(1 << 0);
315 /// Draw the object but exclude it from default interaction targeting.
316 pub const DISABLED: Self = Self(1 << 1);
317 /// Allow the object to be selected by pointer hit testing.
318 pub const CLICKABLE: Self = Self(1 << 2);
319 /// Allow the object to participate in future focus traversal.
320 pub const FOCUSABLE: Self = Self(1 << 3);
321 /// Allow the object to participate in future scroll behavior.
322 pub const SCROLLABLE: Self = Self(1 << 4);
323 /// Opt-in bubbling: propagate events toward root after the target phase
324 /// (LPAR-04 §6.4; mirrors `LV_OBJ_FLAG_EVENT_BUBBLE`).
325 ///
326 /// Default: clear. When clear, bubble phase stops at this node.
327 pub const EVENT_BUBBLE: Self = Self(1 << 5);
328
329 /// Return a flag set from raw bits, dropping unknown bits.
330 pub const fn from_bits_truncate(bits: u32) -> Self {
331 Self(bits & Self::all_bits())
332 }
333
334 /// Return the raw flag bits.
335 pub const fn bits(self) -> u32 {
336 self.0
337 }
338
339 /// Return `true` when all flags in `other` are set.
340 pub const fn contains(self, other: Self) -> bool {
341 (self.0 & other.0) == other.0
342 }
343
344 /// Insert all flags in `other`.
345 pub fn insert(&mut self, other: Self) {
346 self.0 |= other.0;
347 }
348
349 /// Remove all flags in `other`.
350 pub fn remove(&mut self, other: Self) {
351 self.0 &= !other.0;
352 }
353
354 /// Set or clear all flags in `other`.
355 pub fn set(&mut self, other: Self, enabled: bool) {
356 if enabled {
357 self.insert(other);
358 } else {
359 self.remove(other);
360 }
361 }
362
363 const fn all_bits() -> u32 {
364 Self::HIDDEN.0
365 | Self::DISABLED.0
366 | Self::CLICKABLE.0
367 | Self::FOCUSABLE.0
368 | Self::SCROLLABLE.0
369 | Self::EVENT_BUBBLE.0
370 }
371}
372
373impl Default for ObjectFlags {
374 fn default() -> Self {
375 Self::EMPTY
376 }
377}
378
379// ---------------------------------------------------------------------------
380// ObjectStates
381// ---------------------------------------------------------------------------
382
383/// LVGL-like object state bits.
384///
385/// The default state is represented by no bits set. Style resolution consumes
386/// these bits in later LPAR phases; this module only stores and queries them.
387#[derive(Debug, Clone, Copy, PartialEq, Eq)]
388pub struct ObjectStates(u32);
389
390impl ObjectStates {
391 /// No state bits set.
392 pub const DEFAULT: Self = Self(0);
393 /// Disabled state for style selection.
394 pub const DISABLED: Self = Self(1 << 0);
395 /// Focused state for style selection and focus bookkeeping.
396 pub const FOCUSED: Self = Self(1 << 1);
397 /// Pressed state for pointer or key activation.
398 pub const PRESSED: Self = Self(1 << 2);
399 /// Checked state for toggleable controls.
400 pub const CHECKED: Self = Self(1 << 3);
401 /// Edited state for text or value editing modes.
402 pub const EDITED: Self = Self(1 << 4);
403
404 /// Return a state set from raw bits, dropping unknown bits.
405 pub const fn from_bits_truncate(bits: u32) -> Self {
406 Self(bits & Self::all_bits())
407 }
408
409 /// Return the raw state bits.
410 pub const fn bits(self) -> u32 {
411 self.0
412 }
413
414 /// Return `true` when all state bits in `other` are set.
415 pub const fn contains(self, other: Self) -> bool {
416 (self.0 & other.0) == other.0
417 }
418
419 /// Insert all state bits in `other`.
420 pub fn insert(&mut self, other: Self) {
421 self.0 |= other.0;
422 }
423
424 /// Remove all state bits in `other`.
425 pub fn remove(&mut self, other: Self) {
426 self.0 &= !other.0;
427 }
428
429 /// Set or clear all state bits in `other`.
430 pub fn set(&mut self, other: Self, enabled: bool) {
431 if enabled {
432 self.insert(other);
433 } else {
434 self.remove(other);
435 }
436 }
437
438 const fn all_bits() -> u32 {
439 Self::DISABLED.0 | Self::FOCUSED.0 | Self::PRESSED.0 | Self::CHECKED.0 | Self::EDITED.0
440 }
441}
442
443impl Default for ObjectStates {
444 fn default() -> Self {
445 Self::DEFAULT
446 }
447}
448
449// ---------------------------------------------------------------------------
450// ObjectMeta
451// ---------------------------------------------------------------------------
452
453/// Cross-cutting metadata stored by an [`ObjectNode`].
454#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
455pub struct ObjectMeta {
456 flags: ObjectFlags,
457 states: ObjectStates,
458 detached: bool,
459}
460
461impl ObjectMeta {
462 /// Create default object metadata.
463 pub const fn new() -> Self {
464 Self {
465 flags: ObjectFlags::EMPTY,
466 states: ObjectStates::DEFAULT,
467 detached: false,
468 }
469 }
470
471 /// Return the object flags.
472 pub const fn flags(&self) -> ObjectFlags {
473 self.flags
474 }
475
476 /// Mutably borrow the object flags.
477 pub fn flags_mut(&mut self) -> &mut ObjectFlags {
478 &mut self.flags
479 }
480
481 /// Return the object state bits.
482 pub const fn states(&self) -> ObjectStates {
483 self.states
484 }
485
486 /// Mutably borrow the object state bits.
487 pub fn states_mut(&mut self) -> &mut ObjectStates {
488 &mut self.states
489 }
490
491 /// Return whether this node has been detached from a live object tree.
492 pub const fn is_detached(&self) -> bool {
493 self.detached
494 }
495
496 fn set_detached(&mut self, detached: bool) {
497 self.detached = detached;
498 }
499}
500
501// ---------------------------------------------------------------------------
502// DispatchInput and Disposition
503// ---------------------------------------------------------------------------
504
505/// The input to [`dispatch_object_event`].
506///
507/// Pointer-positioned events carry coordinates used for hit-test target
508/// resolution. Key and encoder events route to the focused node.
509#[derive(Debug, Clone, PartialEq, Eq)]
510pub enum DispatchInput {
511 /// A pointer stream event to be delivered to the hit-test target.
512 ///
513 /// The wrapped [`Event`] must be a pointer-positioned variant
514 /// (`PressDown`, `PressRelease`, `DoubleTap`, `LongPress`, etc.). The
515 /// `x`/`y` coordinates are used for hit testing.
516 Pointer {
517 /// Pointer horizontal coordinate for hit-test resolution.
518 x: i32,
519 /// Pointer vertical coordinate for hit-test resolution.
520 y: i32,
521 /// The stream event to translate into an [`ObjectEvent`].
522 event: Event,
523 },
524 /// An [`ObjectEvent`] to deliver to the hit-test target at `(x, y)`.
525 PointerObject {
526 /// Pointer horizontal coordinate for hit-test resolution.
527 x: i32,
528 /// Pointer vertical coordinate for hit-test resolution.
529 y: i32,
530 /// The object-semantic event to dispatch.
531 event: ObjectEvent,
532 },
533 /// An [`ObjectEvent`] to deliver to the currently focused node.
534 Focused {
535 /// The object-semantic event to dispatch.
536 event: ObjectEvent,
537 },
538 /// An [`ObjectEvent`] targeted at the node identified by a structural path,
539 /// bypassing hit-test resolution.
540 ///
541 /// The event is delivered to that node's target handlers, then bubbles
542 /// through ancestors using the existing per-node [`ObjectFlags::EVENT_BUBBLE`]
543 /// gating — identically to how any other event bubbles. The
544 /// [`ObjectFlags::EVENT_BUBBLE`] flag is never mutated (LPAR-05 §6.3/§16).
545 Container {
546 /// Structural path from root to the target node (list of child indices).
547 path: Vec<usize>,
548 /// The object-semantic event to dispatch.
549 event: ObjectEvent,
550 },
551}
552
553/// The outcome of a [`dispatch_object_event`] call.
554#[derive(Debug, Clone, Copy, PartialEq, Eq)]
555pub enum Disposition {
556 /// The event was consumed by a handler during dispatch.
557 Consumed,
558 /// The event completed all phases without any handler consuming it.
559 Unconsumed,
560 /// No target could be resolved (no hit-test result or no focused node).
561 NoTarget,
562}
563
564// ---------------------------------------------------------------------------
565// ObjectNode
566// ---------------------------------------------------------------------------
567
568/// Additive LVGL-like object tree node.
569///
570/// `ObjectNode` mirrors the current widget-tree ownership shape while keeping
571/// object metadata in the node layer. Existing [`WidgetNode`](crate::WidgetNode)
572/// users remain source-compatible; new parity work can opt into this richer
573/// carrier.
574///
575/// # LPAR-04 handler storage
576///
577/// Each node owns three per-phase handler lists:
578///
579/// - **Trickle handlers** run root→target *before* the target phase.
580/// - **Target handlers** run at the target node.
581/// - **Bubble handlers** run target→root *after* the target phase, but only
582/// while the node has [`ObjectFlags::EVENT_BUBBLE`] set.
583///
584/// Handlers are closures stored as `Box<dyn FnMut(…)>` and are invoked in
585/// registration order. A handler returning `true` consumes the event and
586/// stops all remaining phases.
587///
588/// # Mutation and dispatch
589///
590/// Tree mutation helpers ([`append_child`](Self::append_child),
591/// [`insert_child`](Self::insert_child), [`detach_child`](Self::detach_child))
592/// MUST NOT be called from inside an active dispatch. They emit lifecycle
593/// events ([`ObjectEvent::Attached`], [`ObjectEvent::Detached`],
594/// [`ObjectEvent::ChildChanged`]) synchronously after the structural change,
595/// outside any active trickle/target/bubble traversal.
596pub struct ObjectNode {
597 widget: Rc<RefCell<dyn Widget>>,
598 children: Vec<ObjectNode>,
599 tag: Option<&'static str>,
600 meta: ObjectMeta,
601 /// Handlers run root→target (trickle/preprocess phase).
602 trickle_handlers: Vec<ObjectHandler>,
603 /// Handlers run at the target node (target phase).
604 target_handlers: Vec<ObjectHandler>,
605 /// Handlers run target→root (bubble phase; gated by `EVENT_BUBBLE` flag).
606 bubble_handlers: Vec<ObjectHandler>,
607 /// Optional scroll state, present when `ObjectFlags::SCROLLABLE` is set.
608 ///
609 /// Stored as a boxed value to keep `ObjectNode` from growing for
610 /// non-scroll nodes.
611 pub(crate) scroll: Option<Box<crate::scroll::ScrollState>>,
612 /// Optional node-resident animation entries (LPAR-06 §6.1).
613 ///
614 /// Stored as a boxed value to keep `ObjectNode` from growing for
615 /// non-animated nodes. Lazily allocated by
616 /// [`ObjectAnims::bind`](crate::object_anim::ObjectAnims::bind).
617 pub(crate) anims: Option<Box<crate::object_anim::NodeAnimSet>>,
618 /// Optional LPAR-07 style slot holding local and shared style entries.
619 ///
620 /// `None` for nodes that carry no style overrides, keeping `ObjectNode`
621 /// small. Lazily allocated by [`ObjectNode::add_local_style`] or
622 /// [`ObjectNode::add_style`].
623 pub(crate) style: Option<Box<crate::style_cascade::StyleState>>,
624 /// Optional LPAR-10 layout state: role (container/item/none), layout-computed
625 /// bounds override, and dirty flag.
626 ///
627 /// `None` for nodes that are not involved in object-managed layout, keeping
628 /// `ObjectNode` small. Lazily allocated by
629 /// [`set_layout_flex`](Self::set_layout_flex),
630 /// [`set_layout_grid`](Self::set_layout_grid), or
631 /// [`set_item_hints`](Self::set_item_hints).
632 pub(crate) layout: Option<Box<LayoutState>>,
633}
634
635impl ObjectNode {
636 /// Create a new object node with no children, tag, flags, states, or handlers.
637 pub fn new(widget: Rc<RefCell<dyn Widget>>) -> Self {
638 Self {
639 widget,
640 children: Vec::new(),
641 tag: None,
642 meta: ObjectMeta::new(),
643 trickle_handlers: Vec::new(),
644 target_handlers: Vec::new(),
645 bubble_handlers: Vec::new(),
646 scroll: None,
647 anims: None,
648 style: None,
649 layout: None,
650 }
651 }
652
653 /// Recursively adopt a compatibility [`WidgetNode`] into an object tree.
654 ///
655 /// The widget handle, child order, and test-automation tag are preserved.
656 /// New object metadata is initialized to defaults for every adopted node.
657 /// Nodes adopted this way start with empty handler lists.
658 pub fn adopt(node: WidgetNode) -> Self {
659 let mut object = Self::new(node.widget);
660 object.tag = node.tag;
661 object.children = node.children.into_iter().map(Self::adopt).collect();
662 object
663 }
664
665 /// Attach a test-automation tag to this node.
666 pub fn with_tag(mut self, tag: &'static str) -> Self {
667 self.tag = Some(tag);
668 self
669 }
670
671 /// Return the widget handle stored by this node.
672 pub fn widget(&self) -> &Rc<RefCell<dyn Widget>> {
673 &self.widget
674 }
675
676 /// Return this node's test-automation tag.
677 pub const fn tag(&self) -> Option<&'static str> {
678 self.tag
679 }
680
681 /// Return immutable access to the node's object metadata.
682 pub const fn meta(&self) -> &ObjectMeta {
683 &self.meta
684 }
685
686 /// Return mutable access to the node's object metadata.
687 pub fn meta_mut(&mut self) -> &mut ObjectMeta {
688 &mut self.meta
689 }
690
691 /// Return this node's object flags.
692 pub const fn flags(&self) -> ObjectFlags {
693 self.meta.flags()
694 }
695
696 /// Return this node's object state bits.
697 pub const fn states(&self) -> ObjectStates {
698 self.meta.states()
699 }
700
701 /// Set or clear a flag on this node.
702 pub fn set_flag(&mut self, flag: ObjectFlags, enabled: bool) {
703 self.meta.flags_mut().set(flag, enabled);
704 }
705
706 /// Set or clear a state bit on this node.
707 pub fn set_state(&mut self, state: ObjectStates, enabled: bool) {
708 self.meta.states_mut().set(state, enabled);
709 }
710
711 /// Return whether this node is detached from a live object tree.
712 pub const fn is_detached(&self) -> bool {
713 self.meta.is_detached()
714 }
715
716 /// Return this node's child list.
717 pub fn children(&self) -> &[ObjectNode] {
718 &self.children
719 }
720
721 /// Return this node's mutable child list.
722 pub fn children_mut(&mut self) -> &mut Vec<ObjectNode> {
723 &mut self.children
724 }
725
726 // -----------------------------------------------------------------------
727 // Scroll state accessors (LPAR-05 §5)
728 // -----------------------------------------------------------------------
729
730 /// Attach scroll state to this node, enabling it as a scroll container.
731 ///
732 /// The node's `SCROLLABLE` flag is set automatically. This is the primary
733 /// way to make a node a scroll container (LPAR-05 §5).
734 pub fn set_scroll_state(&mut self, state: Box<crate::scroll::ScrollState>) {
735 self.meta.flags_mut().insert(ObjectFlags::SCROLLABLE);
736 self.scroll = Some(state);
737 }
738
739 /// Return an immutable reference to the scroll state, if any.
740 pub fn scroll_state(&self) -> Option<&crate::scroll::ScrollState> {
741 self.scroll.as_deref()
742 }
743
744 /// Return a mutable reference to the scroll state, if any.
745 pub fn scroll_state_mut(&mut self) -> Option<&mut crate::scroll::ScrollState> {
746 self.scroll.as_deref_mut()
747 }
748
749 // -----------------------------------------------------------------------
750 // Style accessors (LPAR-07 §7.1)
751 // -----------------------------------------------------------------------
752
753 /// Add a locally owned style patch keyed by `selector`.
754 ///
755 /// Local style entries take priority over added (shared) entries in the
756 /// cascade. Within local entries, the last-added entry wins when multiple
757 /// selectors match (§7.2 reverse-registration-order rule).
758 ///
759 /// The style slot is lazily allocated on the first call.
760 pub fn add_local_style(
761 &mut self,
762 patch: crate::style_cascade::StylePatch,
763 selector: crate::style_cascade::Selector,
764 ) {
765 let slot = self
766 .style
767 .get_or_insert_with(|| Box::new(crate::style_cascade::StyleState::new()));
768 crate::style_cascade::push_local(slot, patch, selector);
769 }
770
771 /// Add a shared (added) style patch reference keyed by `selector`.
772 ///
773 /// Added entries have lower precedence than local entries. Within added
774 /// entries, the last-added entry wins when multiple selectors match (§7.2).
775 ///
776 /// The `'static` lifetime constraint is conservative in v1; see LPAR-07
777 /// §7.1 for the deferred object-lifetime-scoped extension.
778 ///
779 /// The style slot is lazily allocated on the first call.
780 pub fn add_style(
781 &mut self,
782 patch: &'static crate::style_cascade::StylePatch,
783 selector: crate::style_cascade::Selector,
784 ) {
785 let slot = self
786 .style
787 .get_or_insert_with(|| Box::new(crate::style_cascade::StyleState::new()));
788 crate::style_cascade::push_added(slot, patch, selector);
789 }
790
791 /// Add a default-theme style patch keyed by `selector` (LPAR-07 §9.1).
792 ///
793 /// Theme entries resolve at the **lowest** style precedence — below local
794 /// and added styles — so a widget or application style always wins over the
795 /// theme regardless of registration order. Themes write here via
796 /// [`LparTheme::apply_to_node`](crate::theme::LparTheme::apply_to_node).
797 ///
798 /// The style slot is lazily allocated on the first call.
799 pub fn add_theme_style(
800 &mut self,
801 patch: crate::style_cascade::StylePatch,
802 selector: crate::style_cascade::Selector,
803 ) {
804 let slot = self
805 .style
806 .get_or_insert_with(|| Box::new(crate::style_cascade::StyleState::new()));
807 crate::style_cascade::push_theme(slot, patch, selector);
808 }
809
810 /// Clear all default-theme style entries on this node (for theme re-apply).
811 ///
812 /// Returns the number of entries removed; `0` when the style slot is `None`.
813 pub fn clear_theme_styles(&mut self) -> usize {
814 match self.style.as_deref_mut() {
815 Some(slot) => crate::style_cascade::clear_theme(slot),
816 None => 0,
817 }
818 }
819
820 /// Remove local style entries whose selector matches `(part, states)`.
821 ///
822 /// A selector matches the `(part, states)` query when the selector's part
823 /// equals `part` and all state bits in `states` are present in the
824 /// selector's state mask (same predicate as cascade matching).
825 ///
826 /// Returns the number of entries removed. Returns `0` when the style slot
827 /// is `None`.
828 ///
829 /// To remove all local styles for a part regardless of state, call
830 /// [`remove_all_local_styles_by_part`](Self::remove_all_local_styles_by_part).
831 /// To clear everything, call [`remove_all_local_styles`](Self::remove_all_local_styles).
832 pub fn remove_local_styles(
833 &mut self,
834 part: crate::style_cascade::Part,
835 states: ObjectStates,
836 ) -> usize {
837 match self.style.as_deref_mut() {
838 Some(slot) => crate::style_cascade::remove_local_matching(slot, part, states),
839 None => 0,
840 }
841 }
842
843 /// Remove all local style entries for `part` regardless of state mask.
844 ///
845 /// This is the wildcard form described in §7.5 — removes every local entry
846 /// whose part equals `part`, regardless of its state selector.
847 ///
848 /// Returns the number of entries removed.
849 pub fn remove_all_local_styles_by_part(&mut self, part: crate::style_cascade::Part) -> usize {
850 match self.style.as_deref_mut() {
851 Some(slot) => crate::style_cascade::remove_all_local_by_part(slot, part),
852 None => 0,
853 }
854 }
855
856 /// Remove all local style entries from this node (unconditional clear).
857 ///
858 /// Returns the number of entries removed.
859 pub fn remove_all_local_styles(&mut self) -> usize {
860 match self.style.as_deref_mut() {
861 Some(slot) => crate::style_cascade::remove_all_local(slot),
862 None => 0,
863 }
864 }
865
866 // -----------------------------------------------------------------------
867 // Layout state accessors (LPAR-10 §5.A)
868 // -----------------------------------------------------------------------
869
870 /// Return the node's layout-computed bounds override if present, otherwise
871 /// the widget's intrinsic bounds (`Widget::bounds()`).
872 ///
873 /// This is the canonical bounds query for hit-testing and draw positioning.
874 /// Code that needs layout-aware placement MUST call this rather than
875 /// `widget.borrow().bounds()` directly.
876 pub fn effective_bounds(&self) -> Rect {
877 if let Some(ls) = self.layout.as_deref()
878 && let Some(computed) = ls.computed
879 {
880 return computed;
881 }
882 self.widget.borrow().bounds()
883 }
884
885 /// Configure this node as a flex layout container.
886 ///
887 /// Lazily allocates the `LayoutState` slot, sets the role to
888 /// `Container(Flex(config))`, and marks the node dirty.
889 pub fn set_layout_flex(&mut self, config: crate::layout::FlexConfig) {
890 let slot = self
891 .layout
892 .get_or_insert_with(|| Box::new(LayoutState::default()));
893 slot.role = crate::layout::LayoutRole::Container(crate::layout::EngineConfig::Flex(config));
894 slot.layout_dirty = true;
895 }
896
897 /// Configure this node as a grid layout container.
898 ///
899 /// Lazily allocates the `LayoutState` slot, sets the role to
900 /// `Container(Grid(config))`, and marks the node dirty.
901 pub fn set_layout_grid(&mut self, config: crate::layout::GridConfig) {
902 let slot = self
903 .layout
904 .get_or_insert_with(|| Box::new(LayoutState::default()));
905 slot.role = crate::layout::LayoutRole::Container(crate::layout::EngineConfig::Grid(config));
906 slot.layout_dirty = true;
907 }
908
909 /// Set the layout item hints for this node.
910 ///
911 /// Lazily allocates the `LayoutState` slot, sets the role to
912 /// `Item(hints)`, and marks the node dirty.
913 pub fn set_item_hints(&mut self, hints: crate::layout::ItemHints) {
914 let slot = self
915 .layout
916 .get_or_insert_with(|| Box::new(LayoutState::default()));
917 slot.role = crate::layout::LayoutRole::Item(hints);
918 slot.layout_dirty = true;
919 }
920
921 /// Mark this node's layout as dirty, triggering a re-layout on the next
922 /// [`run_layout`](crate::layout::run_layout) call.
923 pub fn mark_layout_dirty(&mut self) {
924 if let Some(ls) = self.layout.as_deref_mut() {
925 ls.layout_dirty = true;
926 }
927 }
928
929 // -----------------------------------------------------------------------
930 // Handler registration (LPAR-04 §6)
931 // -----------------------------------------------------------------------
932
933 /// Register a trickle-phase handler on this node.
934 ///
935 /// Trickle handlers run root→target before the target phase. A handler
936 /// returning `true` consumes the event and stops all phases.
937 pub fn add_trickle_handler<F>(&mut self, handler: F)
938 where
939 F: FnMut(&ObjectEvent, EventContext) -> bool + 'static,
940 {
941 self.trickle_handlers.push(Box::new(handler));
942 }
943
944 /// Register a target-phase handler on this node.
945 ///
946 /// Target handlers run at the node when it is the dispatch target, after
947 /// the trickle phase. A handler returning `true` consumes the event.
948 pub fn add_target_handler<F>(&mut self, handler: F)
949 where
950 F: FnMut(&ObjectEvent, EventContext) -> bool + 'static,
951 {
952 self.target_handlers.push(Box::new(handler));
953 }
954
955 /// Register a bubble-phase handler on this node.
956 ///
957 /// Bubble handlers run target→root after the target phase, but only while
958 /// [`ObjectFlags::EVENT_BUBBLE`] is set on the target (and on each
959 /// ancestor traversed). A handler returning `true` consumes the event.
960 pub fn add_bubble_handler<F>(&mut self, handler: F)
961 where
962 F: FnMut(&ObjectEvent, EventContext) -> bool + 'static,
963 {
964 self.bubble_handlers.push(Box::new(handler));
965 }
966
967 /// Invoke all registered target-phase handlers for `event`.
968 ///
969 /// This is the direct handler-invocation path used by lifecycle delivery
970 /// (Attached/Detached/ChildChanged/Focused/Defocused), which runs outside
971 /// the trickle/bubble dispatch phases.
972 ///
973 /// Returns `true` if any handler consumed the event.
974 pub fn invoke_handlers_for(&mut self, event: &ObjectEvent) -> bool {
975 let ctx = EventContext {
976 target_tag: self.tag,
977 current_tag: self.tag,
978 };
979 for handler in &mut self.target_handlers {
980 if handler(event, ctx) {
981 return true;
982 }
983 }
984 false
985 }
986
987 // -----------------------------------------------------------------------
988 // Mutation helpers — emit lifecycle events (LPAR-04 §6.6)
989 // -----------------------------------------------------------------------
990
991 /// Append a child node and return its index.
992 ///
993 /// After the structural change, emits [`ObjectEvent::Attached`] to the
994 /// added subtree root's target handlers, then [`ObjectEvent::ChildChanged`]
995 /// to this node's target handlers.
996 ///
997 /// # Dispatch constraint
998 ///
999 /// This method MUST NOT be called from inside an active
1000 /// [`dispatch_object_event`] traversal (LPAR-02 §7.4 / LPAR-04 §6.6).
1001 pub fn append_child(&mut self, mut child: ObjectNode) -> usize {
1002 child.set_detached_recursive(false);
1003 self.children.push(child);
1004 let idx = self.children.len() - 1;
1005 // Lifecycle: Attached → subtree root.
1006 self.children[idx].invoke_handlers_for(&ObjectEvent::Attached);
1007 // Lifecycle: ChildChanged → parent.
1008 self.invoke_handlers_for(&ObjectEvent::ChildChanged);
1009 idx
1010 }
1011
1012 /// Insert a child node at `index`.
1013 ///
1014 /// Returns `false` when `index` is greater than the current child count.
1015 ///
1016 /// After the structural change, emits [`ObjectEvent::Attached`] to the
1017 /// added subtree root's target handlers, then [`ObjectEvent::ChildChanged`]
1018 /// to this node's target handlers.
1019 ///
1020 /// # Dispatch constraint
1021 ///
1022 /// This method MUST NOT be called from inside an active
1023 /// [`dispatch_object_event`] traversal (LPAR-02 §7.4 / LPAR-04 §6.6).
1024 pub fn insert_child(&mut self, index: usize, mut child: ObjectNode) -> bool {
1025 if index > self.children.len() {
1026 return false;
1027 }
1028 child.set_detached_recursive(false);
1029 self.children.insert(index, child);
1030 // Lifecycle: Attached → subtree root.
1031 self.children[index].invoke_handlers_for(&ObjectEvent::Attached);
1032 // Lifecycle: ChildChanged → parent.
1033 self.invoke_handlers_for(&ObjectEvent::ChildChanged);
1034 true
1035 }
1036
1037 /// Detach and return the child at `index`.
1038 ///
1039 /// The returned subtree is marked detached recursively. After the
1040 /// structural change, emits [`ObjectEvent::Detached`] to the detached
1041 /// subtree root's target handlers, then [`ObjectEvent::ChildChanged`] to
1042 /// this node's target handlers.
1043 ///
1044 /// # Dispatch constraint
1045 ///
1046 /// This method MUST NOT be called from inside an active
1047 /// [`dispatch_object_event`] traversal (LPAR-02 §7.4 / LPAR-04 §6.6).
1048 pub fn detach_child(&mut self, index: usize) -> Option<ObjectNode> {
1049 if index >= self.children.len() {
1050 return None;
1051 }
1052 let mut child = self.children.remove(index);
1053 child.set_detached_recursive(true);
1054 // Lifecycle: Detached → detached subtree root.
1055 child.invoke_handlers_for(&ObjectEvent::Detached);
1056 // Lifecycle: ChildChanged → parent.
1057 self.invoke_handlers_for(&ObjectEvent::ChildChanged);
1058 Some(child)
1059 }
1060
1061 // -----------------------------------------------------------------------
1062 // Reorder helpers (emit ChildChanged → parent on an effective move,
1063 // per the LPAR-04 §5.3 "add/remove/reorder" rule)
1064 // -----------------------------------------------------------------------
1065
1066 /// Move the child at `index` to the front of the draw order.
1067 ///
1068 /// The front is the highest index and is considered visually topmost for
1069 /// hit testing. On a successful move, emits [`ObjectEvent::ChildChanged`]
1070 /// to this node's target handlers (LPAR-04 §5.3).
1071 pub fn raise_child(&mut self, index: usize) -> bool {
1072 if index >= self.children.len() {
1073 return false;
1074 }
1075 let child = self.children.remove(index);
1076 self.children.push(child);
1077 self.invoke_handlers_for(&ObjectEvent::ChildChanged);
1078 true
1079 }
1080
1081 /// Move the child at `index` to the back of the draw order.
1082 ///
1083 /// On a successful move, emits [`ObjectEvent::ChildChanged`] to this node's
1084 /// target handlers (LPAR-04 §5.3).
1085 pub fn lower_child(&mut self, index: usize) -> bool {
1086 if index >= self.children.len() {
1087 return false;
1088 }
1089 let child = self.children.remove(index);
1090 self.children.insert(0, child);
1091 self.invoke_handlers_for(&ObjectEvent::ChildChanged);
1092 true
1093 }
1094
1095 /// Move child `from` directly before child `before`.
1096 ///
1097 /// On an effective move (not a `from == before` no-op), emits
1098 /// [`ObjectEvent::ChildChanged`] to this node's target handlers
1099 /// (LPAR-04 §5.3).
1100 pub fn move_child_before(&mut self, from: usize, before: usize) -> bool {
1101 let len = self.children.len();
1102 if from >= len || before >= len {
1103 return false;
1104 }
1105 if from == before {
1106 return true;
1107 }
1108 let child = self.children.remove(from);
1109 let target = if from < before { before - 1 } else { before };
1110 self.children.insert(target, child);
1111 self.invoke_handlers_for(&ObjectEvent::ChildChanged);
1112 true
1113 }
1114
1115 /// Move child `from` directly after child `after`.
1116 ///
1117 /// On an effective move (not a `from == after` no-op), emits
1118 /// [`ObjectEvent::ChildChanged`] to this node's target handlers
1119 /// (LPAR-04 §5.3).
1120 pub fn move_child_after(&mut self, from: usize, after: usize) -> bool {
1121 let len = self.children.len();
1122 if from >= len || after >= len {
1123 return false;
1124 }
1125 if from == after {
1126 return true;
1127 }
1128 let child = self.children.remove(from);
1129 let mut target = after + 1;
1130 if from < target {
1131 target -= 1;
1132 }
1133 if target > self.children.len() {
1134 target = self.children.len();
1135 }
1136 self.children.insert(target, child);
1137 self.invoke_handlers_for(&ObjectEvent::ChildChanged);
1138 true
1139 }
1140
1141 // -----------------------------------------------------------------------
1142 // Draw and hit-test (unchanged from LPAR-02)
1143 // -----------------------------------------------------------------------
1144
1145 /// Recursively draw this object subtree.
1146 ///
1147 /// Hidden or detached subtrees are skipped. Visible nodes draw parent first
1148 /// and then children in sibling order.
1149 ///
1150 /// # LPAR-10 translation mechanism (§5.A)
1151 ///
1152 /// When a node has a layout-computed rect that differs from its widget's
1153 /// intrinsic bounds, drawing is routed through a
1154 /// [`ClipRenderer`](crate::renderer::ClipRenderer) that translates by
1155 /// `(effective_bounds.origin − widget.bounds().origin)` and clips to
1156 /// `effective_bounds`. This repositions the widget's drawing to its
1157 /// computed origin without requiring the widget to know its external
1158 /// position.
1159 ///
1160 /// For resize-aware widgets that override `set_bounds`, the widget's own
1161 /// `bounds()` equals `effective_bounds` so the translation is zero and no
1162 /// `ClipRenderer` is interposed.
1163 pub fn draw(&self, renderer: &mut dyn Renderer) {
1164 if self.is_hidden_or_detached() {
1165 return;
1166 }
1167
1168 let intrinsic = self.widget.borrow().bounds();
1169 let effective = self.effective_bounds();
1170
1171 if intrinsic.x != effective.x || intrinsic.y != effective.y {
1172 // Translation needed: widget draws at intrinsic origin, but must
1173 // appear at effective origin.
1174 let dx = effective.x - intrinsic.x;
1175 let dy = effective.y - intrinsic.y;
1176 let mut clip_r = ClipRenderer::with_offset(renderer, effective, dx, dy);
1177 self.widget.borrow().draw(&mut clip_r);
1178 } else {
1179 self.widget.borrow().draw(renderer);
1180 }
1181
1182 for child in &self.children {
1183 child.draw(renderer);
1184 }
1185 }
1186
1187 /// Return the topmost targetable node at `x`, `y`.
1188 ///
1189 /// Hit testing searches children in reverse sibling order before testing
1190 /// the node itself. Hidden and detached subtrees are skipped. Disabled
1191 /// nodes are not targetable, but their visible children remain eligible in
1192 /// this base object phase.
1193 pub fn hit_test(&self, x: i32, y: i32) -> Option<&ObjectNode> {
1194 if self.is_hidden_or_detached() {
1195 return None;
1196 }
1197 for child in self.children.iter().rev() {
1198 if let Some(hit) = child.hit_test(x, y) {
1199 return Some(hit);
1200 }
1201 }
1202 if self.is_targetable_at(x, y) {
1203 Some(self)
1204 } else {
1205 None
1206 }
1207 }
1208
1209 /// Union of this widget's bounds with every visible descendant's bounds,
1210 /// in logical coordinates (LPAR-03 "subtree visual extent").
1211 ///
1212 /// Returns `None` when this node is hidden or detached, or when neither
1213 /// this widget nor any visible descendant has positive-area bounds.
1214 /// Zero-area bounds contribute nothing; hidden or detached subtrees are
1215 /// excluded entirely, exactly mirroring [`draw`](Self::draw) skipping.
1216 ///
1217 /// # Ordering contract (LPAR-03 §15)
1218 ///
1219 /// A hidden root yields `None` **by design**: invalidation callers order
1220 /// their operations as *compute-then-hide* (capture the extent before
1221 /// setting [`ObjectFlags::HIDDEN`]) and *show-then-compute* (clear the
1222 /// flag, then capture). The same applies to detach: capture the extent
1223 /// before [`detach_child`](Self::detach_child), then push the captured
1224 /// rect into the invalidation planner.
1225 pub fn visible_subtree_extent(&self) -> Option<Rect> {
1226 if self.is_hidden_or_detached() {
1227 return None;
1228 }
1229 // Use effective_bounds so layout-managed nodes contribute their
1230 // computed extent, not their intrinsic (possibly (0,0)) bounds.
1231 let bounds = self.effective_bounds();
1232 let mut extent = if bounds.width > 0 && bounds.height > 0 {
1233 Some(bounds)
1234 } else {
1235 None
1236 };
1237 for child in &self.children {
1238 if let Some(child_extent) = child.visible_subtree_extent() {
1239 extent = Some(match extent {
1240 Some(current) => current.union(child_extent),
1241 None => child_extent,
1242 });
1243 }
1244 }
1245 extent
1246 }
1247
1248 // -----------------------------------------------------------------------
1249 // Internal helpers
1250 // -----------------------------------------------------------------------
1251
1252 fn set_detached_recursive(&mut self, detached: bool) {
1253 self.meta.set_detached(detached);
1254 for child in &mut self.children {
1255 child.set_detached_recursive(detached);
1256 }
1257 }
1258
1259 fn is_hidden_or_detached(&self) -> bool {
1260 self.is_detached() || self.flags().contains(ObjectFlags::HIDDEN)
1261 }
1262
1263 /// Public(crate) version used by `focus.rs` for ancestor-visibility checks.
1264 pub(crate) fn is_hidden_or_detached_pub(&self) -> bool {
1265 self.is_hidden_or_detached()
1266 }
1267
1268 fn is_targetable_at(&self, x: i32, y: i32) -> bool {
1269 let flags = self.flags();
1270 flags.contains(ObjectFlags::CLICKABLE)
1271 && !flags.contains(ObjectFlags::DISABLED)
1272 && rect_contains(self.effective_bounds(), x, y)
1273 }
1274}
1275
1276// ---------------------------------------------------------------------------
1277// dispatch_object_event — the LPAR-04 routing entry point
1278// ---------------------------------------------------------------------------
1279
1280/// Dispatch an [`ObjectEvent`] through the `root` tree using
1281/// trickle → target → bubble propagation (LPAR-04 §6).
1282///
1283/// # Target resolution
1284///
1285/// - [`DispatchInput::Pointer`]: translates the stream `Event` into an
1286/// `ObjectEvent` and resolves the target via `hit_test(x, y)`.
1287/// - [`DispatchInput::PointerObject`]: dispatches the provided `ObjectEvent`
1288/// to the hit-test target at `(x, y)`.
1289/// - [`DispatchInput::Focused`]: dispatches to the node whose
1290/// [`ObjectStates::FOCUSED`] bit is set.
1291///
1292/// # Phase order
1293///
1294/// 1. **Trickle** — root→target, trickle handlers only.
1295/// 2. **Target** — target node: target handlers, then (for `Pointer` inputs)
1296/// the wrapped [`Widget::handle_event`].
1297/// 3. **Bubble** — target→root, bubble handlers; continues past the target
1298/// only when [`ObjectFlags::EVENT_BUBBLE`] is set on the target.
1299///
1300/// Any handler (or `Widget::handle_event` returning `true`) that consumes the
1301/// event terminates all remaining phases.
1302///
1303/// # Dispatch constraint
1304///
1305/// Mutation helpers ([`ObjectNode::append_child`] etc.) MUST NOT be called
1306/// from inside a handler during an active dispatch (LPAR-02 §7.4).
1307pub fn dispatch_object_event(root: &mut ObjectNode, input: DispatchInput) -> Disposition {
1308 // ----- 1. Resolve target path -----
1309 let (target_path, object_event, raw_stream_event) = match resolve_target(root, &input) {
1310 None => return Disposition::NoTarget,
1311 Some(t) => t,
1312 };
1313
1314 let target_tag = node_at_path(root, &target_path).tag();
1315
1316 // ----- 2. Trickle phase: root → target -----
1317 // Visit nodes [0..len] where path[0..i] is the prefix of the target path.
1318 // We visit ancestor nodes only (0..len), not the target itself.
1319 for depth in 0..target_path.len() {
1320 let node_path = &target_path[..depth];
1321 let ctx = EventContext {
1322 target_tag,
1323 current_tag: node_at_path(root, node_path).tag(),
1324 };
1325 let consumed = invoke_trickle(root, node_path, &object_event, ctx);
1326 if consumed {
1327 return Disposition::Consumed;
1328 }
1329 }
1330
1331 // ----- 3. Target phase -----
1332 {
1333 let ctx = EventContext {
1334 target_tag,
1335 current_tag: target_tag,
1336 };
1337 let target_node = node_at_path_mut(root, &target_path);
1338 // Run target handlers.
1339 for handler in &mut target_node.target_handlers {
1340 if handler(&object_event, ctx) {
1341 return Disposition::Consumed;
1342 }
1343 }
1344 // For pointer stream inputs, also invoke Widget::handle_event.
1345 if let Some(ref ev) = raw_stream_event
1346 && target_node.widget.borrow_mut().handle_event(ev)
1347 {
1348 return Disposition::Consumed;
1349 }
1350 }
1351
1352 // ----- 4. Bubble phase: target → root -----
1353 // The event is delivered to the target's bubble handlers, then ascends to
1354 // each ancestor's bubble handlers. Ascent from a node to its parent
1355 // continues only while that node has EVENT_BUBBLE set — the per-object rule
1356 // LVGL uses (LPAR-04 §6.4). A broken flag chain stops the bubble, so a
1357 // target whose ancestor clears the flag does not reach the root.
1358 let mut depth = target_path.len();
1359 loop {
1360 let node_path = &target_path[..depth];
1361 let ctx = EventContext {
1362 target_tag,
1363 current_tag: node_at_path(root, node_path).tag(),
1364 };
1365 if invoke_bubble(root, node_path, &object_event, ctx) {
1366 return Disposition::Consumed;
1367 }
1368 if depth == 0 {
1369 break;
1370 }
1371 // Continue to the parent only if THIS node opts into bubbling.
1372 if !node_at_path(root, node_path)
1373 .flags()
1374 .contains(ObjectFlags::EVENT_BUBBLE)
1375 {
1376 break;
1377 }
1378 depth -= 1;
1379 }
1380
1381 Disposition::Unconsumed
1382}
1383
1384// ---------------------------------------------------------------------------
1385// dispatch_object_event internals
1386// ---------------------------------------------------------------------------
1387
1388/// Resolve target path, object event, and optional raw stream event.
1389fn resolve_target(
1390 root: &ObjectNode,
1391 input: &DispatchInput,
1392) -> Option<(Vec<usize>, ObjectEvent, Option<Event>)> {
1393 match input {
1394 DispatchInput::Pointer { x, y, event } => {
1395 let obj_ev = stream_event_to_object_event(event)?;
1396 let path = hit_test_path(root, *x, *y)?;
1397 Some((path, obj_ev, Some(event.clone())))
1398 }
1399 DispatchInput::PointerObject { x, y, event } => {
1400 let path = hit_test_path(root, *x, *y)?;
1401 Some((path, event.clone(), None))
1402 }
1403 DispatchInput::Focused { event } => {
1404 let path = find_focused_path_ro(root)?;
1405 Some((path, event.clone(), None))
1406 }
1407 DispatchInput::Container { path, event } => Some((path.clone(), event.clone(), None)),
1408 }
1409}
1410
1411/// Map a stream [`Event`] to an [`ObjectEvent`] (pointer events only).
1412///
1413/// Returns `None` for events that have no direct pointer-target mapping
1414/// (e.g. `Tick`, `KeyDown`, `Encoder`).
1415fn stream_event_to_object_event(ev: &Event) -> Option<ObjectEvent> {
1416 match ev {
1417 Event::PressDown { x, y } => Some(ObjectEvent::Pressed { x: *x, y: *y }),
1418 Event::PressRelease { x, y } => Some(ObjectEvent::Clicked { x: *x, y: *y }),
1419 Event::DoubleTap { x, y } => Some(ObjectEvent::DoubleClicked { x: *x, y: *y }),
1420 Event::LongPress { x, y } => Some(ObjectEvent::LongPressed { x: *x, y: *y }),
1421 Event::LongPressRepeat { x, y } => Some(ObjectEvent::LongPressedRepeat { x: *x, y: *y }),
1422 Event::PointerUp { x, y } => Some(ObjectEvent::Released { x: *x, y: *y }),
1423 _ => None,
1424 }
1425}
1426
1427/// Hit-test and return the path to the topmost targetable node, as a sequence
1428/// of child indices from the root.
1429fn hit_test_path(root: &ObjectNode, x: i32, y: i32) -> Option<Vec<usize>> {
1430 hit_test_path_recursive(root, x, y, &mut Vec::new())
1431}
1432
1433fn hit_test_path_recursive(
1434 node: &ObjectNode,
1435 x: i32,
1436 y: i32,
1437 path: &mut Vec<usize>,
1438) -> Option<Vec<usize>> {
1439 if node.is_hidden_or_detached() {
1440 return None;
1441 }
1442 for (i, child) in node.children.iter().enumerate().rev() {
1443 path.push(i);
1444 if let Some(p) = hit_test_path_recursive(child, x, y, path) {
1445 return Some(p);
1446 }
1447 path.pop();
1448 }
1449 if node.is_targetable_at(x, y) {
1450 Some(path.clone())
1451 } else {
1452 None
1453 }
1454}
1455
1456/// Find the path to the focused node (read-only).
1457fn find_focused_path_ro(root: &ObjectNode) -> Option<Vec<usize>> {
1458 find_focused_ro_recursive(root, &mut Vec::new())
1459}
1460
1461fn find_focused_ro_recursive(node: &ObjectNode, path: &mut Vec<usize>) -> Option<Vec<usize>> {
1462 if node.states().contains(ObjectStates::FOCUSED) {
1463 return Some(path.clone());
1464 }
1465 for (i, child) in node.children.iter().enumerate() {
1466 path.push(i);
1467 if let Some(p) = find_focused_ro_recursive(child, path) {
1468 return Some(p);
1469 }
1470 path.pop();
1471 }
1472 None
1473}
1474
1475/// Navigate to a node by structural path (immutable).
1476fn node_at_path<'a>(root: &'a ObjectNode, path: &[usize]) -> &'a ObjectNode {
1477 let mut current = root;
1478 for &idx in path {
1479 current = ¤t.children[idx];
1480 }
1481 current
1482}
1483
1484/// Navigate to a node by structural path (mutable).
1485fn node_at_path_mut<'a>(root: &'a mut ObjectNode, path: &[usize]) -> &'a mut ObjectNode {
1486 let mut current = root;
1487 for &idx in path {
1488 current = &mut current.children[idx];
1489 }
1490 current
1491}
1492
1493/// Invoke trickle handlers at the node at `node_path`.
1494fn invoke_trickle(
1495 root: &mut ObjectNode,
1496 node_path: &[usize],
1497 event: &ObjectEvent,
1498 ctx: EventContext,
1499) -> bool {
1500 let node = node_at_path_mut(root, node_path);
1501 for handler in &mut node.trickle_handlers {
1502 if handler(event, ctx) {
1503 return true;
1504 }
1505 }
1506 false
1507}
1508
1509/// Invoke bubble handlers at the node at `node_path`.
1510fn invoke_bubble(
1511 root: &mut ObjectNode,
1512 node_path: &[usize],
1513 event: &ObjectEvent,
1514 ctx: EventContext,
1515) -> bool {
1516 let node = node_at_path_mut(root, node_path);
1517 for handler in &mut node.bubble_handlers {
1518 if handler(event, ctx) {
1519 return true;
1520 }
1521 }
1522 false
1523}
1524
1525fn rect_contains(rect: Rect, x: i32, y: i32) -> bool {
1526 rect.width > 0
1527 && rect.height > 0
1528 && x >= rect.x
1529 && y >= rect.y
1530 && x < rect.x + rect.width
1531 && y < rect.y + rect.height
1532}
1533
1534// ---------------------------------------------------------------------------
1535// Tests
1536// ---------------------------------------------------------------------------
1537
1538#[cfg(test)]
1539mod tests {
1540 use alloc::rc::Rc;
1541 use alloc::vec;
1542 use alloc::vec::Vec;
1543 use core::cell::RefCell;
1544
1545 use super::*;
1546 use crate::event::Event;
1547 use crate::renderer::Renderer;
1548 use crate::widget::{Color, Widget};
1549
1550 // -----------------------------------------------------------------------
1551 // Minimal test widget
1552 // -----------------------------------------------------------------------
1553
1554 struct TestWidget {
1555 tag: &'static str,
1556 bounds: Rect,
1557 /// If set, handle_event returns true for this event type.
1558 consume_clicks: bool,
1559 }
1560
1561 impl TestWidget {
1562 fn node(tag: &'static str, bounds: Rect) -> ObjectNode {
1563 ObjectNode::new(Rc::new(RefCell::new(Self {
1564 tag,
1565 bounds,
1566 consume_clicks: false,
1567 })))
1568 .with_tag(tag)
1569 }
1570 }
1571
1572 impl Widget for TestWidget {
1573 fn bounds(&self) -> Rect {
1574 self.bounds
1575 }
1576
1577 fn draw(&self, renderer: &mut dyn Renderer) {
1578 renderer.draw_text(
1579 (self.bounds.x, self.bounds.y),
1580 self.tag,
1581 Color(0, 0, 0, 255),
1582 );
1583 }
1584
1585 fn handle_event(&mut self, event: &Event) -> bool {
1586 self.consume_clicks && matches!(event, Event::PressRelease { .. })
1587 }
1588 }
1589
1590 struct TestRenderer {
1591 draws: Vec<&'static str>,
1592 }
1593
1594 impl Renderer for TestRenderer {
1595 fn fill_rect(&mut self, _rect: Rect, _color: Color) {}
1596
1597 fn draw_text(&mut self, _position: (i32, i32), text: &str, _color: Color) {
1598 match text {
1599 "root" => self.draws.push("root"),
1600 "a" => self.draws.push("a"),
1601 "b" => self.draws.push("b"),
1602 "c" => self.draws.push("c"),
1603 "grand" => self.draws.push("grand"),
1604 _ => {}
1605 }
1606 }
1607 }
1608
1609 fn rect(x: i32, y: i32, width: i32, height: i32) -> Rect {
1610 Rect {
1611 x,
1612 y,
1613 width,
1614 height,
1615 }
1616 }
1617
1618 // -----------------------------------------------------------------------
1619 // Existing LPAR-02 tests (must pass unchanged)
1620 // -----------------------------------------------------------------------
1621
1622 #[test]
1623 fn flags_and_states_store_and_clear() {
1624 let mut node = TestWidget::node("root", rect(0, 0, 10, 10));
1625
1626 node.set_flag(ObjectFlags::CLICKABLE, true);
1627 node.set_flag(ObjectFlags::FOCUSABLE, true);
1628 node.set_state(ObjectStates::FOCUSED, true);
1629
1630 assert!(node.flags().contains(ObjectFlags::CLICKABLE));
1631 assert!(node.flags().contains(ObjectFlags::FOCUSABLE));
1632 assert!(node.states().contains(ObjectStates::FOCUSED));
1633
1634 node.set_flag(ObjectFlags::FOCUSABLE, false);
1635 node.set_state(ObjectStates::FOCUSED, false);
1636
1637 assert!(!node.flags().contains(ObjectFlags::FOCUSABLE));
1638 assert!(!node.states().contains(ObjectStates::FOCUSED));
1639 }
1640
1641 #[test]
1642 fn draw_skips_hidden_subtree() {
1643 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
1644 let mut hidden = TestWidget::node("a", rect(0, 0, 10, 10));
1645 hidden.set_flag(ObjectFlags::HIDDEN, true);
1646 hidden.append_child(TestWidget::node("grand", rect(0, 0, 10, 10)));
1647 root.append_child(hidden);
1648 root.append_child(TestWidget::node("b", rect(0, 0, 10, 10)));
1649
1650 let mut renderer = TestRenderer { draws: Vec::new() };
1651 root.draw(&mut renderer);
1652
1653 assert_eq!(renderer.draws, vec!["root", "b"]);
1654 }
1655
1656 #[test]
1657 fn hit_test_returns_topmost_clickable_node() {
1658 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
1659 let mut a = TestWidget::node("a", rect(0, 0, 20, 20));
1660 let mut b = TestWidget::node("b", rect(0, 0, 20, 20));
1661 a.set_flag(ObjectFlags::CLICKABLE, true);
1662 b.set_flag(ObjectFlags::CLICKABLE, true);
1663 root.append_child(a);
1664 root.append_child(b);
1665
1666 assert_eq!(root.hit_test(5, 5).and_then(ObjectNode::tag), Some("b"));
1667
1668 root.children_mut()[1].set_flag(ObjectFlags::DISABLED, true);
1669 assert_eq!(root.hit_test(5, 5).and_then(ObjectNode::tag), Some("a"));
1670
1671 root.children_mut()[0].set_flag(ObjectFlags::HIDDEN, true);
1672 assert_eq!(root.hit_test(5, 5).and_then(ObjectNode::tag), None);
1673 }
1674
1675 #[test]
1676 fn child_reorder_helpers_update_sibling_order() {
1677 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
1678 root.append_child(TestWidget::node("a", rect(0, 0, 10, 10)));
1679 root.append_child(TestWidget::node("b", rect(0, 0, 10, 10)));
1680 root.append_child(TestWidget::node("c", rect(0, 0, 10, 10)));
1681
1682 assert!(root.raise_child(0));
1683 assert_eq!(child_tags(&root), vec!["b", "c", "a"]);
1684
1685 assert!(root.lower_child(1));
1686 assert_eq!(child_tags(&root), vec!["c", "b", "a"]);
1687
1688 assert!(root.move_child_before(2, 1));
1689 assert_eq!(child_tags(&root), vec!["c", "a", "b"]);
1690
1691 assert!(root.move_child_after(0, 2));
1692 assert_eq!(child_tags(&root), vec!["a", "b", "c"]);
1693
1694 assert!(!root.raise_child(3));
1695 assert!(!root.move_child_before(0, 3));
1696 }
1697
1698 #[test]
1699 fn detach_child_marks_returned_subtree_detached() {
1700 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
1701 let mut child = TestWidget::node("a", rect(0, 0, 10, 10));
1702 child.append_child(TestWidget::node("grand", rect(0, 0, 10, 10)));
1703 root.append_child(child);
1704
1705 let detached = root.detach_child(0).expect("child is present");
1706
1707 assert!(root.children().is_empty());
1708 assert!(detached.is_detached());
1709 assert!(detached.children()[0].is_detached());
1710 }
1711
1712 #[test]
1713 fn adopt_preserves_widget_node_shape() {
1714 let mut widget_root = crate::WidgetNode::new(Rc::new(RefCell::new(TestWidget {
1715 tag: "root",
1716 bounds: rect(0, 0, 100, 100),
1717 consume_clicks: false,
1718 })))
1719 .with_tag("root");
1720 widget_root.children.push(
1721 crate::WidgetNode::new(Rc::new(RefCell::new(TestWidget {
1722 tag: "a",
1723 bounds: rect(0, 0, 10, 10),
1724 consume_clicks: false,
1725 })))
1726 .with_tag("a"),
1727 );
1728
1729 let object_root = ObjectNode::adopt(widget_root);
1730
1731 assert_eq!(object_root.tag(), Some("root"));
1732 assert_eq!(object_root.children()[0].tag(), Some("a"));
1733 assert_eq!(object_root.flags(), ObjectFlags::EMPTY);
1734 assert_eq!(object_root.children()[0].states(), ObjectStates::DEFAULT);
1735 }
1736
1737 #[test]
1738 fn visible_subtree_extent_unions_children() {
1739 let mut root = TestWidget::node("root", rect(10, 10, 20, 20));
1740 root.append_child(TestWidget::node("a", rect(40, 5, 10, 10)));
1741 root.append_child(TestWidget::node("b", rect(0, 30, 10, 10)));
1742
1743 assert_eq!(root.visible_subtree_extent(), Some(rect(0, 5, 50, 35)));
1744 }
1745
1746 #[test]
1747 fn visible_subtree_extent_excludes_hidden_child() {
1748 let mut root = TestWidget::node("root", rect(0, 0, 10, 10));
1749 let mut hidden = TestWidget::node("a", rect(50, 50, 10, 10));
1750 hidden.set_flag(ObjectFlags::HIDDEN, true);
1751 root.append_child(hidden);
1752
1753 assert_eq!(root.visible_subtree_extent(), Some(rect(0, 0, 10, 10)));
1754 }
1755
1756 #[test]
1757 fn visible_subtree_extent_skips_zero_area_bounds() {
1758 let mut root = TestWidget::node("root", rect(0, 0, 0, 0));
1759 root.append_child(TestWidget::node("a", rect(5, 5, 10, 10)));
1760
1761 assert_eq!(root.visible_subtree_extent(), Some(rect(5, 5, 10, 10)));
1762 }
1763
1764 #[test]
1765 fn visible_subtree_extent_is_none_for_hidden_or_detached_root() {
1766 let mut hidden = TestWidget::node("a", rect(0, 0, 10, 10));
1767 hidden.set_flag(ObjectFlags::HIDDEN, true);
1768 assert_eq!(hidden.visible_subtree_extent(), None);
1769
1770 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
1771 root.append_child(TestWidget::node("b", rect(0, 0, 10, 10)));
1772 let detached = root.detach_child(0).expect("child is present");
1773 assert_eq!(detached.visible_subtree_extent(), None);
1774 }
1775
1776 #[test]
1777 fn hide_flow_computes_extent_before_hiding() {
1778 use crate::invalidation::{InvalidationList, PresentPlan};
1779
1780 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
1781 let mut panel = TestWidget::node("a", rect(10, 10, 20, 20));
1782 panel.append_child(TestWidget::node("grand", rect(25, 25, 20, 20)));
1783 root.append_child(panel);
1784
1785 let extent = root.children()[0].visible_subtree_extent();
1786 assert_eq!(extent, Some(rect(10, 10, 35, 35)));
1787 root.children_mut()[0].set_flag(ObjectFlags::HIDDEN, true);
1788 assert_eq!(root.children()[0].visible_subtree_extent(), None);
1789
1790 let mut list = InvalidationList::<4>::new(rect(0, 0, 100, 100));
1791 list.push_opt(extent);
1792 assert_eq!(list.plan(), PresentPlan::Rects(&[rect(10, 10, 35, 35)]));
1793 }
1794
1795 #[test]
1796 fn detach_flow_computes_extent_before_detaching() {
1797 use crate::invalidation::{InvalidationList, PresentPlan};
1798
1799 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
1800 let mut panel = TestWidget::node("a", rect(60, 60, 30, 30));
1801 panel.append_child(TestWidget::node("grand", rect(80, 80, 30, 30)));
1802 root.append_child(panel);
1803
1804 let extent = root.children()[0]
1805 .visible_subtree_extent()
1806 .expect("visible subtree has an extent");
1807 assert_eq!(extent, rect(60, 60, 50, 50));
1808 let detached = root.detach_child(0).expect("child is present");
1809 assert_eq!(detached.visible_subtree_extent(), None);
1810
1811 let mut list = InvalidationList::<4>::new(rect(0, 0, 100, 100));
1812 list.push(extent);
1813 assert_eq!(list.plan(), PresentPlan::Rects(&[rect(60, 60, 40, 40)]));
1814 }
1815
1816 // -----------------------------------------------------------------------
1817 // LPAR-04: ObjectEvent tests
1818 // -----------------------------------------------------------------------
1819
1820 /// Build a clickable node in a 10x10 box at position (x,y).
1821 fn clickable(tag: &'static str, x: i32, y: i32) -> ObjectNode {
1822 let mut n = TestWidget::node(tag, rect(x, y, 10, 10));
1823 n.set_flag(ObjectFlags::CLICKABLE, true);
1824 n
1825 }
1826
1827 // -----------------------------------------------------------------------
1828 // LPAR-04: dispatch phase order trickle → target → bubble
1829 // -----------------------------------------------------------------------
1830
1831 #[test]
1832 fn dispatch_phase_order_trickle_target_bubble() {
1833 let visit_log: Rc<RefCell<Vec<(&'static str, &'static str)>>> =
1834 Rc::new(RefCell::new(Vec::new()));
1835
1836 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
1837
1838 // Child node "a" at (0,0) is the click target.
1839 let mut a = clickable("a", 0, 0);
1840 // "a" opts into bubbling.
1841 a.set_flag(ObjectFlags::EVENT_BUBBLE, true);
1842 // Append child BEFORE registering handlers so the Attached lifecycle
1843 // event does not pollute the visit log.
1844 root.append_child(a);
1845
1846 // Register handlers after append so lifecycle emission is isolated.
1847
1848 // Root carries a trickle handler (records "root/trickle").
1849 {
1850 let log = visit_log.clone();
1851 root.add_trickle_handler(move |_ev, ctx| {
1852 log.borrow_mut()
1853 .push((ctx.current_tag.unwrap_or("?"), "trickle"));
1854 false
1855 });
1856 }
1857 // Root also carries a bubble handler.
1858 {
1859 let log = visit_log.clone();
1860 root.add_bubble_handler(move |_ev, ctx| {
1861 log.borrow_mut()
1862 .push((ctx.current_tag.unwrap_or("?"), "bubble"));
1863 false
1864 });
1865 }
1866 // "a" has a target handler.
1867 {
1868 let log = visit_log.clone();
1869 root.children_mut()[0].add_target_handler(move |_ev, ctx| {
1870 log.borrow_mut()
1871 .push((ctx.current_tag.unwrap_or("?"), "target"));
1872 false
1873 });
1874 }
1875
1876 let ev = DispatchInput::PointerObject {
1877 x: 5,
1878 y: 5,
1879 event: ObjectEvent::Clicked { x: 5, y: 5 },
1880 };
1881 let result = dispatch_object_event(&mut root, ev);
1882
1883 assert_eq!(result, Disposition::Unconsumed);
1884 // Expected visit order: root/trickle → a/target → root/bubble
1885 let log = visit_log.borrow();
1886 assert_eq!(
1887 *log,
1888 vec![("root", "trickle"), ("a", "target"), ("root", "bubble"),]
1889 );
1890 }
1891
1892 // -----------------------------------------------------------------------
1893 // LPAR-04: stop-on-consume halts remaining phases
1894 // -----------------------------------------------------------------------
1895
1896 #[test]
1897 fn stop_on_consume_at_target_prevents_bubble() {
1898 let bubble_called: Rc<RefCell<bool>> = Rc::new(RefCell::new(false));
1899
1900 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
1901 {
1902 let called = bubble_called.clone();
1903 root.add_bubble_handler(move |_ev, _ctx| {
1904 *called.borrow_mut() = true;
1905 false
1906 });
1907 }
1908
1909 let mut a = clickable("a", 0, 0);
1910 // Consuming target handler.
1911 a.add_target_handler(|_ev, _ctx| true);
1912 a.set_flag(ObjectFlags::EVENT_BUBBLE, true);
1913 root.append_child(a);
1914
1915 let result = dispatch_object_event(
1916 &mut root,
1917 DispatchInput::PointerObject {
1918 x: 5,
1919 y: 5,
1920 event: ObjectEvent::Clicked { x: 5, y: 5 },
1921 },
1922 );
1923
1924 assert_eq!(result, Disposition::Consumed);
1925 assert!(
1926 !*bubble_called.borrow(),
1927 "bubble must not run after consume"
1928 );
1929 }
1930
1931 #[test]
1932 fn stop_on_consume_at_trickle_prevents_target_and_bubble() {
1933 let target_called: Rc<RefCell<bool>> = Rc::new(RefCell::new(false));
1934 let bubble_called: Rc<RefCell<bool>> = Rc::new(RefCell::new(false));
1935
1936 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
1937
1938 // Append child first, then register handlers so lifecycle emission
1939 // during append_child does not set the flags prematurely.
1940 let mut a = clickable("a", 0, 0);
1941 a.set_flag(ObjectFlags::EVENT_BUBBLE, true);
1942 root.append_child(a);
1943
1944 // Consuming trickle handler at root (registered after child is in tree).
1945 root.add_trickle_handler(|_ev, _ctx| true);
1946
1947 {
1948 let tc = target_called.clone();
1949 root.children_mut()[0].add_target_handler(move |_ev, _ctx| {
1950 *tc.borrow_mut() = true;
1951 false
1952 });
1953 }
1954 {
1955 let bc = bubble_called.clone();
1956 root.add_bubble_handler(move |_ev, _ctx| {
1957 *bc.borrow_mut() = true;
1958 false
1959 });
1960 }
1961
1962 let result = dispatch_object_event(
1963 &mut root,
1964 DispatchInput::PointerObject {
1965 x: 5,
1966 y: 5,
1967 event: ObjectEvent::Clicked { x: 5, y: 5 },
1968 },
1969 );
1970
1971 assert_eq!(result, Disposition::Consumed);
1972 assert!(!*target_called.borrow());
1973 assert!(!*bubble_called.borrow());
1974 }
1975
1976 // -----------------------------------------------------------------------
1977 // LPAR-04: EVENT_BUBBLE gating
1978 // -----------------------------------------------------------------------
1979
1980 #[test]
1981 fn bubble_only_when_event_bubble_flag_set() {
1982 let bubble_called: Rc<RefCell<bool>> = Rc::new(RefCell::new(false));
1983
1984 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
1985 {
1986 let called = bubble_called.clone();
1987 root.add_bubble_handler(move |_ev, _ctx| {
1988 *called.borrow_mut() = true;
1989 false
1990 });
1991 }
1992
1993 // "a" does NOT have EVENT_BUBBLE.
1994 let a = clickable("a", 0, 0);
1995 root.append_child(a);
1996
1997 dispatch_object_event(
1998 &mut root,
1999 DispatchInput::PointerObject {
2000 x: 5,
2001 y: 5,
2002 event: ObjectEvent::Clicked { x: 5, y: 5 },
2003 },
2004 );
2005
2006 assert!(
2007 !*bubble_called.borrow(),
2008 "bubble must not run without EVENT_BUBBLE"
2009 );
2010 }
2011
2012 #[test]
2013 fn bubble_runs_when_event_bubble_flag_set() {
2014 let bubble_called: Rc<RefCell<bool>> = Rc::new(RefCell::new(false));
2015
2016 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
2017 {
2018 let called = bubble_called.clone();
2019 root.add_bubble_handler(move |_ev, _ctx| {
2020 *called.borrow_mut() = true;
2021 false
2022 });
2023 }
2024
2025 let mut a = clickable("a", 0, 0);
2026 a.set_flag(ObjectFlags::EVENT_BUBBLE, true);
2027 root.append_child(a);
2028
2029 dispatch_object_event(
2030 &mut root,
2031 DispatchInput::PointerObject {
2032 x: 5,
2033 y: 5,
2034 event: ObjectEvent::Clicked { x: 5, y: 5 },
2035 },
2036 );
2037
2038 assert!(*bubble_called.borrow(), "bubble must run with EVENT_BUBBLE");
2039 }
2040
2041 #[test]
2042 fn bubble_stops_at_ancestor_without_event_bubble_flag() {
2043 // root → b → a(target). a opts into bubbling, b does NOT. The event
2044 // must reach b's bubble handler but stop there — never reaching root.
2045 let b_called: Rc<RefCell<bool>> = Rc::new(RefCell::new(false));
2046 let root_called: Rc<RefCell<bool>> = Rc::new(RefCell::new(false));
2047
2048 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
2049 let mut b = TestWidget::node("b", rect(0, 0, 20, 20));
2050 let mut a = clickable("a", 0, 0);
2051 a.set_flag(ObjectFlags::EVENT_BUBBLE, true);
2052 b.append_child(a);
2053 // b deliberately has NO EVENT_BUBBLE flag.
2054 root.append_child(b);
2055
2056 // Register handlers after the tree is built so lifecycle emission
2057 // during append does not run them.
2058 {
2059 let called = b_called.clone();
2060 root.children_mut()[0].add_bubble_handler(move |_ev, _ctx| {
2061 *called.borrow_mut() = true;
2062 false
2063 });
2064 }
2065 {
2066 let called = root_called.clone();
2067 root.add_bubble_handler(move |_ev, _ctx| {
2068 *called.borrow_mut() = true;
2069 false
2070 });
2071 }
2072
2073 dispatch_object_event(
2074 &mut root,
2075 DispatchInput::PointerObject {
2076 x: 5,
2077 y: 5,
2078 event: ObjectEvent::Clicked { x: 5, y: 5 },
2079 },
2080 );
2081
2082 assert!(
2083 *b_called.borrow(),
2084 "bubble must reach the flagged target's parent"
2085 );
2086 assert!(
2087 !*root_called.borrow(),
2088 "bubble must stop at an ancestor that lacks EVENT_BUBBLE"
2089 );
2090 }
2091
2092 #[test]
2093 fn reorder_emits_child_changed_to_parent() {
2094 let count: Rc<RefCell<u32>> = Rc::new(RefCell::new(0));
2095
2096 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
2097 root.append_child(TestWidget::node("a", rect(0, 0, 10, 10)));
2098 root.append_child(TestWidget::node("b", rect(0, 0, 10, 10)));
2099 root.append_child(TestWidget::node("c", rect(0, 0, 10, 10)));
2100
2101 // Registered after appends, so only reorders are counted.
2102 {
2103 let c = count.clone();
2104 root.add_target_handler(move |ev, _ctx| {
2105 if matches!(ev, ObjectEvent::ChildChanged) {
2106 *c.borrow_mut() += 1;
2107 }
2108 false
2109 });
2110 }
2111
2112 assert!(root.raise_child(0));
2113 assert!(root.lower_child(1));
2114 assert!(root.move_child_before(2, 0));
2115 assert!(root.move_child_after(0, 2));
2116 assert_eq!(
2117 *count.borrow(),
2118 4,
2119 "each effective reorder emits ChildChanged"
2120 );
2121
2122 // A no-op move emits nothing.
2123 assert!(root.move_child_before(1, 1));
2124 assert_eq!(
2125 *count.borrow(),
2126 4,
2127 "a no-op move must not emit ChildChanged"
2128 );
2129 }
2130
2131 // -----------------------------------------------------------------------
2132 // LPAR-04: pointer target resolution via hit_test
2133 // -----------------------------------------------------------------------
2134
2135 #[test]
2136 fn pointer_target_resolved_via_hit_test() {
2137 let target_seen: Rc<RefCell<Option<&'static str>>> = Rc::new(RefCell::new(None));
2138
2139 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
2140
2141 let mut a = clickable("a", 0, 0);
2142 {
2143 let ts = target_seen.clone();
2144 a.add_target_handler(move |_ev, ctx| {
2145 *ts.borrow_mut() = ctx.target_tag;
2146 false
2147 });
2148 }
2149 root.append_child(a);
2150
2151 dispatch_object_event(
2152 &mut root,
2153 DispatchInput::PointerObject {
2154 x: 5,
2155 y: 5,
2156 event: ObjectEvent::Clicked { x: 5, y: 5 },
2157 },
2158 );
2159
2160 assert_eq!(*target_seen.borrow(), Some("a"));
2161 }
2162
2163 #[test]
2164 fn pointer_no_target_returns_no_target() {
2165 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
2166 // root has no CLICKABLE children; nothing to hit at (50,50).
2167 let result = dispatch_object_event(
2168 &mut root,
2169 DispatchInput::PointerObject {
2170 x: 50,
2171 y: 50,
2172 event: ObjectEvent::Clicked { x: 50, y: 50 },
2173 },
2174 );
2175 assert_eq!(result, Disposition::NoTarget);
2176 }
2177
2178 // -----------------------------------------------------------------------
2179 // LPAR-04: key/encoder via focus
2180 // -----------------------------------------------------------------------
2181
2182 #[test]
2183 fn key_event_resolves_to_focused_node() {
2184 let key_seen: Rc<RefCell<Option<&'static str>>> = Rc::new(RefCell::new(None));
2185
2186 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
2187 let mut a = TestWidget::node("a", rect(0, 0, 10, 10));
2188 a.set_flag(ObjectFlags::FOCUSABLE, true);
2189 {
2190 let ks = key_seen.clone();
2191 a.add_target_handler(move |_ev, ctx| {
2192 *ks.borrow_mut() = ctx.target_tag;
2193 false
2194 });
2195 }
2196 root.append_child(a);
2197
2198 // Manually give "a" focus.
2199 root.children_mut()[0].set_state(ObjectStates::FOCUSED, true);
2200
2201 let result = dispatch_object_event(
2202 &mut root,
2203 DispatchInput::Focused {
2204 event: ObjectEvent::Key(crate::event::Key::Enter),
2205 },
2206 );
2207
2208 assert_eq!(result, Disposition::Unconsumed);
2209 assert_eq!(*key_seen.borrow(), Some("a"));
2210 }
2211
2212 #[test]
2213 fn key_event_no_focused_returns_no_target() {
2214 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
2215 root.append_child(TestWidget::node("a", rect(0, 0, 10, 10)));
2216
2217 let result = dispatch_object_event(
2218 &mut root,
2219 DispatchInput::Focused {
2220 event: ObjectEvent::Key(crate::event::Key::Enter),
2221 },
2222 );
2223 assert_eq!(result, Disposition::NoTarget);
2224 }
2225
2226 // -----------------------------------------------------------------------
2227 // LPAR-04: Pointer stream event mapping
2228 // -----------------------------------------------------------------------
2229
2230 #[test]
2231 fn stream_press_release_maps_to_clicked() {
2232 let ev_seen: Rc<RefCell<Option<ObjectEvent>>> = Rc::new(RefCell::new(None));
2233
2234 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
2235 let mut a = clickable("a", 0, 0);
2236 {
2237 let es = ev_seen.clone();
2238 a.add_target_handler(move |ev, _ctx| {
2239 *es.borrow_mut() = Some(ev.clone());
2240 false
2241 });
2242 }
2243 root.append_child(a);
2244
2245 dispatch_object_event(
2246 &mut root,
2247 DispatchInput::Pointer {
2248 x: 5,
2249 y: 5,
2250 event: Event::PressRelease { x: 5, y: 5 },
2251 },
2252 );
2253
2254 assert_eq!(*ev_seen.borrow(), Some(ObjectEvent::Clicked { x: 5, y: 5 }));
2255 }
2256
2257 #[test]
2258 fn stream_tick_produces_no_target() {
2259 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
2260 root.append_child(clickable("a", 0, 0));
2261
2262 // Tick has no pointer-coordinate mapping; should return NoTarget.
2263 let result = dispatch_object_event(
2264 &mut root,
2265 DispatchInput::Pointer {
2266 x: 5,
2267 y: 5,
2268 event: Event::Tick,
2269 },
2270 );
2271 assert_eq!(result, Disposition::NoTarget);
2272 }
2273
2274 // -----------------------------------------------------------------------
2275 // LPAR-04: Widget::handle_event at target phase
2276 // -----------------------------------------------------------------------
2277
2278 #[test]
2279 fn widget_handle_event_at_target_phase_can_consume() {
2280 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
2281 // Build a node whose widget returns true for PressRelease.
2282 let widget = Rc::new(RefCell::new(TestWidget {
2283 tag: "a",
2284 bounds: rect(0, 0, 10, 10),
2285 consume_clicks: true,
2286 }));
2287 let mut a = ObjectNode::new(widget).with_tag("a");
2288 a.set_flag(ObjectFlags::CLICKABLE, true);
2289 root.append_child(a);
2290
2291 let result = dispatch_object_event(
2292 &mut root,
2293 DispatchInput::Pointer {
2294 x: 5,
2295 y: 5,
2296 event: Event::PressRelease { x: 5, y: 5 },
2297 },
2298 );
2299 assert_eq!(result, Disposition::Consumed);
2300 }
2301
2302 // -----------------------------------------------------------------------
2303 // LPAR-04: Lifecycle events
2304 // -----------------------------------------------------------------------
2305
2306 #[test]
2307 fn append_child_emits_attached_and_child_changed() {
2308 let log: Rc<RefCell<Vec<&'static str>>> = Rc::new(RefCell::new(Vec::new()));
2309
2310 let mut parent = TestWidget::node("parent", rect(0, 0, 100, 100));
2311 {
2312 let l = log.clone();
2313 parent.add_target_handler(move |ev, _ctx| {
2314 if matches!(ev, ObjectEvent::ChildChanged) {
2315 l.borrow_mut().push("parent:child_changed");
2316 }
2317 false
2318 });
2319 }
2320
2321 let mut child = TestWidget::node("child", rect(0, 0, 10, 10));
2322 {
2323 let l = log.clone();
2324 child.add_target_handler(move |ev, _ctx| {
2325 if matches!(ev, ObjectEvent::Attached) {
2326 l.borrow_mut().push("child:attached");
2327 }
2328 false
2329 });
2330 }
2331
2332 parent.append_child(child);
2333
2334 assert_eq!(
2335 *log.borrow(),
2336 vec!["child:attached", "parent:child_changed"]
2337 );
2338 }
2339
2340 #[test]
2341 fn insert_child_emits_attached_and_child_changed() {
2342 let log: Rc<RefCell<Vec<&'static str>>> = Rc::new(RefCell::new(Vec::new()));
2343
2344 let mut parent = TestWidget::node("parent", rect(0, 0, 100, 100));
2345 {
2346 let l = log.clone();
2347 parent.add_target_handler(move |ev, _ctx| {
2348 if matches!(ev, ObjectEvent::ChildChanged) {
2349 l.borrow_mut().push("parent:child_changed");
2350 }
2351 false
2352 });
2353 }
2354
2355 let mut child = TestWidget::node("child", rect(0, 0, 10, 10));
2356 {
2357 let l = log.clone();
2358 child.add_target_handler(move |ev, _ctx| {
2359 if matches!(ev, ObjectEvent::Attached) {
2360 l.borrow_mut().push("child:attached");
2361 }
2362 false
2363 });
2364 }
2365
2366 let ok = parent.insert_child(0, child);
2367 assert!(ok);
2368 assert_eq!(
2369 *log.borrow(),
2370 vec!["child:attached", "parent:child_changed"]
2371 );
2372 }
2373
2374 #[test]
2375 fn detach_child_emits_detached_and_child_changed() {
2376 let log: Rc<RefCell<Vec<&'static str>>> = Rc::new(RefCell::new(Vec::new()));
2377 let mut parent = TestWidget::node("parent", rect(0, 0, 100, 100));
2378 {
2379 let l = log.clone();
2380 parent.add_target_handler(move |ev, _ctx| {
2381 if matches!(ev, ObjectEvent::ChildChanged) {
2382 l.borrow_mut().push("parent:child_changed");
2383 }
2384 false
2385 });
2386 }
2387
2388 let mut child = TestWidget::node("child", rect(0, 0, 10, 10));
2389 {
2390 let l = log.clone();
2391 child.add_target_handler(move |ev, _ctx| {
2392 if matches!(ev, ObjectEvent::Detached) {
2393 l.borrow_mut().push("child:detached");
2394 }
2395 false
2396 });
2397 }
2398
2399 parent.append_child(child);
2400 log.borrow_mut().clear(); // clear the Attached/ChildChanged from append.
2401
2402 let detached = parent.detach_child(0).expect("has child");
2403 drop(detached);
2404
2405 assert_eq!(
2406 *log.borrow(),
2407 vec!["child:detached", "parent:child_changed"]
2408 );
2409 }
2410
2411 #[test]
2412 fn detach_child_still_marks_subtree_detached() {
2413 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
2414 let mut child = TestWidget::node("a", rect(0, 0, 10, 10));
2415 child.append_child(TestWidget::node("grand", rect(0, 0, 10, 10)));
2416 root.append_child(child);
2417
2418 let detached = root.detach_child(0).expect("child is present");
2419
2420 assert!(root.children().is_empty());
2421 assert!(detached.is_detached());
2422 assert!(detached.children()[0].is_detached());
2423 }
2424
2425 // -----------------------------------------------------------------------
2426 // LPAR-04: tag-based focus targeting must not exist
2427 // -----------------------------------------------------------------------
2428
2429 #[test]
2430 fn focus_path_not_tag_based() {
2431 // Verify that focus_path takes a &[usize] structural path, not a tag.
2432 // This test documents the API shape; it would fail to compile if the
2433 // wrong API were provided.
2434 use crate::focus::FocusGroup;
2435
2436 let mut root = TestWidget::node("root", rect(0, 0, 100, 100));
2437 let mut a = TestWidget::node("a", rect(0, 0, 10, 10));
2438 a.set_flag(ObjectFlags::FOCUSABLE, true);
2439 root.append_child(a);
2440
2441 let fg = FocusGroup::new();
2442 // Structural path [0] → child 0 of root ("a").
2443 let ok = fg.focus_path(&mut root, &[0usize]);
2444 assert!(ok);
2445 assert!(root.children()[0].states().contains(ObjectStates::FOCUSED));
2446 }
2447
2448 fn child_tags(root: &ObjectNode) -> Vec<&'static str> {
2449 root.children().iter().filter_map(ObjectNode::tag).collect()
2450 }
2451}