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;