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