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