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