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