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