Skip to main content

cranpose_foundation/
modifier.rs

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