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::{PackedU64, ProvidedValue, hash::default};
18pub use cranpose_ui_graphics::{DrawScope, Size};
19pub use cranpose_ui_layout::{Constraints, Measurable};
20
21use crate::nodes::input::types::PointerEvent;
22
23mod chain;
24mod chain_node_ref;
25mod scroll_axis_range;
26
27pub use chain::ModifierNodeChain;
28pub use chain_node_ref::{ModifierChainIter, ModifierChainNodeRef};
29pub use scroll_axis_range::ScrollAxisRange;
30
31/// Identifies which part of the rendering pipeline should be invalidated
32/// after a modifier node changes state.
33#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
34pub enum InvalidationKind {
35    /// The node's size or placement may have changed, so layout runs again.
36    Layout,
37    /// The node's drawn output changed, so it draws again.
38    Draw,
39    /// The node's pointer input handling changed.
40    PointerInput,
41    /// The node's accessibility semantics changed.
42    Semantics,
43    /// The node's focus behavior changed.
44    Focus,
45}
46
47/// Runtime services exposed to modifier nodes while attached to a tree.
48pub trait ModifierNodeContext {
49    /// Requests that a particular pipeline stage be invalidated.
50    fn invalidate(&mut self, _kind: InvalidationKind) {}
51
52    /// Requests that the node's `update` method run again outside of a
53    /// regular composition pass.
54    fn request_update(&mut self) {}
55
56    /// Returns the ID of the layout node this modifier is attached to, if known.
57    /// This is used by modifiers that need to register callbacks for invalidation (e.g. Scroll).
58    fn node_id(&self) -> Option<cranpose_core::NodeId> {
59        None
60    }
61
62    /// Signals that a node with `capabilities` is about to interact with this context.
63    fn push_active_capabilities(&mut self, _capabilities: NodeCapabilities) {}
64
65    /// Signals that the most recent node interaction has completed.
66    fn pop_active_capabilities(&mut self) {}
67
68    /// The device pixels per layout point of the grid a layout modifier
69    /// measures on, Compose's `MeasureScope` density: lengths it places land
70    /// on whole device pixels of it. A context outside a layout pass answers
71    /// a unit grid.
72    fn density(&self) -> f32 {
73        1.0
74    }
75}
76
77/// Lightweight [`ModifierNodeContext`] implementation that records
78/// invalidation requests and update signals.
79///
80/// The context intentionally avoids leaking runtime details so the core
81/// crate can evolve independently from higher level UI crates. It simply
82/// stores the sequence of requested invalidation kinds and whether an
83/// explicit update was requested. Callers can inspect or drain this state
84/// after driving a [`ModifierNodeChain`] reconciliation pass.
85#[derive(Default, Debug, Clone)]
86pub struct BasicModifierNodeContext {
87    invalidations: ModifierInvalidations,
88    update_requested: bool,
89    /// The capabilities of the nodes interacting with the context, the
90    /// innermost last; nesting stays shallow, so the stack lives inline.
91    active_capabilities: smallvec::SmallVec<[NodeCapabilities; 4]>,
92    node_id: Option<cranpose_core::NodeId>,
93}
94
95impl BasicModifierNodeContext {
96    /// Creates a new empty context.
97    pub fn new() -> Self {
98        Self::default()
99    }
100
101    /// A context for the modifier nodes of node `node_id`, holding
102    /// `invalidations` an earlier operation left for this one to report.
103    pub fn for_node(
104        node_id: Option<cranpose_core::NodeId>,
105        invalidations: ModifierInvalidations,
106    ) -> Self {
107        Self {
108            invalidations,
109            node_id,
110            ..Self::default()
111        }
112    }
113
114    /// Returns the ordered list of invalidation kinds that were requested
115    /// since the last call to `clear_invalidations`. Duplicate requests for
116    /// the same kind are coalesced.
117    pub fn invalidations(&self) -> &[ModifierInvalidation] {
118        &self.invalidations
119    }
120
121    /// Removes all currently recorded invalidation kinds.
122    pub fn clear_invalidations(&mut self) {
123        self.invalidations.clear();
124    }
125
126    /// Drains the recorded invalidations and returns them to the caller.
127    pub fn take_invalidations(&mut self) -> ModifierInvalidations {
128        std::mem::take(&mut self.invalidations)
129    }
130
131    /// Returns whether an update was requested since the last call to
132    /// `take_update_requested`.
133    pub fn update_requested(&self) -> bool {
134        self.update_requested
135    }
136
137    /// Returns whether an update was requested and clears the flag.
138    pub fn take_update_requested(&mut self) -> bool {
139        std::mem::take(&mut self.update_requested)
140    }
141
142    /// Sets the node ID associated with this context.
143    pub fn set_node_id(&mut self, id: Option<cranpose_core::NodeId>) {
144        self.node_id = id;
145    }
146
147    fn push_invalidation(&mut self, kind: InvalidationKind) {
148        let mut capabilities = self.current_capabilities();
149        capabilities.insert(NodeCapabilities::for_invalidation(kind));
150        if let Some(existing) = self
151            .invalidations
152            .iter_mut()
153            .find(|entry| entry.kind() == kind)
154        {
155            let updated = existing.capabilities() | capabilities;
156            *existing = ModifierInvalidation::new(kind, updated);
157        } else {
158            self.invalidations
159                .push(ModifierInvalidation::new(kind, capabilities));
160        }
161    }
162
163    fn current_capabilities(&self) -> NodeCapabilities {
164        self.active_capabilities
165            .last()
166            .copied()
167            .unwrap_or_else(NodeCapabilities::empty)
168    }
169}
170
171impl ModifierNodeContext for BasicModifierNodeContext {
172    fn invalidate(&mut self, kind: InvalidationKind) {
173        self.push_invalidation(kind);
174    }
175
176    fn request_update(&mut self) {
177        self.update_requested = true;
178    }
179
180    fn push_active_capabilities(&mut self, capabilities: NodeCapabilities) {
181        self.active_capabilities.push(capabilities);
182    }
183
184    fn pop_active_capabilities(&mut self) {
185        self.active_capabilities.pop();
186    }
187
188    fn node_id(&self) -> Option<cranpose_core::NodeId> {
189        self.node_id
190    }
191}
192
193const MAX_DELEGATE_DEPTH: usize = 3;
194
195#[derive(Copy, Clone, Debug, PartialEq, Eq)]
196pub(crate) struct NodePath {
197    /// The chain entry, 32 bits wide: a path sits in every link of a
198    /// chain's order, and no chain nears four billion entries.
199    entry: u32,
200    delegate_buf: [u8; MAX_DELEGATE_DEPTH],
201    delegate_len: u8,
202}
203
204impl NodePath {
205    #[inline]
206    fn root(entry: usize) -> Self {
207        debug_assert!(entry <= u32::MAX as usize, "chain entry exceeds u32 range");
208        Self {
209            entry: entry as u32,
210            delegate_buf: [0; MAX_DELEGATE_DEPTH],
211            delegate_len: 0,
212        }
213    }
214
215    #[inline]
216    fn from_slice(entry: usize, path: &[usize]) -> Self {
217        debug_assert!(
218            path.len() <= MAX_DELEGATE_DEPTH,
219            "delegate depth {} exceeds MAX_DELEGATE_DEPTH {}",
220            path.len(),
221            MAX_DELEGATE_DEPTH
222        );
223        debug_assert!(
224            path.iter().all(|&i| i <= u8::MAX as usize),
225            "delegate index exceeds u8 range"
226        );
227        let mut delegate_buf = [0u8; MAX_DELEGATE_DEPTH];
228        for (i, &v) in path.iter().enumerate().take(MAX_DELEGATE_DEPTH) {
229            delegate_buf[i] = v as u8;
230        }
231        Self {
232            delegate_buf,
233            delegate_len: path.len().min(MAX_DELEGATE_DEPTH) as u8,
234            ..Self::root(entry)
235        }
236    }
237
238    #[inline]
239    fn entry(&self) -> usize {
240        self.entry as usize
241    }
242
243    #[inline]
244    fn delegates(&self) -> &[u8] {
245        &self.delegate_buf[..self.delegate_len as usize]
246    }
247}
248
249/// Runtime state tracked for every [`ModifierNode`].
250///
251/// This type is part of the internal node system API and should not be directly
252/// constructed or manipulated by external code. Modifier nodes automatically receive
253/// and manage their NodeState through the modifier chain infrastructure.
254#[derive(Debug)]
255pub struct NodeState {
256    capabilities: Cell<NodeCapabilities>,
257    attached: Cell<bool>,
258}
259
260impl Default for NodeState {
261    fn default() -> Self {
262        Self::new()
263    }
264}
265
266impl NodeState {
267    /// Creates the state of a node that is not attached to a chain yet and has no capabilities.
268    pub const fn new() -> Self {
269        Self {
270            capabilities: Cell::new(NodeCapabilities::empty()),
271            attached: Cell::new(false),
272        }
273    }
274
275    /// Records the capabilities of the node that owns this state. The chain calls it when it adds
276    /// or updates the node.
277    pub fn set_capabilities(&self, capabilities: NodeCapabilities) {
278        self.capabilities.set(capabilities);
279    }
280
281    /// The capabilities of the node that owns this state.
282    #[inline]
283    pub fn capabilities(&self) -> NodeCapabilities {
284        self.capabilities.get()
285    }
286
287    // The chain marks a node attached before `on_attach` and detached after `on_detach`.
288    pub(crate) fn set_attached(&self, attached: bool) {
289        self.attached.set(attached);
290    }
291
292    /// Whether the node is attached to a chain. It is true after `on_attach` ran and before
293    /// `on_detach` runs.
294    pub fn is_attached(&self) -> bool {
295        self.attached.get()
296    }
297}
298
299/// Provides traversal helpers that mirror Jetpack Compose's [`DelegatableNode`] contract.
300pub trait DelegatableNode {
301    /// The runtime state of this node. Return a `NodeState` field of the node; the chain uses it to
302    /// track the node's capabilities and attachment.
303    fn node_state(&self) -> &NodeState;
304}
305
306/// Core trait implemented by modifier nodes.
307///
308/// # Capability-Driven Architecture
309///
310/// This trait follows Jetpack Compose's `Modifier.Node` pattern where nodes declare
311/// their capabilities via [`NodeCapabilities`] and implement specialized traits
312/// ([`DrawModifierNode`], [`PointerInputNode`], [`SemanticsNode`], [`FocusNode`], etc.)
313/// to participate in specific pipeline stages.
314///
315/// ## How to Implement a Modifier Node
316///
317/// 1. **Declare capabilities** in your [`ModifierNodeElement::capabilities()`] implementation
318/// 2. **Implement specialized traits** for the capabilities you declared
319/// 3. **Use helper macros** to reduce boilerplate (recommended)
320///
321/// ### Example: Draw Node
322///
323/// ```text
324/// use cranpose_foundation::*;
325///
326/// struct MyDrawNode {
327///     state: NodeState,
328///     color: Color,
329/// }
330///
331/// impl DelegatableNode for MyDrawNode {
332///     fn node_state(&self) -> &NodeState {
333///         &self.state
334///     }
335/// }
336///
337/// impl ModifierNode for MyDrawNode {
338///     // Use the helper macro instead of manual as_* implementations
339///     impl_modifier_node!(draw);
340/// }
341///
342/// impl DrawModifierNode for MyDrawNode {
343///     fn draw(&mut self, _context: &mut dyn ModifierNodeContext, draw_scope: &mut dyn DrawScope) {
344///         // Drawing logic here
345///     }
346/// }
347/// ```
348///
349/// ### Example: Multi-Capability Node
350///
351/// ```text
352/// impl ModifierNode for MyComplexNode {
353///     // This node participates in draw, pointer input, and semantics
354///     impl_modifier_node!(draw, pointer_input, semantics);
355/// }
356/// ```
357///
358/// ## Lifecycle Callbacks
359///
360/// Nodes receive lifecycle callbacks when they attach to or detach from a
361/// composition and may optionally react to resets triggered by the runtime
362/// (for example, when reusing nodes across modifier list changes).
363pub trait ModifierNode: Any + DelegatableNode {
364    /// Called once when the node joins an attached chain. `context` receives the invalidation
365    /// requests of the node.
366    fn on_attach(&mut self, _context: &mut dyn ModifierNodeContext) {}
367
368    /// Called when the node leaves the chain or when the chain detaches. Release here whatever
369    /// `on_attach` acquired.
370    fn on_detach(&mut self) {}
371
372    /// Called when the chain is reset, as Compose's `onReset`. Clear state that belongs to an
373    /// earlier use of the node.
374    fn on_reset(&mut self) {}
375
376    /// Returns this node as a draw modifier if it implements the trait.
377    fn as_draw_node(&self) -> Option<&dyn DrawModifierNode> {
378        None
379    }
380
381    /// Returns this node as a mutable draw modifier if it implements the trait.
382    fn as_draw_node_mut(&mut self) -> Option<&mut dyn DrawModifierNode> {
383        None
384    }
385
386    /// Returns this node as a pointer-input modifier if it implements the trait.
387    fn as_pointer_input_node(&self) -> Option<&dyn PointerInputNode> {
388        None
389    }
390
391    /// Returns this node as a mutable pointer-input modifier if it implements the trait.
392    fn as_pointer_input_node_mut(&mut self) -> Option<&mut dyn PointerInputNode> {
393        None
394    }
395
396    /// Returns this node as a semantics modifier if it implements the trait.
397    fn as_semantics_node(&self) -> Option<&dyn SemanticsNode> {
398        None
399    }
400
401    /// Returns this node as a mutable semantics modifier if it implements the trait.
402    fn as_semantics_node_mut(&mut self) -> Option<&mut dyn SemanticsNode> {
403        None
404    }
405
406    /// Returns this node as a focus modifier if it implements the trait.
407    fn as_focus_node(&self) -> Option<&dyn FocusNode> {
408        None
409    }
410
411    /// Returns this node as a mutable focus modifier if it implements the trait.
412    fn as_focus_node_mut(&mut self) -> Option<&mut dyn FocusNode> {
413        None
414    }
415
416    /// Returns this node as a layout modifier if it implements the trait.
417    fn as_layout_node(&self) -> Option<&dyn LayoutModifierNode> {
418        None
419    }
420
421    /// Returns this node as a mutable layout modifier if it implements the trait.
422    fn as_layout_node_mut(&mut self) -> Option<&mut dyn LayoutModifierNode> {
423        None
424    }
425
426    /// Visits every delegate node owned by this modifier.
427    fn for_each_delegate<'b>(&'b self, _visitor: &mut dyn FnMut(&'b dyn ModifierNode)) {}
428
429    /// Visits every delegate node mutably.
430    fn for_each_delegate_mut<'b>(&'b mut self, _visitor: &mut dyn FnMut(&'b mut dyn ModifierNode)) {
431    }
432}
433
434/// Marker trait for layout-specific modifier nodes.
435///
436/// Layout nodes participate in the measure and layout passes of the render
437/// pipeline. They can intercept and modify the measurement and placement of
438/// their wrapped content.
439pub trait LayoutModifierNode: ModifierNode {
440    /// Measures the wrapped content and returns both the size this modifier
441    /// occupies and where the wrapped content should be placed.
442    ///
443    /// The node receives a measurable representing the wrapped content and
444    /// the incoming constraints from the parent.
445    ///
446    /// Returns a `LayoutModifierMeasureResult` containing:
447    /// - `size`: The final size this modifier will occupy
448    /// - `placement_offset_x/y`: Where to place the wrapped content relative
449    ///   to this modifier's top-left corner
450    ///
451    /// For example, a padding modifier would:
452    /// - Measure child with deflated constraints
453    /// - Return size = child size + padding
454    /// - Return placement offset = (padding.left, padding.top)
455    ///
456    /// The default implementation delegates to the wrapped content without
457    /// modification (size = child size, offset = 0).
458    ///
459    /// NOTE: This takes `&self` not `&mut self` to match Jetpack Compose semantics.
460    /// Nodes that need mutable state should use interior mutability (Cell/RefCell).
461    fn measure(
462        &self,
463        _context: &mut dyn ModifierNodeContext,
464        measurable: &dyn Measurable,
465        constraints: Constraints,
466    ) -> cranpose_ui_layout::LayoutModifierMeasureResult {
467        let placeable = measurable.measure(constraints);
468        cranpose_ui_layout::LayoutModifierMeasureResult::with_size(Size {
469            width: placeable.width(),
470            height: placeable.height(),
471        })
472    }
473
474    /// The incoming constraints the last measure of this node, which gave
475    /// `size` under `constraints`, holds for: every constraints inside give
476    /// the same result. `wrapped` is what the content this node wraps
477    /// measured. `None`, the default, holds for `constraints` alone; a node
478    /// that tells must give the range its own measure keeps its result and
479    /// hands its content constraints that `wrapped` holds for.
480    fn measure_hold(
481        &self,
482        _density: f32,
483        _constraints: Constraints,
484        _size: Size,
485        _wrapped: cranpose_ui_layout::WrappedHold,
486    ) -> Option<cranpose_ui_layout::ConstraintsHold> {
487        None
488    }
489
490    /// Returns the minimum intrinsic width of this modifier node. The default is
491    /// the wrapped content's, for a node that keeps the content's size.
492    fn min_intrinsic_width(&self, measurable: &dyn Measurable, height: f32, _density: f32) -> f32 {
493        measurable.min_intrinsic_width(height)
494    }
495
496    /// Returns the maximum intrinsic width of this modifier node. The default is
497    /// the wrapped content's, for a node that keeps the content's size.
498    fn max_intrinsic_width(&self, measurable: &dyn Measurable, height: f32, _density: f32) -> f32 {
499        measurable.max_intrinsic_width(height)
500    }
501
502    /// Returns the minimum intrinsic height of this modifier node. The default is
503    /// the wrapped content's, for a node that keeps the content's size.
504    fn min_intrinsic_height(&self, measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
505        measurable.min_intrinsic_height(width)
506    }
507
508    /// Returns the maximum intrinsic height of this modifier node. The default is
509    /// the wrapped content's, for a node that keeps the content's size.
510    fn max_intrinsic_height(&self, measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
511        measurable.max_intrinsic_height(width)
512    }
513}
514
515/// Marker trait for draw-specific modifier nodes.
516///
517/// Draw nodes participate in the draw pass of the render pipeline. A node
518/// draws through the closures it hands out, which the renderer runs at render
519/// time with a live scope of the node's size; slice collection only gathers
520/// them.
521pub trait DrawModifierNode: ModifierNode {
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    /// Whether this node makes its subtree modal or hides it, and whether
605    /// what it merges can change without its semantics being invalidated.
606    /// The default merges into a fresh configuration, reads the two flags and
607    /// counts the node as reading live state; a node that can declare
608    /// neither flag and reports only what its updates bring overrides it to
609    /// skip that work.
610    fn reach(&self) -> SemanticsReach {
611        SemanticsReach::merged_by(self, true)
612    }
613}
614
615/// The semantics flags that decide how a node's semantics reach the tree:
616/// which part of a window is live ([`SemanticsConfiguration::is_modal`] and
617/// [`SemanticsConfiguration::hidden`]), and whether a tree update may keep
618/// what the node reported last.
619#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
620pub struct SemanticsReach {
621    /// The subtree takes over the screen.
622    pub is_modal: bool,
623    /// The subtree is skipped by readers.
624    pub hidden: bool,
625    /// What the node merges can change without its semantics being
626    /// invalidated, as a `.semantics { }` recorder reading live state can,
627    /// so every tree update merges it again.
628    pub merges_live_state: bool,
629}
630
631impl SemanticsReach {
632    /// The modal and hidden flags `node` merges into a fresh configuration,
633    /// with whether what it merges can change without an invalidation.
634    pub fn merged_by(node: &(impl SemanticsNode + ?Sized), merges_live_state: bool) -> Self {
635        let mut config = SemanticsConfiguration::default();
636        node.merge_semantics(&mut config);
637        Self {
638            is_modal: config.is_modal,
639            hidden: config.hidden,
640            merges_live_state,
641        }
642    }
643
644    /// Every flag of `self` and `other`.
645    pub fn union(self, other: Self) -> Self {
646        Self {
647            is_modal: self.is_modal || other.is_modal,
648            hidden: self.hidden || other.hidden,
649            merges_live_state: self.merges_live_state || other.merges_live_state,
650        }
651    }
652}
653
654/// Whether a check box or switch is on, off or partly on: Compose's
655/// `ToggleableState`. A check box over a list whose items are partly checked
656/// is `Indeterminate`.
657#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
658pub enum ToggleableState {
659    /// The control is on or checked.
660    On,
661    /// The control is off or unchecked.
662    Off,
663    /// The control is partly on, as a check box over items of which some are checked.
664    Indeterminate,
665}
666
667impl From<bool> for ToggleableState {
668    fn from(on: bool) -> Self {
669        if on { Self::On } else { Self::Off }
670    }
671}
672
673/// Focus state of a focus target node.
674///
675/// This mirrors Jetpack Compose's FocusState enum which tracks whether
676/// a node is focused, has a focused child, or is inactive.
677#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
678pub enum FocusState {
679    /// The focusable component is currently active (i.e. it receives key events).
680    Active,
681    /// One of the descendants of the focusable component is Active.
682    ActiveParent,
683    /// The focusable component is currently active (has focus), and is in a state
684    /// where it does not want to give up focus. (Eg. a text field with an invalid
685    /// phone number).
686    Captured,
687    /// The focusable component does not receive any key events. (ie it is not active,
688    /// nor are any of its descendants active).
689    #[default]
690    Inactive,
691}
692
693impl FocusState {
694    /// Returns whether the component is focused (Active or Captured).
695    pub fn is_focused(self) -> bool {
696        matches!(self, Self::Active | Self::Captured)
697    }
698
699    /// Returns whether this node or any descendant has focus.
700    pub fn has_focus(self) -> bool {
701        matches!(self, Self::Active | Self::ActiveParent | Self::Captured)
702    }
703
704    /// Returns whether focus is captured.
705    pub fn is_captured(self) -> bool {
706        matches!(self, Self::Captured)
707    }
708}
709
710/// Marker trait for focus modifier nodes.
711///
712/// Focus nodes participate in focus management. They can request focus,
713/// track focus state, and participate in focus traversal.
714pub trait FocusNode: ModifierNode {
715    /// Returns the current focus state of this node.
716    fn focus_state(&self) -> FocusState;
717
718    /// Called when focus state changes for this node.
719    fn on_focus_changed(&mut self, _context: &mut dyn ModifierNodeContext, _state: FocusState) {}
720}
721
722/// What kind of control a node is, as screen readers announce it.
723///
724/// This is Compose's `SemanticsProperties.Role` (`Modifier.semantics { role =
725/// Role.RadioButton }`), not a description of where the node sits in the tree.
726/// A screen reader turns it into the trailing noun it speaks after the label —
727/// TalkBack says "CAMPAIGN, radio button, selected" — and into the on/off
728/// wording it uses for a switch. Compose keeps this separate from the node's
729/// structural kind for the same reason Cranpose does: a `Row` that happens to
730/// be clickable is still a row, and a drawn ring segment that is a radio button
731/// has no layout node of its own at all.
732#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
733pub enum SemanticsWidgetRole {
734    /// A control that acts when pressed. Compose's `Role.Button`.
735    Button,
736    /// A control that is checked or unchecked. Compose's `Role.Checkbox`.
737    Checkbox,
738    /// A control that is on or off. Compose's `Role.Switch`.
739    Switch,
740    /// One choice of a set in which only one choice is selected. Compose's `Role.RadioButton`.
741    RadioButton,
742    /// One page selector of a tab bar. Compose's `Role.Tab`.
743    Tab,
744    /// A picture. Compose's `Role.Image`.
745    Image,
746    /// A control that opens a list of choices and holds the one that is
747    /// picked. Compose's `Role.DropdownList`.
748    DropdownList,
749    /// A control that holds one value out of an ordered set and steps through
750    /// them. Compose's `Role.ValuePicker`.
751    ValuePicker,
752    /// Compose's `heading()`, which is a property rather than a `Role`, but
753    /// reaches the platform through the same field on every backend Cranpose
754    /// targets (`AccessibilityNodeInfo.setHeading`, `Role::Heading`,
755    /// `<h*>`/`UIAccessibilityTraitHeader`).
756    Header,
757    /// A modal surface that takes over the screen until it is dismissed.
758    /// Screen readers announce it and confine their traversal to it, which is
759    /// the accessible half of what makes a dialog modal.
760    Dialog,
761    /// Text that takes the user somewhere else when pressed. Compose has no
762    /// such `Role`; SwiftUI's `.isLink`, ARIA's `link`.
763    Link,
764    /// A field that narrows what is on the screen as the user types. ARIA's
765    /// `searchbox`, VoiceOver's search field trait.
766    SearchField,
767    /// A control that shows how far work has come and takes no input. ARIA's
768    /// `progressbar`.
769    ProgressBar,
770    /// A button that stays pressed or released. ARIA's `button` with
771    /// `aria-pressed`.
772    ToggleButton,
773    /// A message a reader speaks as soon as it shows, with no move to it.
774    /// ARIA's `alert`.
775    Alert,
776    /// A row of controls that act on the content beside them. ARIA's
777    /// `toolbar`.
778    Toolbar,
779    /// A list of commands that opens on a press. ARIA's `menu`.
780    Menu,
781    /// One command inside a menu. ARIA's `menuitem`.
782    MenuItem,
783    /// The row that holds the tabs of a screen. ARIA's `tablist`, VoiceOver's
784    /// tab bar trait.
785    TabBar,
786    /// A container whose rows a reader counts and walks into. ARIA's `list`.
787    List,
788    /// One row of a list. ARIA's `listitem`.
789    ListItem,
790    /// A set of mutually exclusive radio choices. ARIA's `radiogroup`.
791    RadioGroup,
792}
793
794/// The value a control holds inside a range, for a slider, a dial or a
795/// progress bar.
796///
797/// This is Compose's `ProgressBarRangeInfo` (`Modifier.progressSemantics(value,
798/// range, steps)`). A control without it reads as plain text: a screen reader
799/// user hears "47 percent" and has no way to change it, because nothing tells
800/// the platform the control is adjustable.
801#[derive(Clone, Copy, Debug, PartialEq)]
802pub struct ProgressBarRangeInfo {
803    /// The current value, between `start` and `end`.
804    pub current: f32,
805    /// The value at the low end of the range.
806    pub start: f32,
807    /// The value at the high end of the range.
808    pub end: f32,
809    /// How many stops sit between `start` and `end`, as Compose counts them.
810    /// Zero means the value moves without stops.
811    pub steps: u32,
812}
813
814impl ProgressBarRangeInfo {
815    /// Creates range info for `current` between `start` and `end`, with `steps` stops between the
816    /// two ends.
817    pub fn new(current: f32, start: f32, end: f32, steps: u32) -> Self {
818        Self {
819            current,
820            start,
821            end,
822            steps,
823        }
824    }
825
826    /// Where the value sits between the two ends, from 0 to 1.
827    pub fn fraction(&self) -> f32 {
828        let span = self.end - self.start;
829        if span.abs() < f32::EPSILON {
830            return 0.0;
831        }
832        ((self.current - self.start) / span).clamp(0.0, 1.0)
833    }
834
835    /// How far one stop moves the value. With no stops, one tenth of the
836    /// range, which is what a screen reader's swipe up and down expects.
837    pub fn step(&self) -> f32 {
838        let span = self.end - self.start;
839        if self.steps == 0 {
840            span / 10.0
841        } else {
842            span / (self.steps as f32 + 1.0)
843        }
844    }
845}
846
847/// How many rows and columns a list holds, so a screen reader can say
848/// "list, 12 items" as its cursor enters. Compose's `CollectionInfo`.
849#[derive(Clone, Copy, Debug, PartialEq, Eq)]
850pub struct CollectionInfo {
851    /// The number of rows the collection holds. A plain list reports its item count here.
852    pub rows: usize,
853    /// The number of columns the collection holds. A plain list reports 1.
854    pub columns: usize,
855}
856
857/// What a container does when a screen reader pages it, e.g. TalkBack's
858/// scroll forward action or an accesskit scroll down. The two deltas are in
859/// layout pixels; the answer says whether anything moved.
860///
861/// This is Compose's `SemanticsActions.ScrollBy`.
862#[derive(Clone)]
863pub struct SemanticsScrollBy {
864    handler: Rc<dyn Fn(f32, f32) -> bool>,
865}
866
867impl SemanticsScrollBy {
868    /// Wraps `handler`, which receives the horizontal and the vertical distance in layout pixels
869    /// and returns whether the container moved.
870    pub fn new(handler: impl Fn(f32, f32) -> bool + 'static) -> Self {
871        Self {
872            handler: Rc::new(handler),
873        }
874    }
875
876    /// Runs the handler with the distances `dx` and `dy`, in layout pixels. Returns whether the
877    /// container moved.
878    pub fn invoke(&self, dx: f32, dy: f32) -> bool {
879        (self.handler)(dx, dy)
880    }
881}
882
883impl fmt::Debug for SemanticsScrollBy {
884    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
885        f.debug_struct("SemanticsScrollBy").finish_non_exhaustive()
886    }
887}
888
889impl PartialEq for SemanticsScrollBy {
890    fn eq(&self, _other: &Self) -> bool {
891        true
892    }
893}
894
895impl Eq for SemanticsScrollBy {}
896
897/// What a list does when a screen reader asks for the row at an index, e.g. a
898/// TalkBack scroll-to-position action.
899///
900/// This is Compose's `SemanticsActions.ScrollToIndex`. The index counts rows
901/// from zero, and the answer says whether the list moved.
902#[derive(Clone)]
903pub struct SemanticsScrollToIndex {
904    handler: Rc<dyn Fn(usize) -> bool>,
905}
906
907impl SemanticsScrollToIndex {
908    /// Wraps `handler`, which receives a row index counted from zero and returns whether the list
909    /// moved.
910    pub fn new(handler: impl Fn(usize) -> bool + 'static) -> Self {
911        Self {
912            handler: Rc::new(handler),
913        }
914    }
915
916    /// Runs the handler for the row at `index`, counted from zero. Returns whether the list moved.
917    pub fn invoke(&self, index: usize) -> bool {
918        (self.handler)(index)
919    }
920}
921
922impl fmt::Debug for SemanticsScrollToIndex {
923    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
924        f.debug_struct("SemanticsScrollToIndex")
925            .finish_non_exhaustive()
926    }
927}
928
929impl PartialEq for SemanticsScrollToIndex {
930    fn eq(&self, _other: &Self) -> bool {
931        true
932    }
933}
934
935impl Eq for SemanticsScrollToIndex {}
936
937/// What a control does when a screen reader moves its value, e.g. a VoiceOver
938/// swipe up or a TalkBack set-progress action.
939///
940/// This is Compose's `SemanticsActions.SetProgress`. The value comes in the
941/// control's own range, and the answer says whether the control took it.
942#[derive(Clone)]
943pub struct SemanticsSetProgress {
944    handler: Rc<dyn Fn(f32) -> bool>,
945}
946
947impl SemanticsSetProgress {
948    /// Wraps `handler`, which receives a value in the range of the control and returns whether the
949    /// control took it.
950    pub fn new(handler: impl Fn(f32) -> bool + 'static) -> Self {
951        Self {
952            handler: Rc::new(handler),
953        }
954    }
955
956    /// Runs the handler with `value`, in the range of the control. Returns whether the control took
957    /// it.
958    pub fn invoke(&self, value: f32) -> bool {
959        (self.handler)(value)
960    }
961}
962
963impl fmt::Debug for SemanticsSetProgress {
964    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
965        f.debug_struct("SemanticsSetProgress")
966            .finish_non_exhaustive()
967    }
968}
969
970/// What a text field does when a screen reader or a voice tool hands it new
971/// text. This is Compose's `SemanticsActions.SetText`; the answer says
972/// whether the field took the text.
973#[derive(Clone)]
974pub struct SemanticsSetText(Rc<dyn Fn(&str) -> bool>);
975
976impl SemanticsSetText {
977    /// Wraps `handler`, which receives the new text and returns whether the field took it.
978    pub fn new(handler: impl Fn(&str) -> bool + 'static) -> Self {
979        Self(Rc::new(handler))
980    }
981
982    /// Runs the handler with `text`. Returns whether the field took it.
983    pub fn invoke(&self, text: &str) -> bool {
984        (self.0)(text)
985    }
986}
987
988impl fmt::Debug for SemanticsSetText {
989    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
990        f.write_str("SemanticsSetText")
991    }
992}
993
994/// What a text field does when a screen reader moves its caret or picks a
995/// stretch of its text: the two ends of the new selection, as byte offsets
996/// into the field's text, the anchor first and the end that moves second. Equal
997/// ends are a caret. This is Compose's `SemanticsActions.SetSelection`; the
998/// answer says whether the field took the selection.
999#[derive(Clone)]
1000pub struct SemanticsSetSelection(Rc<dyn Fn(usize, usize) -> bool>);
1001
1002impl SemanticsSetSelection {
1003    /// Wraps `handler`, which receives the anchor and the focus offsets of the selection and
1004    /// returns whether the field took it.
1005    pub fn new(handler: impl Fn(usize, usize) -> bool + 'static) -> Self {
1006        Self(Rc::new(handler))
1007    }
1008
1009    /// Runs the handler with the selection from byte offset `anchor` to byte offset `focus`.
1010    /// Returns whether the field took it.
1011    pub fn invoke(&self, anchor: usize, focus: usize) -> bool {
1012        (self.0)(anchor, focus)
1013    }
1014}
1015
1016impl fmt::Debug for SemanticsSetSelection {
1017    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1018        f.write_str("SemanticsSetSelection")
1019    }
1020}
1021
1022/// What a control does when a screen reader asks it to open or to close.
1023/// Compose's `expand` and `collapse` actions.
1024#[derive(Clone)]
1025pub struct SemanticsExpand(Rc<dyn Fn() -> bool>);
1026
1027impl SemanticsExpand {
1028    /// Wraps `handler`, which returns whether the control opened or closed.
1029    pub fn new(handler: impl Fn() -> bool + 'static) -> Self {
1030        Self(Rc::new(handler))
1031    }
1032
1033    /// Runs the handler. Returns whether the control opened or closed.
1034    pub fn invoke(&self) -> bool {
1035        (self.0)()
1036    }
1037}
1038
1039impl fmt::Debug for SemanticsExpand {
1040    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1041        f.write_str("SemanticsExpand")
1042    }
1043}
1044
1045/// What a control does when a screen reader asks for its long press. This is
1046/// Compose's `SemanticsActions.OnLongClick`; the answer says whether the
1047/// control took the ask.
1048#[derive(Clone)]
1049pub struct SemanticsLongClick(Rc<dyn Fn() -> bool>);
1050
1051impl SemanticsLongClick {
1052    /// Wraps `handler`, which returns whether the control took the long press.
1053    pub fn new(handler: impl Fn() -> bool + 'static) -> Self {
1054        Self(Rc::new(handler))
1055    }
1056
1057    /// Runs the handler. Returns whether the control took the long press.
1058    pub fn invoke(&self) -> bool {
1059        (self.0)()
1060    }
1061}
1062
1063impl fmt::Debug for SemanticsLongClick {
1064    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1065        f.write_str("SemanticsLongClick")
1066    }
1067}
1068
1069impl PartialEq for SemanticsLongClick {
1070    fn eq(&self, _other: &Self) -> bool {
1071        true
1072    }
1073}
1074
1075impl PartialEq for SemanticsExpand {
1076    fn eq(&self, _other: &Self) -> bool {
1077        true
1078    }
1079}
1080
1081/// What a control does when a screen reader asks to send it away: a row a
1082/// sighted person swipes off, a sheet a sighted person taps outside of.
1083/// Compose's `dismiss` action.
1084#[derive(Clone)]
1085pub struct SemanticsDismiss(Rc<dyn Fn() -> bool>);
1086
1087impl SemanticsDismiss {
1088    /// Wraps `handler`, which returns whether the control was dismissed.
1089    pub fn new(handler: impl Fn() -> bool + 'static) -> Self {
1090        Self(Rc::new(handler))
1091    }
1092
1093    /// Runs the handler. Returns whether the control was dismissed.
1094    pub fn invoke(&self) -> bool {
1095        (self.0)()
1096    }
1097}
1098
1099impl fmt::Debug for SemanticsDismiss {
1100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1101        f.write_str("SemanticsDismiss")
1102    }
1103}
1104
1105impl PartialEq for SemanticsDismiss {
1106    fn eq(&self, _other: &Self) -> bool {
1107        true
1108    }
1109}
1110
1111impl PartialEq for SemanticsSetText {
1112    fn eq(&self, _other: &Self) -> bool {
1113        true
1114    }
1115}
1116
1117impl PartialEq for SemanticsSetSelection {
1118    fn eq(&self, _other: &Self) -> bool {
1119        true
1120    }
1121}
1122
1123/// What a control does when a VoiceOver user makes the magic tap, the two
1124/// finger double tap that starts or stops the main action of a screen. On
1125/// the other platforms the action is listed by its label among the control's
1126/// actions. SwiftUI's `accessibilityAction(.magicTap)`; the answer says
1127/// whether the control took the tap.
1128#[derive(Clone)]
1129pub struct SemanticsMagicTap(Rc<dyn Fn() -> bool>);
1130
1131impl SemanticsMagicTap {
1132    /// Wraps `handler`, which returns whether the control took the magic tap.
1133    pub fn new(handler: impl Fn() -> bool + 'static) -> Self {
1134        Self(Rc::new(handler))
1135    }
1136
1137    /// Runs the handler. Returns whether the control took the magic tap.
1138    pub fn invoke(&self) -> bool {
1139        (self.0)()
1140    }
1141}
1142
1143impl fmt::Debug for SemanticsMagicTap {
1144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1145        f.write_str("SemanticsMagicTap")
1146    }
1147}
1148
1149impl PartialEq for SemanticsMagicTap {
1150    fn eq(&self, _other: &Self) -> bool {
1151        true
1152    }
1153}
1154
1155impl PartialEq for SemanticsSetProgress {
1156    fn eq(&self, _other: &Self) -> bool {
1157        true
1158    }
1159}
1160
1161impl Eq for SemanticsSetProgress {}
1162
1163/// How urgently a screen reader reads a node whose text changed on its own.
1164///
1165/// This is Compose's `LiveRegionMode` (`Modifier.semantics { liveRegion =
1166/// LiveRegionMode.Polite }`). A node without it stays silent until the reader
1167/// lands on it, which is wrong for a timer, a countdown, a status line, or an
1168/// error that appears next to a text field: a blind user hears nothing.
1169#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1170pub enum LiveRegionMode {
1171    /// Read the new text once the reader finishes what it says now.
1172    Polite,
1173    /// Cut off what the reader says now and read the new text at once. For
1174    /// text a user must hear immediately, such as an error that stops them.
1175    Assertive,
1176}
1177
1178/// A named accessibility operation, e.g. Compose's
1179/// `customActions = listOf(CustomAccessibilityAction("Pause") { … })`.
1180///
1181/// TalkBack surfaces these through its actions menu rather than by activating
1182/// the node, which is the only way to reach a command that has no on-screen
1183/// control — pausing a game whose whole surface is one tap-to-launch target.
1184#[derive(Clone)]
1185pub struct SemanticsCustomAction {
1186    /// What the screen reader reads out in its actions menu.
1187    pub label: String,
1188    handler: Rc<dyn Fn()>,
1189}
1190
1191impl SemanticsCustomAction {
1192    /// Creates an action that a screen reader lists as `label` and that runs `handler` when chosen.
1193    pub fn new(label: impl Into<String>, handler: impl Fn() + 'static) -> Self {
1194        Self {
1195            label: label.into(),
1196            handler: Rc::new(handler),
1197        }
1198    }
1199
1200    /// Runs the handler of the action.
1201    pub fn invoke(&self) {
1202        (self.handler)();
1203    }
1204}
1205
1206impl fmt::Debug for SemanticsCustomAction {
1207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1208        f.debug_struct("SemanticsCustomAction")
1209            .field("label", &self.label)
1210            .finish_non_exhaustive()
1211    }
1212}
1213
1214impl PartialEq for SemanticsCustomAction {
1215    fn eq(&self, other: &Self) -> bool {
1216        self.label == other.label
1217    }
1218}
1219
1220impl Eq for SemanticsCustomAction {}
1221
1222/// A semantics node for content that is *drawn* rather than laid out.
1223///
1224/// An immediate-mode surface — one `Canvas` that paints a whole screen — has
1225/// exactly one layout node, so the semantics tree built from layout has exactly
1226/// one node to offer a screen reader. This is the escape hatch: the drawing
1227/// code already knows where it put every control, so it publishes those
1228/// rectangles as semantics directly. Android's own answer for a canvas-drawn
1229/// `View` is the same shape (`ExploreByTouchHelper` feeding virtual view ids
1230/// into an `AccessibilityNodeProvider`), and Cranpose's Android bridge is
1231/// already an `AccessibilityNodeProvider`, so these land as first-class
1232/// virtual views next to the ones layout produces.
1233///
1234/// `bounds` is in the publishing node's own coordinates (logical px, origin at
1235/// that node's top-left), because that is what a draw scope works in.
1236#[derive(Clone, Debug, PartialEq)]
1237pub struct CanvasSemanticsNode {
1238    /// Identity that must survive a redraw.
1239    ///
1240    /// A screen reader parks its cursor on a virtual view id; if the id for
1241    /// "the Haptics switch" changes when the list scrolls, the cursor jumps.
1242    /// Derive this from what the control *is* (a row index, an enum
1243    /// discriminant), never from where it currently sits.
1244    pub key: u64,
1245    /// Where the control was drawn, relative to the publishing node.
1246    pub bounds: cranpose_ui_graphics::Rect,
1247    /// What the screen reader reads for the control.
1248    pub label: String,
1249    /// What kind of control it is, as the screen reader announces it.
1250    pub role: Option<SemanticsWidgetRole>,
1251    /// Compose's `stateDescription` — what the control currently reads as
1252    /// ("CAMPAIGN", "3 of 18 gold"), spoken after the label and re-spoken on
1253    /// its own when only the state changed.
1254    pub state_description: Option<String>,
1255    /// Compose's `onClick(label = …)`. TalkBack reads it as "double tap to
1256    /// `<label>`", so it is a verb phrase, not a repeat of the label.
1257    pub on_click_label: Option<String>,
1258    /// Whether the control can be activated.
1259    pub clickable: bool,
1260    /// Compose's `selected`, for `Role.RadioButton`/`Role.Tab`.
1261    pub selected: Option<bool>,
1262    /// Compose's `toggleableState`, for `Role.Switch`/`Role.Checkbox`.
1263    pub toggled: Option<ToggleableState>,
1264    /// Whether the control accepts input. A disabled control is announced as such.
1265    pub enabled: bool,
1266    /// Named actions that the screen reader lists in its actions menu for the control.
1267    pub custom_actions: Vec<SemanticsCustomAction>,
1268}
1269
1270impl Default for CanvasSemanticsNode {
1271    fn default() -> Self {
1272        Self {
1273            key: 0,
1274            bounds: cranpose_ui_graphics::Rect {
1275                x: 0.0,
1276                y: 0.0,
1277                width: 0.0,
1278                height: 0.0,
1279            },
1280            label: String::new(),
1281            role: None,
1282            state_description: None,
1283            on_click_label: None,
1284            clickable: false,
1285            selected: None,
1286            toggled: None,
1287            enabled: true,
1288            custom_actions: Vec::new(),
1289        }
1290    }
1291}
1292
1293impl CanvasSemanticsNode {
1294    /// A clickable control drawn at `bounds`.
1295    pub fn control(key: u64, bounds: cranpose_ui_graphics::Rect, label: impl Into<String>) -> Self {
1296        Self {
1297            key,
1298            bounds,
1299            label: label.into(),
1300            clickable: true,
1301            ..Self::default()
1302        }
1303    }
1304
1305    /// A drawn label that is read but not activated.
1306    pub fn text(key: u64, bounds: cranpose_ui_graphics::Rect, label: impl Into<String>) -> Self {
1307        Self {
1308            key,
1309            bounds,
1310            label: label.into(),
1311            ..Self::default()
1312        }
1313    }
1314
1315    /// Returns the node with `role` set.
1316    pub fn with_role(mut self, role: SemanticsWidgetRole) -> Self {
1317        self.role = Some(role);
1318        self
1319    }
1320
1321    /// Returns the node with the state description `state` set. Compose's `stateDescription`.
1322    pub fn with_state_description(mut self, state: impl Into<String>) -> Self {
1323        self.state_description = Some(state.into());
1324        self
1325    }
1326
1327    /// Returns the node with `label` set as the verb phrase for activation. The node becomes
1328    /// clickable.
1329    pub fn with_click_label(mut self, label: impl Into<String>) -> Self {
1330        self.on_click_label = Some(label.into());
1331        self.clickable = true;
1332        self
1333    }
1334
1335    /// Returns the node with its selected state set to `selected`.
1336    pub fn with_selected(mut self, selected: bool) -> Self {
1337        self.selected = Some(selected);
1338        self
1339    }
1340
1341    /// Returns the node with its toggle state set to `toggled`. A `bool` converts to `On` or `Off`.
1342    pub fn with_toggled(mut self, toggled: impl Into<ToggleableState>) -> Self {
1343        self.toggled = Some(toggled.into());
1344        self
1345    }
1346
1347    /// Returns the node with `enabled` set. A disabled node is announced as such.
1348    pub fn with_enabled(mut self, enabled: bool) -> Self {
1349        self.enabled = enabled;
1350        self
1351    }
1352
1353    /// Returns the node with `action` added to its custom actions.
1354    pub fn with_custom_action(mut self, action: SemanticsCustomAction) -> Self {
1355        self.custom_actions.push(action);
1356        self
1357    }
1358}
1359
1360/// Semantics configuration for accessibility.
1361#[derive(Clone, Debug, PartialEq)]
1362pub struct SemanticsConfiguration {
1363    /// What a screen reader reads as the name of the node. Compose's `contentDescription`.
1364    pub content_description: Option<String>,
1365    /// Compose's `stateDescription`.
1366    pub state_description: Option<String>,
1367    /// Compose's `onClick(label = …)`; implies clickable.
1368    pub on_click_label: Option<String>,
1369    /// Activation callback for controls whose pointer gesture is handled separately.
1370    /// Invoked by keyboard and native accessibility activation after validation.
1371    pub on_click: Option<SemanticsCustomAction>,
1372    /// What this control does when a screen reader asks for its long press.
1373    /// Compose's `onLongClick`.
1374    pub on_long_click: Option<SemanticsLongClick>,
1375    /// What the long press does, as a verb phrase a reader reads out:
1376    /// "Remove receipt". Compose's `onLongClick(label = …)`.
1377    pub on_long_click_label: Option<String>,
1378    /// What this control does on VoiceOver's magic tap, and on the other
1379    /// platforms as an action listed by [`Self::on_magic_tap_label`].
1380    /// SwiftUI's `accessibilityAction(.magicTap)`.
1381    pub on_magic_tap: Option<SemanticsMagicTap>,
1382    /// What the magic tap does, as a verb phrase a reader reads out: "Take
1383    /// the photo".
1384    pub on_magic_tap_label: Option<String>,
1385    /// The short names a person says to Voice Control to reach this control,
1386    /// when the name a reader hears is too long to say. SwiftUI's
1387    /// `accessibilityInputLabels`; iOS only.
1388    pub input_labels: Vec<String>,
1389    /// The language of this control's text as a BCP 47 tag, "de" or "pt-BR",
1390    /// so a reader picks the right voice for it. SwiftUI's
1391    /// `accessibilityLanguage`, ARIA's `lang`.
1392    pub language: Option<String>,
1393    /// Compose's `Role`.
1394    pub role: Option<SemanticsWidgetRole>,
1395    /// Whether the control is selected. `None` means the control has no selected state.
1396    pub selected: Option<bool>,
1397    /// Whether the control is on, off or partly on. Compose's
1398    /// `toggleableState`.
1399    pub toggled: Option<ToggleableState>,
1400    /// Whether the control accepts input. A disabled control is announced as such.
1401    pub enabled: bool,
1402    /// Whether the control reacts to a click.
1403    pub is_clickable: bool,
1404    /// Whether the node is a text field a user edits.
1405    pub is_editable_text: bool,
1406    /// Whether an editable field accepts line breaks, independent of its current text.
1407    pub multiline: bool,
1408    /// The text an editable field holds, read as its value. Compose's
1409    /// `editableText`.
1410    pub text: Option<String>,
1411    /// The selection or the caret of an editable field, as byte offsets into its text.
1412    pub text_selection: Option<crate::text::TextRange>,
1413    /// Named actions that a screen reader lists in its actions menu for the node.
1414    pub custom_actions: Vec<SemanticsCustomAction>,
1415    /// Controls this node drew itself instead of laying out. See
1416    /// [`CanvasSemanticsNode`].
1417    pub canvas_children: Vec<CanvasSemanticsNode>,
1418    /// Whether this node takes over the screen: everything outside it is
1419    /// inert, and a screen reader keeps its traversal inside.
1420    pub is_modal: bool,
1421    /// Whether a screen reader skips this node and everything under it: a
1422    /// decorative image, or a placeholder drawn under a named field. Compose's
1423    /// `hideFromAccessibility`.
1424    pub hidden: bool,
1425    /// Whether a screen reader takes this node and the text under it as one
1426    /// stop, the way it does for a button: a row whose name, count and price
1427    /// belong together. Compose's `mergeDescendants`.
1428    pub merge_descendants: bool,
1429    /// Whether the selectable controls under this node form one group, so a
1430    /// screen reader says which of how many a tab or a radio button is.
1431    /// Compose's `selectableGroup`.
1432    pub selectable_group: bool,
1433    /// The title of the screen or pane this node is the root of, read out when
1434    /// the app moves to it. Compose's `paneTitle`.
1435    pub pane_title: Option<String>,
1436    /// Why the control's content is wrong, read after its state: "invalid,
1437    /// the amount needs a number". Compose's `error`.
1438    pub error: Option<String>,
1439    /// What using the control does or what it wants, read after its other
1440    /// words: "double tap to choose a color". Compose's `hintText`.
1441    pub hint_text: Option<String>,
1442    /// Whether this field holds a secret, so screen readers never receive its
1443    /// text and native editors use protected input. Compose's `password`.
1444    pub password: bool,
1445    /// Where a screen reader visits this node among the ones beside it: a
1446    /// smaller number comes first, and nodes left at zero keep the order the
1447    /// app laid them out in. Compose's `traversalIndex`.
1448    pub traversal_index: f32,
1449    /// A name tests find this node by. Screen readers never receive it.
1450    /// Compose's `testTag`.
1451    pub test_tag: Option<String>,
1452    /// Compose's `liveRegion`. When set, a screen reader reads this node again
1453    /// whenever its text changes, without the user moving to it.
1454    pub live_region: Option<LiveRegionMode>,
1455    /// Compose's `progressBarRangeInfo`. A control with it is adjustable: a
1456    /// screen reader offers its own way to move the value.
1457    pub progress: Option<ProgressBarRangeInfo>,
1458    /// What the control does when a screen reader moves its value. Compose's
1459    /// `setProgress`.
1460    pub set_progress: Option<SemanticsSetProgress>,
1461    /// What this field does when a screen reader or a voice tool hands it
1462    /// text. Compose's `setText`.
1463    pub set_text: Option<SemanticsSetText>,
1464    /// What this field does when a screen reader moves its caret or picks a
1465    /// stretch of its text. Compose's `setSelection`.
1466    pub set_selection: Option<SemanticsSetSelection>,
1467    /// What this control does when a screen reader asks it to open. A control
1468    /// that says so reads as closed. Compose's `expand`.
1469    pub expand: Option<SemanticsExpand>,
1470    /// What this control does when a screen reader asks to send it away: a row
1471    /// a sighted person swipes off, a sheet a sighted person taps outside of.
1472    /// Compose's `dismiss`.
1473    pub dismiss: Option<SemanticsDismiss>,
1474    /// What this control does when a screen reader asks it to close. A control
1475    /// that says so reads as open. Compose's `collapse`.
1476    pub collapse: Option<SemanticsExpand>,
1477    /// How far this container scrolled up and down. Compose's
1478    /// `verticalScrollAxisRange`.
1479    pub vertical_scroll: Option<ScrollAxisRange>,
1480    /// How far this container scrolled left and right. Compose's
1481    /// `horizontalScrollAxisRange`.
1482    pub horizontal_scroll: Option<ScrollAxisRange>,
1483    /// What this container does when a screen reader pages it. Compose's
1484    /// `scrollBy`.
1485    pub scroll_by: Option<SemanticsScrollBy>,
1486    /// What this list does when a screen reader asks for the row at an index,
1487    /// so a reader reaches row 300 without paging to it. Compose's
1488    /// `scrollToIndex`.
1489    pub scroll_to_index: Option<SemanticsScrollToIndex>,
1490    /// How many rows and columns this list holds. Compose's `collectionInfo`.
1491    pub collection: Option<CollectionInfo>,
1492}
1493
1494impl Default for SemanticsConfiguration {
1495    fn default() -> Self {
1496        Self {
1497            content_description: None,
1498            state_description: None,
1499            on_click_label: None,
1500            on_click: None,
1501            on_long_click: None,
1502            on_long_click_label: None,
1503            on_magic_tap: None,
1504            on_magic_tap_label: None,
1505            input_labels: Vec::new(),
1506            language: None,
1507            role: None,
1508            selected: None,
1509            toggled: None,
1510            enabled: true,
1511            is_clickable: false,
1512            is_editable_text: false,
1513            multiline: false,
1514            text: None,
1515            text_selection: None,
1516            custom_actions: Vec::new(),
1517            canvas_children: Vec::new(),
1518            is_modal: false,
1519            hidden: false,
1520            merge_descendants: false,
1521            selectable_group: false,
1522            pane_title: None,
1523            error: None,
1524            hint_text: None,
1525            password: false,
1526            traversal_index: 0.0,
1527            test_tag: None,
1528            live_region: None,
1529            progress: None,
1530            set_progress: None,
1531            set_text: None,
1532            set_selection: None,
1533            expand: None,
1534            dismiss: None,
1535            collapse: None,
1536            vertical_scroll: None,
1537            horizontal_scroll: None,
1538            scroll_by: None,
1539            scroll_to_index: None,
1540            collection: None,
1541        }
1542    }
1543}
1544
1545/// One value that says what a screen reader reads for a node, built up with
1546/// the methods below and handed to `Modifier::semantics_spec`.
1547///
1548/// Compose has no such value: it takes a receiver lambda, which Kotlin makes
1549/// read well and Rust has no match for. `SemanticsSpec::new().content_description("Save")`
1550/// reads better than `|config| config.content_description = Some("Save".into())`,
1551/// it is one chain element rather than one per property, and two specs can be
1552/// compared. The closure form stays as `Modifier::semantics` for parity.
1553pub type SemanticsSpec = SemanticsConfiguration;
1554
1555impl SemanticsConfiguration {
1556    /// An empty spec to build on. Every field is what it is with no semantics
1557    /// declared at all.
1558    pub fn new() -> Self {
1559        Self::default()
1560    }
1561
1562    /// The name a screen reader reads for the control. Compose's
1563    /// `contentDescription`.
1564    pub fn content_description(mut self, name: impl Into<String>) -> Self {
1565        self.content_description = Some(name.into());
1566        self
1567    }
1568
1569    /// A name tests find this node by. Screen readers never receive it.
1570    /// Compose's `testTag`.
1571    pub fn test_tag(mut self, tag: impl Into<String>) -> Self {
1572        self.test_tag = Some(tag.into());
1573        self
1574    }
1575
1576    /// What the control says about itself after its name. Compose's
1577    /// `stateDescription`.
1578    pub fn state_description(mut self, state: impl Into<String>) -> Self {
1579        self.state_description = Some(state.into());
1580        self
1581    }
1582
1583    /// A screen reader offers to activate the control. Compose's `onClick`.
1584    pub fn clickable(mut self) -> Self {
1585        self.is_clickable = true;
1586        self
1587    }
1588
1589    /// Handles keyboard and screen-reader activation without adding a pointer gesture.
1590    /// An empty label uses the platform's default activation instruction.
1591    pub fn on_click(mut self, label: impl Into<String>, action: impl Fn() + 'static) -> Self {
1592        self.on_click = Some(SemanticsCustomAction::new(label, action));
1593        self
1594    }
1595
1596    /// What the control does when a screen reader asks for its long press,
1597    /// and the verb phrase a reader reads out for it. Compose's
1598    /// `onLongClick(label) { … }`.
1599    pub fn on_long_click(
1600        mut self,
1601        label: impl Into<String>,
1602        action: impl Fn() -> bool + 'static,
1603    ) -> Self {
1604        self.on_long_click_label = Some(label.into());
1605        self.on_long_click = Some(SemanticsLongClick::new(action));
1606        self
1607    }
1608
1609    /// What the control does on VoiceOver's magic tap, and the verb phrase
1610    /// the other platforms list it under. SwiftUI's
1611    /// `accessibilityAction(.magicTap)`.
1612    pub fn on_magic_tap(
1613        mut self,
1614        label: impl Into<String>,
1615        action: impl Fn() -> bool + 'static,
1616    ) -> Self {
1617        self.on_magic_tap_label = Some(label.into());
1618        self.on_magic_tap = Some(SemanticsMagicTap::new(action));
1619        self
1620    }
1621
1622    /// The short names a person says to Voice Control to reach the control.
1623    /// SwiftUI's `accessibilityInputLabels`.
1624    pub fn input_labels<S: Into<String>>(mut self, labels: impl IntoIterator<Item = S>) -> Self {
1625        self.input_labels = labels.into_iter().map(Into::into).collect();
1626        self
1627    }
1628
1629    /// The language of the control's text, as a BCP 47 tag. SwiftUI's
1630    /// `accessibilityLanguage`, ARIA's `lang`.
1631    pub fn language(mut self, tag: impl Into<String>) -> Self {
1632        self.language = Some(tag.into());
1633        self
1634    }
1635
1636    /// Whether the control is on, off or partly on. Compose's
1637    /// `toggleableState`; a `bool` is on or off.
1638    pub fn toggled(mut self, toggled: impl Into<ToggleableState>) -> Self {
1639        self.toggled = Some(toggled.into());
1640        self
1641    }
1642
1643    /// Whether the control is the one picked out of a group. Compose's
1644    /// `selected`.
1645    pub fn selected(mut self, selected: bool) -> Self {
1646        self.selected = Some(selected);
1647        self
1648    }
1649
1650    /// What kind of control a screen reader reads this as. Compose's `Role`.
1651    pub fn role(mut self, role: SemanticsWidgetRole) -> Self {
1652        self.role = Some(role);
1653        self
1654    }
1655
1656    /// Reads as a heading, so a reader can jump between the headings of a
1657    /// screen. Compose's `heading()`.
1658    pub fn heading(self) -> Self {
1659        self.role(SemanticsWidgetRole::Header)
1660    }
1661
1662    /// Why the control's content is wrong. Compose's `error`.
1663    pub fn error(mut self, message: impl Into<String>) -> Self {
1664        self.error = Some(message.into());
1665        self
1666    }
1667
1668    /// What using the control does or what it wants. Compose's `hintText`.
1669    pub fn hint_text(mut self, hint: impl Into<String>) -> Self {
1670        self.hint_text = Some(hint.into());
1671        self
1672    }
1673
1674    /// Holds a secret, so no reader reads the text out. Compose's `password`.
1675    pub fn password(mut self) -> Self {
1676        self.password = true;
1677        self
1678    }
1679
1680    /// Names the screen or pane this node is the root of. Compose's
1681    /// `paneTitle`.
1682    pub fn pane_title(mut self, title: impl Into<String>) -> Self {
1683        self.pane_title = Some(title.into());
1684        self
1685    }
1686
1687    /// Where a reader visits this node among the ones beside it. Compose's
1688    /// `traversalIndex`.
1689    pub fn traversal_index(mut self, index: f32) -> Self {
1690        self.traversal_index = index;
1691        self
1692    }
1693
1694    /// Skips this node and everything under it. Compose's
1695    /// `hideFromAccessibility`.
1696    pub fn hidden(mut self) -> Self {
1697        self.hidden = true;
1698        self
1699    }
1700
1701    /// Takes this node and the text under it as one stop. Compose's
1702    /// `mergeDescendants`.
1703    pub fn merge_descendants(mut self) -> Self {
1704        self.merge_descendants = true;
1705        self
1706    }
1707
1708    /// Makes the selectable controls under this node one group. Compose's
1709    /// `selectableGroup`.
1710    pub fn selectable_group(mut self) -> Self {
1711        self.selectable_group = true;
1712        self
1713    }
1714
1715    /// Reads this node again whenever its text changes. Compose's
1716    /// `liveRegion`.
1717    pub fn live_region(mut self, mode: LiveRegionMode) -> Self {
1718        self.live_region = Some(mode);
1719        self
1720    }
1721    /// Folds `other` into this configuration. A value that `other` sets replaces the value here.
1722    /// `enabled` stays true only when both are enabled, the boolean flags turn true when either is
1723    /// true, and the lists are appended.
1724    pub fn merge(&mut self, other: &Self) {
1725        if let Some(description) = &other.content_description {
1726            self.content_description = Some(description.clone());
1727        }
1728        if let Some(state) = &other.state_description {
1729            self.state_description = Some(state.clone());
1730        }
1731        if let Some(label) = &other.on_click_label {
1732            self.on_click_label = Some(label.clone());
1733        }
1734        if let Some(label) = &other.on_long_click_label {
1735            self.on_long_click_label = Some(label.clone());
1736        }
1737        if let Some(label) = &other.on_magic_tap_label {
1738            self.on_magic_tap_label = Some(label.clone());
1739        }
1740        if !other.input_labels.is_empty() {
1741            self.input_labels.clone_from(&other.input_labels);
1742        }
1743        if let Some(language) = &other.language {
1744            self.language = Some(language.clone());
1745        }
1746        if let Some(role) = other.role {
1747            self.role = Some(role);
1748        }
1749        if let Some(selected) = other.selected {
1750            self.selected = Some(selected);
1751        }
1752        if let Some(toggled) = other.toggled {
1753            self.toggled = Some(toggled);
1754        }
1755        self.enabled &= other.enabled;
1756        self.is_clickable |= other.is_clickable;
1757        self.is_editable_text |= other.is_editable_text;
1758        self.multiline |= other.multiline;
1759        if let Some(text) = &other.text {
1760            self.text = Some(text.clone());
1761        }
1762        self.is_modal |= other.is_modal;
1763        self.hidden |= other.hidden;
1764        self.merge_descendants |= other.merge_descendants;
1765        self.selectable_group |= other.selectable_group;
1766        self.password |= other.password;
1767        if other.traversal_index != 0.0 {
1768            self.traversal_index = other.traversal_index;
1769        }
1770        if let Some(live_region) = other.live_region {
1771            self.live_region = Some(live_region);
1772        }
1773        self.merge_words(other);
1774        self.merge_actions(other);
1775        self.merge_ranges(other);
1776    }
1777
1778    fn merge_words(&mut self, other: &Self) {
1779        if let Some(title) = &other.pane_title {
1780            self.pane_title = Some(title.clone());
1781        }
1782        if let Some(error) = &other.error {
1783            self.error = Some(error.clone());
1784        }
1785        if let Some(hint) = &other.hint_text {
1786            self.hint_text = Some(hint.clone());
1787        }
1788        if let Some(tag) = &other.test_tag {
1789            self.test_tag = Some(tag.clone());
1790        }
1791    }
1792
1793    fn merge_actions(&mut self, other: &Self) {
1794        if let Some(on_click) = &other.on_click {
1795            self.on_click = Some(on_click.clone());
1796        }
1797        self.custom_actions
1798            .extend(other.custom_actions.iter().cloned());
1799        self.canvas_children
1800            .extend(other.canvas_children.iter().cloned());
1801        if let Some(set_progress) = &other.set_progress {
1802            self.set_progress = Some(set_progress.clone());
1803        }
1804        if let Some(set_text) = &other.set_text {
1805            self.set_text = Some(set_text.clone());
1806        }
1807        if let Some(set_selection) = &other.set_selection {
1808            self.set_selection = Some(set_selection.clone());
1809        }
1810        if let Some(expand) = &other.expand {
1811            self.expand = Some(expand.clone());
1812        }
1813        if let Some(collapse) = &other.collapse {
1814            self.collapse = Some(collapse.clone());
1815        }
1816        if let Some(dismiss) = &other.dismiss {
1817            self.dismiss = Some(dismiss.clone());
1818        }
1819        if let Some(long_click) = &other.on_long_click {
1820            self.on_long_click = Some(long_click.clone());
1821        }
1822        if let Some(magic_tap) = &other.on_magic_tap {
1823            self.on_magic_tap = Some(magic_tap.clone());
1824        }
1825        if let Some(scroll_by) = &other.scroll_by {
1826            self.scroll_by = Some(scroll_by.clone());
1827        }
1828        if let Some(scroll_to_index) = &other.scroll_to_index {
1829            self.scroll_to_index = Some(scroll_to_index.clone());
1830        }
1831    }
1832
1833    fn merge_ranges(&mut self, other: &Self) {
1834        if let Some(selection) = other.text_selection {
1835            self.text_selection = Some(selection);
1836        }
1837        if let Some(progress) = other.progress {
1838            self.progress = Some(progress);
1839        }
1840        if let Some(range) = other.vertical_scroll {
1841            self.vertical_scroll = Some(range);
1842        }
1843        if let Some(range) = other.horizontal_scroll {
1844            self.horizontal_scroll = Some(range);
1845        }
1846        if let Some(collection) = other.collection {
1847            self.collection = Some(collection);
1848        }
1849    }
1850
1851    /// Whether a screen reader should offer activation. A named click label is
1852    /// how Compose declares `onClick`, so it implies the action the same way.
1853    pub fn is_activatable(&self) -> bool {
1854        self.is_clickable || self.on_click_label.is_some() || self.on_click.is_some()
1855    }
1856}
1857
1858impl fmt::Debug for dyn ModifierNode {
1859    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1860        f.debug_struct("ModifierNode").finish_non_exhaustive()
1861    }
1862}
1863
1864impl dyn ModifierNode {
1865    /// Returns the node as `Any`, for downcasting to its concrete type.
1866    pub fn as_any(&self) -> &dyn Any {
1867        self
1868    }
1869
1870    /// Returns the node as mutable `Any`, for downcasting to its concrete type.
1871    pub fn as_any_mut(&mut self) -> &mut dyn Any {
1872        self
1873    }
1874}
1875
1876/// Strongly typed modifier elements that can create and update nodes while
1877/// exposing equality/hash/inspector contracts that mirror Jetpack Compose.
1878pub trait ModifierNodeElement: fmt::Debug + Hash + PartialEq + 'static {
1879    /// The type of node that the element creates and updates.
1880    type Node: ModifierNode;
1881
1882    /// Creates a new modifier node instance for this element.
1883    fn create(&self) -> Self::Node;
1884
1885    /// Brings an existing modifier node up to date with the element's data.
1886    fn update(&self, node: &mut Self::Node);
1887
1888    /// Optional key used to disambiguate multiple instances of the same element type.
1889    fn key(&self) -> Option<u64> {
1890        None
1891    }
1892
1893    /// Human readable name surfaced to inspector tooling.
1894    fn inspector_name(&self) -> &'static str {
1895        type_name::<Self>()
1896    }
1897
1898    /// Records inspector properties for tooling.
1899    fn inspector_properties(&self, _inspector: &mut dyn FnMut(&'static str, String)) {}
1900
1901    /// Returns the capabilities of nodes created by this element.
1902    /// Override this to indicate which specialized traits the node implements.
1903    fn capabilities(&self) -> NodeCapabilities {
1904        NodeCapabilities::default()
1905    }
1906
1907    /// Whether this element requires `update` to be called even if `eq` returns true.
1908    ///
1909    /// This is useful for elements that ignore certain fields in `eq` (e.g. closures)
1910    /// to allow node reuse, but still need those fields updated in the existing node.
1911    /// Defaults to `false`.
1912    fn always_update(&self) -> bool {
1913        false
1914    }
1915
1916    /// Whether modifier reconciliation should request capability-wide invalidations
1917    /// after updating an existing node.
1918    fn auto_invalidate_on_update(&self) -> bool {
1919        true
1920    }
1921
1922    /// Optional targeted invalidation requested after updating an existing node.
1923    ///
1924    /// This is for nodes whose attach/remove capability is broader than the
1925    /// work needed for a value-only update. For example, an offset node
1926    /// participates in layout on attach but an x/y change only needs placement
1927    /// data and draw output refreshed.
1928    fn update_invalidation_kind(&self) -> Option<InvalidationKind> {
1929        None
1930    }
1931
1932    /// Composition locals this element hands to the content composed inside
1933    /// the node it decorates.
1934    ///
1935    /// A modifier that stands for something its subtree lives in, the way a
1936    /// window does, passes that thing down here instead of asking every
1937    /// composable in between to carry it as an argument.
1938    fn provided_composition_locals(&self) -> Vec<ProvidedValue> {
1939        Vec::new()
1940    }
1941}
1942
1943/// Capability flags indicating which specialized traits a modifier node implements.
1944#[derive(Clone, Copy, PartialEq, Eq, Hash)]
1945pub struct NodeCapabilities(u32);
1946
1947impl NodeCapabilities {
1948    /// No capabilities.
1949    pub const NONE: Self = Self(0);
1950    /// Modifier participates in measure/layout.
1951    pub const LAYOUT: Self = Self(1 << 0);
1952    /// Modifier participates in draw.
1953    pub const DRAW: Self = Self(1 << 1);
1954    /// Modifier participates in pointer input.
1955    pub const POINTER_INPUT: Self = Self(1 << 2);
1956    /// Modifier participates in semantics tree construction.
1957    pub const SEMANTICS: Self = Self(1 << 3);
1958    /// Modifier participates in modifier locals.
1959    pub const MODIFIER_LOCALS: Self = Self(1 << 4);
1960    /// Modifier participates in focus management.
1961    pub const FOCUS: Self = Self(1 << 5);
1962    /// Modifier makes its node the root of a separate window: the node's
1963    /// subtree is laid out into that window's size and drawn into that
1964    /// window's scene, and the parent's scene skips it.
1965    pub const WINDOW_ROOT: Self = Self(1 << 6);
1966    /// Modifier handles hardware keyboard events.
1967    pub const KEY_INPUT: Self = Self(1 << 7);
1968
1969    /// Returns an empty capability set.
1970    pub const fn empty() -> Self {
1971        Self::NONE
1972    }
1973
1974    /// Returns whether all bits in `other` are present in `self`.
1975    pub const fn contains(self, other: Self) -> bool {
1976        (self.0 & other.0) == other.0
1977    }
1978
1979    /// Returns whether any bit in `other` is present in `self`.
1980    pub const fn intersects(self, other: Self) -> bool {
1981        (self.0 & other.0) != 0
1982    }
1983
1984    /// Inserts the requested capability bits.
1985    pub fn insert(&mut self, other: Self) {
1986        self.0 |= other.0;
1987    }
1988
1989    /// Returns the raw bit representation.
1990    pub const fn bits(self) -> u32 {
1991        self.0
1992    }
1993
1994    /// Returns true when no capabilities are set.
1995    pub const fn is_empty(self) -> bool {
1996        self.0 == 0
1997    }
1998
1999    /// Returns the capability bit mask required for the given invalidation.
2000    pub const fn for_invalidation(kind: InvalidationKind) -> Self {
2001        match kind {
2002            InvalidationKind::Layout => Self::LAYOUT,
2003            InvalidationKind::Draw => Self::DRAW,
2004            InvalidationKind::PointerInput => Self::POINTER_INPUT,
2005            InvalidationKind::Semantics => Self::SEMANTICS,
2006            InvalidationKind::Focus => Self::FOCUS,
2007        }
2008    }
2009}
2010
2011impl Default for NodeCapabilities {
2012    fn default() -> Self {
2013        Self::NONE
2014    }
2015}
2016
2017impl fmt::Debug for NodeCapabilities {
2018    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2019        f.debug_struct("NodeCapabilities")
2020            .field("layout", &self.contains(Self::LAYOUT))
2021            .field("draw", &self.contains(Self::DRAW))
2022            .field("pointer_input", &self.contains(Self::POINTER_INPUT))
2023            .field("semantics", &self.contains(Self::SEMANTICS))
2024            .field("modifier_locals", &self.contains(Self::MODIFIER_LOCALS))
2025            .field("focus", &self.contains(Self::FOCUS))
2026            .field("window_root", &self.contains(Self::WINDOW_ROOT))
2027            .field("key_input", &self.contains(Self::KEY_INPUT))
2028            .finish()
2029    }
2030}
2031
2032impl BitOr for NodeCapabilities {
2033    type Output = Self;
2034
2035    fn bitor(self, rhs: Self) -> Self::Output {
2036        Self(self.0 | rhs.0)
2037    }
2038}
2039
2040impl BitOrAssign for NodeCapabilities {
2041    fn bitor_assign(&mut self, rhs: Self) {
2042        self.0 |= rhs.0;
2043    }
2044}
2045
2046/// The invalidations a node's modifier update requested: at most one per
2047/// [`InvalidationKind`], held inline so handing them over allocates nothing.
2048pub type ModifierInvalidations = smallvec::SmallVec<[ModifierInvalidation; 5]>;
2049
2050/// Records an invalidation request together with the capability mask that triggered it.
2051#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2052pub struct ModifierInvalidation {
2053    kind: InvalidationKind,
2054    capabilities: NodeCapabilities,
2055}
2056
2057impl ModifierInvalidation {
2058    /// Creates a new modifier invalidation entry.
2059    pub const fn new(kind: InvalidationKind, capabilities: NodeCapabilities) -> Self {
2060        Self { kind, capabilities }
2061    }
2062
2063    /// Returns the invalidated pipeline kind.
2064    pub const fn kind(self) -> InvalidationKind {
2065        self.kind
2066    }
2067
2068    /// Returns the capability mask associated with the invalidation.
2069    pub const fn capabilities(self) -> NodeCapabilities {
2070        self.capabilities
2071    }
2072}
2073
2074/// Type-erased modifier element used by the runtime to reconcile chains.
2075pub trait AnyModifierElement: fmt::Debug {
2076    /// The type of node that the element creates.
2077    fn node_type(&self) -> TypeId;
2078
2079    /// The type of the element itself.
2080    fn element_type(&self) -> TypeId;
2081
2082    /// A new node for this element, in the shared cell its chain entry keeps.
2083    fn create_node(&self) -> Rc<RefCell<dyn ModifierNode>>;
2084
2085    /// Whether `node` is of the type that this element updates.
2086    fn can_update_node(&self, node: &dyn ModifierNode) -> bool;
2087
2088    /// Brings `node` up to date with the data of this element.
2089    fn update_node(&self, node: &mut dyn ModifierNode);
2090
2091    /// The key that tells apart several elements of one type, or `None` when the element has none.
2092    fn key(&self) -> Option<u64>;
2093
2094    /// The capabilities of the nodes that the element creates.
2095    fn capabilities(&self) -> NodeCapabilities {
2096        NodeCapabilities::default()
2097    }
2098
2099    /// The hash of the element, computed once when the element was wrapped.
2100    fn hash_code(&self) -> u64;
2101
2102    /// Whether `other` is an element of the same type with equal data.
2103    fn equals_element(&self, other: &dyn AnyModifierElement) -> bool;
2104
2105    /// The name that inspector tooling shows for the element.
2106    fn inspector_name(&self) -> &'static str;
2107
2108    /// Reports each inspector property of the element to `visitor` as a name and a value.
2109    fn record_inspector_properties(&self, visitor: &mut dyn FnMut(&'static str, String));
2110
2111    /// Whether the node must be updated even when the new element equals the old one.
2112    fn requires_update(&self) -> bool;
2113
2114    /// Whether updating an existing node invalidates all pipeline stages that its capabilities
2115    /// cover.
2116    fn auto_invalidates_on_update(&self) -> bool;
2117
2118    /// The one pipeline stage that an update invalidates, or `None` to invalidate by capabilities.
2119    fn update_invalidation_kind(&self) -> Option<InvalidationKind>;
2120
2121    /// Whether this element has composition locals for its content.
2122    fn provides_composition_locals(&self) -> bool {
2123        false
2124    }
2125
2126    /// The composition locals this element hands to its content.
2127    fn provided_composition_locals(&self) -> Vec<ProvidedValue> {
2128        Vec::new()
2129    }
2130
2131    /// Returns the element as `Any`, for downcasting to its concrete type.
2132    fn as_any(&self) -> &dyn Any;
2133}
2134
2135struct TypedModifierElement<E: ModifierNodeElement> {
2136    element: E,
2137    /// The element's hash, worked out the first time a chain rebuilding
2138    /// asks for it: most elements are built for a recomposition and only
2139    /// compared with the last ones.
2140    hash: Cell<Option<PackedU64>>,
2141    provides_locals: bool,
2142}
2143
2144impl<E: ModifierNodeElement> TypedModifierElement<E> {
2145    fn new(element: E) -> Self {
2146        let provides_locals = !element.provided_composition_locals().is_empty();
2147        Self {
2148            element,
2149            hash: Cell::new(None),
2150            provides_locals,
2151        }
2152    }
2153}
2154
2155impl<E> fmt::Debug for TypedModifierElement<E>
2156where
2157    E: ModifierNodeElement,
2158{
2159    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2160        f.debug_struct("TypedModifierElement")
2161            .field("type", &type_name::<E>())
2162            .finish()
2163    }
2164}
2165
2166impl<E> AnyModifierElement for TypedModifierElement<E>
2167where
2168    E: ModifierNodeElement,
2169{
2170    fn node_type(&self) -> TypeId {
2171        TypeId::of::<E::Node>()
2172    }
2173
2174    fn element_type(&self) -> TypeId {
2175        TypeId::of::<E>()
2176    }
2177
2178    fn create_node(&self) -> Rc<RefCell<dyn ModifierNode>> {
2179        Rc::new(RefCell::new(self.element.create()))
2180    }
2181
2182    fn can_update_node(&self, node: &dyn ModifierNode) -> bool {
2183        node.as_any().is::<E::Node>()
2184    }
2185
2186    fn update_node(&self, node: &mut dyn ModifierNode) {
2187        if let Some(typed) = node.as_any_mut().downcast_mut::<E::Node>() {
2188            self.element.update(typed);
2189        }
2190    }
2191
2192    fn key(&self) -> Option<u64> {
2193        self.element.key()
2194    }
2195
2196    fn capabilities(&self) -> NodeCapabilities {
2197        self.element.capabilities()
2198    }
2199
2200    fn provides_composition_locals(&self) -> bool {
2201        self.provides_locals
2202    }
2203
2204    fn provided_composition_locals(&self) -> Vec<ProvidedValue> {
2205        self.element.provided_composition_locals()
2206    }
2207
2208    fn hash_code(&self) -> u64 {
2209        if let Some(hash) = self.hash.get() {
2210            return hash.get();
2211        }
2212        let mut hasher = default::new();
2213        self.element.hash(&mut hasher);
2214        let hash = hasher.finish();
2215        self.hash.set(Some(PackedU64::new(hash)));
2216        hash
2217    }
2218
2219    fn equals_element(&self, other: &dyn AnyModifierElement) -> bool {
2220        other
2221            .as_any()
2222            .downcast_ref::<Self>()
2223            .is_some_and(|typed| typed.element == self.element)
2224    }
2225
2226    fn inspector_name(&self) -> &'static str {
2227        self.element.inspector_name()
2228    }
2229
2230    fn record_inspector_properties(&self, visitor: &mut dyn FnMut(&'static str, String)) {
2231        self.element.inspector_properties(visitor);
2232    }
2233
2234    fn requires_update(&self) -> bool {
2235        self.element.always_update()
2236    }
2237
2238    fn auto_invalidates_on_update(&self) -> bool {
2239        self.element.auto_invalidate_on_update()
2240    }
2241
2242    fn update_invalidation_kind(&self) -> Option<InvalidationKind> {
2243        self.element.update_invalidation_kind()
2244    }
2245
2246    fn as_any(&self) -> &dyn Any {
2247        self
2248    }
2249}
2250
2251/// Convenience helper for callers to construct a type-erased modifier
2252/// element without having to mention the internal wrapper type.
2253pub fn modifier_element<E: ModifierNodeElement>(element: E) -> DynModifierElement {
2254    Rc::new(TypedModifierElement::new(element))
2255}
2256
2257/// Boxed type-erased modifier element.
2258pub type DynModifierElement = Rc<dyn AnyModifierElement>;
2259
2260#[cfg(test)]
2261#[path = "tests/modifier_tests.rs"]
2262mod tests;