Skip to main content

i_slint_core/
accessibility.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4// cSpell: ignore descendents
5
6use crate::{
7    SharedString,
8    item_tree::ItemTreeVTable,
9    items::{ItemRc, TextInput},
10};
11use alloc::{vec, vec::Vec};
12use bitflags::bitflags;
13use vtable::VRcMapped;
14
15/// The property names of the accessible-properties
16#[repr(u32)]
17#[derive(PartialEq, Eq, Copy, Clone, strum::Display)]
18#[strum(serialize_all = "kebab-case")]
19pub enum AccessibleStringProperty {
20    Checkable,
21    Checked,
22    DelegateFocus,
23    Description,
24    Enabled,
25    Expandable,
26    Expanded,
27    Id,
28    ItemCount,
29    ItemIndex,
30    ItemSelectable,
31    ItemSelected,
32    Label,
33    PlaceholderText,
34    ReadOnly,
35    Value,
36    ValueMaximum,
37    ValueMinimum,
38    ValueStep,
39}
40
41/// The argument of an accessible action.
42#[repr(u32)]
43#[derive(PartialEq, Clone)]
44pub enum AccessibilityAction {
45    Default,
46    Decrement,
47    Increment,
48    Expand,
49    /// This is currently unused
50    ReplaceSelectedText(SharedString),
51    SetValue(SharedString),
52}
53
54bitflags! {
55    /// Define a accessibility actions that supported by an item.
56    #[repr(transparent)]
57    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
58    pub struct SupportedAccessibilityAction: u32 {
59        const Default = 1;
60        const Decrement = 1 << 1;
61        const Increment = 1 << 2;
62        const Expand = 1 << 3;
63        const ReplaceSelectedText = 1 << 4;
64        const SetValue = 1 << 5;
65    }
66}
67
68/// Find accessible descendents of `root_item`.
69///
70/// This will recurse through all children of `root_item`, but will not recurse
71/// into nodes that are accessible.
72pub fn accessible_descendents(root_item: &ItemRc) -> impl Iterator<Item = ItemRc> {
73    fn try_candidate_or_find_next_accessible_descendent(
74        candidate: ItemRc,
75        descendent_candidates: &mut Vec<ItemRc>,
76    ) -> Option<ItemRc> {
77        if candidate.is_accessible() {
78            return Some(candidate);
79        }
80
81        candidate.first_child().and_then(|child| {
82            if let Some(next) = child.next_sibling() {
83                descendent_candidates.push(next);
84            }
85            try_candidate_or_find_next_accessible_descendent(child, descendent_candidates)
86        })
87    }
88
89    // Do not look on the root_item: That is either a component root or an
90    // accessible item already handled!
91    let mut descendent_candidates = Vec::new();
92    if let Some(child) = root_item.first_child() {
93        descendent_candidates.push(child);
94    }
95
96    core::iter::from_fn(move || {
97        loop {
98            let candidate = descendent_candidates.pop()?;
99
100            if let Some(next_candidate) = candidate.next_sibling() {
101                descendent_candidates.push(next_candidate);
102            }
103
104            if let Some(descendent) = try_candidate_or_find_next_accessible_descendent(
105                candidate,
106                &mut descendent_candidates,
107            ) {
108                return Some(descendent);
109            }
110        }
111    })
112}
113
114/// Find the first built-in `TextInput` in the descendents of `item`.
115pub fn find_text_input(item: &ItemRc) -> Option<VRcMapped<ItemTreeVTable, TextInput>> {
116    fn try_candidate_or_find_next_descendent(
117        candidate: ItemRc,
118        descendent_candidates: &mut Vec<ItemRc>,
119    ) -> Option<VRcMapped<ItemTreeVTable, TextInput>> {
120        if let Some(input) = candidate.downcast::<TextInput>() {
121            return Some(input);
122        }
123
124        candidate.first_child().and_then(|child| {
125            if let Some(next) = child.next_sibling() {
126                descendent_candidates.push(next);
127            }
128            try_candidate_or_find_next_descendent(child, descendent_candidates)
129        })
130    }
131
132    let mut descendent_candidates = vec![item.clone()];
133
134    loop {
135        let candidate = descendent_candidates.pop()?;
136
137        if let Some(next_candidate) = candidate.next_sibling() {
138            descendent_candidates.push(next_candidate);
139        }
140
141        if let Some(input) =
142            try_candidate_or_find_next_descendent(candidate, &mut descendent_candidates)
143        {
144            return Some(input);
145        }
146    }
147}