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