Skip to main content

cranpose_foundation/
modifier.rs

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