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