Skip to main content

cranpose_foundation/
modifier.rs

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