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