Skip to main content

cranpose_foundation/
modifier.rs

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