Skip to main content

cranpose_foundation/
modifier.rs

1//! Modifier node scaffolding for Cranpose.
2//!
3//! This module defines the foundational pieces of the Cranpose
4//! `Modifier.Node` system. It introduces traits for modifier nodes and their
5//! contexts as well as a lightweight chain container that reconciles nodes
6//! across updates.
7
8use std::{
9    any::{type_name, Any, TypeId},
10    cell::{Cell, RefCell},
11    fmt,
12    hash::{Hash, Hasher},
13    ops::{BitOr, BitOrAssign},
14    rc::Rc,
15};
16
17use cranpose_core::{collections::map::HashMap, hash::default};
18pub use cranpose_ui_graphics::{DrawScope, Size};
19pub use cranpose_ui_layout::{Constraints, Measurable};
20
21use crate::nodes::input::types::PointerEvent;
22// use cranpose_core::NodeId;
23
24/// Identifies which part of the rendering pipeline should be invalidated
25/// after a modifier node changes state.
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
27pub enum InvalidationKind {
28    Layout,
29    Draw,
30    PointerInput,
31    Semantics,
32    Focus,
33}
34
35/// Runtime services exposed to modifier nodes while attached to a tree.
36pub trait ModifierNodeContext {
37    /// Requests that a particular pipeline stage be invalidated.
38    fn invalidate(&mut self, _kind: InvalidationKind) {}
39
40    /// Requests that the node's `update` method run again outside of a
41    /// regular composition pass.
42    fn request_update(&mut self) {}
43
44    /// Returns the ID of the layout node this modifier is attached to, if known.
45    /// This is used by modifiers that need to register callbacks for invalidation (e.g. Scroll).
46    fn node_id(&self) -> Option<cranpose_core::NodeId> {
47        None
48    }
49
50    /// Signals that a node with `capabilities` is about to interact with this context.
51    fn push_active_capabilities(&mut self, _capabilities: NodeCapabilities) {}
52
53    /// Signals that the most recent node interaction has completed.
54    fn pop_active_capabilities(&mut self) {}
55}
56
57/// Lightweight [`ModifierNodeContext`] implementation that records
58/// invalidation requests and update signals.
59///
60/// The context intentionally avoids leaking runtime details so the core
61/// crate can evolve independently from higher level UI crates. It simply
62/// stores the sequence of requested invalidation kinds and whether an
63/// explicit update was requested. Callers can inspect or drain this state
64/// after driving a [`ModifierNodeChain`] reconciliation pass.
65#[derive(Default, Debug, Clone)]
66pub struct BasicModifierNodeContext {
67    invalidations: Vec<ModifierInvalidation>,
68    update_requested: bool,
69    active_capabilities: Vec<NodeCapabilities>,
70    node_id: Option<cranpose_core::NodeId>,
71}
72
73impl BasicModifierNodeContext {
74    /// Creates a new empty context.
75    pub fn new() -> Self {
76        Self::default()
77    }
78
79    /// Returns the ordered list of invalidation kinds that were requested
80    /// since the last call to `clear_invalidations`. Duplicate requests for
81    /// the same kind are coalesced.
82    pub fn invalidations(&self) -> &[ModifierInvalidation] {
83        &self.invalidations
84    }
85
86    /// Removes all currently recorded invalidation kinds.
87    pub fn clear_invalidations(&mut self) {
88        self.invalidations.clear();
89    }
90
91    /// Drains the recorded invalidations and returns them to the caller.
92    pub fn take_invalidations(&mut self) -> Vec<ModifierInvalidation> {
93        std::mem::take(&mut self.invalidations)
94    }
95
96    /// Returns whether an update was requested since the last call to
97    /// `take_update_requested`.
98    pub fn update_requested(&self) -> bool {
99        self.update_requested
100    }
101
102    /// Returns whether an update was requested and clears the flag.
103    pub fn take_update_requested(&mut self) -> bool {
104        std::mem::take(&mut self.update_requested)
105    }
106
107    /// Sets the node ID associated with this context.
108    pub fn set_node_id(&mut self, id: Option<cranpose_core::NodeId>) {
109        self.node_id = id;
110    }
111
112    fn push_invalidation(&mut self, kind: InvalidationKind) {
113        let mut capabilities = self.current_capabilities();
114        capabilities.insert(NodeCapabilities::for_invalidation(kind));
115        if let Some(existing) = self
116            .invalidations
117            .iter_mut()
118            .find(|entry| entry.kind() == kind)
119        {
120            let updated = existing.capabilities() | capabilities;
121            *existing = ModifierInvalidation::new(kind, updated);
122        } else {
123            self.invalidations
124                .push(ModifierInvalidation::new(kind, capabilities));
125        }
126    }
127
128    fn current_capabilities(&self) -> NodeCapabilities {
129        self.active_capabilities
130            .last()
131            .copied()
132            .unwrap_or_else(NodeCapabilities::empty)
133    }
134}
135
136impl ModifierNodeContext for BasicModifierNodeContext {
137    fn invalidate(&mut self, kind: InvalidationKind) {
138        self.push_invalidation(kind);
139    }
140
141    fn request_update(&mut self) {
142        self.update_requested = true;
143    }
144
145    fn push_active_capabilities(&mut self, capabilities: NodeCapabilities) {
146        self.active_capabilities.push(capabilities);
147    }
148
149    fn pop_active_capabilities(&mut self) {
150        self.active_capabilities.pop();
151    }
152
153    fn node_id(&self) -> Option<cranpose_core::NodeId> {
154        self.node_id
155    }
156}
157
158/// Path to a node within a modifier chain, supporting delegate navigation.
159/// Fixed-size Copy type — delegate depth is bounded at 3 in practice
160/// (modifier delegation rarely exceeds 2–3 levels).
161const MAX_DELEGATE_DEPTH: usize = 3;
162
163#[derive(Copy, Clone, Debug, PartialEq, Eq)]
164pub(crate) struct NodePath {
165    entry: usize,
166    delegate_buf: [u8; MAX_DELEGATE_DEPTH],
167    delegate_len: u8,
168}
169
170impl NodePath {
171    #[inline]
172    fn root(entry: usize) -> Self {
173        Self {
174            entry,
175            delegate_buf: [0; MAX_DELEGATE_DEPTH],
176            delegate_len: 0,
177        }
178    }
179
180    #[inline]
181    fn from_slice(entry: usize, path: &[usize]) -> Self {
182        debug_assert!(
183            path.len() <= MAX_DELEGATE_DEPTH,
184            "delegate depth {} exceeds MAX_DELEGATE_DEPTH {}",
185            path.len(),
186            MAX_DELEGATE_DEPTH
187        );
188        debug_assert!(
189            path.iter().all(|&i| i <= u8::MAX as usize),
190            "delegate index exceeds u8 range"
191        );
192        let mut delegate_buf = [0u8; MAX_DELEGATE_DEPTH];
193        for (i, &v) in path.iter().enumerate().take(MAX_DELEGATE_DEPTH) {
194            delegate_buf[i] = v as u8;
195        }
196        Self {
197            entry,
198            delegate_buf,
199            delegate_len: path.len().min(MAX_DELEGATE_DEPTH) as u8,
200        }
201    }
202
203    #[inline]
204    fn entry(&self) -> usize {
205        self.entry
206    }
207
208    #[inline]
209    fn delegates(&self) -> &[u8] {
210        &self.delegate_buf[..self.delegate_len as usize]
211    }
212}
213
214#[derive(Copy, Clone, Debug, PartialEq, Eq)]
215pub(crate) enum NodeLink {
216    Head,
217    Tail,
218    Entry(NodePath),
219}
220
221/// Runtime state tracked for every [`ModifierNode`].
222///
223/// This type is part of the internal node system API and should not be directly
224/// constructed or manipulated by external code. Modifier nodes automatically receive
225/// and manage their NodeState through the modifier chain infrastructure.
226#[derive(Debug)]
227pub struct NodeState {
228    aggregate_child_capabilities: Cell<NodeCapabilities>,
229    capabilities: Cell<NodeCapabilities>,
230    parent: RefCell<Option<NodeLink>>,
231    child: RefCell<Option<NodeLink>>,
232    attached: Cell<bool>,
233    is_sentinel: bool,
234}
235
236impl Default for NodeState {
237    fn default() -> Self {
238        Self::new()
239    }
240}
241
242impl NodeState {
243    pub const fn new() -> Self {
244        Self {
245            aggregate_child_capabilities: Cell::new(NodeCapabilities::empty()),
246            capabilities: Cell::new(NodeCapabilities::empty()),
247            parent: RefCell::new(None),
248            child: RefCell::new(None),
249            attached: Cell::new(false),
250            is_sentinel: false,
251        }
252    }
253
254    pub const fn sentinel() -> Self {
255        Self {
256            aggregate_child_capabilities: Cell::new(NodeCapabilities::empty()),
257            capabilities: Cell::new(NodeCapabilities::empty()),
258            parent: RefCell::new(None),
259            child: RefCell::new(None),
260            attached: Cell::new(true),
261            is_sentinel: true,
262        }
263    }
264
265    pub fn set_capabilities(&self, capabilities: NodeCapabilities) {
266        self.capabilities.set(capabilities);
267    }
268
269    #[inline]
270    pub fn capabilities(&self) -> NodeCapabilities {
271        self.capabilities.get()
272    }
273
274    pub fn set_aggregate_child_capabilities(&self, capabilities: NodeCapabilities) {
275        self.aggregate_child_capabilities.set(capabilities);
276    }
277
278    #[inline]
279    pub fn aggregate_child_capabilities(&self) -> NodeCapabilities {
280        self.aggregate_child_capabilities.get()
281    }
282
283    pub(crate) fn set_parent_link(&self, parent: Option<NodeLink>) {
284        *self.parent.borrow_mut() = parent;
285    }
286
287    #[inline]
288    pub(crate) fn parent_link(&self) -> Option<NodeLink> {
289        *self.parent.borrow()
290    }
291
292    pub(crate) fn set_child_link(&self, child: Option<NodeLink>) {
293        *self.child.borrow_mut() = child;
294    }
295
296    #[inline]
297    pub(crate) fn child_link(&self) -> Option<NodeLink> {
298        *self.child.borrow()
299    }
300
301    pub fn set_attached(&self, attached: bool) {
302        self.attached.set(attached);
303    }
304
305    pub fn is_attached(&self) -> bool {
306        self.attached.get()
307    }
308
309    pub fn is_sentinel(&self) -> bool {
310        self.is_sentinel
311    }
312}
313
314/// Provides traversal helpers that mirror Jetpack Compose's [`DelegatableNode`] contract.
315pub trait DelegatableNode {
316    fn node_state(&self) -> &NodeState;
317    fn aggregate_child_capabilities(&self) -> NodeCapabilities {
318        self.node_state().aggregate_child_capabilities()
319    }
320}
321
322/// Core trait implemented by modifier nodes.
323///
324/// # Capability-Driven Architecture
325///
326/// This trait follows Jetpack Compose's `Modifier.Node` pattern where nodes declare
327/// their capabilities via [`NodeCapabilities`] and implement specialized traits
328/// ([`DrawModifierNode`], [`PointerInputNode`], [`SemanticsNode`], [`FocusNode`], etc.)
329/// to participate in specific pipeline stages.
330///
331/// ## How to Implement a Modifier Node
332///
333/// 1. **Declare capabilities** in your [`ModifierNodeElement::capabilities()`] implementation
334/// 2. **Implement specialized traits** for the capabilities you declared
335/// 3. **Use helper macros** to reduce boilerplate (recommended)
336///
337/// ### Example: Draw Node
338///
339/// ```text
340/// use cranpose_foundation::*;
341///
342/// struct MyDrawNode {
343///     state: NodeState,
344///     color: Color,
345/// }
346///
347/// impl DelegatableNode for MyDrawNode {
348///     fn node_state(&self) -> &NodeState {
349///         &self.state
350///     }
351/// }
352///
353/// impl ModifierNode for MyDrawNode {
354///     // Use the helper macro instead of manual as_* implementations
355///     impl_modifier_node!(draw);
356/// }
357///
358/// impl DrawModifierNode for MyDrawNode {
359///     fn draw(&mut self, _context: &mut dyn ModifierNodeContext, draw_scope: &mut dyn DrawScope) {
360///         // Drawing logic here
361///     }
362/// }
363/// ```
364///
365/// ### Example: Multi-Capability Node
366///
367/// ```text
368/// impl ModifierNode for MyComplexNode {
369///     // This node participates in draw, pointer input, and semantics
370///     impl_modifier_node!(draw, pointer_input, semantics);
371/// }
372/// ```
373///
374/// ## Lifecycle Callbacks
375///
376/// Nodes receive lifecycle callbacks when they attach to or detach from a
377/// composition and may optionally react to resets triggered by the runtime
378/// (for example, when reusing nodes across modifier list changes).
379pub trait ModifierNode: Any + DelegatableNode {
380    fn on_attach(&mut self, _context: &mut dyn ModifierNodeContext) {}
381
382    fn on_detach(&mut self) {}
383
384    fn on_reset(&mut self) {}
385
386    /// Returns this node as a draw modifier if it implements the trait.
387    fn as_draw_node(&self) -> Option<&dyn DrawModifierNode> {
388        None
389    }
390
391    /// Returns this node as a mutable draw modifier if it implements the trait.
392    fn as_draw_node_mut(&mut self) -> Option<&mut dyn DrawModifierNode> {
393        None
394    }
395
396    /// Returns this node as a pointer-input modifier if it implements the trait.
397    fn as_pointer_input_node(&self) -> Option<&dyn PointerInputNode> {
398        None
399    }
400
401    /// Returns this node as a mutable pointer-input modifier if it implements the trait.
402    fn as_pointer_input_node_mut(&mut self) -> Option<&mut dyn PointerInputNode> {
403        None
404    }
405
406    /// Returns this node as a semantics modifier if it implements the trait.
407    fn as_semantics_node(&self) -> Option<&dyn SemanticsNode> {
408        None
409    }
410
411    /// Returns this node as a mutable semantics modifier if it implements the trait.
412    fn as_semantics_node_mut(&mut self) -> Option<&mut dyn SemanticsNode> {
413        None
414    }
415
416    /// Returns this node as a focus modifier if it implements the trait.
417    fn as_focus_node(&self) -> Option<&dyn FocusNode> {
418        None
419    }
420
421    /// Returns this node as a mutable focus modifier if it implements the trait.
422    fn as_focus_node_mut(&mut self) -> Option<&mut dyn FocusNode> {
423        None
424    }
425
426    /// Returns this node as a layout modifier if it implements the trait.
427    fn as_layout_node(&self) -> Option<&dyn LayoutModifierNode> {
428        None
429    }
430
431    /// Returns this node as a mutable layout modifier if it implements the trait.
432    fn as_layout_node_mut(&mut self) -> Option<&mut dyn LayoutModifierNode> {
433        None
434    }
435
436    /// Visits every delegate node owned by this modifier.
437    fn for_each_delegate<'b>(&'b self, _visitor: &mut dyn FnMut(&'b dyn ModifierNode)) {}
438
439    /// Visits every delegate node mutably.
440    fn for_each_delegate_mut<'b>(&'b mut self, _visitor: &mut dyn FnMut(&'b mut dyn ModifierNode)) {
441    }
442}
443
444/// Marker trait for layout-specific modifier nodes.
445///
446/// Layout nodes participate in the measure and layout passes of the render
447/// pipeline. They can intercept and modify the measurement and placement of
448/// their wrapped content.
449pub trait LayoutModifierNode: ModifierNode {
450    /// Measures the wrapped content and returns both the size this modifier
451    /// occupies and where the wrapped content should be placed.
452    ///
453    /// The node receives a measurable representing the wrapped content and
454    /// the incoming constraints from the parent.
455    ///
456    /// Returns a `LayoutModifierMeasureResult` containing:
457    /// - `size`: The final size this modifier will occupy
458    /// - `placement_offset_x/y`: Where to place the wrapped content relative
459    ///   to this modifier's top-left corner
460    ///
461    /// For example, a padding modifier would:
462    /// - Measure child with deflated constraints
463    /// - Return size = child size + padding
464    /// - Return placement offset = (padding.left, padding.top)
465    ///
466    /// The default implementation delegates to the wrapped content without
467    /// modification (size = child size, offset = 0).
468    ///
469    /// NOTE: This takes `&self` not `&mut self` to match Jetpack Compose semantics.
470    /// Nodes that need mutable state should use interior mutability (Cell/RefCell).
471    fn measure(
472        &self,
473        _context: &mut dyn ModifierNodeContext,
474        measurable: &dyn Measurable,
475        constraints: Constraints,
476    ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
477        // Default: pass through to wrapped content by measuring the child.
478        let placeable = measurable.measure(constraints);
479        cranpose_ui_layout::LayoutModifierMeasureResult::with_size(Size {
480            width: placeable.width(),
481            height: placeable.height(),
482        })
483    }
484
485    /// Returns the minimum intrinsic width of this modifier node.
486    fn min_intrinsic_width(&self, _measurable: &dyn Measurable, _height: f32) -> f32 {
487        0.0
488    }
489
490    /// Returns the maximum intrinsic width of this modifier node.
491    fn max_intrinsic_width(&self, _measurable: &dyn Measurable, _height: f32) -> f32 {
492        0.0
493    }
494
495    /// Returns the minimum intrinsic height of this modifier node.
496    fn min_intrinsic_height(&self, _measurable: &dyn Measurable, _width: f32) -> f32 {
497        0.0
498    }
499
500    /// Returns the maximum intrinsic height of this modifier node.
501    fn max_intrinsic_height(&self, _measurable: &dyn Measurable, _width: f32) -> f32 {
502        0.0
503    }
504}
505
506/// Marker trait for draw-specific modifier nodes.
507///
508/// Draw nodes participate in the draw pass of the render pipeline. They can
509/// intercept and modify the drawing operations of their wrapped content.
510///
511/// Following Jetpack Compose's design, `draw()` is called during the actual
512/// render pass with a live DrawScope, not during layout/slice collection.
513pub trait DrawModifierNode: ModifierNode {
514    /// Draws this modifier node into the provided DrawScope.
515    ///
516    /// This is called during the render pass for each node with DRAW capability.
517    /// The node should draw directly into the scope using methods like
518    /// `draw_scope.draw_rect_at()`.
519    ///
520    /// Takes `&self` to work with immutable chain iteration - use interior
521    /// mutability (RefCell) for any state that needs mutation during draw.
522    fn draw(&self, _draw_scope: &mut dyn DrawScope) {
523        // Default: no custom drawing
524    }
525
526    /// Creates a closure for deferred drawing that will be evaluated at render time.
527    ///
528    /// This is the preferred method for nodes with dynamic content like:
529    /// - Blinking cursors (visibility changes over time)
530    /// - Live selection during drag (selection changes during mouse move)
531    ///
532    /// The returned closure captures the node's internal state (via Rc) and
533    /// evaluates at render time, not at slice collection time.
534    ///
535    /// Returns None by default. Override for nodes needing deferred draw.
536    fn create_draw_closure(&self) -> Option<NodeDrawClosure> {
537        None
538    }
539
540    /// Like [`create_draw_closure`](Self::create_draw_closure), but the
541    /// primitives render BEHIND the node's content — e.g. a text field's
542    /// selection highlight, which must sit under the glyphs (a highlight
543    /// drawn over them tints the text with its translucent fill).
544    fn create_behind_draw_closure(&self) -> Option<NodeDrawClosure> {
545        None
546    }
547}
548
549/// A deferred draw closure returned by
550/// [`DrawModifierNode::create_draw_closure`]: it records into a scope the
551/// renderer provides at render time, so the recording's identity stays with
552/// the consumer rather than with a closure-owned vector.
553pub type NodeDrawClosure = Rc<dyn Fn(&mut cranpose_ui_graphics::DrawScopeDefault)>;
554
555/// Marker trait for pointer input modifier nodes.
556///
557/// Pointer input nodes participate in hit-testing and pointer event
558/// dispatch. They can intercept pointer events and handle them before
559/// they reach the wrapped content.
560pub trait PointerInputNode: ModifierNode {
561    /// Called when a pointer event occurs within the bounds of this node.
562    /// Returns true if the event was consumed and should not propagate further.
563    fn on_pointer_event(
564        &mut self,
565        _context: &mut dyn ModifierNodeContext,
566        _event: &PointerEvent,
567    ) -> bool {
568        false
569    }
570
571    /// Returns true if this node should participate in hit-testing for the
572    /// given pointer position.
573    fn hit_test(&self, _x: f32, _y: f32) -> bool {
574        true
575    }
576
577    /// Returns an event handler closure if the node wants to participate in pointer dispatch.
578    fn pointer_input_handler(&self) -> Option<Rc<dyn Fn(PointerEvent)>> {
579        None
580    }
581
582    /// Returns the cell this node reads its owning layout node's resolved size
583    /// from, if it exposes a size to its handler (Compose's
584    /// `PointerInputScope.size`).
585    ///
586    /// The cell is shared with the node, so the layout pass publishes the size
587    /// into it once per pass and every read — including reads that happen
588    /// before any pointer event arrives — observes the current size. The size
589    /// is the node's layout box in the same local coordinate space the
590    /// dispatched [`PointerEvent`] positions use, so
591    /// `event.local_position / size` is a well-defined fraction of the node.
592    ///
593    /// Returns `None` for pointer nodes with no size-bearing scope.
594    fn layout_size_sink(&self) -> Option<Rc<Cell<Size>>> {
595        None
596    }
597}
598
599/// Marker trait for semantics modifier nodes.
600///
601/// Semantics nodes participate in the semantics tree construction. They can
602/// add or modify semantic properties of their wrapped content for
603/// accessibility and testing purposes.
604pub trait SemanticsNode: ModifierNode {
605    /// Merges semantic properties into the provided configuration.
606    fn merge_semantics(&self, _config: &mut SemanticsConfiguration) {
607        // Default: no semantics added
608    }
609}
610
611/// Focus state of a focus target node.
612///
613/// This mirrors Jetpack Compose's FocusState enum which tracks whether
614/// a node is focused, has a focused child, or is inactive.
615#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
616pub enum FocusState {
617    /// The focusable component is currently active (i.e. it receives key events).
618    Active,
619    /// One of the descendants of the focusable component is Active.
620    ActiveParent,
621    /// The focusable component is currently active (has focus), and is in a state
622    /// where it does not want to give up focus. (Eg. a text field with an invalid
623    /// phone number).
624    Captured,
625    /// The focusable component does not receive any key events. (ie it is not active,
626    /// nor are any of its descendants active).
627    #[default]
628    Inactive,
629}
630
631impl FocusState {
632    /// Returns whether the component is focused (Active or Captured).
633    pub fn is_focused(self) -> bool {
634        matches!(self, FocusState::Active | FocusState::Captured)
635    }
636
637    /// Returns whether this node or any descendant has focus.
638    pub fn has_focus(self) -> bool {
639        matches!(
640            self,
641            FocusState::Active | FocusState::ActiveParent | FocusState::Captured
642        )
643    }
644
645    /// Returns whether focus is captured.
646    pub fn is_captured(self) -> bool {
647        matches!(self, FocusState::Captured)
648    }
649}
650
651/// Marker trait for focus modifier nodes.
652///
653/// Focus nodes participate in focus management. They can request focus,
654/// track focus state, and participate in focus traversal.
655pub trait FocusNode: ModifierNode {
656    /// Returns the current focus state of this node.
657    fn focus_state(&self) -> FocusState;
658
659    /// Called when focus state changes for this node.
660    fn on_focus_changed(&mut self, _context: &mut dyn ModifierNodeContext, _state: FocusState) {
661        // Default: no action on focus change
662    }
663}
664
665/// What kind of control a node is, as screen readers announce it.
666///
667/// This is Compose's `SemanticsProperties.Role` (`Modifier.semantics { role =
668/// Role.RadioButton }`), not a description of where the node sits in the tree.
669/// A screen reader turns it into the trailing noun it speaks after the label —
670/// TalkBack says "CAMPAIGN, radio button, selected" — and into the on/off
671/// wording it uses for a switch. Compose keeps this separate from the node's
672/// structural kind for the same reason Cranpose does: a `Row` that happens to
673/// be clickable is still a row, and a drawn ring segment that is a radio button
674/// has no layout node of its own at all.
675#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
676pub enum SemanticsWidgetRole {
677    Button,
678    Checkbox,
679    Switch,
680    RadioButton,
681    Tab,
682    Image,
683    /// Compose's `heading()`, which is a property rather than a `Role`, but
684    /// reaches the platform through the same field on every backend Cranpose
685    /// targets (`AccessibilityNodeInfo.setHeading`, `Role::Heading`,
686    /// `<h*>`/`UIAccessibilityTraitHeader`).
687    Header,
688    /// A modal surface that takes over the screen until it is dismissed.
689    /// Screen readers announce it and confine their traversal to it, which is
690    /// the accessible half of what makes a dialog modal.
691    Dialog,
692}
693
694/// A screen-reader action that is not a click, e.g. Compose's
695/// `customActions = listOf(CustomAccessibilityAction("Pause") { … })`.
696///
697/// TalkBack surfaces these through its actions menu rather than by activating
698/// the node, which is the only way to reach a command that has no on-screen
699/// control — pausing a game whose whole surface is one tap-to-launch target.
700#[derive(Clone)]
701pub struct SemanticsCustomAction {
702    /// What the screen reader reads out in its actions menu.
703    pub label: String,
704    handler: Rc<dyn Fn()>,
705}
706
707impl SemanticsCustomAction {
708    pub fn new(label: impl Into<String>, handler: impl Fn() + 'static) -> Self {
709        Self {
710            label: label.into(),
711            handler: Rc::new(handler),
712        }
713    }
714
715    pub fn invoke(&self) {
716        (self.handler)();
717    }
718}
719
720impl fmt::Debug for SemanticsCustomAction {
721    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
722        f.debug_struct("SemanticsCustomAction")
723            .field("label", &self.label)
724            .finish_non_exhaustive()
725    }
726}
727
728/// Two custom actions are the same action when they read the same.
729///
730/// The handler is deliberately excluded. A semantics recorder runs on every
731/// collection, so the closure is a fresh `Rc` each time and comparing handler
732/// identity would report "the tree changed" on every frame — which on Android
733/// means re-serialising and re-publishing the whole virtual-view tree across
734/// JNI 60 times a second. Handlers are looked up in the live semantics tree at
735/// the moment the action fires (see `perform_custom_action`), so a handler that
736/// is newer than the last published snapshot is still the one that runs.
737impl PartialEq for SemanticsCustomAction {
738    fn eq(&self, other: &Self) -> bool {
739        self.label == other.label
740    }
741}
742
743impl Eq for SemanticsCustomAction {}
744
745/// A semantics node for content that is *drawn* rather than laid out.
746///
747/// An immediate-mode surface — one `Canvas` that paints a whole screen — has
748/// exactly one layout node, so the semantics tree built from layout has exactly
749/// one node to offer a screen reader. This is the escape hatch: the drawing
750/// code already knows where it put every control, so it publishes those
751/// rectangles as semantics directly. Android's own answer for a canvas-drawn
752/// `View` is the same shape (`ExploreByTouchHelper` feeding virtual view ids
753/// into an `AccessibilityNodeProvider`), and Cranpose's Android bridge is
754/// already an `AccessibilityNodeProvider`, so these land as first-class
755/// virtual views next to the ones layout produces.
756///
757/// `bounds` is in the publishing node's own coordinates (logical px, origin at
758/// that node's top-left), because that is what a draw scope works in.
759#[derive(Clone, Debug, PartialEq)]
760pub struct CanvasSemanticsNode {
761    /// Identity that must survive a redraw.
762    ///
763    /// A screen reader parks its cursor on a virtual view id; if the id for
764    /// "the Haptics switch" changes when the list scrolls, the cursor jumps.
765    /// Derive this from what the control *is* (a row index, an enum
766    /// discriminant), never from where it currently sits.
767    pub key: u64,
768    /// Where the control was drawn, relative to the publishing node.
769    pub bounds: cranpose_ui_graphics::Rect,
770    pub label: String,
771    pub role: Option<SemanticsWidgetRole>,
772    /// Compose's `stateDescription` — what the control currently reads as
773    /// ("CAMPAIGN", "3 of 18 gold"), spoken after the label and re-spoken on
774    /// its own when only the state changed.
775    pub state_description: Option<String>,
776    /// Compose's `onClick(label = …)`. TalkBack reads it as "double tap to
777    /// `<label>`", so it is a verb phrase, not a repeat of the label.
778    pub on_click_label: Option<String>,
779    pub clickable: bool,
780    /// Compose's `selected`, for `Role.RadioButton`/`Role.Tab`.
781    pub selected: Option<bool>,
782    /// Compose's `toggleableState`, for `Role.Switch`/`Role.Checkbox`.
783    pub toggled: Option<bool>,
784    pub enabled: bool,
785    pub custom_actions: Vec<SemanticsCustomAction>,
786}
787
788impl Default for CanvasSemanticsNode {
789    fn default() -> Self {
790        Self {
791            key: 0,
792            bounds: cranpose_ui_graphics::Rect {
793                x: 0.0,
794                y: 0.0,
795                width: 0.0,
796                height: 0.0,
797            },
798            label: String::new(),
799            role: None,
800            state_description: None,
801            on_click_label: None,
802            clickable: false,
803            selected: None,
804            toggled: None,
805            // Matches Compose: a node is enabled unless `disabled()` says
806            // otherwise, so an app that never thinks about it gets the right
807            // answer.
808            enabled: true,
809            custom_actions: Vec::new(),
810        }
811    }
812}
813
814impl CanvasSemanticsNode {
815    /// A clickable control drawn at `bounds`.
816    pub fn control(key: u64, bounds: cranpose_ui_graphics::Rect, label: impl Into<String>) -> Self {
817        Self {
818            key,
819            bounds,
820            label: label.into(),
821            clickable: true,
822            ..Self::default()
823        }
824    }
825
826    /// A drawn label that is read but not activated.
827    pub fn text(key: u64, bounds: cranpose_ui_graphics::Rect, label: impl Into<String>) -> Self {
828        Self {
829            key,
830            bounds,
831            label: label.into(),
832            ..Self::default()
833        }
834    }
835
836    pub fn with_role(mut self, role: SemanticsWidgetRole) -> Self {
837        self.role = Some(role);
838        self
839    }
840
841    pub fn with_state_description(mut self, state: impl Into<String>) -> Self {
842        self.state_description = Some(state.into());
843        self
844    }
845
846    pub fn with_click_label(mut self, label: impl Into<String>) -> Self {
847        self.on_click_label = Some(label.into());
848        self.clickable = true;
849        self
850    }
851
852    pub fn with_selected(mut self, selected: bool) -> Self {
853        self.selected = Some(selected);
854        self
855    }
856
857    pub fn with_toggled(mut self, toggled: bool) -> Self {
858        self.toggled = Some(toggled);
859        self
860    }
861
862    pub fn with_enabled(mut self, enabled: bool) -> Self {
863        self.enabled = enabled;
864        self
865    }
866
867    pub fn with_custom_action(mut self, action: SemanticsCustomAction) -> Self {
868        self.custom_actions.push(action);
869        self
870    }
871}
872
873/// Semantics configuration for accessibility.
874#[derive(Clone, Debug, PartialEq)]
875pub struct SemanticsConfiguration {
876    pub content_description: Option<String>,
877    /// Compose's `stateDescription`.
878    pub state_description: Option<String>,
879    /// Compose's `onClick(label = …)`; implies clickable.
880    pub on_click_label: Option<String>,
881    /// Compose's `Role`.
882    pub role: Option<SemanticsWidgetRole>,
883    pub selected: Option<bool>,
884    pub toggled: Option<bool>,
885    pub enabled: bool,
886    pub is_clickable: bool,
887    pub is_editable_text: bool,
888    pub text_selection: Option<crate::text::TextRange>,
889    pub custom_actions: Vec<SemanticsCustomAction>,
890    /// Controls this node drew itself instead of laying out. See
891    /// [`CanvasSemanticsNode`].
892    pub canvas_children: Vec<CanvasSemanticsNode>,
893    /// Whether this node takes over the screen: everything outside it is
894    /// inert, and a screen reader keeps its traversal inside.
895    pub is_modal: bool,
896}
897
898impl Default for SemanticsConfiguration {
899    fn default() -> Self {
900        Self {
901            content_description: None,
902            state_description: None,
903            on_click_label: None,
904            role: None,
905            selected: None,
906            toggled: None,
907            enabled: true,
908            is_clickable: false,
909            is_editable_text: false,
910            text_selection: None,
911            custom_actions: Vec::new(),
912            canvas_children: Vec::new(),
913            is_modal: false,
914        }
915    }
916}
917
918impl SemanticsConfiguration {
919    pub fn merge(&mut self, other: &SemanticsConfiguration) {
920        if let Some(description) = &other.content_description {
921            self.content_description = Some(description.clone());
922        }
923        if let Some(state) = &other.state_description {
924            self.state_description = Some(state.clone());
925        }
926        if let Some(label) = &other.on_click_label {
927            self.on_click_label = Some(label.clone());
928        }
929        if let Some(role) = other.role {
930            self.role = Some(role);
931        }
932        if let Some(selected) = other.selected {
933            self.selected = Some(selected);
934        }
935        if let Some(toggled) = other.toggled {
936            self.toggled = Some(toggled);
937        }
938        // Disabling wins: a chain that disables the node anywhere disables it,
939        // matching how Compose's `disabled()` is not undone by an inner node.
940        self.enabled &= other.enabled;
941        self.is_clickable |= other.is_clickable;
942        self.is_editable_text |= other.is_editable_text;
943        if let Some(selection) = other.text_selection {
944            self.text_selection = Some(selection);
945        }
946        self.custom_actions
947            .extend(other.custom_actions.iter().cloned());
948        self.canvas_children
949            .extend(other.canvas_children.iter().cloned());
950        self.is_modal |= other.is_modal;
951    }
952
953    /// Whether a screen reader should offer activation. A named click label is
954    /// how Compose declares `onClick`, so it implies the action the same way.
955    pub fn is_activatable(&self) -> bool {
956        self.is_clickable || self.on_click_label.is_some()
957    }
958}
959
960impl fmt::Debug for dyn ModifierNode {
961    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
962        f.debug_struct("ModifierNode").finish_non_exhaustive()
963    }
964}
965
966impl dyn ModifierNode {
967    pub fn as_any(&self) -> &dyn Any {
968        self
969    }
970
971    pub fn as_any_mut(&mut self) -> &mut dyn Any {
972        self
973    }
974}
975
976/// Strongly typed modifier elements that can create and update nodes while
977/// exposing equality/hash/inspector contracts that mirror Jetpack Compose.
978pub trait ModifierNodeElement: fmt::Debug + Hash + PartialEq + 'static {
979    type Node: ModifierNode;
980
981    /// Creates a new modifier node instance for this element.
982    fn create(&self) -> Self::Node;
983
984    /// Brings an existing modifier node up to date with the element's data.
985    fn update(&self, node: &mut Self::Node);
986
987    /// Optional key used to disambiguate multiple instances of the same element type.
988    fn key(&self) -> Option<u64> {
989        None
990    }
991
992    /// Human readable name surfaced to inspector tooling.
993    fn inspector_name(&self) -> &'static str {
994        type_name::<Self>()
995    }
996
997    /// Records inspector properties for tooling.
998    fn inspector_properties(&self, _inspector: &mut dyn FnMut(&'static str, String)) {}
999
1000    /// Returns the capabilities of nodes created by this element.
1001    /// Override this to indicate which specialized traits the node implements.
1002    fn capabilities(&self) -> NodeCapabilities {
1003        NodeCapabilities::default()
1004    }
1005
1006    /// Whether this element requires `update` to be called even if `eq` returns true.
1007    ///
1008    /// This is useful for elements that ignore certain fields in `eq` (e.g. closures)
1009    /// to allow node reuse, but still need those fields updated in the existing node.
1010    /// Defaults to `false`.
1011    fn always_update(&self) -> bool {
1012        false
1013    }
1014
1015    /// Whether modifier reconciliation should request capability-wide invalidations
1016    /// after updating an existing node.
1017    fn auto_invalidate_on_update(&self) -> bool {
1018        true
1019    }
1020
1021    /// Optional targeted invalidation requested after updating an existing node.
1022    ///
1023    /// This is for nodes whose attach/remove capability is broader than the
1024    /// work needed for a value-only update. For example, an offset node
1025    /// participates in layout on attach but an x/y change only needs placement
1026    /// data and draw output refreshed.
1027    fn update_invalidation_kind(&self) -> Option<InvalidationKind> {
1028        None
1029    }
1030}
1031
1032/// Capability flags indicating which specialized traits a modifier node implements.
1033#[derive(Clone, Copy, PartialEq, Eq, Hash)]
1034pub struct NodeCapabilities(u32);
1035
1036impl NodeCapabilities {
1037    /// No capabilities.
1038    pub const NONE: Self = Self(0);
1039    /// Modifier participates in measure/layout.
1040    pub const LAYOUT: Self = Self(1 << 0);
1041    /// Modifier participates in draw.
1042    pub const DRAW: Self = Self(1 << 1);
1043    /// Modifier participates in pointer input.
1044    pub const POINTER_INPUT: Self = Self(1 << 2);
1045    /// Modifier participates in semantics tree construction.
1046    pub const SEMANTICS: Self = Self(1 << 3);
1047    /// Modifier participates in modifier locals.
1048    pub const MODIFIER_LOCALS: Self = Self(1 << 4);
1049    /// Modifier participates in focus management.
1050    pub const FOCUS: Self = Self(1 << 5);
1051
1052    /// Returns an empty capability set.
1053    pub const fn empty() -> Self {
1054        Self::NONE
1055    }
1056
1057    /// Returns whether all bits in `other` are present in `self`.
1058    pub const fn contains(self, other: Self) -> bool {
1059        (self.0 & other.0) == other.0
1060    }
1061
1062    /// Returns whether any bit in `other` is present in `self`.
1063    pub const fn intersects(self, other: Self) -> bool {
1064        (self.0 & other.0) != 0
1065    }
1066
1067    /// Inserts the requested capability bits.
1068    pub fn insert(&mut self, other: Self) {
1069        self.0 |= other.0;
1070    }
1071
1072    /// Returns the raw bit representation.
1073    pub const fn bits(self) -> u32 {
1074        self.0
1075    }
1076
1077    /// Returns true when no capabilities are set.
1078    pub const fn is_empty(self) -> bool {
1079        self.0 == 0
1080    }
1081
1082    /// Returns the capability bit mask required for the given invalidation.
1083    pub const fn for_invalidation(kind: InvalidationKind) -> Self {
1084        match kind {
1085            InvalidationKind::Layout => Self::LAYOUT,
1086            InvalidationKind::Draw => Self::DRAW,
1087            InvalidationKind::PointerInput => Self::POINTER_INPUT,
1088            InvalidationKind::Semantics => Self::SEMANTICS,
1089            InvalidationKind::Focus => Self::FOCUS,
1090        }
1091    }
1092}
1093
1094impl Default for NodeCapabilities {
1095    fn default() -> Self {
1096        Self::NONE
1097    }
1098}
1099
1100impl fmt::Debug for NodeCapabilities {
1101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1102        f.debug_struct("NodeCapabilities")
1103            .field("layout", &self.contains(Self::LAYOUT))
1104            .field("draw", &self.contains(Self::DRAW))
1105            .field("pointer_input", &self.contains(Self::POINTER_INPUT))
1106            .field("semantics", &self.contains(Self::SEMANTICS))
1107            .field("modifier_locals", &self.contains(Self::MODIFIER_LOCALS))
1108            .field("focus", &self.contains(Self::FOCUS))
1109            .finish()
1110    }
1111}
1112
1113impl BitOr for NodeCapabilities {
1114    type Output = Self;
1115
1116    fn bitor(self, rhs: Self) -> Self::Output {
1117        Self(self.0 | rhs.0)
1118    }
1119}
1120
1121impl BitOrAssign for NodeCapabilities {
1122    fn bitor_assign(&mut self, rhs: Self) {
1123        self.0 |= rhs.0;
1124    }
1125}
1126
1127/// Records an invalidation request together with the capability mask that triggered it.
1128#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1129pub struct ModifierInvalidation {
1130    kind: InvalidationKind,
1131    capabilities: NodeCapabilities,
1132}
1133
1134impl ModifierInvalidation {
1135    /// Creates a new modifier invalidation entry.
1136    pub const fn new(kind: InvalidationKind, capabilities: NodeCapabilities) -> Self {
1137        Self { kind, capabilities }
1138    }
1139
1140    /// Returns the invalidated pipeline kind.
1141    pub const fn kind(self) -> InvalidationKind {
1142        self.kind
1143    }
1144
1145    /// Returns the capability mask associated with the invalidation.
1146    pub const fn capabilities(self) -> NodeCapabilities {
1147        self.capabilities
1148    }
1149}
1150
1151/// Type-erased modifier element used by the runtime to reconcile chains.
1152pub trait AnyModifierElement: fmt::Debug {
1153    fn node_type(&self) -> TypeId;
1154
1155    fn element_type(&self) -> TypeId;
1156
1157    fn create_node(&self) -> Box<dyn ModifierNode>;
1158
1159    fn can_update_node(&self, node: &dyn ModifierNode) -> bool;
1160
1161    fn update_node(&self, node: &mut dyn ModifierNode);
1162
1163    fn key(&self) -> Option<u64>;
1164
1165    fn capabilities(&self) -> NodeCapabilities {
1166        NodeCapabilities::default()
1167    }
1168
1169    fn hash_code(&self) -> u64;
1170
1171    fn equals_element(&self, other: &dyn AnyModifierElement) -> bool;
1172
1173    fn inspector_name(&self) -> &'static str;
1174
1175    fn record_inspector_properties(&self, visitor: &mut dyn FnMut(&'static str, String));
1176
1177    fn requires_update(&self) -> bool;
1178
1179    fn auto_invalidates_on_update(&self) -> bool;
1180
1181    fn update_invalidation_kind(&self) -> Option<InvalidationKind>;
1182
1183    fn as_any(&self) -> &dyn Any;
1184}
1185
1186struct TypedModifierElement<E: ModifierNodeElement> {
1187    element: E,
1188    cached_hash: u64,
1189}
1190
1191impl<E: ModifierNodeElement> TypedModifierElement<E> {
1192    fn new(element: E) -> Self {
1193        let mut hasher = default::new();
1194        element.hash(&mut hasher);
1195        Self {
1196            element,
1197            cached_hash: hasher.finish(),
1198        }
1199    }
1200}
1201
1202impl<E> fmt::Debug for TypedModifierElement<E>
1203where
1204    E: ModifierNodeElement,
1205{
1206    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1207        f.debug_struct("TypedModifierElement")
1208            .field("type", &type_name::<E>())
1209            .finish()
1210    }
1211}
1212
1213impl<E> AnyModifierElement for TypedModifierElement<E>
1214where
1215    E: ModifierNodeElement,
1216{
1217    fn node_type(&self) -> TypeId {
1218        TypeId::of::<E::Node>()
1219    }
1220
1221    fn element_type(&self) -> TypeId {
1222        TypeId::of::<E>()
1223    }
1224
1225    fn create_node(&self) -> Box<dyn ModifierNode> {
1226        Box::new(self.element.create())
1227    }
1228
1229    fn can_update_node(&self, node: &dyn ModifierNode) -> bool {
1230        node.as_any().is::<E::Node>()
1231    }
1232
1233    fn update_node(&self, node: &mut dyn ModifierNode) {
1234        if let Some(typed) = node.as_any_mut().downcast_mut::<E::Node>() {
1235            self.element.update(typed);
1236        }
1237    }
1238
1239    fn key(&self) -> Option<u64> {
1240        self.element.key()
1241    }
1242
1243    fn capabilities(&self) -> NodeCapabilities {
1244        self.element.capabilities()
1245    }
1246
1247    fn hash_code(&self) -> u64 {
1248        self.cached_hash
1249    }
1250
1251    fn equals_element(&self, other: &dyn AnyModifierElement) -> bool {
1252        other
1253            .as_any()
1254            .downcast_ref::<Self>()
1255            .map(|typed| typed.element == self.element)
1256            .unwrap_or(false)
1257    }
1258
1259    fn inspector_name(&self) -> &'static str {
1260        self.element.inspector_name()
1261    }
1262
1263    fn record_inspector_properties(&self, visitor: &mut dyn FnMut(&'static str, String)) {
1264        self.element.inspector_properties(visitor);
1265    }
1266
1267    fn requires_update(&self) -> bool {
1268        self.element.always_update()
1269    }
1270
1271    fn auto_invalidates_on_update(&self) -> bool {
1272        self.element.auto_invalidate_on_update()
1273    }
1274
1275    fn update_invalidation_kind(&self) -> Option<InvalidationKind> {
1276        self.element.update_invalidation_kind()
1277    }
1278
1279    fn as_any(&self) -> &dyn Any {
1280        self
1281    }
1282}
1283
1284fn request_update_auto_invalidations(
1285    element: &dyn AnyModifierElement,
1286    context: &mut dyn ModifierNodeContext,
1287    capabilities: NodeCapabilities,
1288) {
1289    if let Some(kind) = element.update_invalidation_kind() {
1290        let capabilities = NodeCapabilities::for_invalidation(kind);
1291        context.push_active_capabilities(capabilities);
1292        context.invalidate(kind);
1293        context.pop_active_capabilities();
1294    } else if element.auto_invalidates_on_update() {
1295        request_auto_invalidations(context, capabilities);
1296    }
1297}
1298
1299/// Convenience helper for callers to construct a type-erased modifier
1300/// element without having to mention the internal wrapper type.
1301pub fn modifier_element<E: ModifierNodeElement>(element: E) -> DynModifierElement {
1302    Rc::new(TypedModifierElement::new(element))
1303}
1304
1305/// Boxed type-erased modifier element.
1306pub type DynModifierElement = Rc<dyn AnyModifierElement>;
1307
1308#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1309enum TraversalDirection {
1310    Forward,
1311    Backward,
1312}
1313
1314/// Iterator walking a modifier chain by indexing into `ordered_nodes`.
1315///
1316/// This avoids the per-step `RefCell::borrow()` + `NodeLink::clone()` cost
1317/// of following the linked-list through `NodeState::child`/`parent`.
1318pub struct ModifierChainIter<'a> {
1319    chain: &'a ModifierNodeChain,
1320    /// Current position in `ordered_nodes`. For forward iteration, starts at 0
1321    /// and increments; for backward, starts at len-1 and decrements.
1322    cursor: usize,
1323    /// Number of elements remaining (avoids underflow on backward iteration).
1324    remaining: usize,
1325    direction: TraversalDirection,
1326}
1327
1328impl<'a> ModifierChainIter<'a> {
1329    fn forward(chain: &'a ModifierNodeChain) -> Self {
1330        Self {
1331            chain,
1332            cursor: 0,
1333            remaining: chain.ordered_nodes.len(),
1334            direction: TraversalDirection::Forward,
1335        }
1336    }
1337
1338    fn backward(chain: &'a ModifierNodeChain) -> Self {
1339        let len = chain.ordered_nodes.len();
1340        Self {
1341            chain,
1342            cursor: len.wrapping_sub(1),
1343            remaining: len,
1344            direction: TraversalDirection::Backward,
1345        }
1346    }
1347}
1348
1349impl<'a> Iterator for ModifierChainIter<'a> {
1350    type Item = ModifierChainNodeRef<'a>;
1351
1352    #[inline]
1353    fn next(&mut self) -> Option<Self::Item> {
1354        if self.remaining == 0 {
1355            return None;
1356        }
1357        let (link, caps, agg) = self.chain.ordered_nodes[self.cursor];
1358        let node_ref = self.chain.make_node_ref_with_caps(link, caps, agg);
1359        self.remaining -= 1;
1360        match self.direction {
1361            TraversalDirection::Forward => self.cursor += 1,
1362            TraversalDirection::Backward => self.cursor = self.cursor.wrapping_sub(1),
1363        }
1364        Some(node_ref)
1365    }
1366
1367    #[inline]
1368    fn size_hint(&self) -> (usize, Option<usize>) {
1369        (self.remaining, Some(self.remaining))
1370    }
1371}
1372
1373impl<'a> ExactSizeIterator for ModifierChainIter<'a> {}
1374impl<'a> std::iter::FusedIterator for ModifierChainIter<'a> {}
1375
1376#[derive(Debug)]
1377struct ModifierNodeEntry {
1378    element_type: TypeId,
1379    node_type: TypeId,
1380    key: Option<u64>,
1381    hash_code: u64,
1382    element: DynModifierElement,
1383    node: Rc<RefCell<Box<dyn ModifierNode>>>,
1384    capabilities: NodeCapabilities,
1385}
1386
1387impl ModifierNodeEntry {
1388    fn new(
1389        element_type: TypeId,
1390        node_type: TypeId,
1391        key: Option<u64>,
1392        element: DynModifierElement,
1393        node: Box<dyn ModifierNode>,
1394        hash_code: u64,
1395        capabilities: NodeCapabilities,
1396    ) -> Self {
1397        // Wrap the boxed node in Rc<RefCell<>> for shared ownership
1398        let node_rc = Rc::new(RefCell::new(node));
1399        let entry = Self {
1400            element_type,
1401            node_type,
1402            key,
1403            hash_code,
1404            element,
1405            node: Rc::clone(&node_rc),
1406            capabilities,
1407        };
1408        entry
1409            .node
1410            .borrow()
1411            .node_state()
1412            .set_capabilities(entry.capabilities);
1413        entry
1414    }
1415}
1416
1417fn visit_node_tree_mut(
1418    node: &mut dyn ModifierNode,
1419    visitor: &mut dyn FnMut(&mut dyn ModifierNode),
1420) {
1421    visitor(node);
1422    node.for_each_delegate_mut(&mut |child| visit_node_tree_mut(child, visitor));
1423}
1424
1425fn nth_delegate(node: &dyn ModifierNode, target: usize) -> Option<&dyn ModifierNode> {
1426    let mut current = 0usize;
1427    let mut result: Option<&dyn ModifierNode> = None;
1428    node.for_each_delegate(&mut |child| {
1429        if result.is_none() && current == target {
1430            result = Some(child);
1431        }
1432        current += 1;
1433    });
1434    result
1435}
1436
1437fn nth_delegate_mut(node: &mut dyn ModifierNode, target: usize) -> Option<&mut dyn ModifierNode> {
1438    let mut current = 0usize;
1439    let mut result: Option<&mut dyn ModifierNode> = None;
1440    node.for_each_delegate_mut(&mut |child| {
1441        if result.is_none() && current == target {
1442            result = Some(child);
1443        }
1444        current += 1;
1445    });
1446    result
1447}
1448
1449fn with_node_context<F, R>(
1450    node: &mut dyn ModifierNode,
1451    context: &mut dyn ModifierNodeContext,
1452    f: F,
1453) -> R
1454where
1455    F: FnOnce(&mut dyn ModifierNode, &mut dyn ModifierNodeContext) -> R,
1456{
1457    context.push_active_capabilities(node.node_state().capabilities());
1458    let result = f(node, context);
1459    context.pop_active_capabilities();
1460    result
1461}
1462
1463fn request_auto_invalidations(
1464    context: &mut dyn ModifierNodeContext,
1465    capabilities: NodeCapabilities,
1466) {
1467    if capabilities.is_empty() {
1468        return;
1469    }
1470
1471    context.push_active_capabilities(capabilities);
1472
1473    if capabilities.contains(NodeCapabilities::LAYOUT) {
1474        context.invalidate(InvalidationKind::Layout);
1475    }
1476    if capabilities.contains(NodeCapabilities::DRAW) {
1477        context.invalidate(InvalidationKind::Draw);
1478    }
1479    if capabilities.contains(NodeCapabilities::POINTER_INPUT) {
1480        context.invalidate(InvalidationKind::PointerInput);
1481    }
1482    if capabilities.contains(NodeCapabilities::SEMANTICS) {
1483        context.invalidate(InvalidationKind::Semantics);
1484    }
1485    if capabilities.contains(NodeCapabilities::FOCUS) {
1486        context.invalidate(InvalidationKind::Focus);
1487    }
1488
1489    context.pop_active_capabilities();
1490}
1491
1492/// Attaches a node tree by calling on_attach for all unattached nodes.
1493///
1494/// # Safety
1495/// Callers must ensure no immutable RefCell borrows are held on the node
1496/// when calling this function. The on_attach callback may trigger mutations
1497/// (invalidations, state updates, etc.) that require mutable access, which
1498/// would panic if an immutable borrow is held across the call.
1499fn attach_node_tree(node: &mut dyn ModifierNode, context: &mut dyn ModifierNodeContext) {
1500    visit_node_tree_mut(node, &mut |n| {
1501        if !n.node_state().is_attached() {
1502            n.node_state().set_attached(true);
1503            with_node_context(n, context, |node, ctx| node.on_attach(ctx));
1504        }
1505    });
1506}
1507
1508fn reset_node_tree(node: &mut dyn ModifierNode) {
1509    visit_node_tree_mut(node, &mut |n| n.on_reset());
1510}
1511
1512fn detach_node_tree(node: &mut dyn ModifierNode) {
1513    visit_node_tree_mut(node, &mut |n| {
1514        if n.node_state().is_attached() {
1515            n.on_detach();
1516            n.node_state().set_attached(false);
1517        }
1518        n.node_state().set_parent_link(None);
1519        n.node_state().set_child_link(None);
1520        n.node_state()
1521            .set_aggregate_child_capabilities(NodeCapabilities::empty());
1522    });
1523}
1524
1525/// Chain of modifier nodes attached to a layout node.
1526///
1527/// The chain tracks ownership of modifier nodes and reuses them across
1528/// updates when the incoming element list still contains a node of the
1529/// same type. Removed nodes detach automatically so callers do not need
1530/// to manually manage their lifetimes.
1531pub struct ModifierNodeChain {
1532    entries: Vec<ModifierNodeEntry>,
1533    aggregated_capabilities: NodeCapabilities,
1534    head_aggregate_child_capabilities: NodeCapabilities,
1535    head_sentinel: Box<SentinelNode>,
1536    tail_sentinel: Box<SentinelNode>,
1537    /// (link, own_capabilities, aggregate_child_capabilities)
1538    ordered_nodes: Vec<(NodeLink, NodeCapabilities, NodeCapabilities)>,
1539    // Scratch buffers reused during update to avoid repeated allocations
1540    scratch_old_used: Vec<bool>,
1541    scratch_match_order: Vec<Option<usize>>,
1542    scratch_final_slots: Vec<Option<ModifierNodeEntry>>,
1543    scratch_elements: Vec<DynModifierElement>,
1544}
1545
1546struct SentinelNode {
1547    state: NodeState,
1548}
1549
1550impl SentinelNode {
1551    fn new() -> Self {
1552        Self {
1553            state: NodeState::sentinel(),
1554        }
1555    }
1556}
1557
1558impl DelegatableNode for SentinelNode {
1559    fn node_state(&self) -> &NodeState {
1560        &self.state
1561    }
1562}
1563
1564impl ModifierNode for SentinelNode {}
1565
1566#[derive(Clone)]
1567pub struct ModifierChainNodeRef<'a> {
1568    chain: &'a ModifierNodeChain,
1569    link: NodeLink,
1570    /// Capabilities cached from `ordered_nodes` build time — avoids RefCell borrow in kind_set().
1571    cached_capabilities: Option<NodeCapabilities>,
1572    /// Aggregate child capabilities cached from `ordered_nodes` — avoids RefCell borrow.
1573    cached_aggregate_child: Option<NodeCapabilities>,
1574}
1575
1576impl Default for ModifierNodeChain {
1577    fn default() -> Self {
1578        Self::new()
1579    }
1580}
1581
1582/// Index structure for O(1) modifier entry lookups during update.
1583///
1584/// This avoids O(n²) complexity by pre-building hash maps that allow constant-time
1585/// lookups for matching entries by key, hash, or type.
1586struct EntryIndex {
1587    /// Map (element type, node type, key) to keyed entries.
1588    keyed: HashMap<(TypeId, TypeId, u64), Vec<usize>>,
1589    /// Map (element type, node type, hash) to unkeyed entries with specific hash.
1590    hashed: HashMap<(TypeId, TypeId, u64), Vec<usize>>,
1591    /// Map (element type, node type) to unkeyed entries.
1592    typed: HashMap<(TypeId, TypeId), Vec<usize>>,
1593}
1594
1595struct EntryMatchQuery<'a> {
1596    element_type: TypeId,
1597    node_type: TypeId,
1598    key: Option<u64>,
1599    hash_code: u64,
1600    element: &'a DynModifierElement,
1601}
1602
1603impl EntryIndex {
1604    fn build(entries: &[ModifierNodeEntry]) -> Self {
1605        let mut keyed = HashMap::default();
1606        let mut hashed = HashMap::default();
1607        let mut typed = HashMap::default();
1608
1609        for (i, entry) in entries.iter().enumerate() {
1610            if let Some(key_value) = entry.key {
1611                // Keyed entry
1612                keyed
1613                    .entry((entry.element_type, entry.node_type, key_value))
1614                    .or_insert_with(Vec::new)
1615                    .push(i);
1616            } else {
1617                // Unkeyed entry - add to both hash and type indices
1618                hashed
1619                    .entry((entry.element_type, entry.node_type, entry.hash_code))
1620                    .or_insert_with(Vec::new)
1621                    .push(i);
1622                typed
1623                    .entry((entry.element_type, entry.node_type))
1624                    .or_insert_with(Vec::new)
1625                    .push(i);
1626            }
1627        }
1628
1629        Self {
1630            keyed,
1631            hashed,
1632            typed,
1633        }
1634    }
1635
1636    /// Find the best matching entry for reuse.
1637    ///
1638    /// Matching priority (from highest to lowest):
1639    /// 1. Keyed match: same element type, node type, and key.
1640    /// 2. Exact match: same retained identity, no key, same hash, and equal element.
1641    /// 3. Retained identity match without equality, which requires update.
1642    fn find_match(
1643        &self,
1644        entries: &[ModifierNodeEntry],
1645        used: &[bool],
1646        query: EntryMatchQuery<'_>,
1647    ) -> Option<usize> {
1648        if let Some(key_value) = query.key {
1649            // Priority 1: Keyed lookup - O(1)
1650            if let Some(candidates) =
1651                self.keyed
1652                    .get(&(query.element_type, query.node_type, key_value))
1653            {
1654                for &i in candidates {
1655                    if !used[i] {
1656                        return Some(i);
1657                    }
1658                }
1659            }
1660        } else {
1661            // Priority 2: Exact match (hash + equality) - O(1) lookup + O(k) equality checks
1662            if let Some(candidates) =
1663                self.hashed
1664                    .get(&(query.element_type, query.node_type, query.hash_code))
1665            {
1666                for &i in candidates {
1667                    if !used[i]
1668                        && entries[i]
1669                            .element
1670                            .as_ref()
1671                            .equals_element(query.element.as_ref())
1672                    {
1673                        return Some(i);
1674                    }
1675                }
1676            }
1677
1678            // Priority 3: Type match only - O(1) lookup + O(k) scan
1679            if let Some(candidates) = self.typed.get(&(query.element_type, query.node_type)) {
1680                for &i in candidates {
1681                    if !used[i] {
1682                        return Some(i);
1683                    }
1684                }
1685            }
1686        }
1687
1688        None
1689    }
1690}
1691
1692impl ModifierNodeChain {
1693    pub fn new() -> Self {
1694        let mut chain = Self {
1695            entries: Vec::new(),
1696            aggregated_capabilities: NodeCapabilities::empty(),
1697            head_aggregate_child_capabilities: NodeCapabilities::empty(),
1698            head_sentinel: Box::new(SentinelNode::new()),
1699            tail_sentinel: Box::new(SentinelNode::new()),
1700            ordered_nodes: Vec::new(),
1701            scratch_old_used: Vec::new(),
1702            scratch_match_order: Vec::new(),
1703            scratch_final_slots: Vec::new(),
1704            scratch_elements: Vec::new(),
1705        };
1706        chain.sync_chain_links();
1707        chain
1708    }
1709
1710    /// Detaches all nodes in the chain.
1711    pub fn detach_nodes(&mut self) {
1712        for entry in &self.entries {
1713            detach_node_tree(&mut **entry.node.borrow_mut());
1714        }
1715    }
1716
1717    /// Attaches all nodes in the chain.
1718    pub fn attach_nodes(&mut self, context: &mut dyn ModifierNodeContext) {
1719        for entry in &self.entries {
1720            attach_node_tree(&mut **entry.node.borrow_mut(), context);
1721        }
1722    }
1723
1724    /// Rebuilds the internal chain links (parent/child relationships).
1725    /// This should be called if nodes have been detached but are intended to be reused.
1726    pub fn repair_chain(&mut self) {
1727        self.sync_chain_links();
1728    }
1729
1730    /// Reconcile the chain against the provided elements, attaching newly
1731    /// created nodes and detaching nodes that are no longer required.
1732    ///
1733    /// This method delegates to `update_from_ref_iter` which handles the
1734    /// actual reconciliation logic.
1735    pub fn update_from_slice(
1736        &mut self,
1737        elements: &[DynModifierElement],
1738        context: &mut dyn ModifierNodeContext,
1739    ) {
1740        self.update_from_ref_iter(elements.iter(), context);
1741    }
1742
1743    /// Reconcile the chain against the provided iterator of element references.
1744    ///
1745    /// This is the preferred method as it avoids requiring a collected slice,
1746    /// enabling zero-allocation traversal of modifier trees.
1747    pub fn update_from_ref_iter<'a, I>(
1748        &mut self,
1749        elements: I,
1750        context: &mut dyn ModifierNodeContext,
1751    ) where
1752        I: Iterator<Item = &'a DynModifierElement>,
1753    {
1754        // Fast path: try to match elements sequentially without building index.
1755        // If all elements match in order (same type and key at same position),
1756        // we skip the expensive EntryIndex building. This is O(n) instead of O(n + m).
1757        let old_len = self.entries.len();
1758        let mut fast_path_failed_at: Option<usize> = None;
1759        let mut elements_count = 0;
1760
1761        // Collect elements we need to process in slow path
1762        self.scratch_elements.clear();
1763
1764        for (idx, element) in elements.enumerate() {
1765            elements_count = idx + 1;
1766
1767            if fast_path_failed_at.is_none() && idx < old_len {
1768                let entry = &mut self.entries[idx];
1769                let same_type = entry.element_type == element.element_type();
1770                let same_node_type = entry.node_type == element.node_type();
1771                let same_key = entry.key == element.key();
1772                let same_hash = entry.hash_code == element.hash_code();
1773
1774                // Fast path requires same type, key, AND hash to ensure we're not
1775                // breaking reordering semantics (where elements can move positions)
1776                let positional_update = element.requires_update();
1777                if same_type && same_node_type && same_key && (same_hash || positional_update) {
1778                    let can_update_node = {
1779                        let node_borrow = entry.node.borrow();
1780                        element.can_update_node(&**node_borrow)
1781                    };
1782                    if !can_update_node {
1783                        fast_path_failed_at = Some(idx);
1784                        self.scratch_elements.push(element.clone());
1785                        continue;
1786                    }
1787
1788                    // Fast path: element matches at same position
1789                    let same_element = entry.element.as_ref().equals_element(element.as_ref());
1790                    let capabilities = element.capabilities();
1791
1792                    // Re-attach node if it was detached during a previous update
1793                    {
1794                        let node_borrow = entry.node.borrow();
1795                        if !node_borrow.node_state().is_attached() {
1796                            drop(node_borrow);
1797                            attach_node_tree(&mut **entry.node.borrow_mut(), context);
1798                        }
1799                    }
1800
1801                    // Optimize updates: only call update_node if element changed
1802                    let needs_update = !same_element || element.requires_update();
1803                    if needs_update {
1804                        element.update_node(&mut **entry.node.borrow_mut());
1805                        entry.element = element.clone();
1806                        entry.hash_code = element.hash_code();
1807                        request_update_auto_invalidations(element.as_ref(), context, capabilities);
1808                    }
1809
1810                    // Always update metadata
1811                    entry.capabilities = capabilities;
1812                    entry
1813                        .node
1814                        .borrow()
1815                        .node_state()
1816                        .set_capabilities(capabilities);
1817                    continue;
1818                }
1819                // Fast path failed - mark position and fall through to collect
1820                fast_path_failed_at = Some(idx);
1821            }
1822
1823            // Collect element for slow path processing
1824            self.scratch_elements.push(element.clone());
1825        }
1826
1827        // Fast path succeeded if:
1828        // 1. No mismatch was found (fast_path_failed_at is None)
1829        // 2. All elements were processed via fast path (scratch_elements is empty)
1830        // Note: If old_len=0 and we have new elements, scratch_elements won't be empty
1831        if fast_path_failed_at.is_none() && self.scratch_elements.is_empty() {
1832            // Detach any removed entries (elements_count <= old_len guaranteed here)
1833            if elements_count < self.entries.len() {
1834                for entry in self.entries.drain(elements_count..) {
1835                    request_auto_invalidations(context, entry.capabilities);
1836                    detach_node_tree(&mut **entry.node.borrow_mut());
1837                }
1838            }
1839            self.sync_chain_links();
1840            return;
1841        }
1842
1843        // Slow path: need full reconciliation starting from failure point
1844        // If no mismatch but we have extra elements, fail_idx is the old length
1845        let fail_idx = fast_path_failed_at.unwrap_or(old_len);
1846
1847        // Move entries that were already processed to a safe place
1848        let mut old_entries: Vec<ModifierNodeEntry> = self.entries.drain(fail_idx..).collect();
1849        let processed_entries_len = self.entries.len();
1850        let old_len = old_entries.len();
1851
1852        // Reuse scratch buffers for the remaining entries only
1853        self.scratch_old_used.clear();
1854        self.scratch_old_used.resize(old_len, false);
1855
1856        self.scratch_match_order.clear();
1857        self.scratch_match_order.resize(old_len, None);
1858
1859        // Build index only for unprocessed entries
1860        let index = EntryIndex::build(&old_entries);
1861
1862        let new_elements_count = self.scratch_elements.len();
1863        self.scratch_final_slots.clear();
1864        self.scratch_final_slots.reserve(new_elements_count);
1865
1866        // Process each remaining element
1867        for (new_pos, element) in self.scratch_elements.drain(..).enumerate() {
1868            self.scratch_final_slots.push(None);
1869            let element_type = element.element_type();
1870            let node_type = element.node_type();
1871            let key = element.key();
1872            let hash_code = element.hash_code();
1873            let capabilities = element.capabilities();
1874
1875            // Find best matching old entry via index
1876            let matched_idx = index.find_match(
1877                &old_entries,
1878                &self.scratch_old_used,
1879                EntryMatchQuery {
1880                    element_type,
1881                    node_type,
1882                    key,
1883                    hash_code,
1884                    element: &element,
1885                },
1886            );
1887
1888            if let Some(idx) = matched_idx {
1889                // Reuse existing entry
1890                let entry = &mut old_entries[idx];
1891                let can_update_node = {
1892                    let node_borrow = entry.node.borrow();
1893                    element.can_update_node(&**node_borrow)
1894                };
1895                if !can_update_node {
1896                    let replacement = ModifierNodeEntry::new(
1897                        element_type,
1898                        node_type,
1899                        key,
1900                        element.clone(),
1901                        element.create_node(),
1902                        hash_code,
1903                        capabilities,
1904                    );
1905                    attach_node_tree(&mut **replacement.node.borrow_mut(), context);
1906                    element.update_node(&mut **replacement.node.borrow_mut());
1907                    request_auto_invalidations(context, capabilities);
1908                    self.scratch_final_slots[new_pos] = Some(replacement);
1909                    continue;
1910                }
1911
1912                self.scratch_old_used[idx] = true;
1913                self.scratch_match_order[idx] = Some(new_pos);
1914                let moved = idx != new_pos;
1915
1916                // Check if element actually changed
1917                let same_element = entry.element.as_ref().equals_element(element.as_ref());
1918
1919                // Re-attach node if it was detached
1920                {
1921                    let node_borrow = entry.node.borrow();
1922                    if !node_borrow.node_state().is_attached() {
1923                        drop(node_borrow);
1924                        attach_node_tree(&mut **entry.node.borrow_mut(), context);
1925                    }
1926                }
1927
1928                // Optimize updates: only call update_node if element changed
1929                let needs_update = !same_element || element.requires_update();
1930                if needs_update {
1931                    element.update_node(&mut **entry.node.borrow_mut());
1932                    entry.element = element;
1933                    entry.hash_code = hash_code;
1934                    request_update_auto_invalidations(
1935                        entry.element.as_ref(),
1936                        context,
1937                        capabilities,
1938                    );
1939                }
1940                if moved {
1941                    request_auto_invalidations(context, capabilities);
1942                }
1943
1944                // Always update metadata
1945                entry.key = key;
1946                entry.element_type = element_type;
1947                entry.node_type = node_type;
1948                entry.capabilities = capabilities;
1949                entry
1950                    .node
1951                    .borrow()
1952                    .node_state()
1953                    .set_capabilities(capabilities);
1954            } else {
1955                // Create new entry
1956                let entry = ModifierNodeEntry::new(
1957                    element_type,
1958                    node_type,
1959                    key,
1960                    element.clone(),
1961                    element.create_node(),
1962                    hash_code,
1963                    capabilities,
1964                );
1965                attach_node_tree(&mut **entry.node.borrow_mut(), context);
1966                element.update_node(&mut **entry.node.borrow_mut());
1967                request_auto_invalidations(context, capabilities);
1968                self.scratch_final_slots[new_pos] = Some(entry);
1969            }
1970        }
1971
1972        // Place matched entries in their new positions
1973        for (i, entry) in old_entries.into_iter().enumerate() {
1974            if self.scratch_old_used[i] {
1975                if let Some(pos) = self.scratch_match_order[i] {
1976                    self.scratch_final_slots[pos] = Some(entry);
1977                } else {
1978                    request_auto_invalidations(context, entry.capabilities);
1979                    detach_node_tree(&mut **entry.node.borrow_mut());
1980                }
1981            } else {
1982                request_auto_invalidations(context, entry.capabilities);
1983                detach_node_tree(&mut **entry.node.borrow_mut());
1984            }
1985        }
1986
1987        // Append processed entries to self.entries
1988        self.entries.reserve(self.scratch_final_slots.len());
1989        for slot in self.scratch_final_slots.drain(..) {
1990            if let Some(entry) = slot {
1991                self.entries.push(entry);
1992            } else {
1993                log::error!("modifier reconciliation produced an empty final slot");
1994            }
1995        }
1996
1997        debug_assert_eq!(
1998            self.entries.len(),
1999            processed_entries_len + new_elements_count
2000        );
2001        self.sync_chain_links();
2002    }
2003
2004    /// Convenience wrapper that accepts any iterator of type-erased
2005    /// modifier elements. Elements are collected into a temporary vector
2006    /// before reconciliation.
2007    pub fn update<I>(&mut self, elements: I, context: &mut dyn ModifierNodeContext)
2008    where
2009        I: IntoIterator<Item = DynModifierElement>,
2010    {
2011        let collected: Vec<DynModifierElement> = elements.into_iter().collect();
2012        self.update_from_slice(&collected, context);
2013    }
2014
2015    /// Resets all nodes in the chain. This mirrors the behaviour of
2016    /// Jetpack Compose's `onReset` callback.
2017    pub fn reset(&mut self) {
2018        for entry in &mut self.entries {
2019            reset_node_tree(&mut **entry.node.borrow_mut());
2020        }
2021    }
2022
2023    /// Detaches every node in the chain and clears internal storage.
2024    pub fn detach_all(&mut self) {
2025        for entry in std::mem::take(&mut self.entries) {
2026            detach_node_tree(&mut **entry.node.borrow_mut());
2027            {
2028                let node_borrow = entry.node.borrow();
2029                let state = node_borrow.node_state();
2030                state.set_capabilities(NodeCapabilities::empty());
2031            }
2032        }
2033        self.aggregated_capabilities = NodeCapabilities::empty();
2034        self.head_aggregate_child_capabilities = NodeCapabilities::empty();
2035        self.ordered_nodes.clear();
2036        self.sync_chain_links();
2037    }
2038
2039    pub fn len(&self) -> usize {
2040        self.entries.len()
2041    }
2042
2043    pub fn is_empty(&self) -> bool {
2044        self.entries.is_empty()
2045    }
2046
2047    /// Returns the aggregated capability mask for the entire chain.
2048    pub fn capabilities(&self) -> NodeCapabilities {
2049        self.aggregated_capabilities
2050    }
2051
2052    /// Returns true if the chain contains at least one node with the requested capability.
2053    pub fn has_capability(&self, capability: NodeCapabilities) -> bool {
2054        self.aggregated_capabilities.contains(capability)
2055    }
2056
2057    /// Returns the sentinel head reference for traversal.
2058    pub fn head(&self) -> ModifierChainNodeRef<'_> {
2059        self.make_node_ref(NodeLink::Head)
2060    }
2061
2062    /// Returns the sentinel tail reference for traversal.
2063    pub fn tail(&self) -> ModifierChainNodeRef<'_> {
2064        self.make_node_ref(NodeLink::Tail)
2065    }
2066
2067    /// Iterates over the chain from head to tail, skipping sentinels.
2068    pub fn head_to_tail(&self) -> ModifierChainIter<'_> {
2069        ModifierChainIter::forward(self)
2070    }
2071
2072    /// Iterates over the chain from tail to head, skipping sentinels.
2073    pub fn tail_to_head(&self) -> ModifierChainIter<'_> {
2074        ModifierChainIter::backward(self)
2075    }
2076
2077    /// Calls `f` for every node in insertion order.
2078    pub fn for_each_forward<F>(&self, mut f: F)
2079    where
2080        F: FnMut(ModifierChainNodeRef<'_>),
2081    {
2082        for node in self.head_to_tail() {
2083            f(node);
2084        }
2085    }
2086
2087    /// Calls `f` for every node containing any capability from `mask`.
2088    pub fn for_each_forward_matching<F>(&self, mask: NodeCapabilities, mut f: F)
2089    where
2090        F: FnMut(ModifierChainNodeRef<'_>),
2091    {
2092        if mask.is_empty() {
2093            self.for_each_forward(f);
2094            return;
2095        }
2096
2097        if !self.head().aggregate_child_capabilities().intersects(mask) {
2098            return;
2099        }
2100
2101        for node in self.head_to_tail() {
2102            if node.kind_set().intersects(mask) {
2103                f(node);
2104            }
2105        }
2106    }
2107
2108    /// Calls `f` for every node containing any capability from `mask`, providing the node ref.
2109    pub fn for_each_node_with_capability<F>(&self, mask: NodeCapabilities, mut f: F)
2110    where
2111        F: FnMut(ModifierChainNodeRef<'_>, &dyn ModifierNode),
2112    {
2113        self.for_each_forward_matching(mask, |node_ref| {
2114            node_ref.with_node(|node| f(node_ref.clone(), node));
2115        });
2116    }
2117
2118    /// Calls `f` for every node in reverse insertion order.
2119    pub fn for_each_backward<F>(&self, mut f: F)
2120    where
2121        F: FnMut(ModifierChainNodeRef<'_>),
2122    {
2123        for node in self.tail_to_head() {
2124            f(node);
2125        }
2126    }
2127
2128    /// Returns the node reference that owns `node`.
2129    pub fn find_node_ref(&self, node: &dyn ModifierNode) -> Option<ModifierChainNodeRef<'_>> {
2130        fn node_data_ptr(node: &dyn ModifierNode) -> *const () {
2131            node as *const dyn ModifierNode as *const ()
2132        }
2133
2134        let target = node_data_ptr(node);
2135        for (index, entry) in self.entries.iter().enumerate() {
2136            if node_data_ptr(&**entry.node.borrow()) == target {
2137                return Some(self.make_node_ref(NodeLink::Entry(NodePath::root(index))));
2138            }
2139        }
2140
2141        self.ordered_nodes.iter().find_map(|(link, _caps, _agg)| {
2142            if matches!(link, NodeLink::Entry(path) if path.delegates().is_empty()) {
2143                return None;
2144            }
2145            let matches_target = match link {
2146                NodeLink::Head => node_data_ptr(self.head_sentinel.as_ref()) == target,
2147                NodeLink::Tail => node_data_ptr(self.tail_sentinel.as_ref()) == target,
2148                NodeLink::Entry(path) => {
2149                    let node_borrow = self.entries[path.entry()].node.borrow();
2150                    node_data_ptr(&**node_borrow) == target
2151                }
2152            };
2153            if matches_target {
2154                Some(self.make_node_ref(*link))
2155            } else {
2156                None
2157            }
2158        })
2159    }
2160
2161    /// Downcasts the node at `index` to the requested type.
2162    /// Returns a `Ref` guard that dereferences to the node type.
2163    pub fn node<N: ModifierNode + 'static>(&self, index: usize) -> Option<std::cell::Ref<'_, N>> {
2164        self.entries.get(index).and_then(|entry| {
2165            std::cell::Ref::filter_map(entry.node.borrow(), |boxed_node| {
2166                boxed_node.as_any().downcast_ref::<N>()
2167            })
2168            .ok()
2169        })
2170    }
2171
2172    /// Downcasts the node at `index` to the requested mutable type.
2173    /// Returns a `RefMut` guard that dereferences to the node type.
2174    pub fn node_mut<N: ModifierNode + 'static>(
2175        &self,
2176        index: usize,
2177    ) -> Option<std::cell::RefMut<'_, N>> {
2178        self.entries.get(index).and_then(|entry| {
2179            std::cell::RefMut::filter_map(entry.node.borrow_mut(), |boxed_node| {
2180                boxed_node.as_any_mut().downcast_mut::<N>()
2181            })
2182            .ok()
2183        })
2184    }
2185
2186    /// Returns an Rc clone of the node at the given index for shared ownership.
2187    /// This is used by coordinators to hold direct references to nodes.
2188    pub fn get_node_rc(&self, index: usize) -> Option<Rc<RefCell<Box<dyn ModifierNode>>>> {
2189        self.entries.get(index).map(|entry| Rc::clone(&entry.node))
2190    }
2191
2192    /// Returns true if the chain contains any nodes matching the given invalidation kind.
2193    pub fn has_nodes_for_invalidation(&self, kind: InvalidationKind) -> bool {
2194        self.aggregated_capabilities
2195            .contains(NodeCapabilities::for_invalidation(kind))
2196    }
2197
2198    /// Visits every node mutably in insertion order together with its capability mask.
2199    pub fn visit_nodes_mut<F>(&mut self, mut f: F)
2200    where
2201        F: FnMut(&mut dyn ModifierNode, NodeCapabilities),
2202    {
2203        for index in 0..self.ordered_nodes.len() {
2204            let (link, cached_caps, _agg) = self.ordered_nodes[index];
2205            match link {
2206                NodeLink::Head => {
2207                    f(self.head_sentinel.as_mut(), cached_caps);
2208                }
2209                NodeLink::Tail => {
2210                    f(self.tail_sentinel.as_mut(), cached_caps);
2211                }
2212                NodeLink::Entry(path) => {
2213                    let mut node_borrow = self.entries[path.entry()].node.borrow_mut();
2214                    if path.delegates().is_empty() {
2215                        f(&mut **node_borrow, cached_caps);
2216                    } else {
2217                        let mut current: &mut dyn ModifierNode = &mut **node_borrow;
2218                        for &delegate_index in path.delegates() {
2219                            if let Some(delegate) =
2220                                nth_delegate_mut(current, delegate_index as usize)
2221                            {
2222                                current = delegate;
2223                            } else {
2224                                return; // Invalid delegate path
2225                            }
2226                        }
2227                        f(current, cached_caps);
2228                    }
2229                }
2230            }
2231        }
2232    }
2233
2234    fn make_node_ref(&self, link: NodeLink) -> ModifierChainNodeRef<'_> {
2235        ModifierChainNodeRef {
2236            chain: self,
2237            link,
2238            cached_capabilities: None,
2239            cached_aggregate_child: None,
2240        }
2241    }
2242
2243    fn make_node_ref_with_caps(
2244        &self,
2245        link: NodeLink,
2246        caps: NodeCapabilities,
2247        aggregate_child: NodeCapabilities,
2248    ) -> ModifierChainNodeRef<'_> {
2249        ModifierChainNodeRef {
2250            chain: self,
2251            link,
2252            cached_capabilities: Some(caps),
2253            cached_aggregate_child: Some(aggregate_child),
2254        }
2255    }
2256
2257    fn sync_chain_links(&mut self) {
2258        self.rebuild_ordered_nodes();
2259
2260        self.head_sentinel.node_state().set_parent_link(None);
2261        self.tail_sentinel.node_state().set_child_link(None);
2262
2263        if self.ordered_nodes.is_empty() {
2264            self.head_sentinel
2265                .node_state()
2266                .set_child_link(Some(NodeLink::Tail));
2267            self.tail_sentinel
2268                .node_state()
2269                .set_parent_link(Some(NodeLink::Head));
2270            self.aggregated_capabilities = NodeCapabilities::empty();
2271            self.head_aggregate_child_capabilities = NodeCapabilities::empty();
2272            self.head_sentinel
2273                .node_state()
2274                .set_aggregate_child_capabilities(NodeCapabilities::empty());
2275            self.tail_sentinel
2276                .node_state()
2277                .set_aggregate_child_capabilities(NodeCapabilities::empty());
2278            return;
2279        }
2280
2281        let mut previous = NodeLink::Head;
2282        for (link, _caps, _agg) in self.ordered_nodes.iter().copied() {
2283            // Set child link on previous
2284            match &previous {
2285                NodeLink::Head => self.head_sentinel.node_state().set_child_link(Some(link)),
2286                NodeLink::Tail => self.tail_sentinel.node_state().set_child_link(Some(link)),
2287                NodeLink::Entry(path) => {
2288                    let node_borrow = self.entries[path.entry()].node.borrow();
2289                    // Navigate to delegate if needed
2290                    if path.delegates().is_empty() {
2291                        node_borrow.node_state().set_child_link(Some(link));
2292                    } else {
2293                        let mut current: &dyn ModifierNode = &**node_borrow;
2294                        for &delegate_index in path.delegates() {
2295                            if let Some(delegate) = nth_delegate(current, delegate_index as usize) {
2296                                current = delegate;
2297                            }
2298                        }
2299                        current.node_state().set_child_link(Some(link));
2300                    }
2301                }
2302            }
2303            // Set parent link on current
2304            match &link {
2305                NodeLink::Head => self
2306                    .head_sentinel
2307                    .node_state()
2308                    .set_parent_link(Some(previous)),
2309                NodeLink::Tail => self
2310                    .tail_sentinel
2311                    .node_state()
2312                    .set_parent_link(Some(previous)),
2313                NodeLink::Entry(path) => {
2314                    let node_borrow = self.entries[path.entry()].node.borrow();
2315                    // Navigate to delegate if needed
2316                    if path.delegates().is_empty() {
2317                        node_borrow.node_state().set_parent_link(Some(previous));
2318                    } else {
2319                        let mut current: &dyn ModifierNode = &**node_borrow;
2320                        for &delegate_index in path.delegates() {
2321                            if let Some(delegate) = nth_delegate(current, delegate_index as usize) {
2322                                current = delegate;
2323                            }
2324                        }
2325                        current.node_state().set_parent_link(Some(previous));
2326                    }
2327                }
2328            }
2329            previous = link;
2330        }
2331
2332        // Set child link on last node to Tail
2333        match &previous {
2334            NodeLink::Head => self
2335                .head_sentinel
2336                .node_state()
2337                .set_child_link(Some(NodeLink::Tail)),
2338            NodeLink::Tail => self
2339                .tail_sentinel
2340                .node_state()
2341                .set_child_link(Some(NodeLink::Tail)),
2342            NodeLink::Entry(path) => {
2343                let node_borrow = self.entries[path.entry()].node.borrow();
2344                // Navigate to delegate if needed
2345                if path.delegates().is_empty() {
2346                    node_borrow
2347                        .node_state()
2348                        .set_child_link(Some(NodeLink::Tail));
2349                } else {
2350                    let mut current: &dyn ModifierNode = &**node_borrow;
2351                    for &delegate_index in path.delegates() {
2352                        if let Some(delegate) = nth_delegate(current, delegate_index as usize) {
2353                            current = delegate;
2354                        }
2355                    }
2356                    current.node_state().set_child_link(Some(NodeLink::Tail));
2357                }
2358            }
2359        }
2360        self.tail_sentinel
2361            .node_state()
2362            .set_parent_link(Some(previous));
2363        self.tail_sentinel.node_state().set_child_link(None);
2364
2365        let mut aggregate = NodeCapabilities::empty();
2366        for (link, cached_caps, cached_aggregate) in self.ordered_nodes.iter_mut().rev() {
2367            aggregate |= *cached_caps;
2368            *cached_aggregate = aggregate;
2369            // Also update NodeState for code that reads through DelegatableNode
2370            match link {
2371                NodeLink::Head => {
2372                    self.head_sentinel
2373                        .node_state()
2374                        .set_aggregate_child_capabilities(aggregate);
2375                }
2376                NodeLink::Tail => {
2377                    self.tail_sentinel
2378                        .node_state()
2379                        .set_aggregate_child_capabilities(aggregate);
2380                }
2381                NodeLink::Entry(path) => {
2382                    let node_borrow = self.entries[path.entry()].node.borrow();
2383                    let state = if path.delegates().is_empty() {
2384                        node_borrow.node_state()
2385                    } else {
2386                        let mut current: &dyn ModifierNode = &**node_borrow;
2387                        for &delegate_index in path.delegates() {
2388                            if let Some(delegate) = nth_delegate(current, delegate_index as usize) {
2389                                current = delegate;
2390                            }
2391                        }
2392                        current.node_state()
2393                    };
2394                    state.set_aggregate_child_capabilities(aggregate);
2395                }
2396            }
2397        }
2398
2399        self.aggregated_capabilities = aggregate;
2400        self.head_aggregate_child_capabilities = aggregate;
2401        self.head_sentinel
2402            .node_state()
2403            .set_aggregate_child_capabilities(aggregate);
2404        self.tail_sentinel
2405            .node_state()
2406            .set_aggregate_child_capabilities(NodeCapabilities::empty());
2407    }
2408
2409    fn rebuild_ordered_nodes(&mut self) {
2410        self.ordered_nodes.clear();
2411        let mut path_buf = [0usize; MAX_DELEGATE_DEPTH];
2412        for (index, entry) in self.entries.iter().enumerate() {
2413            let node_borrow = entry.node.borrow();
2414            Self::enumerate_link_order(
2415                &**node_borrow,
2416                index,
2417                &mut path_buf,
2418                0,
2419                &mut self.ordered_nodes,
2420            );
2421        }
2422    }
2423
2424    fn enumerate_link_order(
2425        node: &dyn ModifierNode,
2426        entry: usize,
2427        path_buf: &mut [usize; MAX_DELEGATE_DEPTH],
2428        path_len: usize,
2429        out: &mut Vec<(NodeLink, NodeCapabilities, NodeCapabilities)>,
2430    ) {
2431        let caps = node.node_state().capabilities();
2432        out.push((
2433            NodeLink::Entry(NodePath::from_slice(entry, &path_buf[..path_len])),
2434            caps,
2435            NodeCapabilities::empty(),
2436        ));
2437        let mut delegate_index = 0usize;
2438        node.for_each_delegate(&mut |child| {
2439            if path_len < MAX_DELEGATE_DEPTH {
2440                path_buf[path_len] = delegate_index;
2441                Self::enumerate_link_order(child, entry, path_buf, path_len + 1, out);
2442            }
2443            delegate_index += 1;
2444        });
2445    }
2446}
2447
2448impl<'a> ModifierChainNodeRef<'a> {
2449    /// Helper to get NodeState, properly handling RefCell for entries.
2450    /// Returns NodeState values by calling a closure with the state.
2451    fn with_state<R>(&self, f: impl FnOnce(&NodeState) -> R) -> R {
2452        match &self.link {
2453            NodeLink::Head => f(self.chain.head_sentinel.node_state()),
2454            NodeLink::Tail => f(self.chain.tail_sentinel.node_state()),
2455            NodeLink::Entry(path) => {
2456                let node_borrow = self.chain.entries[path.entry()].node.borrow();
2457                // Navigate through delegates if path has them
2458                if path.delegates().is_empty() {
2459                    f(node_borrow.node_state())
2460                } else {
2461                    // Navigate to the delegate node
2462                    let mut current: &dyn ModifierNode = &**node_borrow;
2463                    for &delegate_index in path.delegates() {
2464                        if let Some(delegate) = nth_delegate(current, delegate_index as usize) {
2465                            current = delegate;
2466                        } else {
2467                            // Fallback to root node state if delegate path is invalid
2468                            return f(node_borrow.node_state());
2469                        }
2470                    }
2471                    f(current.node_state())
2472                }
2473            }
2474        }
2475    }
2476
2477    /// Provides access to the node via a closure, properly handling RefCell borrows.
2478    /// Returns None for sentinel nodes.
2479    pub fn with_node<R>(&self, f: impl FnOnce(&dyn ModifierNode) -> R) -> Option<R> {
2480        match &self.link {
2481            NodeLink::Head => None, // Head sentinel
2482            NodeLink::Tail => None, // Tail sentinel
2483            NodeLink::Entry(path) => {
2484                let node_borrow = self.chain.entries[path.entry()].node.borrow();
2485                // Navigate through delegates if path has them
2486                if path.delegates().is_empty() {
2487                    Some(f(&**node_borrow))
2488                } else {
2489                    // Navigate to the delegate node
2490                    let mut current: &dyn ModifierNode = &**node_borrow;
2491                    for &delegate_index in path.delegates() {
2492                        // `?`: bail out with None if the delegate path is invalid.
2493                        current = nth_delegate(current, delegate_index as usize)?;
2494                    }
2495                    Some(f(current))
2496                }
2497            }
2498        }
2499    }
2500
2501    /// Returns the parent reference, including sentinel head when applicable.
2502    #[inline]
2503    pub fn parent(&self) -> Option<Self> {
2504        self.with_state(|state| state.parent_link())
2505            .map(|link| self.chain.make_node_ref(link))
2506    }
2507
2508    /// Returns the child reference, including sentinel tail for the last entry.
2509    #[inline]
2510    pub fn child(&self) -> Option<Self> {
2511        self.with_state(|state| state.child_link())
2512            .map(|link| self.chain.make_node_ref(link))
2513    }
2514
2515    /// Returns the capability mask for this specific node.
2516    #[inline]
2517    pub fn kind_set(&self) -> NodeCapabilities {
2518        if let Some(caps) = self.cached_capabilities {
2519            return caps;
2520        }
2521        match &self.link {
2522            NodeLink::Head | NodeLink::Tail => NodeCapabilities::empty(),
2523            NodeLink::Entry(_) => self.with_state(|state| state.capabilities()),
2524        }
2525    }
2526
2527    /// Returns the entry index backing this node when it is part of the chain.
2528    pub fn entry_index(&self) -> Option<usize> {
2529        match &self.link {
2530            NodeLink::Entry(path) => Some(path.entry()),
2531            _ => None,
2532        }
2533    }
2534
2535    /// Returns how many delegate hops separate this node from its root element.
2536    pub fn delegate_depth(&self) -> usize {
2537        match &self.link {
2538            NodeLink::Entry(path) => path.delegates().len(),
2539            _ => 0,
2540        }
2541    }
2542
2543    /// Returns the aggregated capability mask for the subtree rooted at this node.
2544    #[inline]
2545    pub fn aggregate_child_capabilities(&self) -> NodeCapabilities {
2546        if let Some(agg) = self.cached_aggregate_child {
2547            return agg;
2548        }
2549        if self.is_tail() {
2550            NodeCapabilities::empty()
2551        } else {
2552            self.with_state(|state| state.aggregate_child_capabilities())
2553        }
2554    }
2555
2556    /// Returns true if this reference targets the sentinel head.
2557    pub fn is_head(&self) -> bool {
2558        matches!(self.link, NodeLink::Head)
2559    }
2560
2561    /// Returns true if this reference targets the sentinel tail.
2562    pub fn is_tail(&self) -> bool {
2563        matches!(self.link, NodeLink::Tail)
2564    }
2565
2566    /// Returns true if this reference targets either sentinel.
2567    pub fn is_sentinel(&self) -> bool {
2568        matches!(self.link, NodeLink::Head | NodeLink::Tail)
2569    }
2570
2571    /// Returns true if this node has any capability bits present in `mask`.
2572    pub fn has_capability(&self, mask: NodeCapabilities) -> bool {
2573        !mask.is_empty() && self.kind_set().intersects(mask)
2574    }
2575
2576    /// Visits descendant nodes, optionally including `self`, in insertion order.
2577    pub fn visit_descendants<F>(self, include_self: bool, mut f: F)
2578    where
2579        F: FnMut(ModifierChainNodeRef<'a>),
2580    {
2581        let mut current = if include_self {
2582            Some(self)
2583        } else {
2584            self.child()
2585        };
2586        while let Some(node) = current {
2587            if node.is_tail() {
2588                break;
2589            }
2590            if !node.is_sentinel() {
2591                f(node.clone());
2592            }
2593            current = node.child();
2594        }
2595    }
2596
2597    /// Visits descendant nodes that match `mask`, short-circuiting when possible.
2598    pub fn visit_descendants_matching<F>(self, include_self: bool, mask: NodeCapabilities, mut f: F)
2599    where
2600        F: FnMut(ModifierChainNodeRef<'a>),
2601    {
2602        if mask.is_empty() {
2603            self.visit_descendants(include_self, f);
2604            return;
2605        }
2606
2607        if !self.aggregate_child_capabilities().intersects(mask) {
2608            return;
2609        }
2610
2611        self.visit_descendants(include_self, |node| {
2612            if node.kind_set().intersects(mask) {
2613                f(node);
2614            }
2615        });
2616    }
2617
2618    /// Visits ancestor nodes up to (but excluding) the sentinel head.
2619    pub fn visit_ancestors<F>(self, include_self: bool, mut f: F)
2620    where
2621        F: FnMut(ModifierChainNodeRef<'a>),
2622    {
2623        let mut current = if include_self {
2624            Some(self)
2625        } else {
2626            self.parent()
2627        };
2628        while let Some(node) = current {
2629            if node.is_head() {
2630                break;
2631            }
2632            f(node.clone());
2633            current = node.parent();
2634        }
2635    }
2636
2637    /// Visits ancestor nodes that match `mask`.
2638    pub fn visit_ancestors_matching<F>(self, include_self: bool, mask: NodeCapabilities, mut f: F)
2639    where
2640        F: FnMut(ModifierChainNodeRef<'a>),
2641    {
2642        if mask.is_empty() {
2643            self.visit_ancestors(include_self, f);
2644            return;
2645        }
2646
2647        self.visit_ancestors(include_self, |node| {
2648            if node.kind_set().intersects(mask) {
2649                f(node);
2650            }
2651        });
2652    }
2653}
2654
2655#[cfg(test)]
2656#[path = "tests/modifier_tests.rs"]
2657mod tests;