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/// The value a control holds inside a range, for a slider, a dial or a
687/// progress bar.
688///
689/// This is Compose's `ProgressBarRangeInfo` (`Modifier.progressSemantics(value,
690/// range, steps)`). A control without it reads as plain text: a screen reader
691/// user hears "47 percent" and has no way to change it, because nothing tells
692/// the platform the control is adjustable.
693#[derive(Clone, Copy, Debug, PartialEq)]
694pub struct ProgressBarRangeInfo {
695    pub current: f32,
696    pub start: f32,
697    pub end: f32,
698    /// How many stops sit between `start` and `end`, as Compose counts them.
699    /// Zero means the value moves without stops.
700    pub steps: u32,
701}
702
703impl ProgressBarRangeInfo {
704    pub fn new(current: f32, start: f32, end: f32, steps: u32) -> Self {
705        Self {
706            current,
707            start,
708            end,
709            steps,
710        }
711    }
712
713    /// Where the value sits between the two ends, from 0 to 1.
714    pub fn fraction(&self) -> f32 {
715        let span = self.end - self.start;
716        if span.abs() < f32::EPSILON {
717            return 0.0;
718        }
719        ((self.current - self.start) / span).clamp(0.0, 1.0)
720    }
721
722    /// How far one stop moves the value. With no stops, one tenth of the
723    /// range, which is what a screen reader's swipe up and down expects.
724    pub fn step(&self) -> f32 {
725        let span = self.end - self.start;
726        if self.steps == 0 {
727            span / 10.0
728        } else {
729            span / (self.steps as f32 + 1.0)
730        }
731    }
732}
733
734/// How far a container has scrolled along one axis and how far it can go.
735///
736/// How many rows and columns a list holds, so a screen reader can say
737/// "list, 12 items" as its cursor enters. Compose's `CollectionInfo`.
738#[derive(Clone, Copy, Debug, PartialEq, Eq)]
739pub struct CollectionInfo {
740    pub rows: usize,
741    pub columns: usize,
742}
743
744/// This is Compose's `ScrollAxisRange` (`verticalScrollAxisRange`,
745/// `horizontalScrollAxisRange`). A lazy list has no whole extent to give, so
746/// it reports the first visible item as the value and one more than that as
747/// the end while it can still scroll; a reader only needs to know whether it
748/// can page on.
749#[derive(Clone, Copy, Debug, PartialEq)]
750pub struct ScrollAxisRange {
751    pub value: f32,
752    pub max_value: f32,
753    pub reverse: bool,
754}
755
756impl ScrollAxisRange {
757    pub fn new(value: f32, max_value: f32, reverse: bool) -> Self {
758        Self {
759            value,
760            max_value,
761            reverse,
762        }
763    }
764
765    pub fn can_scroll_forward(&self) -> bool {
766        self.value < self.max_value
767    }
768
769    pub fn can_scroll_backward(&self) -> bool {
770        self.value > 0.0
771    }
772}
773
774/// What a container does when a screen reader pages it, e.g. TalkBack's
775/// scroll forward action or an accesskit scroll down. The two deltas are in
776/// layout pixels; the answer says whether anything moved.
777///
778/// This is Compose's `SemanticsActions.ScrollBy`.
779#[derive(Clone)]
780pub struct SemanticsScrollBy {
781    handler: Rc<dyn Fn(f32, f32) -> bool>,
782}
783
784impl SemanticsScrollBy {
785    pub fn new(handler: impl Fn(f32, f32) -> bool + 'static) -> Self {
786        Self {
787            handler: Rc::new(handler),
788        }
789    }
790
791    pub fn invoke(&self, dx: f32, dy: f32) -> bool {
792        (self.handler)(dx, dy)
793    }
794}
795
796impl fmt::Debug for SemanticsScrollBy {
797    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
798        f.debug_struct("SemanticsScrollBy").finish_non_exhaustive()
799    }
800}
801
802/// Two scroll actions always read as the same action, for the reason
803/// [`SemanticsCustomAction`]'s own comparison gives.
804impl PartialEq for SemanticsScrollBy {
805    fn eq(&self, _other: &Self) -> bool {
806        true
807    }
808}
809
810impl Eq for SemanticsScrollBy {}
811
812/// What a control does when a screen reader moves its value, e.g. a VoiceOver
813/// swipe up or a TalkBack set-progress action.
814///
815/// This is Compose's `SemanticsActions.SetProgress`. The value comes in the
816/// control's own range, and the answer says whether the control took it.
817#[derive(Clone)]
818pub struct SemanticsSetProgress {
819    handler: Rc<dyn Fn(f32) -> bool>,
820}
821
822impl SemanticsSetProgress {
823    pub fn new(handler: impl Fn(f32) -> bool + 'static) -> Self {
824        Self {
825            handler: Rc::new(handler),
826        }
827    }
828
829    pub fn invoke(&self, value: f32) -> bool {
830        (self.handler)(value)
831    }
832}
833
834impl fmt::Debug for SemanticsSetProgress {
835    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
836        f.debug_struct("SemanticsSetProgress")
837            .finish_non_exhaustive()
838    }
839}
840
841/// What a text field does when a screen reader or a voice tool hands it new
842/// text. This is Compose's `SemanticsActions.SetText`; the answer says
843/// whether the field took the text.
844#[derive(Clone)]
845pub struct SemanticsSetText(Rc<dyn Fn(&str) -> bool>);
846
847impl SemanticsSetText {
848    pub fn new(handler: impl Fn(&str) -> bool + 'static) -> Self {
849        Self(Rc::new(handler))
850    }
851
852    pub fn invoke(&self, text: &str) -> bool {
853        (self.0)(text)
854    }
855}
856
857impl fmt::Debug for SemanticsSetText {
858    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
859        f.write_str("SemanticsSetText")
860    }
861}
862
863impl PartialEq for SemanticsSetText {
864    fn eq(&self, _other: &Self) -> bool {
865        true
866    }
867}
868
869/// Two set-progress actions always read as the same action, for the reason
870/// [`SemanticsCustomAction`]'s own comparison gives: the closure is rebuilt on
871/// every semantics collection, so comparing handler identity would report a
872/// changed tree on every frame.
873impl PartialEq for SemanticsSetProgress {
874    fn eq(&self, _other: &Self) -> bool {
875        true
876    }
877}
878
879impl Eq for SemanticsSetProgress {}
880
881/// How urgently a screen reader reads a node whose text changed on its own.
882///
883/// This is Compose's `LiveRegionMode` (`Modifier.semantics { liveRegion =
884/// LiveRegionMode.Polite }`). A node without it stays silent until the reader
885/// lands on it, which is wrong for a timer, a countdown, a status line, or an
886/// error that appears next to a text field: a blind user hears nothing.
887#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
888pub enum LiveRegionMode {
889    /// Read the new text once the reader finishes what it says now.
890    Polite,
891    /// Cut off what the reader says now and read the new text at once. For
892    /// text a user must hear immediately, such as an error that stops them.
893    Assertive,
894}
895
896/// A screen-reader action that is not a click, e.g. Compose's
897/// `customActions = listOf(CustomAccessibilityAction("Pause") { … })`.
898///
899/// TalkBack surfaces these through its actions menu rather than by activating
900/// the node, which is the only way to reach a command that has no on-screen
901/// control — pausing a game whose whole surface is one tap-to-launch target.
902#[derive(Clone)]
903pub struct SemanticsCustomAction {
904    /// What the screen reader reads out in its actions menu.
905    pub label: String,
906    handler: Rc<dyn Fn()>,
907}
908
909impl SemanticsCustomAction {
910    pub fn new(label: impl Into<String>, handler: impl Fn() + 'static) -> Self {
911        Self {
912            label: label.into(),
913            handler: Rc::new(handler),
914        }
915    }
916
917    pub fn invoke(&self) {
918        (self.handler)();
919    }
920}
921
922impl fmt::Debug for SemanticsCustomAction {
923    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
924        f.debug_struct("SemanticsCustomAction")
925            .field("label", &self.label)
926            .finish_non_exhaustive()
927    }
928}
929
930/// Two custom actions are the same action when they read the same.
931///
932/// The handler is deliberately excluded. A semantics recorder runs on every
933/// collection, so the closure is a fresh `Rc` each time and comparing handler
934/// identity would report "the tree changed" on every frame — which on Android
935/// means re-serialising and re-publishing the whole virtual-view tree across
936/// JNI 60 times a second. Handlers are looked up in the live semantics tree at
937/// the moment the action fires (see `perform_custom_action`), so a handler that
938/// is newer than the last published snapshot is still the one that runs.
939impl PartialEq for SemanticsCustomAction {
940    fn eq(&self, other: &Self) -> bool {
941        self.label == other.label
942    }
943}
944
945impl Eq for SemanticsCustomAction {}
946
947/// A semantics node for content that is *drawn* rather than laid out.
948///
949/// An immediate-mode surface — one `Canvas` that paints a whole screen — has
950/// exactly one layout node, so the semantics tree built from layout has exactly
951/// one node to offer a screen reader. This is the escape hatch: the drawing
952/// code already knows where it put every control, so it publishes those
953/// rectangles as semantics directly. Android's own answer for a canvas-drawn
954/// `View` is the same shape (`ExploreByTouchHelper` feeding virtual view ids
955/// into an `AccessibilityNodeProvider`), and Cranpose's Android bridge is
956/// already an `AccessibilityNodeProvider`, so these land as first-class
957/// virtual views next to the ones layout produces.
958///
959/// `bounds` is in the publishing node's own coordinates (logical px, origin at
960/// that node's top-left), because that is what a draw scope works in.
961#[derive(Clone, Debug, PartialEq)]
962pub struct CanvasSemanticsNode {
963    /// Identity that must survive a redraw.
964    ///
965    /// A screen reader parks its cursor on a virtual view id; if the id for
966    /// "the Haptics switch" changes when the list scrolls, the cursor jumps.
967    /// Derive this from what the control *is* (a row index, an enum
968    /// discriminant), never from where it currently sits.
969    pub key: u64,
970    /// Where the control was drawn, relative to the publishing node.
971    pub bounds: cranpose_ui_graphics::Rect,
972    pub label: String,
973    pub role: Option<SemanticsWidgetRole>,
974    /// Compose's `stateDescription` — what the control currently reads as
975    /// ("CAMPAIGN", "3 of 18 gold"), spoken after the label and re-spoken on
976    /// its own when only the state changed.
977    pub state_description: Option<String>,
978    /// Compose's `onClick(label = …)`. TalkBack reads it as "double tap to
979    /// `<label>`", so it is a verb phrase, not a repeat of the label.
980    pub on_click_label: Option<String>,
981    pub clickable: bool,
982    /// Compose's `selected`, for `Role.RadioButton`/`Role.Tab`.
983    pub selected: Option<bool>,
984    /// Compose's `toggleableState`, for `Role.Switch`/`Role.Checkbox`.
985    pub toggled: Option<bool>,
986    pub enabled: bool,
987    pub custom_actions: Vec<SemanticsCustomAction>,
988}
989
990impl Default for CanvasSemanticsNode {
991    fn default() -> Self {
992        Self {
993            key: 0,
994            bounds: cranpose_ui_graphics::Rect {
995                x: 0.0,
996                y: 0.0,
997                width: 0.0,
998                height: 0.0,
999            },
1000            label: String::new(),
1001            role: None,
1002            state_description: None,
1003            on_click_label: None,
1004            clickable: false,
1005            selected: None,
1006            toggled: None,
1007            enabled: true,
1008            custom_actions: Vec::new(),
1009        }
1010    }
1011}
1012
1013impl CanvasSemanticsNode {
1014    /// A clickable control drawn at `bounds`.
1015    pub fn control(key: u64, bounds: cranpose_ui_graphics::Rect, label: impl Into<String>) -> Self {
1016        Self {
1017            key,
1018            bounds,
1019            label: label.into(),
1020            clickable: true,
1021            ..Self::default()
1022        }
1023    }
1024
1025    /// A drawn label that is read but not activated.
1026    pub fn text(key: u64, bounds: cranpose_ui_graphics::Rect, label: impl Into<String>) -> Self {
1027        Self {
1028            key,
1029            bounds,
1030            label: label.into(),
1031            ..Self::default()
1032        }
1033    }
1034
1035    pub fn with_role(mut self, role: SemanticsWidgetRole) -> Self {
1036        self.role = Some(role);
1037        self
1038    }
1039
1040    pub fn with_state_description(mut self, state: impl Into<String>) -> Self {
1041        self.state_description = Some(state.into());
1042        self
1043    }
1044
1045    pub fn with_click_label(mut self, label: impl Into<String>) -> Self {
1046        self.on_click_label = Some(label.into());
1047        self.clickable = true;
1048        self
1049    }
1050
1051    pub fn with_selected(mut self, selected: bool) -> Self {
1052        self.selected = Some(selected);
1053        self
1054    }
1055
1056    pub fn with_toggled(mut self, toggled: bool) -> Self {
1057        self.toggled = Some(toggled);
1058        self
1059    }
1060
1061    pub fn with_enabled(mut self, enabled: bool) -> Self {
1062        self.enabled = enabled;
1063        self
1064    }
1065
1066    pub fn with_custom_action(mut self, action: SemanticsCustomAction) -> Self {
1067        self.custom_actions.push(action);
1068        self
1069    }
1070}
1071
1072/// Semantics configuration for accessibility.
1073#[derive(Clone, Debug, PartialEq)]
1074pub struct SemanticsConfiguration {
1075    pub content_description: Option<String>,
1076    /// Compose's `stateDescription`.
1077    pub state_description: Option<String>,
1078    /// Compose's `onClick(label = …)`; implies clickable.
1079    pub on_click_label: Option<String>,
1080    /// Compose's `Role`.
1081    pub role: Option<SemanticsWidgetRole>,
1082    pub selected: Option<bool>,
1083    pub toggled: Option<bool>,
1084    pub enabled: bool,
1085    pub is_clickable: bool,
1086    pub is_editable_text: bool,
1087    /// The text an editable field holds, read as its value. Compose's
1088    /// `editableText`.
1089    pub text: Option<String>,
1090    pub text_selection: Option<crate::text::TextRange>,
1091    pub custom_actions: Vec<SemanticsCustomAction>,
1092    /// Controls this node drew itself instead of laying out. See
1093    /// [`CanvasSemanticsNode`].
1094    pub canvas_children: Vec<CanvasSemanticsNode>,
1095    /// Whether this node takes over the screen: everything outside it is
1096    /// inert, and a screen reader keeps its traversal inside.
1097    pub is_modal: bool,
1098    /// Whether a screen reader skips this node and everything under it: a
1099    /// decorative image, or a placeholder drawn under a named field. Compose's
1100    /// `hideFromAccessibility`.
1101    pub hidden: bool,
1102    /// Whether a screen reader takes this node and the text under it as one
1103    /// stop, the way it does for a button: a row whose name, count and price
1104    /// belong together. Compose's `mergeDescendants`.
1105    pub merge_descendants: bool,
1106    /// Whether the selectable controls under this node form one group, so a
1107    /// screen reader says which of how many a tab or a radio button is.
1108    /// Compose's `selectableGroup`.
1109    pub selectable_group: bool,
1110    /// The title of the screen or pane this node is the root of, read out when
1111    /// the app moves to it. Compose's `paneTitle`.
1112    pub pane_title: Option<String>,
1113    /// Compose's `liveRegion`. When set, a screen reader reads this node again
1114    /// whenever its text changes, without the user moving to it.
1115    pub live_region: Option<LiveRegionMode>,
1116    /// Compose's `progressBarRangeInfo`. A control with it is adjustable: a
1117    /// screen reader offers its own way to move the value.
1118    pub progress: Option<ProgressBarRangeInfo>,
1119    /// What the control does when a screen reader moves its value. Compose's
1120    /// `setProgress`.
1121    pub set_progress: Option<SemanticsSetProgress>,
1122    /// What this field does when a screen reader or a voice tool hands it
1123    /// text. Compose's `setText`.
1124    pub set_text: Option<SemanticsSetText>,
1125    /// How far this container scrolled up and down. Compose's
1126    /// `verticalScrollAxisRange`.
1127    pub vertical_scroll: Option<ScrollAxisRange>,
1128    /// How far this container scrolled left and right. Compose's
1129    /// `horizontalScrollAxisRange`.
1130    pub horizontal_scroll: Option<ScrollAxisRange>,
1131    /// What this container does when a screen reader pages it. Compose's
1132    /// `scrollBy`.
1133    pub scroll_by: Option<SemanticsScrollBy>,
1134    /// How many rows and columns this list holds. Compose's `collectionInfo`.
1135    pub collection: Option<CollectionInfo>,
1136}
1137
1138impl Default for SemanticsConfiguration {
1139    fn default() -> Self {
1140        Self {
1141            content_description: None,
1142            state_description: None,
1143            on_click_label: None,
1144            role: None,
1145            selected: None,
1146            toggled: None,
1147            enabled: true,
1148            is_clickable: false,
1149            is_editable_text: false,
1150            text: None,
1151            text_selection: None,
1152            custom_actions: Vec::new(),
1153            canvas_children: Vec::new(),
1154            is_modal: false,
1155            hidden: false,
1156            merge_descendants: false,
1157            selectable_group: false,
1158            pane_title: None,
1159            live_region: None,
1160            progress: None,
1161            set_progress: None,
1162            set_text: None,
1163            vertical_scroll: None,
1164            horizontal_scroll: None,
1165            scroll_by: None,
1166            collection: None,
1167        }
1168    }
1169}
1170
1171impl SemanticsConfiguration {
1172    pub fn merge(&mut self, other: &SemanticsConfiguration) {
1173        if let Some(description) = &other.content_description {
1174            self.content_description = Some(description.clone());
1175        }
1176        if let Some(state) = &other.state_description {
1177            self.state_description = Some(state.clone());
1178        }
1179        if let Some(label) = &other.on_click_label {
1180            self.on_click_label = Some(label.clone());
1181        }
1182        if let Some(role) = other.role {
1183            self.role = Some(role);
1184        }
1185        if let Some(selected) = other.selected {
1186            self.selected = Some(selected);
1187        }
1188        if let Some(toggled) = other.toggled {
1189            self.toggled = Some(toggled);
1190        }
1191        self.enabled &= other.enabled;
1192        self.is_clickable |= other.is_clickable;
1193        self.is_editable_text |= other.is_editable_text;
1194        if let Some(text) = &other.text {
1195            self.text = Some(text.clone());
1196        }
1197        if let Some(selection) = other.text_selection {
1198            self.text_selection = Some(selection);
1199        }
1200        self.custom_actions
1201            .extend(other.custom_actions.iter().cloned());
1202        self.canvas_children
1203            .extend(other.canvas_children.iter().cloned());
1204        self.is_modal |= other.is_modal;
1205        self.hidden |= other.hidden;
1206        self.merge_descendants |= other.merge_descendants;
1207        self.selectable_group |= other.selectable_group;
1208        if let Some(title) = &other.pane_title {
1209            self.pane_title = Some(title.clone());
1210        }
1211        if let Some(live_region) = other.live_region {
1212            self.live_region = Some(live_region);
1213        }
1214        if let Some(set_progress) = &other.set_progress {
1215            self.set_progress = Some(set_progress.clone());
1216        }
1217        if let Some(set_text) = &other.set_text {
1218            self.set_text = Some(set_text.clone());
1219        }
1220        if let Some(progress) = other.progress {
1221            self.progress = Some(progress);
1222        }
1223        if let Some(range) = other.vertical_scroll {
1224            self.vertical_scroll = Some(range);
1225        }
1226        if let Some(range) = other.horizontal_scroll {
1227            self.horizontal_scroll = Some(range);
1228        }
1229        if let Some(scroll_by) = &other.scroll_by {
1230            self.scroll_by = Some(scroll_by.clone());
1231        }
1232        if let Some(collection) = other.collection {
1233            self.collection = Some(collection);
1234        }
1235    }
1236
1237    /// Whether a screen reader should offer activation. A named click label is
1238    /// how Compose declares `onClick`, so it implies the action the same way.
1239    pub fn is_activatable(&self) -> bool {
1240        self.is_clickable || self.on_click_label.is_some()
1241    }
1242}
1243
1244impl fmt::Debug for dyn ModifierNode {
1245    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1246        f.debug_struct("ModifierNode").finish_non_exhaustive()
1247    }
1248}
1249
1250impl dyn ModifierNode {
1251    pub fn as_any(&self) -> &dyn Any {
1252        self
1253    }
1254
1255    pub fn as_any_mut(&mut self) -> &mut dyn Any {
1256        self
1257    }
1258}
1259
1260/// Strongly typed modifier elements that can create and update nodes while
1261/// exposing equality/hash/inspector contracts that mirror Jetpack Compose.
1262pub trait ModifierNodeElement: fmt::Debug + Hash + PartialEq + 'static {
1263    type Node: ModifierNode;
1264
1265    /// Creates a new modifier node instance for this element.
1266    fn create(&self) -> Self::Node;
1267
1268    /// Brings an existing modifier node up to date with the element's data.
1269    fn update(&self, node: &mut Self::Node);
1270
1271    /// Optional key used to disambiguate multiple instances of the same element type.
1272    fn key(&self) -> Option<u64> {
1273        None
1274    }
1275
1276    /// Human readable name surfaced to inspector tooling.
1277    fn inspector_name(&self) -> &'static str {
1278        type_name::<Self>()
1279    }
1280
1281    /// Records inspector properties for tooling.
1282    fn inspector_properties(&self, _inspector: &mut dyn FnMut(&'static str, String)) {}
1283
1284    /// Returns the capabilities of nodes created by this element.
1285    /// Override this to indicate which specialized traits the node implements.
1286    fn capabilities(&self) -> NodeCapabilities {
1287        NodeCapabilities::default()
1288    }
1289
1290    /// Whether this element requires `update` to be called even if `eq` returns true.
1291    ///
1292    /// This is useful for elements that ignore certain fields in `eq` (e.g. closures)
1293    /// to allow node reuse, but still need those fields updated in the existing node.
1294    /// Defaults to `false`.
1295    fn always_update(&self) -> bool {
1296        false
1297    }
1298
1299    /// Whether modifier reconciliation should request capability-wide invalidations
1300    /// after updating an existing node.
1301    fn auto_invalidate_on_update(&self) -> bool {
1302        true
1303    }
1304
1305    /// Optional targeted invalidation requested after updating an existing node.
1306    ///
1307    /// This is for nodes whose attach/remove capability is broader than the
1308    /// work needed for a value-only update. For example, an offset node
1309    /// participates in layout on attach but an x/y change only needs placement
1310    /// data and draw output refreshed.
1311    fn update_invalidation_kind(&self) -> Option<InvalidationKind> {
1312        None
1313    }
1314}
1315
1316/// Capability flags indicating which specialized traits a modifier node implements.
1317#[derive(Clone, Copy, PartialEq, Eq, Hash)]
1318pub struct NodeCapabilities(u32);
1319
1320impl NodeCapabilities {
1321    /// No capabilities.
1322    pub const NONE: Self = Self(0);
1323    /// Modifier participates in measure/layout.
1324    pub const LAYOUT: Self = Self(1 << 0);
1325    /// Modifier participates in draw.
1326    pub const DRAW: Self = Self(1 << 1);
1327    /// Modifier participates in pointer input.
1328    pub const POINTER_INPUT: Self = Self(1 << 2);
1329    /// Modifier participates in semantics tree construction.
1330    pub const SEMANTICS: Self = Self(1 << 3);
1331    /// Modifier participates in modifier locals.
1332    pub const MODIFIER_LOCALS: Self = Self(1 << 4);
1333    /// Modifier participates in focus management.
1334    pub const FOCUS: Self = Self(1 << 5);
1335
1336    /// Returns an empty capability set.
1337    pub const fn empty() -> Self {
1338        Self::NONE
1339    }
1340
1341    /// Returns whether all bits in `other` are present in `self`.
1342    pub const fn contains(self, other: Self) -> bool {
1343        (self.0 & other.0) == other.0
1344    }
1345
1346    /// Returns whether any bit in `other` is present in `self`.
1347    pub const fn intersects(self, other: Self) -> bool {
1348        (self.0 & other.0) != 0
1349    }
1350
1351    /// Inserts the requested capability bits.
1352    pub fn insert(&mut self, other: Self) {
1353        self.0 |= other.0;
1354    }
1355
1356    /// Returns the raw bit representation.
1357    pub const fn bits(self) -> u32 {
1358        self.0
1359    }
1360
1361    /// Returns true when no capabilities are set.
1362    pub const fn is_empty(self) -> bool {
1363        self.0 == 0
1364    }
1365
1366    /// Returns the capability bit mask required for the given invalidation.
1367    pub const fn for_invalidation(kind: InvalidationKind) -> Self {
1368        match kind {
1369            InvalidationKind::Layout => Self::LAYOUT,
1370            InvalidationKind::Draw => Self::DRAW,
1371            InvalidationKind::PointerInput => Self::POINTER_INPUT,
1372            InvalidationKind::Semantics => Self::SEMANTICS,
1373            InvalidationKind::Focus => Self::FOCUS,
1374        }
1375    }
1376}
1377
1378impl Default for NodeCapabilities {
1379    fn default() -> Self {
1380        Self::NONE
1381    }
1382}
1383
1384impl fmt::Debug for NodeCapabilities {
1385    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1386        f.debug_struct("NodeCapabilities")
1387            .field("layout", &self.contains(Self::LAYOUT))
1388            .field("draw", &self.contains(Self::DRAW))
1389            .field("pointer_input", &self.contains(Self::POINTER_INPUT))
1390            .field("semantics", &self.contains(Self::SEMANTICS))
1391            .field("modifier_locals", &self.contains(Self::MODIFIER_LOCALS))
1392            .field("focus", &self.contains(Self::FOCUS))
1393            .finish()
1394    }
1395}
1396
1397impl BitOr for NodeCapabilities {
1398    type Output = Self;
1399
1400    fn bitor(self, rhs: Self) -> Self::Output {
1401        Self(self.0 | rhs.0)
1402    }
1403}
1404
1405impl BitOrAssign for NodeCapabilities {
1406    fn bitor_assign(&mut self, rhs: Self) {
1407        self.0 |= rhs.0;
1408    }
1409}
1410
1411/// Records an invalidation request together with the capability mask that triggered it.
1412#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1413pub struct ModifierInvalidation {
1414    kind: InvalidationKind,
1415    capabilities: NodeCapabilities,
1416}
1417
1418impl ModifierInvalidation {
1419    /// Creates a new modifier invalidation entry.
1420    pub const fn new(kind: InvalidationKind, capabilities: NodeCapabilities) -> Self {
1421        Self { kind, capabilities }
1422    }
1423
1424    /// Returns the invalidated pipeline kind.
1425    pub const fn kind(self) -> InvalidationKind {
1426        self.kind
1427    }
1428
1429    /// Returns the capability mask associated with the invalidation.
1430    pub const fn capabilities(self) -> NodeCapabilities {
1431        self.capabilities
1432    }
1433}
1434
1435/// Type-erased modifier element used by the runtime to reconcile chains.
1436pub trait AnyModifierElement: fmt::Debug {
1437    fn node_type(&self) -> TypeId;
1438
1439    fn element_type(&self) -> TypeId;
1440
1441    fn create_node(&self) -> Box<dyn ModifierNode>;
1442
1443    fn can_update_node(&self, node: &dyn ModifierNode) -> bool;
1444
1445    fn update_node(&self, node: &mut dyn ModifierNode);
1446
1447    fn key(&self) -> Option<u64>;
1448
1449    fn capabilities(&self) -> NodeCapabilities {
1450        NodeCapabilities::default()
1451    }
1452
1453    fn hash_code(&self) -> u64;
1454
1455    fn equals_element(&self, other: &dyn AnyModifierElement) -> bool;
1456
1457    fn inspector_name(&self) -> &'static str;
1458
1459    fn record_inspector_properties(&self, visitor: &mut dyn FnMut(&'static str, String));
1460
1461    fn requires_update(&self) -> bool;
1462
1463    fn auto_invalidates_on_update(&self) -> bool;
1464
1465    fn update_invalidation_kind(&self) -> Option<InvalidationKind>;
1466
1467    fn as_any(&self) -> &dyn Any;
1468}
1469
1470struct TypedModifierElement<E: ModifierNodeElement> {
1471    element: E,
1472    cached_hash: u64,
1473}
1474
1475impl<E: ModifierNodeElement> TypedModifierElement<E> {
1476    fn new(element: E) -> Self {
1477        let mut hasher = default::new();
1478        element.hash(&mut hasher);
1479        Self {
1480            element,
1481            cached_hash: hasher.finish(),
1482        }
1483    }
1484}
1485
1486impl<E> fmt::Debug for TypedModifierElement<E>
1487where
1488    E: ModifierNodeElement,
1489{
1490    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1491        f.debug_struct("TypedModifierElement")
1492            .field("type", &type_name::<E>())
1493            .finish()
1494    }
1495}
1496
1497impl<E> AnyModifierElement for TypedModifierElement<E>
1498where
1499    E: ModifierNodeElement,
1500{
1501    fn node_type(&self) -> TypeId {
1502        TypeId::of::<E::Node>()
1503    }
1504
1505    fn element_type(&self) -> TypeId {
1506        TypeId::of::<E>()
1507    }
1508
1509    fn create_node(&self) -> Box<dyn ModifierNode> {
1510        Box::new(self.element.create())
1511    }
1512
1513    fn can_update_node(&self, node: &dyn ModifierNode) -> bool {
1514        node.as_any().is::<E::Node>()
1515    }
1516
1517    fn update_node(&self, node: &mut dyn ModifierNode) {
1518        if let Some(typed) = node.as_any_mut().downcast_mut::<E::Node>() {
1519            self.element.update(typed);
1520        }
1521    }
1522
1523    fn key(&self) -> Option<u64> {
1524        self.element.key()
1525    }
1526
1527    fn capabilities(&self) -> NodeCapabilities {
1528        self.element.capabilities()
1529    }
1530
1531    fn hash_code(&self) -> u64 {
1532        self.cached_hash
1533    }
1534
1535    fn equals_element(&self, other: &dyn AnyModifierElement) -> bool {
1536        other
1537            .as_any()
1538            .downcast_ref::<Self>()
1539            .map(|typed| typed.element == self.element)
1540            .unwrap_or(false)
1541    }
1542
1543    fn inspector_name(&self) -> &'static str {
1544        self.element.inspector_name()
1545    }
1546
1547    fn record_inspector_properties(&self, visitor: &mut dyn FnMut(&'static str, String)) {
1548        self.element.inspector_properties(visitor);
1549    }
1550
1551    fn requires_update(&self) -> bool {
1552        self.element.always_update()
1553    }
1554
1555    fn auto_invalidates_on_update(&self) -> bool {
1556        self.element.auto_invalidate_on_update()
1557    }
1558
1559    fn update_invalidation_kind(&self) -> Option<InvalidationKind> {
1560        self.element.update_invalidation_kind()
1561    }
1562
1563    fn as_any(&self) -> &dyn Any {
1564        self
1565    }
1566}
1567
1568fn request_update_auto_invalidations(
1569    element: &dyn AnyModifierElement,
1570    context: &mut dyn ModifierNodeContext,
1571    capabilities: NodeCapabilities,
1572) {
1573    if let Some(kind) = element.update_invalidation_kind() {
1574        let capabilities = NodeCapabilities::for_invalidation(kind);
1575        context.push_active_capabilities(capabilities);
1576        context.invalidate(kind);
1577        context.pop_active_capabilities();
1578    } else if element.auto_invalidates_on_update() {
1579        request_auto_invalidations(context, capabilities);
1580    }
1581}
1582
1583/// Convenience helper for callers to construct a type-erased modifier
1584/// element without having to mention the internal wrapper type.
1585pub fn modifier_element<E: ModifierNodeElement>(element: E) -> DynModifierElement {
1586    Rc::new(TypedModifierElement::new(element))
1587}
1588
1589/// Boxed type-erased modifier element.
1590pub type DynModifierElement = Rc<dyn AnyModifierElement>;
1591
1592#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1593enum TraversalDirection {
1594    Forward,
1595    Backward,
1596}
1597
1598/// Iterator walking a modifier chain by indexing into `ordered_nodes`.
1599///
1600/// This avoids the per-step `RefCell::borrow()` + `NodeLink::clone()` cost
1601/// of following the linked-list through `NodeState::child`/`parent`.
1602pub struct ModifierChainIter<'a> {
1603    chain: &'a ModifierNodeChain,
1604    cursor: usize,
1605    remaining: usize,
1606    direction: TraversalDirection,
1607}
1608
1609impl<'a> ModifierChainIter<'a> {
1610    fn forward(chain: &'a ModifierNodeChain) -> Self {
1611        Self {
1612            chain,
1613            cursor: 0,
1614            remaining: chain.ordered_nodes.len(),
1615            direction: TraversalDirection::Forward,
1616        }
1617    }
1618
1619    fn backward(chain: &'a ModifierNodeChain) -> Self {
1620        let len = chain.ordered_nodes.len();
1621        Self {
1622            chain,
1623            cursor: len.wrapping_sub(1),
1624            remaining: len,
1625            direction: TraversalDirection::Backward,
1626        }
1627    }
1628}
1629
1630impl<'a> Iterator for ModifierChainIter<'a> {
1631    type Item = ModifierChainNodeRef<'a>;
1632
1633    #[inline]
1634    fn next(&mut self) -> Option<Self::Item> {
1635        if self.remaining == 0 {
1636            return None;
1637        }
1638        let (link, caps, agg) = self.chain.ordered_nodes[self.cursor];
1639        let node_ref = self.chain.make_node_ref_with_caps(link, caps, agg);
1640        self.remaining -= 1;
1641        match self.direction {
1642            TraversalDirection::Forward => self.cursor += 1,
1643            TraversalDirection::Backward => self.cursor = self.cursor.wrapping_sub(1),
1644        }
1645        Some(node_ref)
1646    }
1647
1648    #[inline]
1649    fn size_hint(&self) -> (usize, Option<usize>) {
1650        (self.remaining, Some(self.remaining))
1651    }
1652}
1653
1654impl<'a> ExactSizeIterator for ModifierChainIter<'a> {}
1655impl<'a> std::iter::FusedIterator for ModifierChainIter<'a> {}
1656
1657#[derive(Debug)]
1658struct ModifierNodeEntry {
1659    element_type: TypeId,
1660    node_type: TypeId,
1661    key: Option<u64>,
1662    hash_code: u64,
1663    element: DynModifierElement,
1664    node: Rc<RefCell<Box<dyn ModifierNode>>>,
1665    capabilities: NodeCapabilities,
1666}
1667
1668impl ModifierNodeEntry {
1669    fn new(
1670        element_type: TypeId,
1671        node_type: TypeId,
1672        key: Option<u64>,
1673        element: DynModifierElement,
1674        node: Box<dyn ModifierNode>,
1675        hash_code: u64,
1676        capabilities: NodeCapabilities,
1677    ) -> Self {
1678        let node_rc = Rc::new(RefCell::new(node));
1679        let entry = Self {
1680            element_type,
1681            node_type,
1682            key,
1683            hash_code,
1684            element,
1685            node: Rc::clone(&node_rc),
1686            capabilities,
1687        };
1688        entry
1689            .node
1690            .borrow()
1691            .node_state()
1692            .set_capabilities(entry.capabilities);
1693        entry
1694    }
1695}
1696
1697fn visit_node_tree_mut(
1698    node: &mut dyn ModifierNode,
1699    visitor: &mut dyn FnMut(&mut dyn ModifierNode),
1700) {
1701    visitor(node);
1702    node.for_each_delegate_mut(&mut |child| visit_node_tree_mut(child, visitor));
1703}
1704
1705fn nth_delegate(node: &dyn ModifierNode, target: usize) -> Option<&dyn ModifierNode> {
1706    let mut current = 0usize;
1707    let mut result: Option<&dyn ModifierNode> = None;
1708    node.for_each_delegate(&mut |child| {
1709        if result.is_none() && current == target {
1710            result = Some(child);
1711        }
1712        current += 1;
1713    });
1714    result
1715}
1716
1717fn nth_delegate_mut(node: &mut dyn ModifierNode, target: usize) -> Option<&mut dyn ModifierNode> {
1718    let mut current = 0usize;
1719    let mut result: Option<&mut dyn ModifierNode> = None;
1720    node.for_each_delegate_mut(&mut |child| {
1721        if result.is_none() && current == target {
1722            result = Some(child);
1723        }
1724        current += 1;
1725    });
1726    result
1727}
1728
1729fn with_node_context<F, R>(
1730    node: &mut dyn ModifierNode,
1731    context: &mut dyn ModifierNodeContext,
1732    f: F,
1733) -> R
1734where
1735    F: FnOnce(&mut dyn ModifierNode, &mut dyn ModifierNodeContext) -> R,
1736{
1737    context.push_active_capabilities(node.node_state().capabilities());
1738    let result = f(node, context);
1739    context.pop_active_capabilities();
1740    result
1741}
1742
1743fn request_auto_invalidations(
1744    context: &mut dyn ModifierNodeContext,
1745    capabilities: NodeCapabilities,
1746) {
1747    if capabilities.is_empty() {
1748        return;
1749    }
1750
1751    context.push_active_capabilities(capabilities);
1752
1753    if capabilities.contains(NodeCapabilities::LAYOUT) {
1754        context.invalidate(InvalidationKind::Layout);
1755    }
1756    if capabilities.contains(NodeCapabilities::DRAW) {
1757        context.invalidate(InvalidationKind::Draw);
1758    }
1759    if capabilities.contains(NodeCapabilities::POINTER_INPUT) {
1760        context.invalidate(InvalidationKind::PointerInput);
1761    }
1762    if capabilities.contains(NodeCapabilities::SEMANTICS) {
1763        context.invalidate(InvalidationKind::Semantics);
1764    }
1765    if capabilities.contains(NodeCapabilities::FOCUS) {
1766        context.invalidate(InvalidationKind::Focus);
1767    }
1768
1769    context.pop_active_capabilities();
1770}
1771
1772/// Attaches a node tree by calling on_attach for all unattached nodes.
1773///
1774/// # Safety
1775/// Callers must ensure no immutable RefCell borrows are held on the node
1776/// when calling this function. The on_attach callback may trigger mutations
1777/// (invalidations, state updates, etc.) that require mutable access, which
1778/// would panic if an immutable borrow is held across the call.
1779fn attach_node_tree(node: &mut dyn ModifierNode, context: &mut dyn ModifierNodeContext) {
1780    visit_node_tree_mut(node, &mut |n| {
1781        if !n.node_state().is_attached() {
1782            n.node_state().set_attached(true);
1783            with_node_context(n, context, |node, ctx| node.on_attach(ctx));
1784        }
1785    });
1786}
1787
1788fn reset_node_tree(node: &mut dyn ModifierNode) {
1789    visit_node_tree_mut(node, &mut |n| n.on_reset());
1790}
1791
1792fn detach_node_tree(node: &mut dyn ModifierNode) {
1793    visit_node_tree_mut(node, &mut |n| {
1794        if n.node_state().is_attached() {
1795            n.on_detach();
1796            n.node_state().set_attached(false);
1797        }
1798        n.node_state().set_parent_link(None);
1799        n.node_state().set_child_link(None);
1800        n.node_state()
1801            .set_aggregate_child_capabilities(NodeCapabilities::empty());
1802    });
1803}
1804
1805/// Chain of modifier nodes attached to a layout node.
1806///
1807/// The chain tracks ownership of modifier nodes and reuses them across
1808/// updates when the incoming element list still contains a node of the
1809/// same type. Removed nodes detach automatically so callers do not need
1810/// to manually manage their lifetimes.
1811pub struct ModifierNodeChain {
1812    entries: Vec<ModifierNodeEntry>,
1813    aggregated_capabilities: NodeCapabilities,
1814    head_aggregate_child_capabilities: NodeCapabilities,
1815    head_sentinel: Box<SentinelNode>,
1816    tail_sentinel: Box<SentinelNode>,
1817    ordered_nodes: Vec<(NodeLink, NodeCapabilities, NodeCapabilities)>,
1818    scratch_old_used: Vec<bool>,
1819    scratch_match_order: Vec<Option<usize>>,
1820    scratch_final_slots: Vec<Option<ModifierNodeEntry>>,
1821    scratch_elements: Vec<DynModifierElement>,
1822}
1823
1824struct SentinelNode {
1825    state: NodeState,
1826}
1827
1828impl SentinelNode {
1829    fn new() -> Self {
1830        Self {
1831            state: NodeState::sentinel(),
1832        }
1833    }
1834}
1835
1836impl DelegatableNode for SentinelNode {
1837    fn node_state(&self) -> &NodeState {
1838        &self.state
1839    }
1840}
1841
1842impl ModifierNode for SentinelNode {}
1843
1844#[derive(Clone)]
1845pub struct ModifierChainNodeRef<'a> {
1846    chain: &'a ModifierNodeChain,
1847    link: NodeLink,
1848    cached_capabilities: Option<NodeCapabilities>,
1849    cached_aggregate_child: Option<NodeCapabilities>,
1850}
1851
1852impl Default for ModifierNodeChain {
1853    fn default() -> Self {
1854        Self::new()
1855    }
1856}
1857
1858/// Index structure for O(1) modifier entry lookups during update.
1859///
1860/// This avoids O(n²) complexity by pre-building hash maps that allow constant-time
1861/// lookups for matching entries by key, hash, or type.
1862struct EntryIndex {
1863    keyed: HashMap<(TypeId, TypeId, u64), Vec<usize>>,
1864    hashed: HashMap<(TypeId, TypeId, u64), Vec<usize>>,
1865    typed: HashMap<(TypeId, TypeId), Vec<usize>>,
1866}
1867
1868struct EntryMatchQuery<'a> {
1869    element_type: TypeId,
1870    node_type: TypeId,
1871    key: Option<u64>,
1872    hash_code: u64,
1873    element: &'a DynModifierElement,
1874}
1875
1876impl EntryIndex {
1877    fn build(entries: &[ModifierNodeEntry]) -> Self {
1878        let mut keyed = HashMap::default();
1879        let mut hashed = HashMap::default();
1880        let mut typed = HashMap::default();
1881
1882        for (i, entry) in entries.iter().enumerate() {
1883            if let Some(key_value) = entry.key {
1884                keyed
1885                    .entry((entry.element_type, entry.node_type, key_value))
1886                    .or_insert_with(Vec::new)
1887                    .push(i);
1888            } else {
1889                hashed
1890                    .entry((entry.element_type, entry.node_type, entry.hash_code))
1891                    .or_insert_with(Vec::new)
1892                    .push(i);
1893                typed
1894                    .entry((entry.element_type, entry.node_type))
1895                    .or_insert_with(Vec::new)
1896                    .push(i);
1897            }
1898        }
1899
1900        Self {
1901            keyed,
1902            hashed,
1903            typed,
1904        }
1905    }
1906
1907    fn find_match(
1908        &self,
1909        entries: &[ModifierNodeEntry],
1910        used: &[bool],
1911        query: EntryMatchQuery<'_>,
1912    ) -> Option<usize> {
1913        if let Some(key_value) = query.key {
1914            if let Some(candidates) =
1915                self.keyed
1916                    .get(&(query.element_type, query.node_type, key_value))
1917            {
1918                for &i in candidates {
1919                    if !used[i] {
1920                        return Some(i);
1921                    }
1922                }
1923            }
1924        } else {
1925            if let Some(candidates) =
1926                self.hashed
1927                    .get(&(query.element_type, query.node_type, query.hash_code))
1928            {
1929                for &i in candidates {
1930                    if !used[i]
1931                        && entries[i]
1932                            .element
1933                            .as_ref()
1934                            .equals_element(query.element.as_ref())
1935                    {
1936                        return Some(i);
1937                    }
1938                }
1939            }
1940
1941            if let Some(candidates) = self.typed.get(&(query.element_type, query.node_type)) {
1942                for &i in candidates {
1943                    if !used[i] {
1944                        return Some(i);
1945                    }
1946                }
1947            }
1948        }
1949
1950        None
1951    }
1952}
1953
1954impl ModifierNodeChain {
1955    pub fn new() -> Self {
1956        let mut chain = Self {
1957            entries: Vec::new(),
1958            aggregated_capabilities: NodeCapabilities::empty(),
1959            head_aggregate_child_capabilities: NodeCapabilities::empty(),
1960            head_sentinel: Box::new(SentinelNode::new()),
1961            tail_sentinel: Box::new(SentinelNode::new()),
1962            ordered_nodes: Vec::new(),
1963            scratch_old_used: Vec::new(),
1964            scratch_match_order: Vec::new(),
1965            scratch_final_slots: Vec::new(),
1966            scratch_elements: Vec::new(),
1967        };
1968        chain.sync_chain_links();
1969        chain
1970    }
1971
1972    /// Detaches all nodes in the chain.
1973    pub fn detach_nodes(&mut self) {
1974        for entry in &self.entries {
1975            detach_node_tree(&mut **entry.node.borrow_mut());
1976        }
1977    }
1978
1979    /// Attaches all nodes in the chain.
1980    pub fn attach_nodes(&mut self, context: &mut dyn ModifierNodeContext) {
1981        for entry in &self.entries {
1982            attach_node_tree(&mut **entry.node.borrow_mut(), context);
1983        }
1984    }
1985
1986    /// Rebuilds the internal chain links (parent/child relationships).
1987    /// This should be called if nodes have been detached but are intended to be reused.
1988    pub fn repair_chain(&mut self) {
1989        self.sync_chain_links();
1990    }
1991
1992    /// Reconcile the chain against the provided elements, attaching newly
1993    /// created nodes and detaching nodes that are no longer required.
1994    ///
1995    /// This method delegates to `update_from_ref_iter` which handles the
1996    /// actual reconciliation logic.
1997    pub fn update_from_slice(
1998        &mut self,
1999        elements: &[DynModifierElement],
2000        context: &mut dyn ModifierNodeContext,
2001    ) {
2002        self.update_from_ref_iter(elements.iter(), context);
2003    }
2004
2005    /// Reconcile the chain against the provided iterator of element references.
2006    ///
2007    /// This is the preferred method as it avoids requiring a collected slice,
2008    /// enabling zero-allocation traversal of modifier trees.
2009    pub fn update_from_ref_iter<'a, I>(
2010        &mut self,
2011        elements: I,
2012        context: &mut dyn ModifierNodeContext,
2013    ) where
2014        I: Iterator<Item = &'a DynModifierElement>,
2015    {
2016        let old_len = self.entries.len();
2017        let mut fast_path_failed_at: Option<usize> = None;
2018        let mut elements_count = 0;
2019
2020        self.scratch_elements.clear();
2021
2022        for (idx, element) in elements.enumerate() {
2023            elements_count = idx + 1;
2024
2025            if fast_path_failed_at.is_none() && idx < old_len {
2026                let entry = &mut self.entries[idx];
2027                let same_type = entry.element_type == element.element_type();
2028                let same_node_type = entry.node_type == element.node_type();
2029                let same_key = entry.key == element.key();
2030                let same_hash = entry.hash_code == element.hash_code();
2031
2032                let positional_update = element.requires_update();
2033                if same_type && same_node_type && same_key && (same_hash || positional_update) {
2034                    let can_update_node = {
2035                        let node_borrow = entry.node.borrow();
2036                        element.can_update_node(&**node_borrow)
2037                    };
2038                    if !can_update_node {
2039                        fast_path_failed_at = Some(idx);
2040                        self.scratch_elements.push(element.clone());
2041                        continue;
2042                    }
2043
2044                    let same_element = entry.element.as_ref().equals_element(element.as_ref());
2045                    let capabilities = element.capabilities();
2046
2047                    {
2048                        let node_borrow = entry.node.borrow();
2049                        if !node_borrow.node_state().is_attached() {
2050                            drop(node_borrow);
2051                            attach_node_tree(&mut **entry.node.borrow_mut(), context);
2052                        }
2053                    }
2054
2055                    let needs_update = !same_element || element.requires_update();
2056                    if needs_update {
2057                        element.update_node(&mut **entry.node.borrow_mut());
2058                        entry.element = element.clone();
2059                        entry.hash_code = element.hash_code();
2060                        request_update_auto_invalidations(element.as_ref(), context, capabilities);
2061                    }
2062
2063                    entry.capabilities = capabilities;
2064                    entry
2065                        .node
2066                        .borrow()
2067                        .node_state()
2068                        .set_capabilities(capabilities);
2069                    continue;
2070                }
2071                fast_path_failed_at = Some(idx);
2072            }
2073
2074            self.scratch_elements.push(element.clone());
2075        }
2076
2077        if fast_path_failed_at.is_none() && self.scratch_elements.is_empty() {
2078            if elements_count < self.entries.len() {
2079                for entry in self.entries.drain(elements_count..) {
2080                    request_auto_invalidations(context, entry.capabilities);
2081                    detach_node_tree(&mut **entry.node.borrow_mut());
2082                }
2083            }
2084            self.sync_chain_links();
2085            return;
2086        }
2087
2088        let fail_idx = fast_path_failed_at.unwrap_or(old_len);
2089
2090        let mut old_entries: Vec<ModifierNodeEntry> = self.entries.drain(fail_idx..).collect();
2091        let processed_entries_len = self.entries.len();
2092        let old_len = old_entries.len();
2093
2094        self.scratch_old_used.clear();
2095        self.scratch_old_used.resize(old_len, false);
2096
2097        self.scratch_match_order.clear();
2098        self.scratch_match_order.resize(old_len, None);
2099
2100        let index = EntryIndex::build(&old_entries);
2101
2102        let new_elements_count = self.scratch_elements.len();
2103        self.scratch_final_slots.clear();
2104        self.scratch_final_slots.reserve(new_elements_count);
2105
2106        for (new_pos, element) in self.scratch_elements.drain(..).enumerate() {
2107            self.scratch_final_slots.push(None);
2108            let element_type = element.element_type();
2109            let node_type = element.node_type();
2110            let key = element.key();
2111            let hash_code = element.hash_code();
2112            let capabilities = element.capabilities();
2113
2114            let matched_idx = index.find_match(
2115                &old_entries,
2116                &self.scratch_old_used,
2117                EntryMatchQuery {
2118                    element_type,
2119                    node_type,
2120                    key,
2121                    hash_code,
2122                    element: &element,
2123                },
2124            );
2125
2126            if let Some(idx) = matched_idx {
2127                let entry = &mut old_entries[idx];
2128                let can_update_node = {
2129                    let node_borrow = entry.node.borrow();
2130                    element.can_update_node(&**node_borrow)
2131                };
2132                if !can_update_node {
2133                    let replacement = ModifierNodeEntry::new(
2134                        element_type,
2135                        node_type,
2136                        key,
2137                        element.clone(),
2138                        element.create_node(),
2139                        hash_code,
2140                        capabilities,
2141                    );
2142                    attach_node_tree(&mut **replacement.node.borrow_mut(), context);
2143                    element.update_node(&mut **replacement.node.borrow_mut());
2144                    request_auto_invalidations(context, capabilities);
2145                    self.scratch_final_slots[new_pos] = Some(replacement);
2146                    continue;
2147                }
2148
2149                self.scratch_old_used[idx] = true;
2150                self.scratch_match_order[idx] = Some(new_pos);
2151                let moved = idx != new_pos;
2152
2153                let same_element = entry.element.as_ref().equals_element(element.as_ref());
2154
2155                {
2156                    let node_borrow = entry.node.borrow();
2157                    if !node_borrow.node_state().is_attached() {
2158                        drop(node_borrow);
2159                        attach_node_tree(&mut **entry.node.borrow_mut(), context);
2160                    }
2161                }
2162
2163                let needs_update = !same_element || element.requires_update();
2164                if needs_update {
2165                    element.update_node(&mut **entry.node.borrow_mut());
2166                    entry.element = element;
2167                    entry.hash_code = hash_code;
2168                    request_update_auto_invalidations(
2169                        entry.element.as_ref(),
2170                        context,
2171                        capabilities,
2172                    );
2173                }
2174                if moved {
2175                    request_auto_invalidations(context, capabilities);
2176                }
2177
2178                entry.key = key;
2179                entry.element_type = element_type;
2180                entry.node_type = node_type;
2181                entry.capabilities = capabilities;
2182                entry
2183                    .node
2184                    .borrow()
2185                    .node_state()
2186                    .set_capabilities(capabilities);
2187            } else {
2188                let entry = ModifierNodeEntry::new(
2189                    element_type,
2190                    node_type,
2191                    key,
2192                    element.clone(),
2193                    element.create_node(),
2194                    hash_code,
2195                    capabilities,
2196                );
2197                attach_node_tree(&mut **entry.node.borrow_mut(), context);
2198                element.update_node(&mut **entry.node.borrow_mut());
2199                request_auto_invalidations(context, capabilities);
2200                self.scratch_final_slots[new_pos] = Some(entry);
2201            }
2202        }
2203
2204        for (i, entry) in old_entries.into_iter().enumerate() {
2205            if self.scratch_old_used[i] {
2206                if let Some(pos) = self.scratch_match_order[i] {
2207                    self.scratch_final_slots[pos] = Some(entry);
2208                } else {
2209                    request_auto_invalidations(context, entry.capabilities);
2210                    detach_node_tree(&mut **entry.node.borrow_mut());
2211                }
2212            } else {
2213                request_auto_invalidations(context, entry.capabilities);
2214                detach_node_tree(&mut **entry.node.borrow_mut());
2215            }
2216        }
2217
2218        self.entries.reserve(self.scratch_final_slots.len());
2219        for slot in self.scratch_final_slots.drain(..) {
2220            if let Some(entry) = slot {
2221                self.entries.push(entry);
2222            } else {
2223                log::error!("modifier reconciliation produced an empty final slot");
2224            }
2225        }
2226
2227        debug_assert_eq!(
2228            self.entries.len(),
2229            processed_entries_len + new_elements_count
2230        );
2231        self.sync_chain_links();
2232    }
2233
2234    /// Convenience wrapper that accepts any iterator of type-erased
2235    /// modifier elements. Elements are collected into a temporary vector
2236    /// before reconciliation.
2237    pub fn update<I>(&mut self, elements: I, context: &mut dyn ModifierNodeContext)
2238    where
2239        I: IntoIterator<Item = DynModifierElement>,
2240    {
2241        let collected: Vec<DynModifierElement> = elements.into_iter().collect();
2242        self.update_from_slice(&collected, context);
2243    }
2244
2245    /// Resets all nodes in the chain. This mirrors the behaviour of
2246    /// Jetpack Compose's `onReset` callback.
2247    pub fn reset(&mut self) {
2248        for entry in &mut self.entries {
2249            reset_node_tree(&mut **entry.node.borrow_mut());
2250        }
2251    }
2252
2253    /// Detaches every node in the chain and clears internal storage.
2254    pub fn detach_all(&mut self) {
2255        for entry in std::mem::take(&mut self.entries) {
2256            detach_node_tree(&mut **entry.node.borrow_mut());
2257            {
2258                let node_borrow = entry.node.borrow();
2259                let state = node_borrow.node_state();
2260                state.set_capabilities(NodeCapabilities::empty());
2261            }
2262        }
2263        self.aggregated_capabilities = NodeCapabilities::empty();
2264        self.head_aggregate_child_capabilities = NodeCapabilities::empty();
2265        self.ordered_nodes.clear();
2266        self.sync_chain_links();
2267    }
2268
2269    pub fn len(&self) -> usize {
2270        self.entries.len()
2271    }
2272
2273    pub fn is_empty(&self) -> bool {
2274        self.entries.is_empty()
2275    }
2276
2277    /// Returns the aggregated capability mask for the entire chain.
2278    pub fn capabilities(&self) -> NodeCapabilities {
2279        self.aggregated_capabilities
2280    }
2281
2282    /// Returns true if the chain contains at least one node with the requested capability.
2283    pub fn has_capability(&self, capability: NodeCapabilities) -> bool {
2284        self.aggregated_capabilities.contains(capability)
2285    }
2286
2287    /// Returns the sentinel head reference for traversal.
2288    pub fn head(&self) -> ModifierChainNodeRef<'_> {
2289        self.make_node_ref(NodeLink::Head)
2290    }
2291
2292    /// Returns the sentinel tail reference for traversal.
2293    pub fn tail(&self) -> ModifierChainNodeRef<'_> {
2294        self.make_node_ref(NodeLink::Tail)
2295    }
2296
2297    /// Iterates over the chain from head to tail, skipping sentinels.
2298    pub fn head_to_tail(&self) -> ModifierChainIter<'_> {
2299        ModifierChainIter::forward(self)
2300    }
2301
2302    /// Iterates over the chain from tail to head, skipping sentinels.
2303    pub fn tail_to_head(&self) -> ModifierChainIter<'_> {
2304        ModifierChainIter::backward(self)
2305    }
2306
2307    /// Calls `f` for every node in insertion order.
2308    pub fn for_each_forward<F>(&self, mut f: F)
2309    where
2310        F: FnMut(ModifierChainNodeRef<'_>),
2311    {
2312        for node in self.head_to_tail() {
2313            f(node);
2314        }
2315    }
2316
2317    /// Calls `f` for every node containing any capability from `mask`.
2318    pub fn for_each_forward_matching<F>(&self, mask: NodeCapabilities, mut f: F)
2319    where
2320        F: FnMut(ModifierChainNodeRef<'_>),
2321    {
2322        if mask.is_empty() {
2323            self.for_each_forward(f);
2324            return;
2325        }
2326
2327        if !self.head().aggregate_child_capabilities().intersects(mask) {
2328            return;
2329        }
2330
2331        for node in self.head_to_tail() {
2332            if node.kind_set().intersects(mask) {
2333                f(node);
2334            }
2335        }
2336    }
2337
2338    /// Calls `f` for every node containing any capability from `mask`, providing the node ref.
2339    pub fn for_each_node_with_capability<F>(&self, mask: NodeCapabilities, mut f: F)
2340    where
2341        F: FnMut(ModifierChainNodeRef<'_>, &dyn ModifierNode),
2342    {
2343        self.for_each_forward_matching(mask, |node_ref| {
2344            node_ref.with_node(|node| f(node_ref.clone(), node));
2345        });
2346    }
2347
2348    /// Calls `f` for every node in reverse insertion order.
2349    pub fn for_each_backward<F>(&self, mut f: F)
2350    where
2351        F: FnMut(ModifierChainNodeRef<'_>),
2352    {
2353        for node in self.tail_to_head() {
2354            f(node);
2355        }
2356    }
2357
2358    /// Returns the node reference that owns `node`.
2359    pub fn find_node_ref(&self, node: &dyn ModifierNode) -> Option<ModifierChainNodeRef<'_>> {
2360        fn node_data_ptr(node: &dyn ModifierNode) -> *const () {
2361            node as *const dyn ModifierNode as *const ()
2362        }
2363
2364        let target = node_data_ptr(node);
2365        for (index, entry) in self.entries.iter().enumerate() {
2366            if node_data_ptr(&**entry.node.borrow()) == target {
2367                return Some(self.make_node_ref(NodeLink::Entry(NodePath::root(index))));
2368            }
2369        }
2370
2371        self.ordered_nodes.iter().find_map(|(link, _caps, _agg)| {
2372            if matches!(link, NodeLink::Entry(path) if path.delegates().is_empty()) {
2373                return None;
2374            }
2375            let matches_target = match link {
2376                NodeLink::Head => node_data_ptr(self.head_sentinel.as_ref()) == target,
2377                NodeLink::Tail => node_data_ptr(self.tail_sentinel.as_ref()) == target,
2378                NodeLink::Entry(path) => {
2379                    let node_borrow = self.entries[path.entry()].node.borrow();
2380                    node_data_ptr(&**node_borrow) == target
2381                }
2382            };
2383            if matches_target {
2384                Some(self.make_node_ref(*link))
2385            } else {
2386                None
2387            }
2388        })
2389    }
2390
2391    /// Downcasts the node at `index` to the requested type.
2392    /// Returns a `Ref` guard that dereferences to the node type.
2393    pub fn node<N: ModifierNode + 'static>(&self, index: usize) -> Option<std::cell::Ref<'_, N>> {
2394        self.entries.get(index).and_then(|entry| {
2395            std::cell::Ref::filter_map(entry.node.borrow(), |boxed_node| {
2396                boxed_node.as_any().downcast_ref::<N>()
2397            })
2398            .ok()
2399        })
2400    }
2401
2402    /// Downcasts the node at `index` to the requested mutable type.
2403    /// Returns a `RefMut` guard that dereferences to the node type.
2404    pub fn node_mut<N: ModifierNode + 'static>(
2405        &self,
2406        index: usize,
2407    ) -> Option<std::cell::RefMut<'_, N>> {
2408        self.entries.get(index).and_then(|entry| {
2409            std::cell::RefMut::filter_map(entry.node.borrow_mut(), |boxed_node| {
2410                boxed_node.as_any_mut().downcast_mut::<N>()
2411            })
2412            .ok()
2413        })
2414    }
2415
2416    /// Returns an Rc clone of the node at the given index for shared ownership.
2417    /// This is used by coordinators to hold direct references to nodes.
2418    pub fn get_node_rc(&self, index: usize) -> Option<Rc<RefCell<Box<dyn ModifierNode>>>> {
2419        self.entries.get(index).map(|entry| Rc::clone(&entry.node))
2420    }
2421
2422    /// Returns true if the chain contains any nodes matching the given invalidation kind.
2423    pub fn has_nodes_for_invalidation(&self, kind: InvalidationKind) -> bool {
2424        self.aggregated_capabilities
2425            .contains(NodeCapabilities::for_invalidation(kind))
2426    }
2427
2428    /// Visits every node mutably in insertion order together with its capability mask.
2429    pub fn visit_nodes_mut<F>(&mut self, mut f: F)
2430    where
2431        F: FnMut(&mut dyn ModifierNode, NodeCapabilities),
2432    {
2433        for index in 0..self.ordered_nodes.len() {
2434            let (link, cached_caps, _agg) = self.ordered_nodes[index];
2435            match link {
2436                NodeLink::Head => {
2437                    f(self.head_sentinel.as_mut(), cached_caps);
2438                }
2439                NodeLink::Tail => {
2440                    f(self.tail_sentinel.as_mut(), cached_caps);
2441                }
2442                NodeLink::Entry(path) => {
2443                    let mut node_borrow = self.entries[path.entry()].node.borrow_mut();
2444                    if path.delegates().is_empty() {
2445                        f(&mut **node_borrow, cached_caps);
2446                    } else {
2447                        let mut current: &mut dyn ModifierNode = &mut **node_borrow;
2448                        for &delegate_index in path.delegates() {
2449                            if let Some(delegate) =
2450                                nth_delegate_mut(current, delegate_index as usize)
2451                            {
2452                                current = delegate;
2453                            } else {
2454                                return;
2455                            }
2456                        }
2457                        f(current, cached_caps);
2458                    }
2459                }
2460            }
2461        }
2462    }
2463
2464    fn make_node_ref(&self, link: NodeLink) -> ModifierChainNodeRef<'_> {
2465        ModifierChainNodeRef {
2466            chain: self,
2467            link,
2468            cached_capabilities: None,
2469            cached_aggregate_child: None,
2470        }
2471    }
2472
2473    fn make_node_ref_with_caps(
2474        &self,
2475        link: NodeLink,
2476        caps: NodeCapabilities,
2477        aggregate_child: NodeCapabilities,
2478    ) -> ModifierChainNodeRef<'_> {
2479        ModifierChainNodeRef {
2480            chain: self,
2481            link,
2482            cached_capabilities: Some(caps),
2483            cached_aggregate_child: Some(aggregate_child),
2484        }
2485    }
2486
2487    fn sync_chain_links(&mut self) {
2488        self.rebuild_ordered_nodes();
2489
2490        self.head_sentinel.node_state().set_parent_link(None);
2491        self.tail_sentinel.node_state().set_child_link(None);
2492
2493        if self.ordered_nodes.is_empty() {
2494            self.head_sentinel
2495                .node_state()
2496                .set_child_link(Some(NodeLink::Tail));
2497            self.tail_sentinel
2498                .node_state()
2499                .set_parent_link(Some(NodeLink::Head));
2500            self.aggregated_capabilities = NodeCapabilities::empty();
2501            self.head_aggregate_child_capabilities = NodeCapabilities::empty();
2502            self.head_sentinel
2503                .node_state()
2504                .set_aggregate_child_capabilities(NodeCapabilities::empty());
2505            self.tail_sentinel
2506                .node_state()
2507                .set_aggregate_child_capabilities(NodeCapabilities::empty());
2508            return;
2509        }
2510
2511        let mut previous = NodeLink::Head;
2512        for (link, _caps, _agg) in self.ordered_nodes.iter().copied() {
2513            match &previous {
2514                NodeLink::Head => self.head_sentinel.node_state().set_child_link(Some(link)),
2515                NodeLink::Tail => self.tail_sentinel.node_state().set_child_link(Some(link)),
2516                NodeLink::Entry(path) => {
2517                    let node_borrow = self.entries[path.entry()].node.borrow();
2518                    if path.delegates().is_empty() {
2519                        node_borrow.node_state().set_child_link(Some(link));
2520                    } else {
2521                        let mut current: &dyn ModifierNode = &**node_borrow;
2522                        for &delegate_index in path.delegates() {
2523                            if let Some(delegate) = nth_delegate(current, delegate_index as usize) {
2524                                current = delegate;
2525                            }
2526                        }
2527                        current.node_state().set_child_link(Some(link));
2528                    }
2529                }
2530            }
2531            match &link {
2532                NodeLink::Head => self
2533                    .head_sentinel
2534                    .node_state()
2535                    .set_parent_link(Some(previous)),
2536                NodeLink::Tail => self
2537                    .tail_sentinel
2538                    .node_state()
2539                    .set_parent_link(Some(previous)),
2540                NodeLink::Entry(path) => {
2541                    let node_borrow = self.entries[path.entry()].node.borrow();
2542                    if path.delegates().is_empty() {
2543                        node_borrow.node_state().set_parent_link(Some(previous));
2544                    } else {
2545                        let mut current: &dyn ModifierNode = &**node_borrow;
2546                        for &delegate_index in path.delegates() {
2547                            if let Some(delegate) = nth_delegate(current, delegate_index as usize) {
2548                                current = delegate;
2549                            }
2550                        }
2551                        current.node_state().set_parent_link(Some(previous));
2552                    }
2553                }
2554            }
2555            previous = link;
2556        }
2557
2558        match &previous {
2559            NodeLink::Head => self
2560                .head_sentinel
2561                .node_state()
2562                .set_child_link(Some(NodeLink::Tail)),
2563            NodeLink::Tail => self
2564                .tail_sentinel
2565                .node_state()
2566                .set_child_link(Some(NodeLink::Tail)),
2567            NodeLink::Entry(path) => {
2568                let node_borrow = self.entries[path.entry()].node.borrow();
2569                if path.delegates().is_empty() {
2570                    node_borrow
2571                        .node_state()
2572                        .set_child_link(Some(NodeLink::Tail));
2573                } else {
2574                    let mut current: &dyn ModifierNode = &**node_borrow;
2575                    for &delegate_index in path.delegates() {
2576                        if let Some(delegate) = nth_delegate(current, delegate_index as usize) {
2577                            current = delegate;
2578                        }
2579                    }
2580                    current.node_state().set_child_link(Some(NodeLink::Tail));
2581                }
2582            }
2583        }
2584        self.tail_sentinel
2585            .node_state()
2586            .set_parent_link(Some(previous));
2587        self.tail_sentinel.node_state().set_child_link(None);
2588
2589        let mut aggregate = NodeCapabilities::empty();
2590        for (link, cached_caps, cached_aggregate) in self.ordered_nodes.iter_mut().rev() {
2591            aggregate |= *cached_caps;
2592            *cached_aggregate = aggregate;
2593            match link {
2594                NodeLink::Head => {
2595                    self.head_sentinel
2596                        .node_state()
2597                        .set_aggregate_child_capabilities(aggregate);
2598                }
2599                NodeLink::Tail => {
2600                    self.tail_sentinel
2601                        .node_state()
2602                        .set_aggregate_child_capabilities(aggregate);
2603                }
2604                NodeLink::Entry(path) => {
2605                    let node_borrow = self.entries[path.entry()].node.borrow();
2606                    let state = if path.delegates().is_empty() {
2607                        node_borrow.node_state()
2608                    } else {
2609                        let mut current: &dyn ModifierNode = &**node_borrow;
2610                        for &delegate_index in path.delegates() {
2611                            if let Some(delegate) = nth_delegate(current, delegate_index as usize) {
2612                                current = delegate;
2613                            }
2614                        }
2615                        current.node_state()
2616                    };
2617                    state.set_aggregate_child_capabilities(aggregate);
2618                }
2619            }
2620        }
2621
2622        self.aggregated_capabilities = aggregate;
2623        self.head_aggregate_child_capabilities = aggregate;
2624        self.head_sentinel
2625            .node_state()
2626            .set_aggregate_child_capabilities(aggregate);
2627        self.tail_sentinel
2628            .node_state()
2629            .set_aggregate_child_capabilities(NodeCapabilities::empty());
2630    }
2631
2632    fn rebuild_ordered_nodes(&mut self) {
2633        self.ordered_nodes.clear();
2634        let mut path_buf = [0usize; MAX_DELEGATE_DEPTH];
2635        for (index, entry) in self.entries.iter().enumerate() {
2636            let node_borrow = entry.node.borrow();
2637            Self::enumerate_link_order(
2638                &**node_borrow,
2639                index,
2640                &mut path_buf,
2641                0,
2642                &mut self.ordered_nodes,
2643            );
2644        }
2645    }
2646
2647    fn enumerate_link_order(
2648        node: &dyn ModifierNode,
2649        entry: usize,
2650        path_buf: &mut [usize; MAX_DELEGATE_DEPTH],
2651        path_len: usize,
2652        out: &mut Vec<(NodeLink, NodeCapabilities, NodeCapabilities)>,
2653    ) {
2654        let caps = node.node_state().capabilities();
2655        out.push((
2656            NodeLink::Entry(NodePath::from_slice(entry, &path_buf[..path_len])),
2657            caps,
2658            NodeCapabilities::empty(),
2659        ));
2660        let mut delegate_index = 0usize;
2661        node.for_each_delegate(&mut |child| {
2662            if path_len < MAX_DELEGATE_DEPTH {
2663                path_buf[path_len] = delegate_index;
2664                Self::enumerate_link_order(child, entry, path_buf, path_len + 1, out);
2665            }
2666            delegate_index += 1;
2667        });
2668    }
2669}
2670
2671impl<'a> ModifierChainNodeRef<'a> {
2672    fn with_state<R>(&self, f: impl FnOnce(&NodeState) -> R) -> R {
2673        match &self.link {
2674            NodeLink::Head => f(self.chain.head_sentinel.node_state()),
2675            NodeLink::Tail => f(self.chain.tail_sentinel.node_state()),
2676            NodeLink::Entry(path) => {
2677                let node_borrow = self.chain.entries[path.entry()].node.borrow();
2678                if path.delegates().is_empty() {
2679                    f(node_borrow.node_state())
2680                } else {
2681                    let mut current: &dyn ModifierNode = &**node_borrow;
2682                    for &delegate_index in path.delegates() {
2683                        if let Some(delegate) = nth_delegate(current, delegate_index as usize) {
2684                            current = delegate;
2685                        } else {
2686                            return f(node_borrow.node_state());
2687                        }
2688                    }
2689                    f(current.node_state())
2690                }
2691            }
2692        }
2693    }
2694
2695    /// Provides access to the node via a closure, properly handling RefCell borrows.
2696    /// Returns None for sentinel nodes.
2697    pub fn with_node<R>(&self, f: impl FnOnce(&dyn ModifierNode) -> R) -> Option<R> {
2698        match &self.link {
2699            NodeLink::Head => None,
2700            NodeLink::Tail => None,
2701            NodeLink::Entry(path) => {
2702                let node_borrow = self.chain.entries[path.entry()].node.borrow();
2703                if path.delegates().is_empty() {
2704                    Some(f(&**node_borrow))
2705                } else {
2706                    let mut current: &dyn ModifierNode = &**node_borrow;
2707                    for &delegate_index in path.delegates() {
2708                        current = nth_delegate(current, delegate_index as usize)?;
2709                    }
2710                    Some(f(current))
2711                }
2712            }
2713        }
2714    }
2715
2716    /// Returns the parent reference, including sentinel head when applicable.
2717    #[inline]
2718    pub fn parent(&self) -> Option<Self> {
2719        self.with_state(|state| state.parent_link())
2720            .map(|link| self.chain.make_node_ref(link))
2721    }
2722
2723    /// Returns the child reference, including sentinel tail for the last entry.
2724    #[inline]
2725    pub fn child(&self) -> Option<Self> {
2726        self.with_state(|state| state.child_link())
2727            .map(|link| self.chain.make_node_ref(link))
2728    }
2729
2730    /// Returns the capability mask for this specific node.
2731    #[inline]
2732    pub fn kind_set(&self) -> NodeCapabilities {
2733        if let Some(caps) = self.cached_capabilities {
2734            return caps;
2735        }
2736        match &self.link {
2737            NodeLink::Head | NodeLink::Tail => NodeCapabilities::empty(),
2738            NodeLink::Entry(_) => self.with_state(|state| state.capabilities()),
2739        }
2740    }
2741
2742    /// Returns the entry index backing this node when it is part of the chain.
2743    pub fn entry_index(&self) -> Option<usize> {
2744        match &self.link {
2745            NodeLink::Entry(path) => Some(path.entry()),
2746            _ => None,
2747        }
2748    }
2749
2750    /// Returns how many delegate hops separate this node from its root element.
2751    pub fn delegate_depth(&self) -> usize {
2752        match &self.link {
2753            NodeLink::Entry(path) => path.delegates().len(),
2754            _ => 0,
2755        }
2756    }
2757
2758    /// Returns the aggregated capability mask for the subtree rooted at this node.
2759    #[inline]
2760    pub fn aggregate_child_capabilities(&self) -> NodeCapabilities {
2761        if let Some(agg) = self.cached_aggregate_child {
2762            return agg;
2763        }
2764        if self.is_tail() {
2765            NodeCapabilities::empty()
2766        } else {
2767            self.with_state(|state| state.aggregate_child_capabilities())
2768        }
2769    }
2770
2771    /// Returns true if this reference targets the sentinel head.
2772    pub fn is_head(&self) -> bool {
2773        matches!(self.link, NodeLink::Head)
2774    }
2775
2776    /// Returns true if this reference targets the sentinel tail.
2777    pub fn is_tail(&self) -> bool {
2778        matches!(self.link, NodeLink::Tail)
2779    }
2780
2781    /// Returns true if this reference targets either sentinel.
2782    pub fn is_sentinel(&self) -> bool {
2783        matches!(self.link, NodeLink::Head | NodeLink::Tail)
2784    }
2785
2786    /// Returns true if this node has any capability bits present in `mask`.
2787    pub fn has_capability(&self, mask: NodeCapabilities) -> bool {
2788        !mask.is_empty() && self.kind_set().intersects(mask)
2789    }
2790
2791    /// Visits descendant nodes, optionally including `self`, in insertion order.
2792    pub fn visit_descendants<F>(self, include_self: bool, mut f: F)
2793    where
2794        F: FnMut(ModifierChainNodeRef<'a>),
2795    {
2796        let mut current = if include_self {
2797            Some(self)
2798        } else {
2799            self.child()
2800        };
2801        while let Some(node) = current {
2802            if node.is_tail() {
2803                break;
2804            }
2805            if !node.is_sentinel() {
2806                f(node.clone());
2807            }
2808            current = node.child();
2809        }
2810    }
2811
2812    /// Visits descendant nodes that match `mask`, short-circuiting when possible.
2813    pub fn visit_descendants_matching<F>(self, include_self: bool, mask: NodeCapabilities, mut f: F)
2814    where
2815        F: FnMut(ModifierChainNodeRef<'a>),
2816    {
2817        if mask.is_empty() {
2818            self.visit_descendants(include_self, f);
2819            return;
2820        }
2821
2822        if !self.aggregate_child_capabilities().intersects(mask) {
2823            return;
2824        }
2825
2826        self.visit_descendants(include_self, |node| {
2827            if node.kind_set().intersects(mask) {
2828                f(node);
2829            }
2830        });
2831    }
2832
2833    /// Visits ancestor nodes up to (but excluding) the sentinel head.
2834    pub fn visit_ancestors<F>(self, include_self: bool, mut f: F)
2835    where
2836        F: FnMut(ModifierChainNodeRef<'a>),
2837    {
2838        let mut current = if include_self {
2839            Some(self)
2840        } else {
2841            self.parent()
2842        };
2843        while let Some(node) = current {
2844            if node.is_head() {
2845                break;
2846            }
2847            f(node.clone());
2848            current = node.parent();
2849        }
2850    }
2851
2852    /// Visits ancestor nodes that match `mask`.
2853    pub fn visit_ancestors_matching<F>(self, include_self: bool, mask: NodeCapabilities, mut f: F)
2854    where
2855        F: FnMut(ModifierChainNodeRef<'a>),
2856    {
2857        if mask.is_empty() {
2858            self.visit_ancestors(include_self, f);
2859            return;
2860        }
2861
2862        self.visit_ancestors(include_self, |node| {
2863            if node.kind_set().intersects(mask) {
2864                f(node);
2865            }
2866        });
2867    }
2868}
2869
2870#[cfg(test)]
2871#[path = "tests/modifier_tests.rs"]
2872mod tests;