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::{ProvidedValue, 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    /// The device pixels per layout point of the grid a layout modifier
56    /// measures on, Compose's `MeasureScope` density: lengths it places land
57    /// on whole device pixels of it. A context outside a layout pass answers
58    /// a unit grid.
59    fn density(&self) -> f32 {
60        1.0
61    }
62}
63
64/// Lightweight [`ModifierNodeContext`] implementation that records
65/// invalidation requests and update signals.
66///
67/// The context intentionally avoids leaking runtime details so the core
68/// crate can evolve independently from higher level UI crates. It simply
69/// stores the sequence of requested invalidation kinds and whether an
70/// explicit update was requested. Callers can inspect or drain this state
71/// after driving a [`ModifierNodeChain`] reconciliation pass.
72#[derive(Default, Debug, Clone)]
73pub struct BasicModifierNodeContext {
74    invalidations: ModifierInvalidations,
75    update_requested: bool,
76    active_capabilities: Vec<NodeCapabilities>,
77    node_id: Option<cranpose_core::NodeId>,
78}
79
80impl BasicModifierNodeContext {
81    /// Creates a new empty context.
82    pub fn new() -> Self {
83        Self::default()
84    }
85
86    /// Returns the ordered list of invalidation kinds that were requested
87    /// since the last call to `clear_invalidations`. Duplicate requests for
88    /// the same kind are coalesced.
89    pub fn invalidations(&self) -> &[ModifierInvalidation] {
90        &self.invalidations
91    }
92
93    /// Removes all currently recorded invalidation kinds.
94    pub fn clear_invalidations(&mut self) {
95        self.invalidations.clear();
96    }
97
98    /// Drains the recorded invalidations and returns them to the caller.
99    pub fn take_invalidations(&mut self) -> ModifierInvalidations {
100        std::mem::take(&mut self.invalidations)
101    }
102
103    /// Returns whether an update was requested since the last call to
104    /// `take_update_requested`.
105    pub fn update_requested(&self) -> bool {
106        self.update_requested
107    }
108
109    /// Returns whether an update was requested and clears the flag.
110    pub fn take_update_requested(&mut self) -> bool {
111        std::mem::take(&mut self.update_requested)
112    }
113
114    /// Sets the node ID associated with this context.
115    pub fn set_node_id(&mut self, id: Option<cranpose_core::NodeId>) {
116        self.node_id = id;
117    }
118
119    fn push_invalidation(&mut self, kind: InvalidationKind) {
120        let mut capabilities = self.current_capabilities();
121        capabilities.insert(NodeCapabilities::for_invalidation(kind));
122        if let Some(existing) = self
123            .invalidations
124            .iter_mut()
125            .find(|entry| entry.kind() == kind)
126        {
127            let updated = existing.capabilities() | capabilities;
128            *existing = ModifierInvalidation::new(kind, updated);
129        } else {
130            self.invalidations
131                .push(ModifierInvalidation::new(kind, capabilities));
132        }
133    }
134
135    fn current_capabilities(&self) -> NodeCapabilities {
136        self.active_capabilities
137            .last()
138            .copied()
139            .unwrap_or_else(NodeCapabilities::empty)
140    }
141}
142
143impl ModifierNodeContext for BasicModifierNodeContext {
144    fn invalidate(&mut self, kind: InvalidationKind) {
145        self.push_invalidation(kind);
146    }
147
148    fn request_update(&mut self) {
149        self.update_requested = true;
150    }
151
152    fn push_active_capabilities(&mut self, capabilities: NodeCapabilities) {
153        self.active_capabilities.push(capabilities);
154    }
155
156    fn pop_active_capabilities(&mut self) {
157        self.active_capabilities.pop();
158    }
159
160    fn node_id(&self) -> Option<cranpose_core::NodeId> {
161        self.node_id
162    }
163}
164
165const MAX_DELEGATE_DEPTH: usize = 3;
166
167#[derive(Copy, Clone, Debug, PartialEq, Eq)]
168pub(crate) struct NodePath {
169    /// The chain entry, 32 bits wide: a path sits twice in every node's
170    /// state, and no chain nears four billion entries.
171    entry: u32,
172    delegate_buf: [u8; MAX_DELEGATE_DEPTH],
173    delegate_len: u8,
174}
175
176impl NodePath {
177    #[inline]
178    fn root(entry: usize) -> Self {
179        debug_assert!(entry <= u32::MAX as usize, "chain entry exceeds u32 range");
180        Self {
181            entry: entry as u32,
182            delegate_buf: [0; MAX_DELEGATE_DEPTH],
183            delegate_len: 0,
184        }
185    }
186
187    #[inline]
188    fn from_slice(entry: usize, path: &[usize]) -> Self {
189        debug_assert!(
190            path.len() <= MAX_DELEGATE_DEPTH,
191            "delegate depth {} exceeds MAX_DELEGATE_DEPTH {}",
192            path.len(),
193            MAX_DELEGATE_DEPTH
194        );
195        debug_assert!(
196            path.iter().all(|&i| i <= u8::MAX as usize),
197            "delegate index exceeds u8 range"
198        );
199        let mut delegate_buf = [0u8; MAX_DELEGATE_DEPTH];
200        for (i, &v) in path.iter().enumerate().take(MAX_DELEGATE_DEPTH) {
201            delegate_buf[i] = v as u8;
202        }
203        Self {
204            delegate_buf,
205            delegate_len: path.len().min(MAX_DELEGATE_DEPTH) as u8,
206            ..Self::root(entry)
207        }
208    }
209
210    #[inline]
211    fn entry(&self) -> usize {
212        self.entry as usize
213    }
214
215    #[inline]
216    fn delegates(&self) -> &[u8] {
217        &self.delegate_buf[..self.delegate_len as usize]
218    }
219}
220
221#[derive(Copy, Clone, Debug, PartialEq, Eq)]
222pub(crate) enum NodeLink {
223    Head,
224    Tail,
225    Entry(NodePath),
226}
227
228/// Runtime state tracked for every [`ModifierNode`].
229///
230/// This type is part of the internal node system API and should not be directly
231/// constructed or manipulated by external code. Modifier nodes automatically receive
232/// and manage their NodeState through the modifier chain infrastructure.
233#[derive(Debug)]
234pub struct NodeState {
235    aggregate_child_capabilities: Cell<NodeCapabilities>,
236    capabilities: Cell<NodeCapabilities>,
237    parent: Cell<Option<NodeLink>>,
238    child: Cell<Option<NodeLink>>,
239    attached: Cell<bool>,
240    is_sentinel: bool,
241}
242
243impl Default for NodeState {
244    fn default() -> Self {
245        Self::new()
246    }
247}
248
249impl NodeState {
250    pub const fn new() -> Self {
251        Self {
252            aggregate_child_capabilities: Cell::new(NodeCapabilities::empty()),
253            capabilities: Cell::new(NodeCapabilities::empty()),
254            parent: Cell::new(None),
255            child: Cell::new(None),
256            attached: Cell::new(false),
257            is_sentinel: false,
258        }
259    }
260
261    pub const fn sentinel() -> Self {
262        Self {
263            aggregate_child_capabilities: Cell::new(NodeCapabilities::empty()),
264            capabilities: Cell::new(NodeCapabilities::empty()),
265            parent: Cell::new(None),
266            child: Cell::new(None),
267            attached: Cell::new(true),
268            is_sentinel: true,
269        }
270    }
271
272    pub fn set_capabilities(&self, capabilities: NodeCapabilities) {
273        self.capabilities.set(capabilities);
274    }
275
276    #[inline]
277    pub fn capabilities(&self) -> NodeCapabilities {
278        self.capabilities.get()
279    }
280
281    pub fn set_aggregate_child_capabilities(&self, capabilities: NodeCapabilities) {
282        self.aggregate_child_capabilities.set(capabilities);
283    }
284
285    #[inline]
286    pub fn aggregate_child_capabilities(&self) -> NodeCapabilities {
287        self.aggregate_child_capabilities.get()
288    }
289
290    pub(crate) fn set_parent_link(&self, parent: Option<NodeLink>) {
291        self.parent.set(parent);
292    }
293
294    #[inline]
295    pub(crate) fn parent_link(&self) -> Option<NodeLink> {
296        self.parent.get()
297    }
298
299    pub(crate) fn set_child_link(&self, child: Option<NodeLink>) {
300        self.child.set(child);
301    }
302
303    #[inline]
304    pub(crate) fn child_link(&self) -> Option<NodeLink> {
305        self.child.get()
306    }
307
308    pub fn set_attached(&self, attached: bool) {
309        self.attached.set(attached);
310    }
311
312    pub fn is_attached(&self) -> bool {
313        self.attached.get()
314    }
315
316    pub fn is_sentinel(&self) -> bool {
317        self.is_sentinel
318    }
319}
320
321/// Provides traversal helpers that mirror Jetpack Compose's [`DelegatableNode`] contract.
322pub trait DelegatableNode {
323    fn node_state(&self) -> &NodeState;
324    fn aggregate_child_capabilities(&self) -> NodeCapabilities {
325        self.node_state().aggregate_child_capabilities()
326    }
327}
328
329/// Core trait implemented by modifier nodes.
330///
331/// # Capability-Driven Architecture
332///
333/// This trait follows Jetpack Compose's `Modifier.Node` pattern where nodes declare
334/// their capabilities via [`NodeCapabilities`] and implement specialized traits
335/// ([`DrawModifierNode`], [`PointerInputNode`], [`SemanticsNode`], [`FocusNode`], etc.)
336/// to participate in specific pipeline stages.
337///
338/// ## How to Implement a Modifier Node
339///
340/// 1. **Declare capabilities** in your [`ModifierNodeElement::capabilities()`] implementation
341/// 2. **Implement specialized traits** for the capabilities you declared
342/// 3. **Use helper macros** to reduce boilerplate (recommended)
343///
344/// ### Example: Draw Node
345///
346/// ```text
347/// use cranpose_foundation::*;
348///
349/// struct MyDrawNode {
350///     state: NodeState,
351///     color: Color,
352/// }
353///
354/// impl DelegatableNode for MyDrawNode {
355///     fn node_state(&self) -> &NodeState {
356///         &self.state
357///     }
358/// }
359///
360/// impl ModifierNode for MyDrawNode {
361///     // Use the helper macro instead of manual as_* implementations
362///     impl_modifier_node!(draw);
363/// }
364///
365/// impl DrawModifierNode for MyDrawNode {
366///     fn draw(&mut self, _context: &mut dyn ModifierNodeContext, draw_scope: &mut dyn DrawScope) {
367///         // Drawing logic here
368///     }
369/// }
370/// ```
371///
372/// ### Example: Multi-Capability Node
373///
374/// ```text
375/// impl ModifierNode for MyComplexNode {
376///     // This node participates in draw, pointer input, and semantics
377///     impl_modifier_node!(draw, pointer_input, semantics);
378/// }
379/// ```
380///
381/// ## Lifecycle Callbacks
382///
383/// Nodes receive lifecycle callbacks when they attach to or detach from a
384/// composition and may optionally react to resets triggered by the runtime
385/// (for example, when reusing nodes across modifier list changes).
386pub trait ModifierNode: Any + DelegatableNode {
387    fn on_attach(&mut self, _context: &mut dyn ModifierNodeContext) {}
388
389    fn on_detach(&mut self) {}
390
391    fn on_reset(&mut self) {}
392
393    /// Returns this node as a draw modifier if it implements the trait.
394    fn as_draw_node(&self) -> Option<&dyn DrawModifierNode> {
395        None
396    }
397
398    /// Returns this node as a mutable draw modifier if it implements the trait.
399    fn as_draw_node_mut(&mut self) -> Option<&mut dyn DrawModifierNode> {
400        None
401    }
402
403    /// Returns this node as a pointer-input modifier if it implements the trait.
404    fn as_pointer_input_node(&self) -> Option<&dyn PointerInputNode> {
405        None
406    }
407
408    /// Returns this node as a mutable pointer-input modifier if it implements the trait.
409    fn as_pointer_input_node_mut(&mut self) -> Option<&mut dyn PointerInputNode> {
410        None
411    }
412
413    /// Returns this node as a semantics modifier if it implements the trait.
414    fn as_semantics_node(&self) -> Option<&dyn SemanticsNode> {
415        None
416    }
417
418    /// Returns this node as a mutable semantics modifier if it implements the trait.
419    fn as_semantics_node_mut(&mut self) -> Option<&mut dyn SemanticsNode> {
420        None
421    }
422
423    /// Returns this node as a focus modifier if it implements the trait.
424    fn as_focus_node(&self) -> Option<&dyn FocusNode> {
425        None
426    }
427
428    /// Returns this node as a mutable focus modifier if it implements the trait.
429    fn as_focus_node_mut(&mut self) -> Option<&mut dyn FocusNode> {
430        None
431    }
432
433    /// Returns this node as a layout modifier if it implements the trait.
434    fn as_layout_node(&self) -> Option<&dyn LayoutModifierNode> {
435        None
436    }
437
438    /// Returns this node as a mutable layout modifier if it implements the trait.
439    fn as_layout_node_mut(&mut self) -> Option<&mut dyn LayoutModifierNode> {
440        None
441    }
442
443    /// Visits every delegate node owned by this modifier.
444    fn for_each_delegate<'b>(&'b self, _visitor: &mut dyn FnMut(&'b dyn ModifierNode)) {}
445
446    /// Visits every delegate node mutably.
447    fn for_each_delegate_mut<'b>(&'b mut self, _visitor: &mut dyn FnMut(&'b mut dyn ModifierNode)) {
448    }
449}
450
451/// Marker trait for layout-specific modifier nodes.
452///
453/// Layout nodes participate in the measure and layout passes of the render
454/// pipeline. They can intercept and modify the measurement and placement of
455/// their wrapped content.
456pub trait LayoutModifierNode: ModifierNode {
457    /// Measures the wrapped content and returns both the size this modifier
458    /// occupies and where the wrapped content should be placed.
459    ///
460    /// The node receives a measurable representing the wrapped content and
461    /// the incoming constraints from the parent.
462    ///
463    /// Returns a `LayoutModifierMeasureResult` containing:
464    /// - `size`: The final size this modifier will occupy
465    /// - `placement_offset_x/y`: Where to place the wrapped content relative
466    ///   to this modifier's top-left corner
467    ///
468    /// For example, a padding modifier would:
469    /// - Measure child with deflated constraints
470    /// - Return size = child size + padding
471    /// - Return placement offset = (padding.left, padding.top)
472    ///
473    /// The default implementation delegates to the wrapped content without
474    /// modification (size = child size, offset = 0).
475    ///
476    /// NOTE: This takes `&self` not `&mut self` to match Jetpack Compose semantics.
477    /// Nodes that need mutable state should use interior mutability (Cell/RefCell).
478    fn measure(
479        &self,
480        _context: &mut dyn ModifierNodeContext,
481        measurable: &dyn Measurable,
482        constraints: Constraints,
483    ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
484        let placeable = measurable.measure(constraints);
485        cranpose_ui_layout::LayoutModifierMeasureResult::with_size(Size {
486            width: placeable.width(),
487            height: placeable.height(),
488        })
489    }
490
491    /// Returns the minimum intrinsic width of this modifier node. The default is
492    /// the wrapped content's, for a node that keeps the content's size.
493    fn min_intrinsic_width(&self, measurable: &dyn Measurable, height: f32, _density: f32) -> f32 {
494        measurable.min_intrinsic_width(height)
495    }
496
497    /// Returns the maximum intrinsic width of this modifier node. The default is
498    /// the wrapped content's, for a node that keeps the content's size.
499    fn max_intrinsic_width(&self, measurable: &dyn Measurable, height: f32, _density: f32) -> f32 {
500        measurable.max_intrinsic_width(height)
501    }
502
503    /// Returns the minimum intrinsic height of this modifier node. The default is
504    /// the wrapped content's, for a node that keeps the content's size.
505    fn min_intrinsic_height(&self, measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
506        measurable.min_intrinsic_height(width)
507    }
508
509    /// Returns the maximum intrinsic height of this modifier node. The default is
510    /// the wrapped content's, for a node that keeps the content's size.
511    fn max_intrinsic_height(&self, measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
512        measurable.max_intrinsic_height(width)
513    }
514}
515
516/// Marker trait for draw-specific modifier nodes.
517///
518/// Draw nodes participate in the draw pass of the render pipeline. A node
519/// draws through the closures it hands out, which the renderer runs at render
520/// time with a live scope of the node's size; slice collection only gathers
521/// them.
522pub trait DrawModifierNode: ModifierNode {
523    /// Creates a closure for deferred drawing that will be evaluated at render time.
524    ///
525    /// This is the preferred method for nodes with dynamic content like:
526    /// - Blinking cursors (visibility changes over time)
527    /// - Live selection during drag (selection changes during mouse move)
528    ///
529    /// The returned closure captures the node's internal state (via Rc) and
530    /// evaluates at render time, not at slice collection time.
531    ///
532    /// Returns None by default. Override for nodes needing deferred draw.
533    fn create_draw_closure(&self) -> Option<NodeDrawClosure> {
534        None
535    }
536
537    /// Like [`create_draw_closure`](Self::create_draw_closure), but the
538    /// primitives render BEHIND the node's content — e.g. a text field's
539    /// selection highlight, which must sit under the glyphs (a highlight
540    /// drawn over them tints the text with its translucent fill).
541    fn create_behind_draw_closure(&self) -> Option<NodeDrawClosure> {
542        None
543    }
544}
545
546/// A deferred draw closure returned by
547/// [`DrawModifierNode::create_draw_closure`]: it records into a scope the
548/// renderer provides at render time, so the recording's identity stays with
549/// the consumer rather than with a closure-owned vector.
550pub type NodeDrawClosure = Rc<dyn Fn(&mut cranpose_ui_graphics::DrawScopeDefault)>;
551
552/// Marker trait for pointer input modifier nodes.
553///
554/// Pointer input nodes participate in hit-testing and pointer event
555/// dispatch. They can intercept pointer events and handle them before
556/// they reach the wrapped content.
557pub trait PointerInputNode: ModifierNode {
558    /// Called when a pointer event occurs within the bounds of this node.
559    /// Returns true if the event was consumed and should not propagate further.
560    fn on_pointer_event(
561        &mut self,
562        _context: &mut dyn ModifierNodeContext,
563        _event: &PointerEvent,
564    ) -> bool {
565        false
566    }
567
568    /// Returns true if this node should participate in hit-testing for the
569    /// given pointer position.
570    fn hit_test(&self, _x: f32, _y: f32) -> bool {
571        true
572    }
573
574    /// Returns an event handler closure if the node wants to participate in pointer dispatch.
575    fn pointer_input_handler(&self) -> Option<Rc<dyn Fn(PointerEvent)>> {
576        None
577    }
578
579    /// Returns the cell this node reads its owning layout node's resolved size
580    /// from, if it exposes a size to its handler (Compose's
581    /// `PointerInputScope.size`).
582    ///
583    /// The cell is shared with the node, so the layout pass publishes the size
584    /// into it once per pass and every read — including reads that happen
585    /// before any pointer event arrives — observes the current size. The size
586    /// is the node's layout box in the same local coordinate space the
587    /// dispatched [`PointerEvent`] positions use, so
588    /// `event.local_position / size` is a well-defined fraction of the node.
589    ///
590    /// Returns `None` for pointer nodes with no size-bearing scope.
591    fn layout_size_sink(&self) -> Option<Rc<Cell<Size>>> {
592        None
593    }
594}
595
596/// Marker trait for semantics modifier nodes.
597///
598/// Semantics nodes participate in the semantics tree construction. They can
599/// add or modify semantic properties of their wrapped content for
600/// accessibility and testing purposes.
601pub trait SemanticsNode: ModifierNode {
602    /// Merges semantic properties into the provided configuration.
603    fn merge_semantics(&self, _config: &mut SemanticsConfiguration) {}
604
605    /// Whether this node makes its subtree modal or hides it, and whether
606    /// what it merges can change without its semantics being invalidated.
607    /// The default merges into a fresh configuration, reads the two flags and
608    /// counts the node as reading live state; a node that can declare
609    /// neither flag and reports only what its updates bring overrides it to
610    /// skip that work.
611    fn reach(&self) -> SemanticsReach {
612        SemanticsReach::merged_by(self, true)
613    }
614}
615
616/// The semantics flags that decide how a node's semantics reach the tree:
617/// which part of a window is live ([`SemanticsConfiguration::is_modal`] and
618/// [`SemanticsConfiguration::hidden`]), and whether a tree update may keep
619/// what the node reported last.
620#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
621pub struct SemanticsReach {
622    /// The subtree takes over the screen.
623    pub is_modal: bool,
624    /// The subtree is skipped by readers.
625    pub hidden: bool,
626    /// What the node merges can change without its semantics being
627    /// invalidated, as a `.semantics { }` recorder reading live state can,
628    /// so every tree update merges it again.
629    pub merges_live_state: bool,
630}
631
632impl SemanticsReach {
633    /// The modal and hidden flags `node` merges into a fresh configuration,
634    /// with whether what it merges can change without an invalidation.
635    pub fn merged_by(node: &(impl SemanticsNode + ?Sized), merges_live_state: bool) -> Self {
636        let mut config = SemanticsConfiguration::default();
637        node.merge_semantics(&mut config);
638        Self {
639            is_modal: config.is_modal,
640            hidden: config.hidden,
641            merges_live_state,
642        }
643    }
644
645    /// Every flag of `self` and `other`.
646    pub fn union(self, other: Self) -> Self {
647        Self {
648            is_modal: self.is_modal || other.is_modal,
649            hidden: self.hidden || other.hidden,
650            merges_live_state: self.merges_live_state || other.merges_live_state,
651        }
652    }
653}
654
655/// Focus state of a focus target node.
656///
657/// This mirrors Jetpack Compose's FocusState enum which tracks whether
658/// a node is focused, has a focused child, or is inactive.
659#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
660pub enum FocusState {
661    /// The focusable component is currently active (i.e. it receives key events).
662    Active,
663    /// One of the descendants of the focusable component is Active.
664    ActiveParent,
665    /// The focusable component is currently active (has focus), and is in a state
666    /// where it does not want to give up focus. (Eg. a text field with an invalid
667    /// phone number).
668    Captured,
669    /// The focusable component does not receive any key events. (ie it is not active,
670    /// nor are any of its descendants active).
671    #[default]
672    Inactive,
673}
674
675impl FocusState {
676    /// Returns whether the component is focused (Active or Captured).
677    pub fn is_focused(self) -> bool {
678        matches!(self, FocusState::Active | FocusState::Captured)
679    }
680
681    /// Returns whether this node or any descendant has focus.
682    pub fn has_focus(self) -> bool {
683        matches!(
684            self,
685            FocusState::Active | FocusState::ActiveParent | FocusState::Captured
686        )
687    }
688
689    /// Returns whether focus is captured.
690    pub fn is_captured(self) -> bool {
691        matches!(self, FocusState::Captured)
692    }
693}
694
695/// Marker trait for focus modifier nodes.
696///
697/// Focus nodes participate in focus management. They can request focus,
698/// track focus state, and participate in focus traversal.
699pub trait FocusNode: ModifierNode {
700    /// Returns the current focus state of this node.
701    fn focus_state(&self) -> FocusState;
702
703    /// Called when focus state changes for this node.
704    fn on_focus_changed(&mut self, _context: &mut dyn ModifierNodeContext, _state: FocusState) {}
705}
706
707/// What kind of control a node is, as screen readers announce it.
708///
709/// This is Compose's `SemanticsProperties.Role` (`Modifier.semantics { role =
710/// Role.RadioButton }`), not a description of where the node sits in the tree.
711/// A screen reader turns it into the trailing noun it speaks after the label —
712/// TalkBack says "CAMPAIGN, radio button, selected" — and into the on/off
713/// wording it uses for a switch. Compose keeps this separate from the node's
714/// structural kind for the same reason Cranpose does: a `Row` that happens to
715/// be clickable is still a row, and a drawn ring segment that is a radio button
716/// has no layout node of its own at all.
717#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
718pub enum SemanticsWidgetRole {
719    Button,
720    Checkbox,
721    Switch,
722    RadioButton,
723    Tab,
724    Image,
725    /// A control that opens a list of choices and holds the one that is
726    /// picked. Compose's `Role.DropdownList`.
727    DropdownList,
728    /// A control that holds one value out of an ordered set and steps through
729    /// them. Compose's `Role.ValuePicker`.
730    ValuePicker,
731    /// Compose's `heading()`, which is a property rather than a `Role`, but
732    /// reaches the platform through the same field on every backend Cranpose
733    /// targets (`AccessibilityNodeInfo.setHeading`, `Role::Heading`,
734    /// `<h*>`/`UIAccessibilityTraitHeader`).
735    Header,
736    /// A modal surface that takes over the screen until it is dismissed.
737    /// Screen readers announce it and confine their traversal to it, which is
738    /// the accessible half of what makes a dialog modal.
739    Dialog,
740    /// Text that takes the user somewhere else when pressed. Compose has no
741    /// such `Role`; SwiftUI's `.isLink`, ARIA's `link`.
742    Link,
743    /// A field that narrows what is on the screen as the user types. ARIA's
744    /// `searchbox`, VoiceOver's search field trait.
745    SearchField,
746    /// A control that shows how far work has come and takes no input. ARIA's
747    /// `progressbar`.
748    ProgressBar,
749    /// A button that stays pressed or released. ARIA's `button` with
750    /// `aria-pressed`.
751    ToggleButton,
752    /// A message a reader speaks as soon as it shows, with no move to it.
753    /// ARIA's `alert`.
754    Alert,
755    /// A row of controls that act on the content beside them. ARIA's
756    /// `toolbar`.
757    Toolbar,
758    /// A list of commands that opens on a press. ARIA's `menu`.
759    Menu,
760    /// One command inside a menu. ARIA's `menuitem`.
761    MenuItem,
762    /// The row that holds the tabs of a screen. ARIA's `tablist`, VoiceOver's
763    /// tab bar trait.
764    TabBar,
765    /// A container whose rows a reader counts and walks into. ARIA's `list`.
766    List,
767    /// One row of a list. ARIA's `listitem`.
768    ListItem,
769    /// A set of mutually exclusive radio choices. ARIA's `radiogroup`.
770    RadioGroup,
771}
772
773/// The value a control holds inside a range, for a slider, a dial or a
774/// progress bar.
775///
776/// This is Compose's `ProgressBarRangeInfo` (`Modifier.progressSemantics(value,
777/// range, steps)`). A control without it reads as plain text: a screen reader
778/// user hears "47 percent" and has no way to change it, because nothing tells
779/// the platform the control is adjustable.
780#[derive(Clone, Copy, Debug, PartialEq)]
781pub struct ProgressBarRangeInfo {
782    pub current: f32,
783    pub start: f32,
784    pub end: f32,
785    /// How many stops sit between `start` and `end`, as Compose counts them.
786    /// Zero means the value moves without stops.
787    pub steps: u32,
788}
789
790impl ProgressBarRangeInfo {
791    pub fn new(current: f32, start: f32, end: f32, steps: u32) -> Self {
792        Self {
793            current,
794            start,
795            end,
796            steps,
797        }
798    }
799
800    /// Where the value sits between the two ends, from 0 to 1.
801    pub fn fraction(&self) -> f32 {
802        let span = self.end - self.start;
803        if span.abs() < f32::EPSILON {
804            return 0.0;
805        }
806        ((self.current - self.start) / span).clamp(0.0, 1.0)
807    }
808
809    /// How far one stop moves the value. With no stops, one tenth of the
810    /// range, which is what a screen reader's swipe up and down expects.
811    pub fn step(&self) -> f32 {
812        let span = self.end - self.start;
813        if self.steps == 0 {
814            span / 10.0
815        } else {
816            span / (self.steps as f32 + 1.0)
817        }
818    }
819}
820
821/// How far a container has scrolled along one axis and how far it can go.
822///
823/// How many rows and columns a list holds, so a screen reader can say
824/// "list, 12 items" as its cursor enters. Compose's `CollectionInfo`.
825#[derive(Clone, Copy, Debug, PartialEq, Eq)]
826pub struct CollectionInfo {
827    pub rows: usize,
828    pub columns: usize,
829}
830
831/// This is Compose's `ScrollAxisRange` (`verticalScrollAxisRange`,
832/// `horizontalScrollAxisRange`). A lazy list has no whole extent to give, so
833/// it reports the first visible item as the value and one more than that as
834/// the end while it can still scroll; a reader only needs to know whether it
835/// can page on.
836///
837/// `content_padding_start` and `content_padding_end` are the container's
838/// content padding at its top and bottom (or left and right) edges, in layout
839/// pixels. Content scrolls under that padding, where a bar may cover it, so a
840/// control a reader or the keyboard moves to is brought in past it.
841#[derive(Clone, Copy, Debug, PartialEq)]
842pub struct ScrollAxisRange {
843    pub value: f32,
844    pub max_value: f32,
845    pub reverse: bool,
846    pub content_padding_start: f32,
847    pub content_padding_end: f32,
848}
849
850impl ScrollAxisRange {
851    pub fn new(value: f32, max_value: f32, reverse: bool) -> Self {
852        Self {
853            value,
854            max_value,
855            reverse,
856            content_padding_start: 0.0,
857            content_padding_end: 0.0,
858        }
859    }
860
861    /// The same range with the container's content padding at its start and
862    /// end edges, in layout pixels.
863    pub fn with_content_padding(self, start: f32, end: f32) -> Self {
864        Self {
865            content_padding_start: start,
866            content_padding_end: end,
867            ..self
868        }
869    }
870
871    pub fn can_scroll_forward(&self) -> bool {
872        self.value < self.max_value
873    }
874
875    pub fn can_scroll_backward(&self) -> bool {
876        self.value > 0.0
877    }
878}
879
880/// What a container does when a screen reader pages it, e.g. TalkBack's
881/// scroll forward action or an accesskit scroll down. The two deltas are in
882/// layout pixels; the answer says whether anything moved.
883///
884/// This is Compose's `SemanticsActions.ScrollBy`.
885#[derive(Clone)]
886pub struct SemanticsScrollBy {
887    handler: Rc<dyn Fn(f32, f32) -> bool>,
888}
889
890impl SemanticsScrollBy {
891    pub fn new(handler: impl Fn(f32, f32) -> bool + 'static) -> Self {
892        Self {
893            handler: Rc::new(handler),
894        }
895    }
896
897    pub fn invoke(&self, dx: f32, dy: f32) -> bool {
898        (self.handler)(dx, dy)
899    }
900}
901
902impl fmt::Debug for SemanticsScrollBy {
903    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
904        f.debug_struct("SemanticsScrollBy").finish_non_exhaustive()
905    }
906}
907
908impl PartialEq for SemanticsScrollBy {
909    fn eq(&self, _other: &Self) -> bool {
910        true
911    }
912}
913
914impl Eq for SemanticsScrollBy {}
915
916/// What a list does when a screen reader asks for the row at an index, e.g. a
917/// TalkBack scroll-to-position action.
918///
919/// This is Compose's `SemanticsActions.ScrollToIndex`. The index counts rows
920/// from zero, and the answer says whether the list moved.
921#[derive(Clone)]
922pub struct SemanticsScrollToIndex {
923    handler: Rc<dyn Fn(usize) -> bool>,
924}
925
926impl SemanticsScrollToIndex {
927    pub fn new(handler: impl Fn(usize) -> bool + 'static) -> Self {
928        Self {
929            handler: Rc::new(handler),
930        }
931    }
932
933    pub fn invoke(&self, index: usize) -> bool {
934        (self.handler)(index)
935    }
936}
937
938impl fmt::Debug for SemanticsScrollToIndex {
939    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
940        f.debug_struct("SemanticsScrollToIndex")
941            .finish_non_exhaustive()
942    }
943}
944
945impl PartialEq for SemanticsScrollToIndex {
946    fn eq(&self, _other: &Self) -> bool {
947        true
948    }
949}
950
951impl Eq for SemanticsScrollToIndex {}
952
953/// What a control does when a screen reader moves its value, e.g. a VoiceOver
954/// swipe up or a TalkBack set-progress action.
955///
956/// This is Compose's `SemanticsActions.SetProgress`. The value comes in the
957/// control's own range, and the answer says whether the control took it.
958#[derive(Clone)]
959pub struct SemanticsSetProgress {
960    handler: Rc<dyn Fn(f32) -> bool>,
961}
962
963impl SemanticsSetProgress {
964    pub fn new(handler: impl Fn(f32) -> bool + 'static) -> Self {
965        Self {
966            handler: Rc::new(handler),
967        }
968    }
969
970    pub fn invoke(&self, value: f32) -> bool {
971        (self.handler)(value)
972    }
973}
974
975impl fmt::Debug for SemanticsSetProgress {
976    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
977        f.debug_struct("SemanticsSetProgress")
978            .finish_non_exhaustive()
979    }
980}
981
982/// What a text field does when a screen reader or a voice tool hands it new
983/// text. This is Compose's `SemanticsActions.SetText`; the answer says
984/// whether the field took the text.
985#[derive(Clone)]
986pub struct SemanticsSetText(Rc<dyn Fn(&str) -> bool>);
987
988impl SemanticsSetText {
989    pub fn new(handler: impl Fn(&str) -> bool + 'static) -> Self {
990        Self(Rc::new(handler))
991    }
992
993    pub fn invoke(&self, text: &str) -> bool {
994        (self.0)(text)
995    }
996}
997
998impl fmt::Debug for SemanticsSetText {
999    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1000        f.write_str("SemanticsSetText")
1001    }
1002}
1003
1004/// What a text field does when a screen reader moves its caret or picks a
1005/// stretch of its text: the two ends of the new selection, as byte offsets
1006/// into the field's text, the anchor first and the end that moves second. Equal
1007/// ends are a caret. This is Compose's `SemanticsActions.SetSelection`; the
1008/// answer says whether the field took the selection.
1009#[derive(Clone)]
1010pub struct SemanticsSetSelection(Rc<dyn Fn(usize, usize) -> bool>);
1011
1012impl SemanticsSetSelection {
1013    pub fn new(handler: impl Fn(usize, usize) -> bool + 'static) -> Self {
1014        Self(Rc::new(handler))
1015    }
1016
1017    pub fn invoke(&self, anchor: usize, focus: usize) -> bool {
1018        (self.0)(anchor, focus)
1019    }
1020}
1021
1022impl fmt::Debug for SemanticsSetSelection {
1023    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1024        f.write_str("SemanticsSetSelection")
1025    }
1026}
1027
1028/// What a control does when a screen reader asks it to open or to close.
1029/// Compose's `expand` and `collapse` actions.
1030#[derive(Clone)]
1031pub struct SemanticsExpand(Rc<dyn Fn() -> bool>);
1032
1033impl SemanticsExpand {
1034    pub fn new(handler: impl Fn() -> bool + 'static) -> Self {
1035        Self(Rc::new(handler))
1036    }
1037
1038    pub fn invoke(&self) -> bool {
1039        (self.0)()
1040    }
1041}
1042
1043impl fmt::Debug for SemanticsExpand {
1044    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1045        f.write_str("SemanticsExpand")
1046    }
1047}
1048
1049/// What a control does when a screen reader asks for its long press. This is
1050/// Compose's `SemanticsActions.OnLongClick`; the answer says whether the
1051/// control took the ask.
1052#[derive(Clone)]
1053pub struct SemanticsLongClick(Rc<dyn Fn() -> bool>);
1054
1055impl SemanticsLongClick {
1056    pub fn new(handler: impl Fn() -> bool + 'static) -> Self {
1057        Self(Rc::new(handler))
1058    }
1059
1060    pub fn invoke(&self) -> bool {
1061        (self.0)()
1062    }
1063}
1064
1065impl fmt::Debug for SemanticsLongClick {
1066    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1067        f.write_str("SemanticsLongClick")
1068    }
1069}
1070
1071impl PartialEq for SemanticsLongClick {
1072    fn eq(&self, _other: &Self) -> bool {
1073        true
1074    }
1075}
1076
1077impl PartialEq for SemanticsExpand {
1078    fn eq(&self, _other: &Self) -> bool {
1079        true
1080    }
1081}
1082
1083/// What a control does when a screen reader asks to send it away: a row a
1084/// sighted person swipes off, a sheet a sighted person taps outside of.
1085/// Compose's `dismiss` action.
1086#[derive(Clone)]
1087pub struct SemanticsDismiss(Rc<dyn Fn() -> bool>);
1088
1089impl SemanticsDismiss {
1090    pub fn new(handler: impl Fn() -> bool + 'static) -> Self {
1091        Self(Rc::new(handler))
1092    }
1093
1094    pub fn invoke(&self) -> bool {
1095        (self.0)()
1096    }
1097}
1098
1099impl fmt::Debug for SemanticsDismiss {
1100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1101        f.write_str("SemanticsDismiss")
1102    }
1103}
1104
1105impl PartialEq for SemanticsDismiss {
1106    fn eq(&self, _other: &Self) -> bool {
1107        true
1108    }
1109}
1110
1111impl PartialEq for SemanticsSetText {
1112    fn eq(&self, _other: &Self) -> bool {
1113        true
1114    }
1115}
1116
1117impl PartialEq for SemanticsSetSelection {
1118    fn eq(&self, _other: &Self) -> bool {
1119        true
1120    }
1121}
1122
1123/// What a control does when a VoiceOver user makes the magic tap, the two
1124/// finger double tap that starts or stops the main action of a screen. On
1125/// the other platforms the action is listed by its label among the control's
1126/// actions. SwiftUI's `accessibilityAction(.magicTap)`; the answer says
1127/// whether the control took the tap.
1128#[derive(Clone)]
1129pub struct SemanticsMagicTap(Rc<dyn Fn() -> bool>);
1130
1131impl SemanticsMagicTap {
1132    pub fn new(handler: impl Fn() -> bool + 'static) -> Self {
1133        Self(Rc::new(handler))
1134    }
1135
1136    pub fn invoke(&self) -> bool {
1137        (self.0)()
1138    }
1139}
1140
1141impl fmt::Debug for SemanticsMagicTap {
1142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1143        f.write_str("SemanticsMagicTap")
1144    }
1145}
1146
1147impl PartialEq for SemanticsMagicTap {
1148    fn eq(&self, _other: &Self) -> bool {
1149        true
1150    }
1151}
1152
1153impl PartialEq for SemanticsSetProgress {
1154    fn eq(&self, _other: &Self) -> bool {
1155        true
1156    }
1157}
1158
1159impl Eq for SemanticsSetProgress {}
1160
1161/// How urgently a screen reader reads a node whose text changed on its own.
1162///
1163/// This is Compose's `LiveRegionMode` (`Modifier.semantics { liveRegion =
1164/// LiveRegionMode.Polite }`). A node without it stays silent until the reader
1165/// lands on it, which is wrong for a timer, a countdown, a status line, or an
1166/// error that appears next to a text field: a blind user hears nothing.
1167#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1168pub enum LiveRegionMode {
1169    /// Read the new text once the reader finishes what it says now.
1170    Polite,
1171    /// Cut off what the reader says now and read the new text at once. For
1172    /// text a user must hear immediately, such as an error that stops them.
1173    Assertive,
1174}
1175
1176/// A named accessibility operation, e.g. Compose's
1177/// `customActions = listOf(CustomAccessibilityAction("Pause") { … })`.
1178///
1179/// TalkBack surfaces these through its actions menu rather than by activating
1180/// the node, which is the only way to reach a command that has no on-screen
1181/// control — pausing a game whose whole surface is one tap-to-launch target.
1182#[derive(Clone)]
1183pub struct SemanticsCustomAction {
1184    /// What the screen reader reads out in its actions menu.
1185    pub label: String,
1186    handler: Rc<dyn Fn()>,
1187}
1188
1189impl SemanticsCustomAction {
1190    pub fn new(label: impl Into<String>, handler: impl Fn() + 'static) -> Self {
1191        Self {
1192            label: label.into(),
1193            handler: Rc::new(handler),
1194        }
1195    }
1196
1197    pub fn invoke(&self) {
1198        (self.handler)();
1199    }
1200}
1201
1202impl fmt::Debug for SemanticsCustomAction {
1203    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1204        f.debug_struct("SemanticsCustomAction")
1205            .field("label", &self.label)
1206            .finish_non_exhaustive()
1207    }
1208}
1209
1210impl PartialEq for SemanticsCustomAction {
1211    fn eq(&self, other: &Self) -> bool {
1212        self.label == other.label
1213    }
1214}
1215
1216impl Eq for SemanticsCustomAction {}
1217
1218/// A semantics node for content that is *drawn* rather than laid out.
1219///
1220/// An immediate-mode surface — one `Canvas` that paints a whole screen — has
1221/// exactly one layout node, so the semantics tree built from layout has exactly
1222/// one node to offer a screen reader. This is the escape hatch: the drawing
1223/// code already knows where it put every control, so it publishes those
1224/// rectangles as semantics directly. Android's own answer for a canvas-drawn
1225/// `View` is the same shape (`ExploreByTouchHelper` feeding virtual view ids
1226/// into an `AccessibilityNodeProvider`), and Cranpose's Android bridge is
1227/// already an `AccessibilityNodeProvider`, so these land as first-class
1228/// virtual views next to the ones layout produces.
1229///
1230/// `bounds` is in the publishing node's own coordinates (logical px, origin at
1231/// that node's top-left), because that is what a draw scope works in.
1232#[derive(Clone, Debug, PartialEq)]
1233pub struct CanvasSemanticsNode {
1234    /// Identity that must survive a redraw.
1235    ///
1236    /// A screen reader parks its cursor on a virtual view id; if the id for
1237    /// "the Haptics switch" changes when the list scrolls, the cursor jumps.
1238    /// Derive this from what the control *is* (a row index, an enum
1239    /// discriminant), never from where it currently sits.
1240    pub key: u64,
1241    /// Where the control was drawn, relative to the publishing node.
1242    pub bounds: cranpose_ui_graphics::Rect,
1243    pub label: String,
1244    pub role: Option<SemanticsWidgetRole>,
1245    /// Compose's `stateDescription` — what the control currently reads as
1246    /// ("CAMPAIGN", "3 of 18 gold"), spoken after the label and re-spoken on
1247    /// its own when only the state changed.
1248    pub state_description: Option<String>,
1249    /// Compose's `onClick(label = …)`. TalkBack reads it as "double tap to
1250    /// `<label>`", so it is a verb phrase, not a repeat of the label.
1251    pub on_click_label: Option<String>,
1252    pub clickable: bool,
1253    /// Compose's `selected`, for `Role.RadioButton`/`Role.Tab`.
1254    pub selected: Option<bool>,
1255    /// Compose's `toggleableState`, for `Role.Switch`/`Role.Checkbox`.
1256    pub toggled: Option<bool>,
1257    pub enabled: bool,
1258    pub custom_actions: Vec<SemanticsCustomAction>,
1259}
1260
1261impl Default for CanvasSemanticsNode {
1262    fn default() -> Self {
1263        Self {
1264            key: 0,
1265            bounds: cranpose_ui_graphics::Rect {
1266                x: 0.0,
1267                y: 0.0,
1268                width: 0.0,
1269                height: 0.0,
1270            },
1271            label: String::new(),
1272            role: None,
1273            state_description: None,
1274            on_click_label: None,
1275            clickable: false,
1276            selected: None,
1277            toggled: None,
1278            enabled: true,
1279            custom_actions: Vec::new(),
1280        }
1281    }
1282}
1283
1284impl CanvasSemanticsNode {
1285    /// A clickable control drawn at `bounds`.
1286    pub fn control(key: u64, bounds: cranpose_ui_graphics::Rect, label: impl Into<String>) -> Self {
1287        Self {
1288            key,
1289            bounds,
1290            label: label.into(),
1291            clickable: true,
1292            ..Self::default()
1293        }
1294    }
1295
1296    /// A drawn label that is read but not activated.
1297    pub fn text(key: u64, bounds: cranpose_ui_graphics::Rect, label: impl Into<String>) -> Self {
1298        Self {
1299            key,
1300            bounds,
1301            label: label.into(),
1302            ..Self::default()
1303        }
1304    }
1305
1306    pub fn with_role(mut self, role: SemanticsWidgetRole) -> Self {
1307        self.role = Some(role);
1308        self
1309    }
1310
1311    pub fn with_state_description(mut self, state: impl Into<String>) -> Self {
1312        self.state_description = Some(state.into());
1313        self
1314    }
1315
1316    pub fn with_click_label(mut self, label: impl Into<String>) -> Self {
1317        self.on_click_label = Some(label.into());
1318        self.clickable = true;
1319        self
1320    }
1321
1322    pub fn with_selected(mut self, selected: bool) -> Self {
1323        self.selected = Some(selected);
1324        self
1325    }
1326
1327    pub fn with_toggled(mut self, toggled: bool) -> Self {
1328        self.toggled = Some(toggled);
1329        self
1330    }
1331
1332    pub fn with_enabled(mut self, enabled: bool) -> Self {
1333        self.enabled = enabled;
1334        self
1335    }
1336
1337    pub fn with_custom_action(mut self, action: SemanticsCustomAction) -> Self {
1338        self.custom_actions.push(action);
1339        self
1340    }
1341}
1342
1343/// Semantics configuration for accessibility.
1344#[derive(Clone, Debug, PartialEq)]
1345pub struct SemanticsConfiguration {
1346    pub content_description: Option<String>,
1347    /// Compose's `stateDescription`.
1348    pub state_description: Option<String>,
1349    /// Compose's `onClick(label = …)`; implies clickable.
1350    pub on_click_label: Option<String>,
1351    /// Activation callback for controls whose pointer gesture is handled separately.
1352    /// Invoked by keyboard and native accessibility activation after validation.
1353    pub on_click: Option<SemanticsCustomAction>,
1354    /// What this control does when a screen reader asks for its long press.
1355    /// Compose's `onLongClick`.
1356    pub on_long_click: Option<SemanticsLongClick>,
1357    /// What the long press does, as a verb phrase a reader reads out:
1358    /// "Remove receipt". Compose's `onLongClick(label = …)`.
1359    pub on_long_click_label: Option<String>,
1360    /// What this control does on VoiceOver's magic tap, and on the other
1361    /// platforms as an action listed by [`Self::on_magic_tap_label`].
1362    /// SwiftUI's `accessibilityAction(.magicTap)`.
1363    pub on_magic_tap: Option<SemanticsMagicTap>,
1364    /// What the magic tap does, as a verb phrase a reader reads out: "Take
1365    /// the photo".
1366    pub on_magic_tap_label: Option<String>,
1367    /// The short names a person says to Voice Control to reach this control,
1368    /// when the name a reader hears is too long to say. SwiftUI's
1369    /// `accessibilityInputLabels`; iOS only.
1370    pub input_labels: Vec<String>,
1371    /// The language of this control's text as a BCP 47 tag, "de" or "pt-BR",
1372    /// so a reader picks the right voice for it. SwiftUI's
1373    /// `accessibilityLanguage`, ARIA's `lang`.
1374    pub language: Option<String>,
1375    /// Compose's `Role`.
1376    pub role: Option<SemanticsWidgetRole>,
1377    pub selected: Option<bool>,
1378    pub toggled: Option<bool>,
1379    pub enabled: bool,
1380    pub is_clickable: bool,
1381    pub is_editable_text: bool,
1382    /// Whether an editable field accepts line breaks, independent of its current text.
1383    pub multiline: bool,
1384    /// The text an editable field holds, read as its value. Compose's
1385    /// `editableText`.
1386    pub text: Option<String>,
1387    pub text_selection: Option<crate::text::TextRange>,
1388    pub custom_actions: Vec<SemanticsCustomAction>,
1389    /// Controls this node drew itself instead of laying out. See
1390    /// [`CanvasSemanticsNode`].
1391    pub canvas_children: Vec<CanvasSemanticsNode>,
1392    /// Whether this node takes over the screen: everything outside it is
1393    /// inert, and a screen reader keeps its traversal inside.
1394    pub is_modal: bool,
1395    /// Whether a screen reader skips this node and everything under it: a
1396    /// decorative image, or a placeholder drawn under a named field. Compose's
1397    /// `hideFromAccessibility`.
1398    pub hidden: bool,
1399    /// Whether a screen reader takes this node and the text under it as one
1400    /// stop, the way it does for a button: a row whose name, count and price
1401    /// belong together. Compose's `mergeDescendants`.
1402    pub merge_descendants: bool,
1403    /// Whether the selectable controls under this node form one group, so a
1404    /// screen reader says which of how many a tab or a radio button is.
1405    /// Compose's `selectableGroup`.
1406    pub selectable_group: bool,
1407    /// The title of the screen or pane this node is the root of, read out when
1408    /// the app moves to it. Compose's `paneTitle`.
1409    pub pane_title: Option<String>,
1410    /// Why the control's content is wrong, read after its state: "invalid,
1411    /// the amount needs a number". Compose's `error`.
1412    pub error: Option<String>,
1413    /// Whether this field holds a secret, so screen readers never receive its
1414    /// text and native editors use protected input. Compose's `password`.
1415    pub password: bool,
1416    /// Where a screen reader visits this node among the ones beside it: a
1417    /// smaller number comes first, and nodes left at zero keep the order the
1418    /// app laid them out in. Compose's `traversalIndex`.
1419    pub traversal_index: f32,
1420    /// Compose's `liveRegion`. When set, a screen reader reads this node again
1421    /// whenever its text changes, without the user moving to it.
1422    pub live_region: Option<LiveRegionMode>,
1423    /// Compose's `progressBarRangeInfo`. A control with it is adjustable: a
1424    /// screen reader offers its own way to move the value.
1425    pub progress: Option<ProgressBarRangeInfo>,
1426    /// What the control does when a screen reader moves its value. Compose's
1427    /// `setProgress`.
1428    pub set_progress: Option<SemanticsSetProgress>,
1429    /// What this field does when a screen reader or a voice tool hands it
1430    /// text. Compose's `setText`.
1431    pub set_text: Option<SemanticsSetText>,
1432    /// What this field does when a screen reader moves its caret or picks a
1433    /// stretch of its text. Compose's `setSelection`.
1434    pub set_selection: Option<SemanticsSetSelection>,
1435    /// What this control does when a screen reader asks it to open. A control
1436    /// that says so reads as closed. Compose's `expand`.
1437    pub expand: Option<SemanticsExpand>,
1438    /// What this control does when a screen reader asks to send it away: a row
1439    /// a sighted person swipes off, a sheet a sighted person taps outside of.
1440    /// Compose's `dismiss`.
1441    pub dismiss: Option<SemanticsDismiss>,
1442    /// What this control does when a screen reader asks it to close. A control
1443    /// that says so reads as open. Compose's `collapse`.
1444    pub collapse: Option<SemanticsExpand>,
1445    /// How far this container scrolled up and down. Compose's
1446    /// `verticalScrollAxisRange`.
1447    pub vertical_scroll: Option<ScrollAxisRange>,
1448    /// How far this container scrolled left and right. Compose's
1449    /// `horizontalScrollAxisRange`.
1450    pub horizontal_scroll: Option<ScrollAxisRange>,
1451    /// What this container does when a screen reader pages it. Compose's
1452    /// `scrollBy`.
1453    pub scroll_by: Option<SemanticsScrollBy>,
1454    /// What this list does when a screen reader asks for the row at an index,
1455    /// so a reader reaches row 300 without paging to it. Compose's
1456    /// `scrollToIndex`.
1457    pub scroll_to_index: Option<SemanticsScrollToIndex>,
1458    /// How many rows and columns this list holds. Compose's `collectionInfo`.
1459    pub collection: Option<CollectionInfo>,
1460}
1461
1462impl Default for SemanticsConfiguration {
1463    fn default() -> Self {
1464        Self {
1465            content_description: None,
1466            state_description: None,
1467            on_click_label: None,
1468            on_click: None,
1469            on_long_click: None,
1470            on_long_click_label: None,
1471            on_magic_tap: None,
1472            on_magic_tap_label: None,
1473            input_labels: Vec::new(),
1474            language: None,
1475            role: None,
1476            selected: None,
1477            toggled: None,
1478            enabled: true,
1479            is_clickable: false,
1480            is_editable_text: false,
1481            multiline: false,
1482            text: None,
1483            text_selection: None,
1484            custom_actions: Vec::new(),
1485            canvas_children: Vec::new(),
1486            is_modal: false,
1487            hidden: false,
1488            merge_descendants: false,
1489            selectable_group: false,
1490            pane_title: None,
1491            error: None,
1492            password: false,
1493            traversal_index: 0.0,
1494            live_region: None,
1495            progress: None,
1496            set_progress: None,
1497            set_text: None,
1498            set_selection: None,
1499            expand: None,
1500            dismiss: None,
1501            collapse: None,
1502            vertical_scroll: None,
1503            horizontal_scroll: None,
1504            scroll_by: None,
1505            scroll_to_index: None,
1506            collection: None,
1507        }
1508    }
1509}
1510
1511/// One value that says what a screen reader reads for a node, built up with
1512/// the methods below and handed to `Modifier::semantics_spec`.
1513///
1514/// Compose has no such value: it takes a receiver lambda, which Kotlin makes
1515/// read well and Rust has no match for. `SemanticsSpec::new().content_description("Save")`
1516/// reads better than `|config| config.content_description = Some("Save".into())`,
1517/// it is one chain element rather than one per property, and two specs can be
1518/// compared. The closure form stays as `Modifier::semantics` for parity.
1519pub type SemanticsSpec = SemanticsConfiguration;
1520
1521impl SemanticsConfiguration {
1522    /// An empty spec to build on. Every field is what it is with no semantics
1523    /// declared at all.
1524    pub fn new() -> Self {
1525        Self::default()
1526    }
1527
1528    /// The name a screen reader reads for the control. Compose's
1529    /// `contentDescription`.
1530    pub fn content_description(mut self, name: impl Into<String>) -> Self {
1531        self.content_description = Some(name.into());
1532        self
1533    }
1534
1535    /// What the control says about itself after its name. Compose's
1536    /// `stateDescription`.
1537    pub fn state_description(mut self, state: impl Into<String>) -> Self {
1538        self.state_description = Some(state.into());
1539        self
1540    }
1541
1542    /// A screen reader offers to activate the control. Compose's `onClick`.
1543    pub fn clickable(mut self) -> Self {
1544        self.is_clickable = true;
1545        self
1546    }
1547
1548    /// Handles keyboard and screen-reader activation without adding a pointer gesture.
1549    /// An empty label uses the platform's default activation instruction.
1550    pub fn on_click(mut self, label: impl Into<String>, action: impl Fn() + 'static) -> Self {
1551        self.on_click = Some(SemanticsCustomAction::new(label, action));
1552        self
1553    }
1554
1555    /// What the control does when a screen reader asks for its long press,
1556    /// and the verb phrase a reader reads out for it. Compose's
1557    /// `onLongClick(label) { … }`.
1558    pub fn on_long_click(
1559        mut self,
1560        label: impl Into<String>,
1561        action: impl Fn() -> bool + 'static,
1562    ) -> Self {
1563        self.on_long_click_label = Some(label.into());
1564        self.on_long_click = Some(SemanticsLongClick::new(action));
1565        self
1566    }
1567
1568    /// What the control does on VoiceOver's magic tap, and the verb phrase
1569    /// the other platforms list it under. SwiftUI's
1570    /// `accessibilityAction(.magicTap)`.
1571    pub fn on_magic_tap(
1572        mut self,
1573        label: impl Into<String>,
1574        action: impl Fn() -> bool + 'static,
1575    ) -> Self {
1576        self.on_magic_tap_label = Some(label.into());
1577        self.on_magic_tap = Some(SemanticsMagicTap::new(action));
1578        self
1579    }
1580
1581    /// The short names a person says to Voice Control to reach the control.
1582    /// SwiftUI's `accessibilityInputLabels`.
1583    pub fn input_labels<S: Into<String>>(mut self, labels: impl IntoIterator<Item = S>) -> Self {
1584        self.input_labels = labels.into_iter().map(Into::into).collect();
1585        self
1586    }
1587
1588    /// The language of the control's text, as a BCP 47 tag. SwiftUI's
1589    /// `accessibilityLanguage`, ARIA's `lang`.
1590    pub fn language(mut self, tag: impl Into<String>) -> Self {
1591        self.language = Some(tag.into());
1592        self
1593    }
1594
1595    /// Whether the control is on or off. Compose's `toggleableState`.
1596    pub fn toggled(mut self, toggled: bool) -> Self {
1597        self.toggled = Some(toggled);
1598        self
1599    }
1600
1601    /// Whether the control is the one picked out of a group. Compose's
1602    /// `selected`.
1603    pub fn selected(mut self, selected: bool) -> Self {
1604        self.selected = Some(selected);
1605        self
1606    }
1607
1608    /// What kind of control a screen reader reads this as. Compose's `Role`.
1609    pub fn role(mut self, role: SemanticsWidgetRole) -> Self {
1610        self.role = Some(role);
1611        self
1612    }
1613
1614    /// Reads as a heading, so a reader can jump between the headings of a
1615    /// screen. Compose's `heading()`.
1616    pub fn heading(self) -> Self {
1617        self.role(SemanticsWidgetRole::Header)
1618    }
1619
1620    /// Why the control's content is wrong. Compose's `error`.
1621    pub fn error(mut self, message: impl Into<String>) -> Self {
1622        self.error = Some(message.into());
1623        self
1624    }
1625
1626    /// Holds a secret, so no reader reads the text out. Compose's `password`.
1627    pub fn password(mut self) -> Self {
1628        self.password = true;
1629        self
1630    }
1631
1632    /// Names the screen or pane this node is the root of. Compose's
1633    /// `paneTitle`.
1634    pub fn pane_title(mut self, title: impl Into<String>) -> Self {
1635        self.pane_title = Some(title.into());
1636        self
1637    }
1638
1639    /// Where a reader visits this node among the ones beside it. Compose's
1640    /// `traversalIndex`.
1641    pub fn traversal_index(mut self, index: f32) -> Self {
1642        self.traversal_index = index;
1643        self
1644    }
1645
1646    /// Skips this node and everything under it. Compose's
1647    /// `hideFromAccessibility`.
1648    pub fn hidden(mut self) -> Self {
1649        self.hidden = true;
1650        self
1651    }
1652
1653    /// Takes this node and the text under it as one stop. Compose's
1654    /// `mergeDescendants`.
1655    pub fn merge_descendants(mut self) -> Self {
1656        self.merge_descendants = true;
1657        self
1658    }
1659
1660    /// Makes the selectable controls under this node one group. Compose's
1661    /// `selectableGroup`.
1662    pub fn selectable_group(mut self) -> Self {
1663        self.selectable_group = true;
1664        self
1665    }
1666
1667    /// Reads this node again whenever its text changes. Compose's
1668    /// `liveRegion`.
1669    pub fn live_region(mut self, mode: LiveRegionMode) -> Self {
1670        self.live_region = Some(mode);
1671        self
1672    }
1673    pub fn merge(&mut self, other: &SemanticsConfiguration) {
1674        if let Some(description) = &other.content_description {
1675            self.content_description = Some(description.clone());
1676        }
1677        if let Some(state) = &other.state_description {
1678            self.state_description = Some(state.clone());
1679        }
1680        if let Some(label) = &other.on_click_label {
1681            self.on_click_label = Some(label.clone());
1682        }
1683        if let Some(label) = &other.on_long_click_label {
1684            self.on_long_click_label = Some(label.clone());
1685        }
1686        if let Some(label) = &other.on_magic_tap_label {
1687            self.on_magic_tap_label = Some(label.clone());
1688        }
1689        if !other.input_labels.is_empty() {
1690            self.input_labels.clone_from(&other.input_labels);
1691        }
1692        if let Some(language) = &other.language {
1693            self.language = Some(language.clone());
1694        }
1695        if let Some(role) = other.role {
1696            self.role = Some(role);
1697        }
1698        if let Some(selected) = other.selected {
1699            self.selected = Some(selected);
1700        }
1701        if let Some(toggled) = other.toggled {
1702            self.toggled = Some(toggled);
1703        }
1704        self.enabled &= other.enabled;
1705        self.is_clickable |= other.is_clickable;
1706        self.is_editable_text |= other.is_editable_text;
1707        self.multiline |= other.multiline;
1708        if let Some(text) = &other.text {
1709            self.text = Some(text.clone());
1710        }
1711        self.is_modal |= other.is_modal;
1712        self.hidden |= other.hidden;
1713        self.merge_descendants |= other.merge_descendants;
1714        self.selectable_group |= other.selectable_group;
1715        self.password |= other.password;
1716        if other.traversal_index != 0.0 {
1717            self.traversal_index = other.traversal_index;
1718        }
1719        if let Some(live_region) = other.live_region {
1720            self.live_region = Some(live_region);
1721        }
1722        self.merge_words(other);
1723        self.merge_actions(other);
1724        self.merge_ranges(other);
1725    }
1726
1727    fn merge_words(&mut self, other: &SemanticsConfiguration) {
1728        if let Some(title) = &other.pane_title {
1729            self.pane_title = Some(title.clone());
1730        }
1731        if let Some(error) = &other.error {
1732            self.error = Some(error.clone());
1733        }
1734    }
1735
1736    fn merge_actions(&mut self, other: &SemanticsConfiguration) {
1737        if let Some(on_click) = &other.on_click {
1738            self.on_click = Some(on_click.clone());
1739        }
1740        self.custom_actions
1741            .extend(other.custom_actions.iter().cloned());
1742        self.canvas_children
1743            .extend(other.canvas_children.iter().cloned());
1744        if let Some(set_progress) = &other.set_progress {
1745            self.set_progress = Some(set_progress.clone());
1746        }
1747        if let Some(set_text) = &other.set_text {
1748            self.set_text = Some(set_text.clone());
1749        }
1750        if let Some(set_selection) = &other.set_selection {
1751            self.set_selection = Some(set_selection.clone());
1752        }
1753        if let Some(expand) = &other.expand {
1754            self.expand = Some(expand.clone());
1755        }
1756        if let Some(collapse) = &other.collapse {
1757            self.collapse = Some(collapse.clone());
1758        }
1759        if let Some(dismiss) = &other.dismiss {
1760            self.dismiss = Some(dismiss.clone());
1761        }
1762        if let Some(long_click) = &other.on_long_click {
1763            self.on_long_click = Some(long_click.clone());
1764        }
1765        if let Some(magic_tap) = &other.on_magic_tap {
1766            self.on_magic_tap = Some(magic_tap.clone());
1767        }
1768        if let Some(scroll_by) = &other.scroll_by {
1769            self.scroll_by = Some(scroll_by.clone());
1770        }
1771        if let Some(scroll_to_index) = &other.scroll_to_index {
1772            self.scroll_to_index = Some(scroll_to_index.clone());
1773        }
1774    }
1775
1776    fn merge_ranges(&mut self, other: &SemanticsConfiguration) {
1777        if let Some(selection) = other.text_selection {
1778            self.text_selection = Some(selection);
1779        }
1780        if let Some(progress) = other.progress {
1781            self.progress = Some(progress);
1782        }
1783        if let Some(range) = other.vertical_scroll {
1784            self.vertical_scroll = Some(range);
1785        }
1786        if let Some(range) = other.horizontal_scroll {
1787            self.horizontal_scroll = Some(range);
1788        }
1789        if let Some(collection) = other.collection {
1790            self.collection = Some(collection);
1791        }
1792    }
1793
1794    /// Whether a screen reader should offer activation. A named click label is
1795    /// how Compose declares `onClick`, so it implies the action the same way.
1796    pub fn is_activatable(&self) -> bool {
1797        self.is_clickable || self.on_click_label.is_some() || self.on_click.is_some()
1798    }
1799}
1800
1801impl fmt::Debug for dyn ModifierNode {
1802    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1803        f.debug_struct("ModifierNode").finish_non_exhaustive()
1804    }
1805}
1806
1807impl dyn ModifierNode {
1808    pub fn as_any(&self) -> &dyn Any {
1809        self
1810    }
1811
1812    pub fn as_any_mut(&mut self) -> &mut dyn Any {
1813        self
1814    }
1815}
1816
1817/// Strongly typed modifier elements that can create and update nodes while
1818/// exposing equality/hash/inspector contracts that mirror Jetpack Compose.
1819pub trait ModifierNodeElement: fmt::Debug + Hash + PartialEq + 'static {
1820    type Node: ModifierNode;
1821
1822    /// Creates a new modifier node instance for this element.
1823    fn create(&self) -> Self::Node;
1824
1825    /// Brings an existing modifier node up to date with the element's data.
1826    fn update(&self, node: &mut Self::Node);
1827
1828    /// Optional key used to disambiguate multiple instances of the same element type.
1829    fn key(&self) -> Option<u64> {
1830        None
1831    }
1832
1833    /// Human readable name surfaced to inspector tooling.
1834    fn inspector_name(&self) -> &'static str {
1835        type_name::<Self>()
1836    }
1837
1838    /// Records inspector properties for tooling.
1839    fn inspector_properties(&self, _inspector: &mut dyn FnMut(&'static str, String)) {}
1840
1841    /// Returns the capabilities of nodes created by this element.
1842    /// Override this to indicate which specialized traits the node implements.
1843    fn capabilities(&self) -> NodeCapabilities {
1844        NodeCapabilities::default()
1845    }
1846
1847    /// Whether this element requires `update` to be called even if `eq` returns true.
1848    ///
1849    /// This is useful for elements that ignore certain fields in `eq` (e.g. closures)
1850    /// to allow node reuse, but still need those fields updated in the existing node.
1851    /// Defaults to `false`.
1852    fn always_update(&self) -> bool {
1853        false
1854    }
1855
1856    /// Whether modifier reconciliation should request capability-wide invalidations
1857    /// after updating an existing node.
1858    fn auto_invalidate_on_update(&self) -> bool {
1859        true
1860    }
1861
1862    /// Optional targeted invalidation requested after updating an existing node.
1863    ///
1864    /// This is for nodes whose attach/remove capability is broader than the
1865    /// work needed for a value-only update. For example, an offset node
1866    /// participates in layout on attach but an x/y change only needs placement
1867    /// data and draw output refreshed.
1868    fn update_invalidation_kind(&self) -> Option<InvalidationKind> {
1869        None
1870    }
1871
1872    /// Composition locals this element hands to the content composed inside
1873    /// the node it decorates.
1874    ///
1875    /// A modifier that stands for something its subtree lives in, the way a
1876    /// window does, passes that thing down here instead of asking every
1877    /// composable in between to carry it as an argument.
1878    fn provided_composition_locals(&self) -> Vec<ProvidedValue> {
1879        Vec::new()
1880    }
1881}
1882
1883/// Capability flags indicating which specialized traits a modifier node implements.
1884#[derive(Clone, Copy, PartialEq, Eq, Hash)]
1885pub struct NodeCapabilities(u32);
1886
1887impl NodeCapabilities {
1888    /// No capabilities.
1889    pub const NONE: Self = Self(0);
1890    /// Modifier participates in measure/layout.
1891    pub const LAYOUT: Self = Self(1 << 0);
1892    /// Modifier participates in draw.
1893    pub const DRAW: Self = Self(1 << 1);
1894    /// Modifier participates in pointer input.
1895    pub const POINTER_INPUT: Self = Self(1 << 2);
1896    /// Modifier participates in semantics tree construction.
1897    pub const SEMANTICS: Self = Self(1 << 3);
1898    /// Modifier participates in modifier locals.
1899    pub const MODIFIER_LOCALS: Self = Self(1 << 4);
1900    /// Modifier participates in focus management.
1901    pub const FOCUS: Self = Self(1 << 5);
1902    /// Modifier makes its node the root of a separate window: the node's
1903    /// subtree is laid out into that window's size and drawn into that
1904    /// window's scene, and the parent's scene skips it.
1905    pub const WINDOW_ROOT: Self = Self(1 << 6);
1906    /// Modifier handles hardware keyboard events.
1907    pub const KEY_INPUT: Self = Self(1 << 7);
1908
1909    /// Returns an empty capability set.
1910    pub const fn empty() -> Self {
1911        Self::NONE
1912    }
1913
1914    /// Returns whether all bits in `other` are present in `self`.
1915    pub const fn contains(self, other: Self) -> bool {
1916        (self.0 & other.0) == other.0
1917    }
1918
1919    /// Returns whether any bit in `other` is present in `self`.
1920    pub const fn intersects(self, other: Self) -> bool {
1921        (self.0 & other.0) != 0
1922    }
1923
1924    /// Inserts the requested capability bits.
1925    pub fn insert(&mut self, other: Self) {
1926        self.0 |= other.0;
1927    }
1928
1929    /// Returns the raw bit representation.
1930    pub const fn bits(self) -> u32 {
1931        self.0
1932    }
1933
1934    /// Returns true when no capabilities are set.
1935    pub const fn is_empty(self) -> bool {
1936        self.0 == 0
1937    }
1938
1939    /// Returns the capability bit mask required for the given invalidation.
1940    pub const fn for_invalidation(kind: InvalidationKind) -> Self {
1941        match kind {
1942            InvalidationKind::Layout => Self::LAYOUT,
1943            InvalidationKind::Draw => Self::DRAW,
1944            InvalidationKind::PointerInput => Self::POINTER_INPUT,
1945            InvalidationKind::Semantics => Self::SEMANTICS,
1946            InvalidationKind::Focus => Self::FOCUS,
1947        }
1948    }
1949}
1950
1951impl Default for NodeCapabilities {
1952    fn default() -> Self {
1953        Self::NONE
1954    }
1955}
1956
1957impl fmt::Debug for NodeCapabilities {
1958    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1959        f.debug_struct("NodeCapabilities")
1960            .field("layout", &self.contains(Self::LAYOUT))
1961            .field("draw", &self.contains(Self::DRAW))
1962            .field("pointer_input", &self.contains(Self::POINTER_INPUT))
1963            .field("semantics", &self.contains(Self::SEMANTICS))
1964            .field("modifier_locals", &self.contains(Self::MODIFIER_LOCALS))
1965            .field("focus", &self.contains(Self::FOCUS))
1966            .field("window_root", &self.contains(Self::WINDOW_ROOT))
1967            .field("key_input", &self.contains(Self::KEY_INPUT))
1968            .finish()
1969    }
1970}
1971
1972impl BitOr for NodeCapabilities {
1973    type Output = Self;
1974
1975    fn bitor(self, rhs: Self) -> Self::Output {
1976        Self(self.0 | rhs.0)
1977    }
1978}
1979
1980impl BitOrAssign for NodeCapabilities {
1981    fn bitor_assign(&mut self, rhs: Self) {
1982        self.0 |= rhs.0;
1983    }
1984}
1985
1986/// The invalidations a node's modifier update requested: at most one per
1987/// [`InvalidationKind`], held inline so handing them over allocates nothing.
1988pub type ModifierInvalidations = smallvec::SmallVec<[ModifierInvalidation; 5]>;
1989
1990/// Records an invalidation request together with the capability mask that triggered it.
1991#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1992pub struct ModifierInvalidation {
1993    kind: InvalidationKind,
1994    capabilities: NodeCapabilities,
1995}
1996
1997impl ModifierInvalidation {
1998    /// Creates a new modifier invalidation entry.
1999    pub const fn new(kind: InvalidationKind, capabilities: NodeCapabilities) -> Self {
2000        Self { kind, capabilities }
2001    }
2002
2003    /// Returns the invalidated pipeline kind.
2004    pub const fn kind(self) -> InvalidationKind {
2005        self.kind
2006    }
2007
2008    /// Returns the capability mask associated with the invalidation.
2009    pub const fn capabilities(self) -> NodeCapabilities {
2010        self.capabilities
2011    }
2012}
2013
2014/// Type-erased modifier element used by the runtime to reconcile chains.
2015pub trait AnyModifierElement: fmt::Debug {
2016    fn node_type(&self) -> TypeId;
2017
2018    fn element_type(&self) -> TypeId;
2019
2020    /// A new node for this element, in the shared cell its chain entry keeps.
2021    fn create_node(&self) -> Rc<RefCell<dyn ModifierNode>>;
2022
2023    fn can_update_node(&self, node: &dyn ModifierNode) -> bool;
2024
2025    fn update_node(&self, node: &mut dyn ModifierNode);
2026
2027    fn key(&self) -> Option<u64>;
2028
2029    fn capabilities(&self) -> NodeCapabilities {
2030        NodeCapabilities::default()
2031    }
2032
2033    fn hash_code(&self) -> u64;
2034
2035    fn equals_element(&self, other: &dyn AnyModifierElement) -> bool;
2036
2037    fn inspector_name(&self) -> &'static str;
2038
2039    fn record_inspector_properties(&self, visitor: &mut dyn FnMut(&'static str, String));
2040
2041    fn requires_update(&self) -> bool;
2042
2043    fn auto_invalidates_on_update(&self) -> bool;
2044
2045    fn update_invalidation_kind(&self) -> Option<InvalidationKind>;
2046
2047    /// Whether this element has composition locals for its content.
2048    fn provides_composition_locals(&self) -> bool {
2049        false
2050    }
2051
2052    /// The composition locals this element hands to its content.
2053    fn provided_composition_locals(&self) -> Vec<ProvidedValue> {
2054        Vec::new()
2055    }
2056
2057    fn as_any(&self) -> &dyn Any;
2058}
2059
2060struct TypedModifierElement<E: ModifierNodeElement> {
2061    element: E,
2062    cached_hash: u64,
2063    provides_locals: bool,
2064}
2065
2066impl<E: ModifierNodeElement> TypedModifierElement<E> {
2067    fn new(element: E) -> Self {
2068        let mut hasher = default::new();
2069        element.hash(&mut hasher);
2070        let provides_locals = !element.provided_composition_locals().is_empty();
2071        Self {
2072            element,
2073            cached_hash: hasher.finish(),
2074            provides_locals,
2075        }
2076    }
2077}
2078
2079impl<E> fmt::Debug for TypedModifierElement<E>
2080where
2081    E: ModifierNodeElement,
2082{
2083    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2084        f.debug_struct("TypedModifierElement")
2085            .field("type", &type_name::<E>())
2086            .finish()
2087    }
2088}
2089
2090impl<E> AnyModifierElement for TypedModifierElement<E>
2091where
2092    E: ModifierNodeElement,
2093{
2094    fn node_type(&self) -> TypeId {
2095        TypeId::of::<E::Node>()
2096    }
2097
2098    fn element_type(&self) -> TypeId {
2099        TypeId::of::<E>()
2100    }
2101
2102    fn create_node(&self) -> Rc<RefCell<dyn ModifierNode>> {
2103        Rc::new(RefCell::new(self.element.create()))
2104    }
2105
2106    fn can_update_node(&self, node: &dyn ModifierNode) -> bool {
2107        node.as_any().is::<E::Node>()
2108    }
2109
2110    fn update_node(&self, node: &mut dyn ModifierNode) {
2111        if let Some(typed) = node.as_any_mut().downcast_mut::<E::Node>() {
2112            self.element.update(typed);
2113        }
2114    }
2115
2116    fn key(&self) -> Option<u64> {
2117        self.element.key()
2118    }
2119
2120    fn capabilities(&self) -> NodeCapabilities {
2121        self.element.capabilities()
2122    }
2123
2124    fn provides_composition_locals(&self) -> bool {
2125        self.provides_locals
2126    }
2127
2128    fn provided_composition_locals(&self) -> Vec<ProvidedValue> {
2129        self.element.provided_composition_locals()
2130    }
2131
2132    fn hash_code(&self) -> u64 {
2133        self.cached_hash
2134    }
2135
2136    fn equals_element(&self, other: &dyn AnyModifierElement) -> bool {
2137        other
2138            .as_any()
2139            .downcast_ref::<Self>()
2140            .is_some_and(|typed| typed.element == self.element)
2141    }
2142
2143    fn inspector_name(&self) -> &'static str {
2144        self.element.inspector_name()
2145    }
2146
2147    fn record_inspector_properties(&self, visitor: &mut dyn FnMut(&'static str, String)) {
2148        self.element.inspector_properties(visitor);
2149    }
2150
2151    fn requires_update(&self) -> bool {
2152        self.element.always_update()
2153    }
2154
2155    fn auto_invalidates_on_update(&self) -> bool {
2156        self.element.auto_invalidate_on_update()
2157    }
2158
2159    fn update_invalidation_kind(&self) -> Option<InvalidationKind> {
2160        self.element.update_invalidation_kind()
2161    }
2162
2163    fn as_any(&self) -> &dyn Any {
2164        self
2165    }
2166}
2167
2168fn request_update_auto_invalidations(
2169    element: &dyn AnyModifierElement,
2170    context: &mut dyn ModifierNodeContext,
2171    capabilities: NodeCapabilities,
2172) {
2173    if let Some(kind) = element.update_invalidation_kind() {
2174        let capabilities = NodeCapabilities::for_invalidation(kind);
2175        context.push_active_capabilities(capabilities);
2176        context.invalidate(kind);
2177        context.pop_active_capabilities();
2178    } else if element.auto_invalidates_on_update() {
2179        request_auto_invalidations(context, capabilities);
2180    }
2181}
2182
2183/// Convenience helper for callers to construct a type-erased modifier
2184/// element without having to mention the internal wrapper type.
2185pub fn modifier_element<E: ModifierNodeElement>(element: E) -> DynModifierElement {
2186    Rc::new(TypedModifierElement::new(element))
2187}
2188
2189/// Boxed type-erased modifier element.
2190pub type DynModifierElement = Rc<dyn AnyModifierElement>;
2191
2192#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2193enum TraversalDirection {
2194    Forward,
2195    Backward,
2196}
2197
2198/// Iterator walking a modifier chain by indexing into `ordered_nodes`.
2199///
2200/// This avoids the per-step `RefCell::borrow()` + `NodeLink::clone()` cost
2201/// of following the linked-list through `NodeState::child`/`parent`.
2202pub struct ModifierChainIter<'a> {
2203    chain: &'a ModifierNodeChain,
2204    cursor: usize,
2205    remaining: usize,
2206    direction: TraversalDirection,
2207}
2208
2209impl<'a> ModifierChainIter<'a> {
2210    fn forward(chain: &'a ModifierNodeChain) -> Self {
2211        Self {
2212            chain,
2213            cursor: 0,
2214            remaining: chain.ordered_nodes.len(),
2215            direction: TraversalDirection::Forward,
2216        }
2217    }
2218
2219    fn backward(chain: &'a ModifierNodeChain) -> Self {
2220        let len = chain.ordered_nodes.len();
2221        Self {
2222            chain,
2223            cursor: len.wrapping_sub(1),
2224            remaining: len,
2225            direction: TraversalDirection::Backward,
2226        }
2227    }
2228}
2229
2230impl<'a> Iterator for ModifierChainIter<'a> {
2231    type Item = ModifierChainNodeRef<'a>;
2232
2233    #[inline]
2234    fn next(&mut self) -> Option<Self::Item> {
2235        if self.remaining == 0 {
2236            return None;
2237        }
2238        let (link, caps, agg) = self.chain.ordered_nodes[self.cursor];
2239        let node_ref = self.chain.make_node_ref_with_caps(link, caps, agg);
2240        self.remaining -= 1;
2241        match self.direction {
2242            TraversalDirection::Forward => self.cursor += 1,
2243            TraversalDirection::Backward => self.cursor = self.cursor.wrapping_sub(1),
2244        }
2245        Some(node_ref)
2246    }
2247
2248    #[inline]
2249    fn size_hint(&self) -> (usize, Option<usize>) {
2250        (self.remaining, Some(self.remaining))
2251    }
2252}
2253
2254impl ExactSizeIterator for ModifierChainIter<'_> {}
2255impl std::iter::FusedIterator for ModifierChainIter<'_> {}
2256
2257#[derive(Debug)]
2258struct ModifierNodeEntry {
2259    key: Option<u64>,
2260    hash_code: u64,
2261    element: DynModifierElement,
2262    node: Rc<RefCell<dyn ModifierNode>>,
2263    capabilities: NodeCapabilities,
2264}
2265
2266impl ModifierNodeEntry {
2267    fn new(
2268        key: Option<u64>,
2269        element: DynModifierElement,
2270        node: Rc<RefCell<dyn ModifierNode>>,
2271        hash_code: u64,
2272        capabilities: NodeCapabilities,
2273    ) -> Self {
2274        let entry = Self {
2275            key,
2276            hash_code,
2277            element,
2278            node,
2279            capabilities,
2280        };
2281        entry
2282            .node
2283            .borrow()
2284            .node_state()
2285            .set_capabilities(entry.capabilities);
2286        entry
2287    }
2288}
2289
2290fn visit_node_tree_mut(
2291    node: &mut dyn ModifierNode,
2292    visitor: &mut dyn FnMut(&mut dyn ModifierNode),
2293) {
2294    visitor(node);
2295    node.for_each_delegate_mut(&mut |child| visit_node_tree_mut(child, visitor));
2296}
2297
2298fn nth_delegate(node: &dyn ModifierNode, target: usize) -> Option<&dyn ModifierNode> {
2299    let mut current = 0usize;
2300    let mut result: Option<&dyn ModifierNode> = None;
2301    node.for_each_delegate(&mut |child| {
2302        if result.is_none() && current == target {
2303            result = Some(child);
2304        }
2305        current += 1;
2306    });
2307    result
2308}
2309
2310fn nth_delegate_mut(node: &mut dyn ModifierNode, target: usize) -> Option<&mut dyn ModifierNode> {
2311    let mut current = 0usize;
2312    let mut result: Option<&mut dyn ModifierNode> = None;
2313    node.for_each_delegate_mut(&mut |child| {
2314        if result.is_none() && current == target {
2315            result = Some(child);
2316        }
2317        current += 1;
2318    });
2319    result
2320}
2321
2322fn with_node_context<F, R>(
2323    node: &mut dyn ModifierNode,
2324    context: &mut dyn ModifierNodeContext,
2325    f: F,
2326) -> R
2327where
2328    F: FnOnce(&mut dyn ModifierNode, &mut dyn ModifierNodeContext) -> R,
2329{
2330    context.push_active_capabilities(node.node_state().capabilities());
2331    let result = f(node, context);
2332    context.pop_active_capabilities();
2333    result
2334}
2335
2336fn request_auto_invalidations(
2337    context: &mut dyn ModifierNodeContext,
2338    capabilities: NodeCapabilities,
2339) {
2340    if capabilities.is_empty() {
2341        return;
2342    }
2343
2344    context.push_active_capabilities(capabilities);
2345
2346    if capabilities.contains(NodeCapabilities::LAYOUT) {
2347        context.invalidate(InvalidationKind::Layout);
2348    }
2349    if capabilities.contains(NodeCapabilities::DRAW) {
2350        context.invalidate(InvalidationKind::Draw);
2351    }
2352    if capabilities.contains(NodeCapabilities::POINTER_INPUT) {
2353        context.invalidate(InvalidationKind::PointerInput);
2354    }
2355    if capabilities.contains(NodeCapabilities::SEMANTICS) {
2356        context.invalidate(InvalidationKind::Semantics);
2357    }
2358    if capabilities.contains(NodeCapabilities::FOCUS) {
2359        context.invalidate(InvalidationKind::Focus);
2360    }
2361
2362    context.pop_active_capabilities();
2363}
2364
2365fn attach_node_tree(node: &mut dyn ModifierNode, context: &mut dyn ModifierNodeContext) {
2366    visit_node_tree_mut(node, &mut |n| {
2367        if !n.node_state().is_attached() {
2368            n.node_state().set_attached(true);
2369            with_node_context(n, context, |node, ctx| node.on_attach(ctx));
2370        }
2371    });
2372}
2373
2374fn reset_node_tree(node: &mut dyn ModifierNode) {
2375    visit_node_tree_mut(node, &mut |n| n.on_reset());
2376}
2377
2378fn detach_node_tree(node: &mut dyn ModifierNode) {
2379    visit_node_tree_mut(node, &mut |n| {
2380        if n.node_state().is_attached() {
2381            n.on_detach();
2382            n.node_state().set_attached(false);
2383        }
2384        n.node_state().set_parent_link(None);
2385        n.node_state().set_child_link(None);
2386        n.node_state()
2387            .set_aggregate_child_capabilities(NodeCapabilities::empty());
2388    });
2389}
2390
2391/// Chain of modifier nodes attached to a layout node.
2392///
2393/// The chain tracks ownership of modifier nodes and reuses them across
2394/// updates when the incoming element list still contains a node of the
2395/// same type. Removed nodes detach automatically so callers do not need
2396/// to manually manage their lifetimes.
2397pub struct ModifierNodeChain {
2398    entries: Vec<ModifierNodeEntry>,
2399    aggregated_capabilities: NodeCapabilities,
2400    head_aggregate_child_capabilities: NodeCapabilities,
2401    head_sentinel: SentinelNode,
2402    tail_sentinel: SentinelNode,
2403    ordered_nodes: Vec<(NodeLink, NodeCapabilities, NodeCapabilities)>,
2404}
2405
2406/// Buffers the reconciliation of a chain reuses, held per thread rather than
2407/// per chain: a chain kept its own after its first reconciliation, a few
2408/// hundred bytes on every node of a tree.
2409#[derive(Default)]
2410struct ReconcileScratch {
2411    old_used: Vec<bool>,
2412    match_order: Vec<Option<usize>>,
2413    final_slots: Vec<Option<ModifierNodeEntry>>,
2414    elements: Vec<DynModifierElement>,
2415}
2416
2417thread_local! {
2418    static RECONCILE_SCRATCH: std::cell::RefCell<ReconcileScratch> =
2419        std::cell::RefCell::default();
2420}
2421
2422struct SentinelNode {
2423    state: NodeState,
2424}
2425
2426impl SentinelNode {
2427    fn new() -> Self {
2428        Self {
2429            state: NodeState::sentinel(),
2430        }
2431    }
2432}
2433
2434impl DelegatableNode for SentinelNode {
2435    fn node_state(&self) -> &NodeState {
2436        &self.state
2437    }
2438}
2439
2440impl ModifierNode for SentinelNode {}
2441
2442#[derive(Clone)]
2443pub struct ModifierChainNodeRef<'a> {
2444    chain: &'a ModifierNodeChain,
2445    link: NodeLink,
2446    cached_capabilities: Option<NodeCapabilities>,
2447    cached_aggregate_child: Option<NodeCapabilities>,
2448}
2449
2450impl Default for ModifierNodeChain {
2451    fn default() -> Self {
2452        Self::new()
2453    }
2454}
2455
2456struct EntryIndex {
2457    keyed: HashMap<(TypeId, TypeId, u64), Vec<usize>>,
2458    hashed: HashMap<(TypeId, TypeId, u64), Vec<usize>>,
2459    typed: HashMap<(TypeId, TypeId), Vec<usize>>,
2460}
2461
2462struct EntryMatchQuery<'a> {
2463    element_type: TypeId,
2464    node_type: TypeId,
2465    key: Option<u64>,
2466    hash_code: u64,
2467    element: &'a DynModifierElement,
2468}
2469
2470impl EntryIndex {
2471    fn build(entries: &[ModifierNodeEntry]) -> Self {
2472        let mut keyed = HashMap::default();
2473        let mut hashed = HashMap::default();
2474        let mut typed = HashMap::default();
2475
2476        for (i, entry) in entries.iter().enumerate() {
2477            let (element_type, node_type) =
2478                (entry.element.element_type(), entry.element.node_type());
2479            if let Some(key_value) = entry.key {
2480                keyed
2481                    .entry((element_type, node_type, key_value))
2482                    .or_insert_with(Vec::new)
2483                    .push(i);
2484            } else {
2485                hashed
2486                    .entry((element_type, node_type, entry.hash_code))
2487                    .or_insert_with(Vec::new)
2488                    .push(i);
2489                typed
2490                    .entry((element_type, node_type))
2491                    .or_insert_with(Vec::new)
2492                    .push(i);
2493            }
2494        }
2495
2496        Self {
2497            keyed,
2498            hashed,
2499            typed,
2500        }
2501    }
2502
2503    fn find_match(
2504        &self,
2505        entries: &[ModifierNodeEntry],
2506        used: &[bool],
2507        query: EntryMatchQuery<'_>,
2508    ) -> Option<usize> {
2509        if let Some(key_value) = query.key {
2510            if let Some(candidates) =
2511                self.keyed
2512                    .get(&(query.element_type, query.node_type, key_value))
2513            {
2514                for &i in candidates {
2515                    if !used[i] {
2516                        return Some(i);
2517                    }
2518                }
2519            }
2520        } else {
2521            if let Some(candidates) =
2522                self.hashed
2523                    .get(&(query.element_type, query.node_type, query.hash_code))
2524            {
2525                for &i in candidates {
2526                    if !used[i]
2527                        && entries[i]
2528                            .element
2529                            .as_ref()
2530                            .equals_element(query.element.as_ref())
2531                    {
2532                        return Some(i);
2533                    }
2534                }
2535            }
2536
2537            if let Some(candidates) = self.typed.get(&(query.element_type, query.node_type)) {
2538                for &i in candidates {
2539                    if !used[i] {
2540                        return Some(i);
2541                    }
2542                }
2543            }
2544        }
2545
2546        None
2547    }
2548}
2549
2550/// Updates `entry` for `element` where it is, when the element is of the
2551/// entry's type and key and its node can take it: an element of a node's
2552/// type and key updates the node whatever its content, as Compose does.
2553/// `false` leaves the entry as it was.
2554fn update_entry_in_place(
2555    entry: &mut ModifierNodeEntry,
2556    element: &DynModifierElement,
2557    context: &mut dyn ModifierNodeContext,
2558) -> bool {
2559    if entry.element.element_type() != element.element_type()
2560        || entry.element.node_type() != element.node_type()
2561        || entry.key != element.key()
2562        || !element.can_update_node(&*entry.node.borrow())
2563    {
2564        return false;
2565    }
2566    let same_element = entry.element.as_ref().equals_element(element.as_ref());
2567    let capabilities = element.capabilities();
2568    attach_if_detached(entry, context);
2569    if !same_element || element.requires_update() {
2570        element.update_node(&mut *entry.node.borrow_mut());
2571        entry.element = element.clone();
2572        entry.hash_code = element.hash_code();
2573        request_update_auto_invalidations(element.as_ref(), context, capabilities);
2574    }
2575    entry.capabilities = capabilities;
2576    entry
2577        .node
2578        .borrow()
2579        .node_state()
2580        .set_capabilities(capabilities);
2581    true
2582}
2583
2584fn attach_if_detached(entry: &ModifierNodeEntry, context: &mut dyn ModifierNodeContext) {
2585    let attached = entry.node.borrow().node_state().is_attached();
2586    if !attached {
2587        attach_node_tree(&mut *entry.node.borrow_mut(), context);
2588    }
2589}
2590
2591/// A new entry for `element` and its node, attached and updated.
2592fn fresh_entry(
2593    element: DynModifierElement,
2594    context: &mut dyn ModifierNodeContext,
2595) -> ModifierNodeEntry {
2596    let capabilities = element.capabilities();
2597    let entry = ModifierNodeEntry::new(
2598        element.key(),
2599        element.clone(),
2600        element.create_node(),
2601        element.hash_code(),
2602        capabilities,
2603    );
2604    attach_node_tree(&mut *entry.node.borrow_mut(), context);
2605    element.update_node(&mut *entry.node.borrow_mut());
2606    request_auto_invalidations(context, capabilities);
2607    entry
2608}
2609
2610/// Matches `element`, at `new_pos` of the rebuilt tail, against the old
2611/// entries: one of its type and key whose node can take it is updated and
2612/// marked for `new_pos`, and `None` is returned; otherwise the element's
2613/// new entry is.
2614fn reconcile_element(
2615    element: DynModifierElement,
2616    new_pos: usize,
2617    (old_entries, index): (&mut [ModifierNodeEntry], &EntryIndex),
2618    (used, match_order): (&mut [bool], &mut [Option<usize>]),
2619    context: &mut dyn ModifierNodeContext,
2620) -> Option<ModifierNodeEntry> {
2621    let element_type = element.element_type();
2622    let node_type = element.node_type();
2623    let key = element.key();
2624    let hash_code = element.hash_code();
2625    let capabilities = element.capabilities();
2626    let Some(idx) = index.find_match(
2627        old_entries,
2628        used,
2629        EntryMatchQuery {
2630            element_type,
2631            node_type,
2632            key,
2633            hash_code,
2634            element: &element,
2635        },
2636    ) else {
2637        return Some(fresh_entry(element, context));
2638    };
2639    let entry = &mut old_entries[idx];
2640    if !element.can_update_node(&*entry.node.borrow()) {
2641        return Some(fresh_entry(element, context));
2642    }
2643
2644    used[idx] = true;
2645    match_order[idx] = Some(new_pos);
2646    let same_element = entry.element.as_ref().equals_element(element.as_ref());
2647    attach_if_detached(entry, context);
2648    if !same_element || element.requires_update() {
2649        element.update_node(&mut *entry.node.borrow_mut());
2650        entry.element = element;
2651        entry.hash_code = hash_code;
2652        request_update_auto_invalidations(entry.element.as_ref(), context, capabilities);
2653    }
2654    if idx != new_pos {
2655        request_auto_invalidations(context, capabilities);
2656    }
2657    entry.key = key;
2658    entry.capabilities = capabilities;
2659    entry
2660        .node
2661        .borrow()
2662        .node_state()
2663        .set_capabilities(capabilities);
2664    None
2665}
2666
2667impl ModifierNodeChain {
2668    pub fn new() -> Self {
2669        let mut chain = Self {
2670            entries: Vec::new(),
2671            aggregated_capabilities: NodeCapabilities::empty(),
2672            head_aggregate_child_capabilities: NodeCapabilities::empty(),
2673            head_sentinel: SentinelNode::new(),
2674            tail_sentinel: SentinelNode::new(),
2675            ordered_nodes: Vec::new(),
2676        };
2677        chain.sync_chain_links();
2678        chain
2679    }
2680
2681    /// Detaches all nodes in the chain.
2682    pub fn detach_nodes(&mut self) {
2683        for entry in &self.entries {
2684            detach_node_tree(&mut *entry.node.borrow_mut());
2685        }
2686    }
2687
2688    /// Attaches all nodes in the chain.
2689    pub fn attach_nodes(&mut self, context: &mut dyn ModifierNodeContext) {
2690        for entry in &self.entries {
2691            attach_node_tree(&mut *entry.node.borrow_mut(), context);
2692        }
2693    }
2694
2695    /// Rebuilds the internal chain links (parent/child relationships).
2696    /// This should be called if nodes have been detached but are intended to be reused.
2697    pub fn repair_chain(&mut self) {
2698        self.sync_chain_links();
2699    }
2700
2701    /// Reconcile the chain against the provided elements, attaching newly
2702    /// created nodes and detaching nodes that are no longer required.
2703    ///
2704    /// This method delegates to `update_from_ref_iter` which handles the
2705    /// actual reconciliation logic.
2706    pub fn update_from_slice(
2707        &mut self,
2708        elements: &[DynModifierElement],
2709        context: &mut dyn ModifierNodeContext,
2710    ) {
2711        self.update_from_ref_iter(elements.iter(), context);
2712    }
2713
2714    /// Reconcile the chain against the provided iterator of element references.
2715    ///
2716    /// This is the preferred method as it avoids requiring a collected slice,
2717    /// enabling zero-allocation traversal of modifier trees.
2718    /// Updates in place the entries whose elements are of `element_type`,
2719    /// for `elements` that differ from the chain's only in those: entries and
2720    /// elements line up one to one, and every other entry stays as it is.
2721    /// `false` when they do not line up or an entry cannot take its element;
2722    /// a full update then reconciles the chain.
2723    pub fn update_entries_of_type<'a, I>(
2724        &mut self,
2725        elements: I,
2726        element_count: usize,
2727        element_type: TypeId,
2728        context: &mut dyn ModifierNodeContext,
2729    ) -> bool
2730    where
2731        I: Iterator<Item = &'a DynModifierElement>,
2732    {
2733        if element_count != self.entries.len() {
2734            return false;
2735        }
2736        self.entries
2737            .iter_mut()
2738            .zip(elements)
2739            .filter(|(_, element)| element.element_type() == element_type)
2740            .all(|(entry, element)| update_entry_in_place(entry, element, context))
2741    }
2742
2743    pub fn update_from_ref_iter<'a, I>(
2744        &mut self,
2745        elements: I,
2746        context: &mut dyn ModifierNodeContext,
2747    ) where
2748        I: Iterator<Item = &'a DynModifierElement>,
2749    {
2750        // Taken out for the reconciliation, so one that reconciles another
2751        // chain inside it works on buffers of its own.
2752        let mut scratch = RECONCILE_SCRATCH
2753            .try_with(|scratch| std::mem::take(&mut *scratch.borrow_mut()))
2754            .unwrap_or_default();
2755        self.reconcile(elements, context, &mut scratch);
2756        scratch.elements.clear();
2757        scratch.final_slots.clear();
2758        let _ = RECONCILE_SCRATCH.try_with(|slot| *slot.borrow_mut() = scratch);
2759    }
2760
2761    fn reconcile<'a, I>(
2762        &mut self,
2763        elements: I,
2764        context: &mut dyn ModifierNodeContext,
2765        scratch: &mut ReconcileScratch,
2766    ) where
2767        I: Iterator<Item = &'a DynModifierElement>,
2768    {
2769        let old_len = self.entries.len();
2770        let mut fast_path_failed_at: Option<usize> = None;
2771        let mut elements_count = 0;
2772
2773        scratch.elements.clear();
2774
2775        for (idx, element) in elements.enumerate() {
2776            elements_count = idx + 1;
2777            if fast_path_failed_at.is_none() && idx < old_len {
2778                if update_entry_in_place(&mut self.entries[idx], element, context) {
2779                    continue;
2780                }
2781                fast_path_failed_at = Some(idx);
2782            }
2783            scratch.elements.push(element.clone());
2784        }
2785
2786        if fast_path_failed_at.is_none() && scratch.elements.is_empty() {
2787            if elements_count < self.entries.len() {
2788                for entry in self.entries.drain(elements_count..) {
2789                    request_auto_invalidations(context, entry.capabilities);
2790                    detach_node_tree(&mut *entry.node.borrow_mut());
2791                }
2792            }
2793            self.sync_chain_links();
2794            return;
2795        }
2796
2797        self.rebuild_tail(fast_path_failed_at.unwrap_or(old_len), context, scratch);
2798    }
2799
2800    /// Rebuilds the chain from `fail_idx` for the elements in `scratch`:
2801    /// each takes over an old entry of its type and key, wherever it was,
2802    /// or gets a node of its own; old entries nothing took over detach.
2803    fn rebuild_tail(
2804        &mut self,
2805        fail_idx: usize,
2806        context: &mut dyn ModifierNodeContext,
2807        scratch: &mut ReconcileScratch,
2808    ) {
2809        let mut old_entries: Vec<ModifierNodeEntry> = self.entries.drain(fail_idx..).collect();
2810        let processed_entries_len = self.entries.len();
2811        let old_len = old_entries.len();
2812
2813        scratch.old_used.clear();
2814        scratch.old_used.resize(old_len, false);
2815
2816        scratch.match_order.clear();
2817        scratch.match_order.resize(old_len, None);
2818
2819        let index = EntryIndex::build(&old_entries);
2820
2821        let new_elements_count = scratch.elements.len();
2822        scratch.final_slots.clear();
2823        scratch.final_slots.reserve(new_elements_count);
2824
2825        for (new_pos, element) in scratch.elements.drain(..).enumerate() {
2826            let fresh = reconcile_element(
2827                element,
2828                new_pos,
2829                (&mut old_entries, &index),
2830                (&mut scratch.old_used, &mut scratch.match_order),
2831                context,
2832            );
2833            scratch.final_slots.push(fresh);
2834        }
2835
2836        for (i, entry) in old_entries.into_iter().enumerate() {
2837            match scratch.match_order[i] {
2838                Some(pos) if scratch.old_used[i] => scratch.final_slots[pos] = Some(entry),
2839                _ => {
2840                    request_auto_invalidations(context, entry.capabilities);
2841                    detach_node_tree(&mut *entry.node.borrow_mut());
2842                }
2843            }
2844        }
2845
2846        self.entries.reserve(scratch.final_slots.len());
2847        for slot in scratch.final_slots.drain(..) {
2848            if let Some(entry) = slot {
2849                self.entries.push(entry);
2850            } else {
2851                log::error!("modifier reconciliation produced an empty final slot");
2852            }
2853        }
2854
2855        debug_assert_eq!(
2856            self.entries.len(),
2857            processed_entries_len + new_elements_count
2858        );
2859        self.sync_chain_links();
2860    }
2861
2862    /// Convenience wrapper that accepts any iterator of type-erased
2863    /// modifier elements. Elements are collected into a temporary vector
2864    /// before reconciliation.
2865    pub fn update<I>(&mut self, elements: I, context: &mut dyn ModifierNodeContext)
2866    where
2867        I: IntoIterator<Item = DynModifierElement>,
2868    {
2869        let collected: Vec<DynModifierElement> = elements.into_iter().collect();
2870        self.update_from_slice(&collected, context);
2871    }
2872
2873    /// Resets all nodes in the chain. This mirrors the behaviour of
2874    /// Jetpack Compose's `onReset` callback.
2875    pub fn reset(&mut self) {
2876        for entry in &mut self.entries {
2877            reset_node_tree(&mut *entry.node.borrow_mut());
2878        }
2879    }
2880
2881    /// Detaches every node in the chain and clears internal storage.
2882    pub fn detach_all(&mut self) {
2883        for entry in std::mem::take(&mut self.entries) {
2884            detach_node_tree(&mut *entry.node.borrow_mut());
2885            {
2886                let node_borrow = entry.node.borrow();
2887                let state = node_borrow.node_state();
2888                state.set_capabilities(NodeCapabilities::empty());
2889            }
2890        }
2891        self.aggregated_capabilities = NodeCapabilities::empty();
2892        self.head_aggregate_child_capabilities = NodeCapabilities::empty();
2893        self.ordered_nodes.clear();
2894        self.sync_chain_links();
2895    }
2896
2897    pub fn len(&self) -> usize {
2898        self.entries.len()
2899    }
2900
2901    pub fn is_empty(&self) -> bool {
2902        self.entries.is_empty()
2903    }
2904
2905    /// Returns the aggregated capability mask for the entire chain.
2906    pub fn capabilities(&self) -> NodeCapabilities {
2907        self.aggregated_capabilities
2908    }
2909
2910    /// Returns true if the chain contains at least one node with the requested capability.
2911    pub fn has_capability(&self, capability: NodeCapabilities) -> bool {
2912        self.aggregated_capabilities.contains(capability)
2913    }
2914
2915    /// Returns the sentinel head reference for traversal.
2916    pub fn head(&self) -> ModifierChainNodeRef<'_> {
2917        self.make_node_ref(NodeLink::Head)
2918    }
2919
2920    /// Returns the sentinel tail reference for traversal.
2921    pub fn tail(&self) -> ModifierChainNodeRef<'_> {
2922        self.make_node_ref(NodeLink::Tail)
2923    }
2924
2925    /// Iterates over the chain from head to tail, skipping sentinels.
2926    pub fn head_to_tail(&self) -> ModifierChainIter<'_> {
2927        ModifierChainIter::forward(self)
2928    }
2929
2930    /// Iterates over the chain from tail to head, skipping sentinels.
2931    pub fn tail_to_head(&self) -> ModifierChainIter<'_> {
2932        ModifierChainIter::backward(self)
2933    }
2934
2935    /// Calls `f` for every node in insertion order.
2936    pub fn for_each_forward<F>(&self, mut f: F)
2937    where
2938        F: FnMut(ModifierChainNodeRef<'_>),
2939    {
2940        for node in self.head_to_tail() {
2941            f(node);
2942        }
2943    }
2944
2945    /// Calls `f` for every node containing any capability from `mask`.
2946    pub fn for_each_forward_matching<F>(&self, mask: NodeCapabilities, mut f: F)
2947    where
2948        F: FnMut(ModifierChainNodeRef<'_>),
2949    {
2950        if mask.is_empty() {
2951            self.for_each_forward(f);
2952            return;
2953        }
2954
2955        if !self.head().aggregate_child_capabilities().intersects(mask) {
2956            return;
2957        }
2958
2959        for node in self.head_to_tail() {
2960            if node.kind_set().intersects(mask) {
2961                f(node);
2962            }
2963        }
2964    }
2965
2966    /// Calls `f` for every node containing any capability from `mask`, providing the node ref.
2967    pub fn for_each_node_with_capability<F>(&self, mask: NodeCapabilities, mut f: F)
2968    where
2969        F: FnMut(ModifierChainNodeRef<'_>, &dyn ModifierNode),
2970    {
2971        self.for_each_forward_matching(mask, |node_ref| {
2972            node_ref.with_node(|node| f(node_ref.clone(), node));
2973        });
2974    }
2975
2976    /// Calls `f` for every node in reverse insertion order.
2977    pub fn for_each_backward<F>(&self, mut f: F)
2978    where
2979        F: FnMut(ModifierChainNodeRef<'_>),
2980    {
2981        for node in self.tail_to_head() {
2982            f(node);
2983        }
2984    }
2985
2986    /// Returns the node reference that owns `node`.
2987    pub fn find_node_ref(&self, node: &dyn ModifierNode) -> Option<ModifierChainNodeRef<'_>> {
2988        fn node_data_ptr(node: &dyn ModifierNode) -> *const () {
2989            node as *const dyn ModifierNode as *const ()
2990        }
2991
2992        let target = node_data_ptr(node);
2993        for (index, entry) in self.entries.iter().enumerate() {
2994            if node_data_ptr(&*entry.node.borrow()) == target {
2995                return Some(self.make_node_ref(NodeLink::Entry(NodePath::root(index))));
2996            }
2997        }
2998
2999        self.ordered_nodes.iter().find_map(|(link, _caps, _agg)| {
3000            if matches!(link, NodeLink::Entry(path) if path.delegates().is_empty()) {
3001                return None;
3002            }
3003            let matches_target = match link {
3004                NodeLink::Head => node_data_ptr(&self.head_sentinel) == target,
3005                NodeLink::Tail => node_data_ptr(&self.tail_sentinel) == target,
3006                NodeLink::Entry(path) => {
3007                    let node_borrow = self.entries[path.entry()].node.borrow();
3008                    node_data_ptr(&*node_borrow) == target
3009                }
3010            };
3011            if matches_target {
3012                Some(self.make_node_ref(*link))
3013            } else {
3014                None
3015            }
3016        })
3017    }
3018
3019    /// Downcasts the node at `index` to the requested type.
3020    /// Returns a `Ref` guard that dereferences to the node type.
3021    pub fn node<N: ModifierNode + 'static>(&self, index: usize) -> Option<std::cell::Ref<'_, N>> {
3022        self.entries.get(index).and_then(|entry| {
3023            std::cell::Ref::filter_map(entry.node.borrow(), |boxed_node| {
3024                boxed_node.as_any().downcast_ref::<N>()
3025            })
3026            .ok()
3027        })
3028    }
3029
3030    /// Downcasts the node at `index` to the requested mutable type.
3031    /// Returns a `RefMut` guard that dereferences to the node type.
3032    pub fn node_mut<N: ModifierNode + 'static>(
3033        &self,
3034        index: usize,
3035    ) -> Option<std::cell::RefMut<'_, N>> {
3036        self.entries.get(index).and_then(|entry| {
3037            std::cell::RefMut::filter_map(entry.node.borrow_mut(), |boxed_node| {
3038                boxed_node.as_any_mut().downcast_mut::<N>()
3039            })
3040            .ok()
3041        })
3042    }
3043
3044    /// Returns the shared node at the given index. Coordinators compare it
3045    /// with the node they hold and clone it only to hold a new one.
3046    pub fn get_node_rc(&self, index: usize) -> Option<&Rc<RefCell<dyn ModifierNode>>> {
3047        self.entries.get(index).map(|entry| &entry.node)
3048    }
3049
3050    /// Returns true if the chain contains any nodes matching the given invalidation kind.
3051    pub fn has_nodes_for_invalidation(&self, kind: InvalidationKind) -> bool {
3052        self.aggregated_capabilities
3053            .contains(NodeCapabilities::for_invalidation(kind))
3054    }
3055
3056    /// Visits every node mutably in insertion order together with its capability mask.
3057    pub fn visit_nodes_mut<F>(&mut self, mut f: F)
3058    where
3059        F: FnMut(&mut dyn ModifierNode, NodeCapabilities),
3060    {
3061        for index in 0..self.ordered_nodes.len() {
3062            let (link, cached_caps, _agg) = self.ordered_nodes[index];
3063            match link {
3064                NodeLink::Head => {
3065                    f(&mut self.head_sentinel, cached_caps);
3066                }
3067                NodeLink::Tail => {
3068                    f(&mut self.tail_sentinel, cached_caps);
3069                }
3070                NodeLink::Entry(path) => {
3071                    let mut node_borrow = self.entries[path.entry()].node.borrow_mut();
3072                    if path.delegates().is_empty() {
3073                        f(&mut *node_borrow, cached_caps);
3074                    } else {
3075                        let mut current: &mut dyn ModifierNode = &mut *node_borrow;
3076                        for &delegate_index in path.delegates() {
3077                            if let Some(delegate) =
3078                                nth_delegate_mut(current, delegate_index as usize)
3079                            {
3080                                current = delegate;
3081                            } else {
3082                                return;
3083                            }
3084                        }
3085                        f(current, cached_caps);
3086                    }
3087                }
3088            }
3089        }
3090    }
3091
3092    fn make_node_ref(&self, link: NodeLink) -> ModifierChainNodeRef<'_> {
3093        ModifierChainNodeRef {
3094            chain: self,
3095            link,
3096            cached_capabilities: None,
3097            cached_aggregate_child: None,
3098        }
3099    }
3100
3101    fn make_node_ref_with_caps(
3102        &self,
3103        link: NodeLink,
3104        caps: NodeCapabilities,
3105        aggregate_child: NodeCapabilities,
3106    ) -> ModifierChainNodeRef<'_> {
3107        ModifierChainNodeRef {
3108            chain: self,
3109            link,
3110            cached_capabilities: Some(caps),
3111            cached_aggregate_child: Some(aggregate_child),
3112        }
3113    }
3114
3115    fn sync_chain_links(&mut self) {
3116        self.rebuild_ordered_nodes();
3117
3118        self.head_sentinel.node_state().set_parent_link(None);
3119        self.tail_sentinel.node_state().set_child_link(None);
3120
3121        if self.ordered_nodes.is_empty() {
3122            self.head_sentinel
3123                .node_state()
3124                .set_child_link(Some(NodeLink::Tail));
3125            self.tail_sentinel
3126                .node_state()
3127                .set_parent_link(Some(NodeLink::Head));
3128            self.aggregated_capabilities = NodeCapabilities::empty();
3129            self.head_aggregate_child_capabilities = NodeCapabilities::empty();
3130            self.head_sentinel
3131                .node_state()
3132                .set_aggregate_child_capabilities(NodeCapabilities::empty());
3133            self.tail_sentinel
3134                .node_state()
3135                .set_aggregate_child_capabilities(NodeCapabilities::empty());
3136            return;
3137        }
3138
3139        let mut previous = NodeLink::Head;
3140        for (link, _caps, _agg) in self.ordered_nodes.iter().copied() {
3141            match &previous {
3142                NodeLink::Head => self.head_sentinel.node_state().set_child_link(Some(link)),
3143                NodeLink::Tail => self.tail_sentinel.node_state().set_child_link(Some(link)),
3144                NodeLink::Entry(path) => {
3145                    let node_borrow = self.entries[path.entry()].node.borrow();
3146                    if path.delegates().is_empty() {
3147                        node_borrow.node_state().set_child_link(Some(link));
3148                    } else {
3149                        let mut current: &dyn ModifierNode = &*node_borrow;
3150                        for &delegate_index in path.delegates() {
3151                            if let Some(delegate) = nth_delegate(current, delegate_index as usize) {
3152                                current = delegate;
3153                            }
3154                        }
3155                        current.node_state().set_child_link(Some(link));
3156                    }
3157                }
3158            }
3159            match &link {
3160                NodeLink::Head => self
3161                    .head_sentinel
3162                    .node_state()
3163                    .set_parent_link(Some(previous)),
3164                NodeLink::Tail => self
3165                    .tail_sentinel
3166                    .node_state()
3167                    .set_parent_link(Some(previous)),
3168                NodeLink::Entry(path) => {
3169                    let node_borrow = self.entries[path.entry()].node.borrow();
3170                    if path.delegates().is_empty() {
3171                        node_borrow.node_state().set_parent_link(Some(previous));
3172                    } else {
3173                        let mut current: &dyn ModifierNode = &*node_borrow;
3174                        for &delegate_index in path.delegates() {
3175                            if let Some(delegate) = nth_delegate(current, delegate_index as usize) {
3176                                current = delegate;
3177                            }
3178                        }
3179                        current.node_state().set_parent_link(Some(previous));
3180                    }
3181                }
3182            }
3183            previous = link;
3184        }
3185
3186        match &previous {
3187            NodeLink::Head => self
3188                .head_sentinel
3189                .node_state()
3190                .set_child_link(Some(NodeLink::Tail)),
3191            NodeLink::Tail => self
3192                .tail_sentinel
3193                .node_state()
3194                .set_child_link(Some(NodeLink::Tail)),
3195            NodeLink::Entry(path) => {
3196                let node_borrow = self.entries[path.entry()].node.borrow();
3197                if path.delegates().is_empty() {
3198                    node_borrow
3199                        .node_state()
3200                        .set_child_link(Some(NodeLink::Tail));
3201                } else {
3202                    let mut current: &dyn ModifierNode = &*node_borrow;
3203                    for &delegate_index in path.delegates() {
3204                        if let Some(delegate) = nth_delegate(current, delegate_index as usize) {
3205                            current = delegate;
3206                        }
3207                    }
3208                    current.node_state().set_child_link(Some(NodeLink::Tail));
3209                }
3210            }
3211        }
3212        self.tail_sentinel
3213            .node_state()
3214            .set_parent_link(Some(previous));
3215        self.tail_sentinel.node_state().set_child_link(None);
3216
3217        let mut aggregate = NodeCapabilities::empty();
3218        for (link, cached_caps, cached_aggregate) in self.ordered_nodes.iter_mut().rev() {
3219            aggregate |= *cached_caps;
3220            *cached_aggregate = aggregate;
3221            match link {
3222                NodeLink::Head => {
3223                    self.head_sentinel
3224                        .node_state()
3225                        .set_aggregate_child_capabilities(aggregate);
3226                }
3227                NodeLink::Tail => {
3228                    self.tail_sentinel
3229                        .node_state()
3230                        .set_aggregate_child_capabilities(aggregate);
3231                }
3232                NodeLink::Entry(path) => {
3233                    let node_borrow = self.entries[path.entry()].node.borrow();
3234                    let state = if path.delegates().is_empty() {
3235                        node_borrow.node_state()
3236                    } else {
3237                        let mut current: &dyn ModifierNode = &*node_borrow;
3238                        for &delegate_index in path.delegates() {
3239                            if let Some(delegate) = nth_delegate(current, delegate_index as usize) {
3240                                current = delegate;
3241                            }
3242                        }
3243                        current.node_state()
3244                    };
3245                    state.set_aggregate_child_capabilities(aggregate);
3246                }
3247            }
3248        }
3249
3250        self.aggregated_capabilities = aggregate;
3251        self.head_aggregate_child_capabilities = aggregate;
3252        self.head_sentinel
3253            .node_state()
3254            .set_aggregate_child_capabilities(aggregate);
3255        self.tail_sentinel
3256            .node_state()
3257            .set_aggregate_child_capabilities(NodeCapabilities::empty());
3258    }
3259
3260    fn rebuild_ordered_nodes(&mut self) {
3261        self.ordered_nodes.clear();
3262        let mut path_buf = [0usize; MAX_DELEGATE_DEPTH];
3263        for (index, entry) in self.entries.iter().enumerate() {
3264            let node_borrow = entry.node.borrow();
3265            Self::enumerate_link_order(
3266                &*node_borrow,
3267                index,
3268                &mut path_buf,
3269                0,
3270                &mut self.ordered_nodes,
3271            );
3272        }
3273    }
3274
3275    fn enumerate_link_order(
3276        node: &dyn ModifierNode,
3277        entry: usize,
3278        path_buf: &mut [usize; MAX_DELEGATE_DEPTH],
3279        path_len: usize,
3280        out: &mut Vec<(NodeLink, NodeCapabilities, NodeCapabilities)>,
3281    ) {
3282        let caps = node.node_state().capabilities();
3283        out.push((
3284            NodeLink::Entry(NodePath::from_slice(entry, &path_buf[..path_len])),
3285            caps,
3286            NodeCapabilities::empty(),
3287        ));
3288        let mut delegate_index = 0usize;
3289        node.for_each_delegate(&mut |child| {
3290            if path_len < MAX_DELEGATE_DEPTH {
3291                path_buf[path_len] = delegate_index;
3292                Self::enumerate_link_order(child, entry, path_buf, path_len + 1, out);
3293            }
3294            delegate_index += 1;
3295        });
3296    }
3297}
3298
3299impl<'a> ModifierChainNodeRef<'a> {
3300    fn with_state<R>(&self, f: impl FnOnce(&NodeState) -> R) -> R {
3301        match &self.link {
3302            NodeLink::Head => f(self.chain.head_sentinel.node_state()),
3303            NodeLink::Tail => f(self.chain.tail_sentinel.node_state()),
3304            NodeLink::Entry(path) => {
3305                let node_borrow = self.chain.entries[path.entry()].node.borrow();
3306                if path.delegates().is_empty() {
3307                    f(node_borrow.node_state())
3308                } else {
3309                    let mut current: &dyn ModifierNode = &*node_borrow;
3310                    for &delegate_index in path.delegates() {
3311                        if let Some(delegate) = nth_delegate(current, delegate_index as usize) {
3312                            current = delegate;
3313                        } else {
3314                            return f(node_borrow.node_state());
3315                        }
3316                    }
3317                    f(current.node_state())
3318                }
3319            }
3320        }
3321    }
3322
3323    /// Provides access to the node via a closure, properly handling RefCell borrows.
3324    /// Returns None for sentinel nodes.
3325    pub fn with_node<R>(&self, f: impl FnOnce(&dyn ModifierNode) -> R) -> Option<R> {
3326        match &self.link {
3327            NodeLink::Head => None,
3328            NodeLink::Tail => None,
3329            NodeLink::Entry(path) => {
3330                let node_borrow = self.chain.entries[path.entry()].node.borrow();
3331                if path.delegates().is_empty() {
3332                    Some(f(&*node_borrow))
3333                } else {
3334                    let mut current: &dyn ModifierNode = &*node_borrow;
3335                    for &delegate_index in path.delegates() {
3336                        current = nth_delegate(current, delegate_index as usize)?;
3337                    }
3338                    Some(f(current))
3339                }
3340            }
3341        }
3342    }
3343
3344    /// Returns the parent reference, including sentinel head when applicable.
3345    #[inline]
3346    pub fn parent(&self) -> Option<Self> {
3347        self.with_state(NodeState::parent_link)
3348            .map(|link| self.chain.make_node_ref(link))
3349    }
3350
3351    /// Returns the child reference, including sentinel tail for the last entry.
3352    #[inline]
3353    pub fn child(&self) -> Option<Self> {
3354        self.with_state(NodeState::child_link)
3355            .map(|link| self.chain.make_node_ref(link))
3356    }
3357
3358    /// Returns the capability mask for this specific node.
3359    #[inline]
3360    pub fn kind_set(&self) -> NodeCapabilities {
3361        if let Some(caps) = self.cached_capabilities {
3362            return caps;
3363        }
3364        match &self.link {
3365            NodeLink::Head | NodeLink::Tail => NodeCapabilities::empty(),
3366            NodeLink::Entry(_) => self.with_state(NodeState::capabilities),
3367        }
3368    }
3369
3370    /// Returns the entry index backing this node when it is part of the chain.
3371    pub fn entry_index(&self) -> Option<usize> {
3372        match &self.link {
3373            NodeLink::Entry(path) => Some(path.entry()),
3374            _ => None,
3375        }
3376    }
3377
3378    /// Returns how many delegate hops separate this node from its root element.
3379    pub fn delegate_depth(&self) -> usize {
3380        match &self.link {
3381            NodeLink::Entry(path) => path.delegates().len(),
3382            _ => 0,
3383        }
3384    }
3385
3386    /// Returns the aggregated capability mask for the subtree rooted at this node.
3387    #[inline]
3388    pub fn aggregate_child_capabilities(&self) -> NodeCapabilities {
3389        if let Some(agg) = self.cached_aggregate_child {
3390            return agg;
3391        }
3392        if self.is_tail() {
3393            NodeCapabilities::empty()
3394        } else {
3395            self.with_state(NodeState::aggregate_child_capabilities)
3396        }
3397    }
3398
3399    /// Returns true if this reference targets the sentinel head.
3400    pub fn is_head(&self) -> bool {
3401        matches!(self.link, NodeLink::Head)
3402    }
3403
3404    /// Returns true if this reference targets the sentinel tail.
3405    pub fn is_tail(&self) -> bool {
3406        matches!(self.link, NodeLink::Tail)
3407    }
3408
3409    /// Returns true if this reference targets either sentinel.
3410    pub fn is_sentinel(&self) -> bool {
3411        matches!(self.link, NodeLink::Head | NodeLink::Tail)
3412    }
3413
3414    /// Returns true if this node has any capability bits present in `mask`.
3415    pub fn has_capability(&self, mask: NodeCapabilities) -> bool {
3416        !mask.is_empty() && self.kind_set().intersects(mask)
3417    }
3418
3419    /// Visits descendant nodes, optionally including `self`, in insertion order.
3420    pub fn visit_descendants<F>(self, include_self: bool, mut f: F)
3421    where
3422        F: FnMut(ModifierChainNodeRef<'a>),
3423    {
3424        let mut current = if include_self {
3425            Some(self)
3426        } else {
3427            self.child()
3428        };
3429        while let Some(node) = current {
3430            if node.is_tail() {
3431                break;
3432            }
3433            if !node.is_sentinel() {
3434                f(node.clone());
3435            }
3436            current = node.child();
3437        }
3438    }
3439
3440    /// Visits descendant nodes that match `mask`, short-circuiting when possible.
3441    pub fn visit_descendants_matching<F>(self, include_self: bool, mask: NodeCapabilities, mut f: F)
3442    where
3443        F: FnMut(ModifierChainNodeRef<'a>),
3444    {
3445        if mask.is_empty() {
3446            self.visit_descendants(include_self, f);
3447            return;
3448        }
3449
3450        if !self.aggregate_child_capabilities().intersects(mask) {
3451            return;
3452        }
3453
3454        self.visit_descendants(include_self, |node| {
3455            if node.kind_set().intersects(mask) {
3456                f(node);
3457            }
3458        });
3459    }
3460
3461    /// Visits ancestor nodes up to (but excluding) the sentinel head.
3462    pub fn visit_ancestors<F>(self, include_self: bool, mut f: F)
3463    where
3464        F: FnMut(ModifierChainNodeRef<'a>),
3465    {
3466        let mut current = if include_self {
3467            Some(self)
3468        } else {
3469            self.parent()
3470        };
3471        while let Some(node) = current {
3472            if node.is_head() {
3473                break;
3474            }
3475            f(node.clone());
3476            current = node.parent();
3477        }
3478    }
3479
3480    /// Visits ancestor nodes that match `mask`.
3481    pub fn visit_ancestors_matching<F>(self, include_self: bool, mask: NodeCapabilities, mut f: F)
3482    where
3483        F: FnMut(ModifierChainNodeRef<'a>),
3484    {
3485        if mask.is_empty() {
3486            self.visit_ancestors(include_self, f);
3487            return;
3488        }
3489
3490        self.visit_ancestors(include_self, |node| {
3491            if node.kind_set().intersects(mask) {
3492                f(node);
3493            }
3494        });
3495    }
3496}
3497
3498#[cfg(test)]
3499#[path = "tests/modifier_tests.rs"]
3500mod tests;