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