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    item_tree::ItemTreeVTable,
8    items::{ItemRc, TextInput},
9    SharedString,
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    ItemCount,
28    ItemIndex,
29    ItemSelectable,
30    ItemSelected,
31    Label,
32    PlaceholderText,
33    ReadOnly,
34    Value,
35    ValueMaximum,
36    ValueMinimum,
37    ValueStep,
38}
39
40/// The argument of an accessible action.
41#[repr(u32)]
42#[derive(PartialEq, Clone)]
43pub enum AccessibilityAction {
44    Default,
45    Decrement,
46    Increment,
47    Expand,
48    /// This is currently unused
49    ReplaceSelectedText(SharedString),
50    SetValue(SharedString),
51}
52
53bitflags! {
54    /// Define a accessibility actions that supported by an item.
55    #[repr(transparent)]
56    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
57    pub struct SupportedAccessibilityAction: u32 {
58        const Default = 1;
59        const Decrement = 1 << 1;
60        const Increment = 1 << 2;
61        const Expand = 1 << 3;
62        const ReplaceSelectedText = 1 << 4;
63        const SetValue = 1 << 5;
64    }
65}
66
67/// Find accessible descendents of `root_item`.
68///
69/// This will recurse through all children of `root_item`, but will not recurse
70/// into nodes that are accessible.
71pub fn accessible_descendents(root_item: &ItemRc) -> impl Iterator<Item = ItemRc> {
72    fn try_candidate_or_find_next_accessible_descendent(
73        candidate: ItemRc,
74        descendent_candidates: &mut Vec<ItemRc>,
75    ) -> Option<ItemRc> {
76        if candidate.is_accessible() {
77            return Some(candidate);
78        }
79
80        candidate.first_child().and_then(|child| {
81            if let Some(next) = child.next_sibling() {
82                descendent_candidates.push(next);
83            }
84            try_candidate_or_find_next_accessible_descendent(child, descendent_candidates)
85        })
86    }
87
88    // Do not look on the root_item: That is either a component root or an
89    // accessible item already handled!
90    let mut descendent_candidates = Vec::new();
91    if let Some(child) = root_item.first_child() {
92        descendent_candidates.push(child);
93    }
94
95    core::iter::from_fn(move || loop {
96        let candidate = descendent_candidates.pop()?;
97
98        if let Some(next_candidate) = candidate.next_sibling() {
99            descendent_candidates.push(next_candidate);
100        }
101
102        if let Some(descendent) =
103            try_candidate_or_find_next_accessible_descendent(candidate, &mut descendent_candidates)
104        {
105            return Some(descendent);
106        }
107    })
108}
109
110/// Find the first built-in `TextInput` in the descendents of `item`.
111pub fn find_text_input(item: &ItemRc) -> Option<VRcMapped<ItemTreeVTable, TextInput>> {
112    fn try_candidate_or_find_next_descendent(
113        candidate: ItemRc,
114        descendent_candidates: &mut Vec<ItemRc>,
115    ) -> Option<VRcMapped<ItemTreeVTable, TextInput>> {
116        if let Some(input) = candidate.downcast::<TextInput>() {
117            return Some(input);
118        }
119
120        candidate.first_child().and_then(|child| {
121            if let Some(next) = child.next_sibling() {
122                descendent_candidates.push(next);
123            }
124            try_candidate_or_find_next_descendent(child, descendent_candidates)
125        })
126    }
127
128    let mut descendent_candidates = vec![item.clone()];
129
130    loop {
131        let candidate = descendent_candidates.pop()?;
132
133        if let Some(next_candidate) = candidate.next_sibling() {
134            descendent_candidates.push(next_candidate);
135        }
136
137        if let Some(input) =
138            try_candidate_or_find_next_descendent(candidate, &mut descendent_candidates)
139        {
140            return Some(input);
141        }
142    }
143}