use std::{
any::{Any, TypeId, type_name},
cell::{Cell, RefCell},
fmt,
hash::{Hash, Hasher},
ops::{BitOr, BitOrAssign},
rc::Rc,
};
use cranpose_core::{PackedU64, ProvidedValue, hash::default};
pub use cranpose_ui_graphics::{DrawScope, Size};
pub use cranpose_ui_layout::{Constraints, Measurable};
use crate::nodes::input::types::PointerEvent;
mod chain;
mod chain_node_ref;
mod scroll_axis_range;
pub use chain::ModifierNodeChain;
pub use chain_node_ref::{ModifierChainIter, ModifierChainNodeRef};
pub use scroll_axis_range::ScrollAxisRange;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum InvalidationKind {
Layout,
Draw,
PointerInput,
Semantics,
Focus,
}
pub trait ModifierNodeContext {
fn invalidate(&mut self, _kind: InvalidationKind) {}
fn request_update(&mut self) {}
fn node_id(&self) -> Option<cranpose_core::NodeId> {
None
}
fn push_active_capabilities(&mut self, _capabilities: NodeCapabilities) {}
fn pop_active_capabilities(&mut self) {}
fn density(&self) -> f32 {
1.0
}
}
#[derive(Default, Debug, Clone)]
pub struct BasicModifierNodeContext {
invalidations: ModifierInvalidations,
update_requested: bool,
active_capabilities: smallvec::SmallVec<[NodeCapabilities; 4]>,
node_id: Option<cranpose_core::NodeId>,
}
impl BasicModifierNodeContext {
pub fn new() -> Self {
Self::default()
}
pub fn for_node(
node_id: Option<cranpose_core::NodeId>,
invalidations: ModifierInvalidations,
) -> Self {
Self {
invalidations,
node_id,
..Self::default()
}
}
pub fn invalidations(&self) -> &[ModifierInvalidation] {
&self.invalidations
}
pub fn clear_invalidations(&mut self) {
self.invalidations.clear();
}
pub fn take_invalidations(&mut self) -> ModifierInvalidations {
std::mem::take(&mut self.invalidations)
}
pub fn update_requested(&self) -> bool {
self.update_requested
}
pub fn take_update_requested(&mut self) -> bool {
std::mem::take(&mut self.update_requested)
}
pub fn set_node_id(&mut self, id: Option<cranpose_core::NodeId>) {
self.node_id = id;
}
fn push_invalidation(&mut self, kind: InvalidationKind) {
let mut capabilities = self.current_capabilities();
capabilities.insert(NodeCapabilities::for_invalidation(kind));
if let Some(existing) = self
.invalidations
.iter_mut()
.find(|entry| entry.kind() == kind)
{
let updated = existing.capabilities() | capabilities;
*existing = ModifierInvalidation::new(kind, updated);
} else {
self.invalidations
.push(ModifierInvalidation::new(kind, capabilities));
}
}
fn current_capabilities(&self) -> NodeCapabilities {
self.active_capabilities
.last()
.copied()
.unwrap_or_else(NodeCapabilities::empty)
}
}
impl ModifierNodeContext for BasicModifierNodeContext {
fn invalidate(&mut self, kind: InvalidationKind) {
self.push_invalidation(kind);
}
fn request_update(&mut self) {
self.update_requested = true;
}
fn push_active_capabilities(&mut self, capabilities: NodeCapabilities) {
self.active_capabilities.push(capabilities);
}
fn pop_active_capabilities(&mut self) {
self.active_capabilities.pop();
}
fn node_id(&self) -> Option<cranpose_core::NodeId> {
self.node_id
}
}
const MAX_DELEGATE_DEPTH: usize = 3;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) struct NodePath {
entry: u32,
delegate_buf: [u8; MAX_DELEGATE_DEPTH],
delegate_len: u8,
}
impl NodePath {
#[inline]
fn root(entry: usize) -> Self {
debug_assert!(entry <= u32::MAX as usize, "chain entry exceeds u32 range");
Self {
entry: entry as u32,
delegate_buf: [0; MAX_DELEGATE_DEPTH],
delegate_len: 0,
}
}
#[inline]
fn from_slice(entry: usize, path: &[usize]) -> Self {
debug_assert!(
path.len() <= MAX_DELEGATE_DEPTH,
"delegate depth {} exceeds MAX_DELEGATE_DEPTH {}",
path.len(),
MAX_DELEGATE_DEPTH
);
debug_assert!(
path.iter().all(|&i| i <= u8::MAX as usize),
"delegate index exceeds u8 range"
);
let mut delegate_buf = [0u8; MAX_DELEGATE_DEPTH];
for (i, &v) in path.iter().enumerate().take(MAX_DELEGATE_DEPTH) {
delegate_buf[i] = v as u8;
}
Self {
delegate_buf,
delegate_len: path.len().min(MAX_DELEGATE_DEPTH) as u8,
..Self::root(entry)
}
}
#[inline]
fn entry(&self) -> usize {
self.entry as usize
}
#[inline]
fn delegates(&self) -> &[u8] {
&self.delegate_buf[..self.delegate_len as usize]
}
}
#[derive(Debug)]
pub struct NodeState {
capabilities: Cell<NodeCapabilities>,
attached: Cell<bool>,
}
impl Default for NodeState {
fn default() -> Self {
Self::new()
}
}
impl NodeState {
pub const fn new() -> Self {
Self {
capabilities: Cell::new(NodeCapabilities::empty()),
attached: Cell::new(false),
}
}
pub fn set_capabilities(&self, capabilities: NodeCapabilities) {
self.capabilities.set(capabilities);
}
#[inline]
pub fn capabilities(&self) -> NodeCapabilities {
self.capabilities.get()
}
pub(crate) fn set_attached(&self, attached: bool) {
self.attached.set(attached);
}
pub fn is_attached(&self) -> bool {
self.attached.get()
}
}
pub trait DelegatableNode {
fn node_state(&self) -> &NodeState;
}
pub trait ModifierNode: Any + DelegatableNode {
fn on_attach(&mut self, _context: &mut dyn ModifierNodeContext) {}
fn on_detach(&mut self) {}
fn on_reset(&mut self) {}
fn as_draw_node(&self) -> Option<&dyn DrawModifierNode> {
None
}
fn as_draw_node_mut(&mut self) -> Option<&mut dyn DrawModifierNode> {
None
}
fn as_pointer_input_node(&self) -> Option<&dyn PointerInputNode> {
None
}
fn as_pointer_input_node_mut(&mut self) -> Option<&mut dyn PointerInputNode> {
None
}
fn as_semantics_node(&self) -> Option<&dyn SemanticsNode> {
None
}
fn as_semantics_node_mut(&mut self) -> Option<&mut dyn SemanticsNode> {
None
}
fn as_focus_node(&self) -> Option<&dyn FocusNode> {
None
}
fn as_focus_node_mut(&mut self) -> Option<&mut dyn FocusNode> {
None
}
fn as_layout_node(&self) -> Option<&dyn LayoutModifierNode> {
None
}
fn as_layout_node_mut(&mut self) -> Option<&mut dyn LayoutModifierNode> {
None
}
fn for_each_delegate<'b>(&'b self, _visitor: &mut dyn FnMut(&'b dyn ModifierNode)) {}
fn for_each_delegate_mut<'b>(&'b mut self, _visitor: &mut dyn FnMut(&'b mut dyn ModifierNode)) {
}
}
pub trait LayoutModifierNode: ModifierNode {
fn measure(
&self,
_context: &mut dyn ModifierNodeContext,
measurable: &dyn Measurable,
constraints: Constraints,
) -> cranpose_ui_layout::LayoutModifierMeasureResult {
let placeable = measurable.measure(constraints);
cranpose_ui_layout::LayoutModifierMeasureResult::with_size(Size {
width: placeable.width(),
height: placeable.height(),
})
}
fn measure_hold(
&self,
_density: f32,
_constraints: Constraints,
_size: Size,
_wrapped: cranpose_ui_layout::WrappedHold,
) -> Option<cranpose_ui_layout::ConstraintsHold> {
None
}
fn min_intrinsic_width(&self, measurable: &dyn Measurable, height: f32, _density: f32) -> f32 {
measurable.min_intrinsic_width(height)
}
fn max_intrinsic_width(&self, measurable: &dyn Measurable, height: f32, _density: f32) -> f32 {
measurable.max_intrinsic_width(height)
}
fn min_intrinsic_height(&self, measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
measurable.min_intrinsic_height(width)
}
fn max_intrinsic_height(&self, measurable: &dyn Measurable, width: f32, _density: f32) -> f32 {
measurable.max_intrinsic_height(width)
}
}
pub trait DrawModifierNode: ModifierNode {
fn create_draw_closure(&self) -> Option<NodeDrawClosure> {
None
}
fn create_behind_draw_closure(&self) -> Option<NodeDrawClosure> {
None
}
}
pub type NodeDrawClosure = Rc<dyn Fn(&mut cranpose_ui_graphics::DrawScopeDefault)>;
pub trait PointerInputNode: ModifierNode {
fn on_pointer_event(
&mut self,
_context: &mut dyn ModifierNodeContext,
_event: &PointerEvent,
) -> bool {
false
}
fn hit_test(&self, _x: f32, _y: f32) -> bool {
true
}
fn pointer_input_handler(&self) -> Option<Rc<dyn Fn(PointerEvent)>> {
None
}
fn layout_size_sink(&self) -> Option<Rc<Cell<Size>>> {
None
}
}
pub trait SemanticsNode: ModifierNode {
fn merge_semantics(&self, _config: &mut SemanticsConfiguration) {}
fn reach(&self) -> SemanticsReach {
SemanticsReach::merged_by(self, true)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct SemanticsReach {
pub is_modal: bool,
pub hidden: bool,
pub merges_live_state: bool,
}
impl SemanticsReach {
pub fn merged_by(node: &(impl SemanticsNode + ?Sized), merges_live_state: bool) -> Self {
let mut config = SemanticsConfiguration::default();
node.merge_semantics(&mut config);
Self {
is_modal: config.is_modal,
hidden: config.hidden,
merges_live_state,
}
}
pub fn union(self, other: Self) -> Self {
Self {
is_modal: self.is_modal || other.is_modal,
hidden: self.hidden || other.hidden,
merges_live_state: self.merges_live_state || other.merges_live_state,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ToggleableState {
On,
Off,
Indeterminate,
}
impl From<bool> for ToggleableState {
fn from(on: bool) -> Self {
if on { Self::On } else { Self::Off }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub enum FocusState {
Active,
ActiveParent,
Captured,
#[default]
Inactive,
}
impl FocusState {
pub fn is_focused(self) -> bool {
matches!(self, Self::Active | Self::Captured)
}
pub fn has_focus(self) -> bool {
matches!(self, Self::Active | Self::ActiveParent | Self::Captured)
}
pub fn is_captured(self) -> bool {
matches!(self, Self::Captured)
}
}
pub trait FocusNode: ModifierNode {
fn focus_state(&self) -> FocusState;
fn on_focus_changed(&mut self, _context: &mut dyn ModifierNodeContext, _state: FocusState) {}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum SemanticsWidgetRole {
Button,
Checkbox,
Switch,
RadioButton,
Tab,
Image,
DropdownList,
ValuePicker,
Header,
Dialog,
Link,
SearchField,
ProgressBar,
ToggleButton,
Alert,
Toolbar,
Menu,
MenuItem,
TabBar,
List,
ListItem,
RadioGroup,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ProgressBarRangeInfo {
pub current: f32,
pub start: f32,
pub end: f32,
pub steps: u32,
}
impl ProgressBarRangeInfo {
pub fn new(current: f32, start: f32, end: f32, steps: u32) -> Self {
Self {
current,
start,
end,
steps,
}
}
pub fn fraction(&self) -> f32 {
let span = self.end - self.start;
if span.abs() < f32::EPSILON {
return 0.0;
}
((self.current - self.start) / span).clamp(0.0, 1.0)
}
pub fn step(&self) -> f32 {
let span = self.end - self.start;
if self.steps == 0 {
span / 10.0
} else {
span / (self.steps as f32 + 1.0)
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CollectionInfo {
pub rows: usize,
pub columns: usize,
}
#[derive(Clone)]
pub struct SemanticsScrollBy {
handler: Rc<dyn Fn(f32, f32) -> bool>,
}
impl SemanticsScrollBy {
pub fn new(handler: impl Fn(f32, f32) -> bool + 'static) -> Self {
Self {
handler: Rc::new(handler),
}
}
pub fn invoke(&self, dx: f32, dy: f32) -> bool {
(self.handler)(dx, dy)
}
}
impl fmt::Debug for SemanticsScrollBy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SemanticsScrollBy").finish_non_exhaustive()
}
}
impl PartialEq for SemanticsScrollBy {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for SemanticsScrollBy {}
#[derive(Clone)]
pub struct SemanticsScrollToIndex {
handler: Rc<dyn Fn(usize) -> bool>,
}
impl SemanticsScrollToIndex {
pub fn new(handler: impl Fn(usize) -> bool + 'static) -> Self {
Self {
handler: Rc::new(handler),
}
}
pub fn invoke(&self, index: usize) -> bool {
(self.handler)(index)
}
}
impl fmt::Debug for SemanticsScrollToIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SemanticsScrollToIndex")
.finish_non_exhaustive()
}
}
impl PartialEq for SemanticsScrollToIndex {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for SemanticsScrollToIndex {}
#[derive(Clone)]
pub struct SemanticsSetProgress {
handler: Rc<dyn Fn(f32) -> bool>,
}
impl SemanticsSetProgress {
pub fn new(handler: impl Fn(f32) -> bool + 'static) -> Self {
Self {
handler: Rc::new(handler),
}
}
pub fn invoke(&self, value: f32) -> bool {
(self.handler)(value)
}
}
impl fmt::Debug for SemanticsSetProgress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SemanticsSetProgress")
.finish_non_exhaustive()
}
}
#[derive(Clone)]
pub struct SemanticsSetText(Rc<dyn Fn(&str) -> bool>);
impl SemanticsSetText {
pub fn new(handler: impl Fn(&str) -> bool + 'static) -> Self {
Self(Rc::new(handler))
}
pub fn invoke(&self, text: &str) -> bool {
(self.0)(text)
}
}
impl fmt::Debug for SemanticsSetText {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("SemanticsSetText")
}
}
#[derive(Clone)]
pub struct SemanticsSetSelection(Rc<dyn Fn(usize, usize) -> bool>);
impl SemanticsSetSelection {
pub fn new(handler: impl Fn(usize, usize) -> bool + 'static) -> Self {
Self(Rc::new(handler))
}
pub fn invoke(&self, anchor: usize, focus: usize) -> bool {
(self.0)(anchor, focus)
}
}
impl fmt::Debug for SemanticsSetSelection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("SemanticsSetSelection")
}
}
#[derive(Clone)]
pub struct SemanticsExpand(Rc<dyn Fn() -> bool>);
impl SemanticsExpand {
pub fn new(handler: impl Fn() -> bool + 'static) -> Self {
Self(Rc::new(handler))
}
pub fn invoke(&self) -> bool {
(self.0)()
}
}
impl fmt::Debug for SemanticsExpand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("SemanticsExpand")
}
}
#[derive(Clone)]
pub struct SemanticsLongClick(Rc<dyn Fn() -> bool>);
impl SemanticsLongClick {
pub fn new(handler: impl Fn() -> bool + 'static) -> Self {
Self(Rc::new(handler))
}
pub fn invoke(&self) -> bool {
(self.0)()
}
}
impl fmt::Debug for SemanticsLongClick {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("SemanticsLongClick")
}
}
impl PartialEq for SemanticsLongClick {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl PartialEq for SemanticsExpand {
fn eq(&self, _other: &Self) -> bool {
true
}
}
#[derive(Clone)]
pub struct SemanticsDismiss(Rc<dyn Fn() -> bool>);
impl SemanticsDismiss {
pub fn new(handler: impl Fn() -> bool + 'static) -> Self {
Self(Rc::new(handler))
}
pub fn invoke(&self) -> bool {
(self.0)()
}
}
impl fmt::Debug for SemanticsDismiss {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("SemanticsDismiss")
}
}
impl PartialEq for SemanticsDismiss {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl PartialEq for SemanticsSetText {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl PartialEq for SemanticsSetSelection {
fn eq(&self, _other: &Self) -> bool {
true
}
}
#[derive(Clone)]
pub struct SemanticsMagicTap(Rc<dyn Fn() -> bool>);
impl SemanticsMagicTap {
pub fn new(handler: impl Fn() -> bool + 'static) -> Self {
Self(Rc::new(handler))
}
pub fn invoke(&self) -> bool {
(self.0)()
}
}
impl fmt::Debug for SemanticsMagicTap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("SemanticsMagicTap")
}
}
impl PartialEq for SemanticsMagicTap {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl PartialEq for SemanticsSetProgress {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl Eq for SemanticsSetProgress {}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum LiveRegionMode {
Polite,
Assertive,
}
#[derive(Clone)]
pub struct SemanticsCustomAction {
pub label: String,
handler: Rc<dyn Fn()>,
}
impl SemanticsCustomAction {
pub fn new(label: impl Into<String>, handler: impl Fn() + 'static) -> Self {
Self {
label: label.into(),
handler: Rc::new(handler),
}
}
pub fn invoke(&self) {
(self.handler)();
}
}
impl fmt::Debug for SemanticsCustomAction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SemanticsCustomAction")
.field("label", &self.label)
.finish_non_exhaustive()
}
}
impl PartialEq for SemanticsCustomAction {
fn eq(&self, other: &Self) -> bool {
self.label == other.label
}
}
impl Eq for SemanticsCustomAction {}
#[derive(Clone, Debug, PartialEq)]
pub struct CanvasSemanticsNode {
pub key: u64,
pub bounds: cranpose_ui_graphics::Rect,
pub label: String,
pub role: Option<SemanticsWidgetRole>,
pub state_description: Option<String>,
pub on_click_label: Option<String>,
pub clickable: bool,
pub selected: Option<bool>,
pub toggled: Option<ToggleableState>,
pub enabled: bool,
pub custom_actions: Vec<SemanticsCustomAction>,
}
impl Default for CanvasSemanticsNode {
fn default() -> Self {
Self {
key: 0,
bounds: cranpose_ui_graphics::Rect {
x: 0.0,
y: 0.0,
width: 0.0,
height: 0.0,
},
label: String::new(),
role: None,
state_description: None,
on_click_label: None,
clickable: false,
selected: None,
toggled: None,
enabled: true,
custom_actions: Vec::new(),
}
}
}
impl CanvasSemanticsNode {
pub fn control(key: u64, bounds: cranpose_ui_graphics::Rect, label: impl Into<String>) -> Self {
Self {
key,
bounds,
label: label.into(),
clickable: true,
..Self::default()
}
}
pub fn text(key: u64, bounds: cranpose_ui_graphics::Rect, label: impl Into<String>) -> Self {
Self {
key,
bounds,
label: label.into(),
..Self::default()
}
}
pub fn with_role(mut self, role: SemanticsWidgetRole) -> Self {
self.role = Some(role);
self
}
pub fn with_state_description(mut self, state: impl Into<String>) -> Self {
self.state_description = Some(state.into());
self
}
pub fn with_click_label(mut self, label: impl Into<String>) -> Self {
self.on_click_label = Some(label.into());
self.clickable = true;
self
}
pub fn with_selected(mut self, selected: bool) -> Self {
self.selected = Some(selected);
self
}
pub fn with_toggled(mut self, toggled: impl Into<ToggleableState>) -> Self {
self.toggled = Some(toggled.into());
self
}
pub fn with_enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn with_custom_action(mut self, action: SemanticsCustomAction) -> Self {
self.custom_actions.push(action);
self
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct SemanticsConfiguration {
pub content_description: Option<String>,
pub state_description: Option<String>,
pub on_click_label: Option<String>,
pub on_click: Option<SemanticsCustomAction>,
pub on_long_click: Option<SemanticsLongClick>,
pub on_long_click_label: Option<String>,
pub on_magic_tap: Option<SemanticsMagicTap>,
pub on_magic_tap_label: Option<String>,
pub input_labels: Vec<String>,
pub language: Option<String>,
pub role: Option<SemanticsWidgetRole>,
pub selected: Option<bool>,
pub toggled: Option<ToggleableState>,
pub enabled: bool,
pub is_clickable: bool,
pub is_editable_text: bool,
pub multiline: bool,
pub text: Option<String>,
pub text_selection: Option<crate::text::TextRange>,
pub custom_actions: Vec<SemanticsCustomAction>,
pub canvas_children: Vec<CanvasSemanticsNode>,
pub is_modal: bool,
pub hidden: bool,
pub merge_descendants: bool,
pub selectable_group: bool,
pub pane_title: Option<String>,
pub error: Option<String>,
pub hint_text: Option<String>,
pub password: bool,
pub traversal_index: f32,
pub test_tag: Option<String>,
pub live_region: Option<LiveRegionMode>,
pub progress: Option<ProgressBarRangeInfo>,
pub set_progress: Option<SemanticsSetProgress>,
pub set_text: Option<SemanticsSetText>,
pub set_selection: Option<SemanticsSetSelection>,
pub expand: Option<SemanticsExpand>,
pub dismiss: Option<SemanticsDismiss>,
pub collapse: Option<SemanticsExpand>,
pub vertical_scroll: Option<ScrollAxisRange>,
pub horizontal_scroll: Option<ScrollAxisRange>,
pub scroll_by: Option<SemanticsScrollBy>,
pub scroll_to_index: Option<SemanticsScrollToIndex>,
pub collection: Option<CollectionInfo>,
}
impl Default for SemanticsConfiguration {
fn default() -> Self {
Self {
content_description: None,
state_description: None,
on_click_label: None,
on_click: None,
on_long_click: None,
on_long_click_label: None,
on_magic_tap: None,
on_magic_tap_label: None,
input_labels: Vec::new(),
language: None,
role: None,
selected: None,
toggled: None,
enabled: true,
is_clickable: false,
is_editable_text: false,
multiline: false,
text: None,
text_selection: None,
custom_actions: Vec::new(),
canvas_children: Vec::new(),
is_modal: false,
hidden: false,
merge_descendants: false,
selectable_group: false,
pane_title: None,
error: None,
hint_text: None,
password: false,
traversal_index: 0.0,
test_tag: None,
live_region: None,
progress: None,
set_progress: None,
set_text: None,
set_selection: None,
expand: None,
dismiss: None,
collapse: None,
vertical_scroll: None,
horizontal_scroll: None,
scroll_by: None,
scroll_to_index: None,
collection: None,
}
}
}
pub type SemanticsSpec = SemanticsConfiguration;
impl SemanticsConfiguration {
pub fn new() -> Self {
Self::default()
}
pub fn content_description(mut self, name: impl Into<String>) -> Self {
self.content_description = Some(name.into());
self
}
pub fn test_tag(mut self, tag: impl Into<String>) -> Self {
self.test_tag = Some(tag.into());
self
}
pub fn state_description(mut self, state: impl Into<String>) -> Self {
self.state_description = Some(state.into());
self
}
pub fn clickable(mut self) -> Self {
self.is_clickable = true;
self
}
pub fn on_click(mut self, label: impl Into<String>, action: impl Fn() + 'static) -> Self {
self.on_click = Some(SemanticsCustomAction::new(label, action));
self
}
pub fn on_long_click(
mut self,
label: impl Into<String>,
action: impl Fn() -> bool + 'static,
) -> Self {
self.on_long_click_label = Some(label.into());
self.on_long_click = Some(SemanticsLongClick::new(action));
self
}
pub fn on_magic_tap(
mut self,
label: impl Into<String>,
action: impl Fn() -> bool + 'static,
) -> Self {
self.on_magic_tap_label = Some(label.into());
self.on_magic_tap = Some(SemanticsMagicTap::new(action));
self
}
pub fn input_labels<S: Into<String>>(mut self, labels: impl IntoIterator<Item = S>) -> Self {
self.input_labels = labels.into_iter().map(Into::into).collect();
self
}
pub fn language(mut self, tag: impl Into<String>) -> Self {
self.language = Some(tag.into());
self
}
pub fn toggled(mut self, toggled: impl Into<ToggleableState>) -> Self {
self.toggled = Some(toggled.into());
self
}
pub fn selected(mut self, selected: bool) -> Self {
self.selected = Some(selected);
self
}
pub fn role(mut self, role: SemanticsWidgetRole) -> Self {
self.role = Some(role);
self
}
pub fn heading(self) -> Self {
self.role(SemanticsWidgetRole::Header)
}
pub fn error(mut self, message: impl Into<String>) -> Self {
self.error = Some(message.into());
self
}
pub fn hint_text(mut self, hint: impl Into<String>) -> Self {
self.hint_text = Some(hint.into());
self
}
pub fn password(mut self) -> Self {
self.password = true;
self
}
pub fn pane_title(mut self, title: impl Into<String>) -> Self {
self.pane_title = Some(title.into());
self
}
pub fn traversal_index(mut self, index: f32) -> Self {
self.traversal_index = index;
self
}
pub fn hidden(mut self) -> Self {
self.hidden = true;
self
}
pub fn merge_descendants(mut self) -> Self {
self.merge_descendants = true;
self
}
pub fn selectable_group(mut self) -> Self {
self.selectable_group = true;
self
}
pub fn live_region(mut self, mode: LiveRegionMode) -> Self {
self.live_region = Some(mode);
self
}
pub fn merge(&mut self, other: &Self) {
if let Some(description) = &other.content_description {
self.content_description = Some(description.clone());
}
if let Some(state) = &other.state_description {
self.state_description = Some(state.clone());
}
if let Some(label) = &other.on_click_label {
self.on_click_label = Some(label.clone());
}
if let Some(label) = &other.on_long_click_label {
self.on_long_click_label = Some(label.clone());
}
if let Some(label) = &other.on_magic_tap_label {
self.on_magic_tap_label = Some(label.clone());
}
if !other.input_labels.is_empty() {
self.input_labels.clone_from(&other.input_labels);
}
if let Some(language) = &other.language {
self.language = Some(language.clone());
}
if let Some(role) = other.role {
self.role = Some(role);
}
if let Some(selected) = other.selected {
self.selected = Some(selected);
}
if let Some(toggled) = other.toggled {
self.toggled = Some(toggled);
}
self.enabled &= other.enabled;
self.is_clickable |= other.is_clickable;
self.is_editable_text |= other.is_editable_text;
self.multiline |= other.multiline;
if let Some(text) = &other.text {
self.text = Some(text.clone());
}
self.is_modal |= other.is_modal;
self.hidden |= other.hidden;
self.merge_descendants |= other.merge_descendants;
self.selectable_group |= other.selectable_group;
self.password |= other.password;
if other.traversal_index != 0.0 {
self.traversal_index = other.traversal_index;
}
if let Some(live_region) = other.live_region {
self.live_region = Some(live_region);
}
self.merge_words(other);
self.merge_actions(other);
self.merge_ranges(other);
}
fn merge_words(&mut self, other: &Self) {
if let Some(title) = &other.pane_title {
self.pane_title = Some(title.clone());
}
if let Some(error) = &other.error {
self.error = Some(error.clone());
}
if let Some(hint) = &other.hint_text {
self.hint_text = Some(hint.clone());
}
if let Some(tag) = &other.test_tag {
self.test_tag = Some(tag.clone());
}
}
fn merge_actions(&mut self, other: &Self) {
if let Some(on_click) = &other.on_click {
self.on_click = Some(on_click.clone());
}
self.custom_actions
.extend(other.custom_actions.iter().cloned());
self.canvas_children
.extend(other.canvas_children.iter().cloned());
if let Some(set_progress) = &other.set_progress {
self.set_progress = Some(set_progress.clone());
}
if let Some(set_text) = &other.set_text {
self.set_text = Some(set_text.clone());
}
if let Some(set_selection) = &other.set_selection {
self.set_selection = Some(set_selection.clone());
}
if let Some(expand) = &other.expand {
self.expand = Some(expand.clone());
}
if let Some(collapse) = &other.collapse {
self.collapse = Some(collapse.clone());
}
if let Some(dismiss) = &other.dismiss {
self.dismiss = Some(dismiss.clone());
}
if let Some(long_click) = &other.on_long_click {
self.on_long_click = Some(long_click.clone());
}
if let Some(magic_tap) = &other.on_magic_tap {
self.on_magic_tap = Some(magic_tap.clone());
}
if let Some(scroll_by) = &other.scroll_by {
self.scroll_by = Some(scroll_by.clone());
}
if let Some(scroll_to_index) = &other.scroll_to_index {
self.scroll_to_index = Some(scroll_to_index.clone());
}
}
fn merge_ranges(&mut self, other: &Self) {
if let Some(selection) = other.text_selection {
self.text_selection = Some(selection);
}
if let Some(progress) = other.progress {
self.progress = Some(progress);
}
if let Some(range) = other.vertical_scroll {
self.vertical_scroll = Some(range);
}
if let Some(range) = other.horizontal_scroll {
self.horizontal_scroll = Some(range);
}
if let Some(collection) = other.collection {
self.collection = Some(collection);
}
}
pub fn is_activatable(&self) -> bool {
self.is_clickable || self.on_click_label.is_some() || self.on_click.is_some()
}
}
impl fmt::Debug for dyn ModifierNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ModifierNode").finish_non_exhaustive()
}
}
impl dyn ModifierNode {
pub fn as_any(&self) -> &dyn Any {
self
}
pub fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
pub trait ModifierNodeElement: fmt::Debug + Hash + PartialEq + 'static {
type Node: ModifierNode;
fn create(&self) -> Self::Node;
fn update(&self, node: &mut Self::Node);
fn key(&self) -> Option<u64> {
None
}
fn inspector_name(&self) -> &'static str {
type_name::<Self>()
}
fn inspector_properties(&self, _inspector: &mut dyn FnMut(&'static str, String)) {}
fn capabilities(&self) -> NodeCapabilities {
NodeCapabilities::default()
}
fn always_update(&self) -> bool {
false
}
fn auto_invalidate_on_update(&self) -> bool {
true
}
fn update_invalidation_kind(&self) -> Option<InvalidationKind> {
None
}
fn provided_composition_locals(&self) -> Vec<ProvidedValue> {
Vec::new()
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct NodeCapabilities(u32);
impl NodeCapabilities {
pub const NONE: Self = Self(0);
pub const LAYOUT: Self = Self(1 << 0);
pub const DRAW: Self = Self(1 << 1);
pub const POINTER_INPUT: Self = Self(1 << 2);
pub const SEMANTICS: Self = Self(1 << 3);
pub const MODIFIER_LOCALS: Self = Self(1 << 4);
pub const FOCUS: Self = Self(1 << 5);
pub const WINDOW_ROOT: Self = Self(1 << 6);
pub const KEY_INPUT: Self = Self(1 << 7);
pub const fn empty() -> Self {
Self::NONE
}
pub const fn contains(self, other: Self) -> bool {
(self.0 & other.0) == other.0
}
pub const fn intersects(self, other: Self) -> bool {
(self.0 & other.0) != 0
}
pub fn insert(&mut self, other: Self) {
self.0 |= other.0;
}
pub const fn bits(self) -> u32 {
self.0
}
pub const fn is_empty(self) -> bool {
self.0 == 0
}
pub const fn for_invalidation(kind: InvalidationKind) -> Self {
match kind {
InvalidationKind::Layout => Self::LAYOUT,
InvalidationKind::Draw => Self::DRAW,
InvalidationKind::PointerInput => Self::POINTER_INPUT,
InvalidationKind::Semantics => Self::SEMANTICS,
InvalidationKind::Focus => Self::FOCUS,
}
}
}
impl Default for NodeCapabilities {
fn default() -> Self {
Self::NONE
}
}
impl fmt::Debug for NodeCapabilities {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NodeCapabilities")
.field("layout", &self.contains(Self::LAYOUT))
.field("draw", &self.contains(Self::DRAW))
.field("pointer_input", &self.contains(Self::POINTER_INPUT))
.field("semantics", &self.contains(Self::SEMANTICS))
.field("modifier_locals", &self.contains(Self::MODIFIER_LOCALS))
.field("focus", &self.contains(Self::FOCUS))
.field("window_root", &self.contains(Self::WINDOW_ROOT))
.field("key_input", &self.contains(Self::KEY_INPUT))
.finish()
}
}
impl BitOr for NodeCapabilities {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self(self.0 | rhs.0)
}
}
impl BitOrAssign for NodeCapabilities {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
pub type ModifierInvalidations = smallvec::SmallVec<[ModifierInvalidation; 5]>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ModifierInvalidation {
kind: InvalidationKind,
capabilities: NodeCapabilities,
}
impl ModifierInvalidation {
pub const fn new(kind: InvalidationKind, capabilities: NodeCapabilities) -> Self {
Self { kind, capabilities }
}
pub const fn kind(self) -> InvalidationKind {
self.kind
}
pub const fn capabilities(self) -> NodeCapabilities {
self.capabilities
}
}
pub trait AnyModifierElement: fmt::Debug {
fn node_type(&self) -> TypeId;
fn element_type(&self) -> TypeId;
fn create_node(&self) -> Rc<RefCell<dyn ModifierNode>>;
fn can_update_node(&self, node: &dyn ModifierNode) -> bool;
fn update_node(&self, node: &mut dyn ModifierNode);
fn key(&self) -> Option<u64>;
fn capabilities(&self) -> NodeCapabilities {
NodeCapabilities::default()
}
fn hash_code(&self) -> u64;
fn equals_element(&self, other: &dyn AnyModifierElement) -> bool;
fn inspector_name(&self) -> &'static str;
fn record_inspector_properties(&self, visitor: &mut dyn FnMut(&'static str, String));
fn requires_update(&self) -> bool;
fn auto_invalidates_on_update(&self) -> bool;
fn update_invalidation_kind(&self) -> Option<InvalidationKind>;
fn provides_composition_locals(&self) -> bool {
false
}
fn provided_composition_locals(&self) -> Vec<ProvidedValue> {
Vec::new()
}
fn as_any(&self) -> &dyn Any;
}
struct TypedModifierElement<E: ModifierNodeElement> {
element: E,
hash: Cell<Option<PackedU64>>,
provides_locals: bool,
}
impl<E: ModifierNodeElement> TypedModifierElement<E> {
fn new(element: E) -> Self {
let provides_locals = !element.provided_composition_locals().is_empty();
Self {
element,
hash: Cell::new(None),
provides_locals,
}
}
}
impl<E> fmt::Debug for TypedModifierElement<E>
where
E: ModifierNodeElement,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TypedModifierElement")
.field("type", &type_name::<E>())
.finish()
}
}
impl<E> AnyModifierElement for TypedModifierElement<E>
where
E: ModifierNodeElement,
{
fn node_type(&self) -> TypeId {
TypeId::of::<E::Node>()
}
fn element_type(&self) -> TypeId {
TypeId::of::<E>()
}
fn create_node(&self) -> Rc<RefCell<dyn ModifierNode>> {
Rc::new(RefCell::new(self.element.create()))
}
fn can_update_node(&self, node: &dyn ModifierNode) -> bool {
node.as_any().is::<E::Node>()
}
fn update_node(&self, node: &mut dyn ModifierNode) {
if let Some(typed) = node.as_any_mut().downcast_mut::<E::Node>() {
self.element.update(typed);
}
}
fn key(&self) -> Option<u64> {
self.element.key()
}
fn capabilities(&self) -> NodeCapabilities {
self.element.capabilities()
}
fn provides_composition_locals(&self) -> bool {
self.provides_locals
}
fn provided_composition_locals(&self) -> Vec<ProvidedValue> {
self.element.provided_composition_locals()
}
fn hash_code(&self) -> u64 {
if let Some(hash) = self.hash.get() {
return hash.get();
}
let mut hasher = default::new();
self.element.hash(&mut hasher);
let hash = hasher.finish();
self.hash.set(Some(PackedU64::new(hash)));
hash
}
fn equals_element(&self, other: &dyn AnyModifierElement) -> bool {
other
.as_any()
.downcast_ref::<Self>()
.is_some_and(|typed| typed.element == self.element)
}
fn inspector_name(&self) -> &'static str {
self.element.inspector_name()
}
fn record_inspector_properties(&self, visitor: &mut dyn FnMut(&'static str, String)) {
self.element.inspector_properties(visitor);
}
fn requires_update(&self) -> bool {
self.element.always_update()
}
fn auto_invalidates_on_update(&self) -> bool {
self.element.auto_invalidate_on_update()
}
fn update_invalidation_kind(&self) -> Option<InvalidationKind> {
self.element.update_invalidation_kind()
}
fn as_any(&self) -> &dyn Any {
self
}
}
pub fn modifier_element<E: ModifierNodeElement>(element: E) -> DynModifierElement {
Rc::new(TypedModifierElement::new(element))
}
pub type DynModifierElement = Rc<dyn AnyModifierElement>;
#[cfg(test)]
#[path = "tests/modifier_tests.rs"]
mod tests;