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