Skip to main content

densa_ui/
access.rs

1//! Accessibility: what each widget is to assistive technology such as screen readers, and the
2//! requests it makes of them (press this, type that, scroll there).
3//!
4//! A window layer turns an [`AccessTree`] into the platform's accessibility tree and hands
5//! the requests it receives back with [`Ui::access_action`](crate::Ui::access_action).
6//! Nothing here depends on a platform API.
7
8use densa_core::Rect;
9
10/// What a widget is to assistive technology.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12#[non_exhaustive]
13pub enum Role {
14    /// A container with no meaning of its own (the default).
15    #[default]
16    Group,
17    /// The window: the tree's root.
18    Window,
19    /// Static text.
20    Label,
21    /// Something pressed to act.
22    Button,
23    /// Editable text.
24    TextInput,
25    /// A region whose content scrolls.
26    ScrollView,
27    /// A divider that resizes the panes either side of it.
28    Splitter,
29    /// A row of tabs.
30    TabList,
31    /// A tab, which shows its panel when chosen.
32    Tab,
33    /// The content a tab shows.
34    TabPanel,
35    /// Rows of cells under column headers.
36    Table,
37    /// A row of a table.
38    Row,
39    /// A column's title in a table.
40    ColumnHeader,
41    /// A cell of a table row.
42    Cell,
43    /// A picture of data, such as a chart: read by its label.
44    Image,
45}
46
47/// Something assistive technology can ask a widget to do.
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49#[non_exhaustive]
50pub enum AccessAction {
51    /// The equivalent of a click.
52    Click,
53    /// Take keyboard focus.
54    Focus,
55    /// Replace the text (the request carries it).
56    SetValue,
57    /// Scroll the content up.
58    ScrollUp,
59    /// Scroll the content down.
60    ScrollDown,
61    /// Scroll the content left.
62    ScrollLeft,
63    /// Scroll the content right.
64    ScrollRight,
65    /// Scroll whatever contains the widget so that it is in view.
66    ScrollIntoView,
67}
68
69impl AccessAction {
70    const ALL: [Self; 8] = [
71        Self::Click,
72        Self::Focus,
73        Self::SetValue,
74        Self::ScrollUp,
75        Self::ScrollDown,
76        Self::ScrollLeft,
77        Self::ScrollRight,
78        Self::ScrollIntoView,
79    ];
80
81    fn bit(self) -> u16 {
82        1 << (self as u16)
83    }
84}
85
86/// A request from assistive technology, as a widget receives it in
87/// [`Widget::access_action`](crate::Widget::access_action).
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89#[non_exhaustive]
90pub struct AccessRequest<'a> {
91    /// What to do.
92    pub action: AccessAction,
93    /// The part of the widget it is for (see [`AccessNode::push_part`]), or the widget itself.
94    pub part: Option<u32>,
95    /// The text, for [`AccessAction::SetValue`].
96    pub value: Option<&'a str>,
97}
98
99/// A node of the accessibility tree: a widget, or a part of one (such as a dock's tab).
100///
101/// Bits a node does not support are not asked of it: [`AccessNode::add_action`] says which
102/// requests it handles.
103#[derive(Debug, Clone, PartialEq, Default)]
104pub struct AccessNode {
105    role: Role,
106    label: Option<String>,
107    value: Option<String>,
108    bounds: Rect,
109    actions: u16,
110    selected: Option<bool>,
111    disabled: bool,
112    clips: bool,
113    placeholder: Option<String>,
114    focused_part: Option<u32>,
115    parts: Vec<(u32, AccessNode)>,
116    children: Vec<AccessId>,
117}
118
119impl AccessNode {
120    /// A node with `role`.
121    pub fn new(role: Role) -> Self {
122        Self {
123            role,
124            ..Self::default()
125        }
126    }
127
128    /// Sets what the node is.
129    pub fn set_role(&mut self, role: Role) {
130        self.role = role;
131    }
132
133    /// Sets its name, such as a button's text. Static text goes in
134    /// [`set_value`](Self::set_value).
135    pub fn set_label(&mut self, label: impl Into<String>) {
136        self.label = Some(label.into());
137    }
138
139    /// Sets its text content: a label's text, a text input's text.
140    pub fn set_value(&mut self, value: impl Into<String>) {
141        self.value = Some(value.into());
142    }
143
144    /// Sets where it is, in physical pixels from the window's top-left corner (a widget's
145    /// node starts with the widget's rectangle).
146    pub fn set_bounds(&mut self, bounds: Rect) {
147        self.bounds = bounds;
148    }
149
150    /// Says it handles `action`.
151    pub fn add_action(&mut self, action: AccessAction) {
152        self.actions |= action.bit();
153    }
154
155    /// Sets whether it is the chosen one of its kind, such as the shown tab.
156    pub fn set_selected(&mut self, selected: bool) {
157        self.selected = Some(selected);
158    }
159
160    /// Sets whether it is unavailable.
161    pub fn set_disabled(&mut self, disabled: bool) {
162        self.disabled = disabled;
163    }
164
165    /// Says which of its parts has keyboard focus while the widget does (such as a dock's
166    /// chosen tab), so assistive technology reads that part rather than the whole widget.
167    pub fn set_focused_part(&mut self, part: u32) {
168        self.focused_part = Some(part);
169    }
170
171    /// The part with keyboard focus while the widget has it.
172    pub fn focused_part(&self) -> Option<u32> {
173        self.focused_part
174    }
175
176    /// Adds `node` as a part of this one, numbered `part`: a piece that is not a widget of its
177    /// own, such as a tab of a dock. Requests for it come to the widget with that number.
178    /// Parts come before the widget's children. Numbers must be unique within the widget, at
179    /// any depth, and at most [`MAX_PART`]; a part breaking either rule is left out. Keep a
180    /// part's number while it lasts (number by identity, not by position), or a screen reader
181    /// tracking it follows another one.
182    pub fn push_part(&mut self, part: u32, node: AccessNode) {
183        self.parts.push((part, node));
184    }
185
186    /// What the node is.
187    pub fn role(&self) -> Role {
188        self.role
189    }
190
191    /// Its name.
192    pub fn label(&self) -> Option<&str> {
193        self.label.as_deref()
194    }
195
196    /// Its text content.
197    pub fn value(&self) -> Option<&str> {
198        self.value.as_deref()
199    }
200
201    /// Where it is, in physical pixels.
202    pub fn bounds(&self) -> Rect {
203        self.bounds
204    }
205
206    /// Whether it handles `action`.
207    pub fn has_action(&self, action: AccessAction) -> bool {
208        self.actions & action.bit() != 0
209    }
210
211    /// The actions it handles.
212    pub fn actions(&self) -> impl Iterator<Item = AccessAction> + '_ {
213        AccessAction::ALL
214            .into_iter()
215            .filter(|a| self.has_action(*a))
216    }
217
218    /// Whether it is the chosen one of its kind, if that applies.
219    pub fn selected(&self) -> Option<bool> {
220        self.selected
221    }
222
223    /// Whether it is unavailable.
224    pub fn is_disabled(&self) -> bool {
225        self.disabled
226    }
227
228    /// Whether it cuts off its children where they leave its bounds (it scrolls or clips),
229    /// so children outside it are off screen.
230    pub fn clips_children(&self) -> bool {
231        self.clips
232    }
233
234    /// Sets whether it cuts off its children where they leave its bounds (a widget's node
235    /// starts with this set when its style clips or scrolls).
236    pub fn set_clips_children(&mut self, clips: bool) {
237        self.clips = clips;
238    }
239
240    /// Sets the hint shown while it is empty, such as a text input's placeholder.
241    pub fn set_placeholder(&mut self, placeholder: impl Into<String>) {
242        self.placeholder = Some(placeholder.into());
243    }
244
245    /// The hint shown while it is empty.
246    pub fn placeholder(&self) -> Option<&str> {
247        self.placeholder.as_deref()
248    }
249
250    /// Its children in the tree, in order (filled in by the [`Ui`](crate::Ui)).
251    pub fn children(&self) -> &[AccessId] {
252        &self.children
253    }
254
255    pub(crate) fn take_parts(&mut self) -> Vec<(u32, AccessNode)> {
256        std::mem::take(&mut self.parts)
257    }
258
259    pub(crate) fn push_child(&mut self, child: AccessId) {
260        self.children.push(child);
261    }
262}
263
264/// The highest part number a widget can use (see [`AccessNode::push_part`]).
265pub const MAX_PART: u32 = (1 << PART_BITS) - 2;
266
267/// Bits of an [`AccessId`] numbering a widget's parts; the rest number widgets.
268const PART_BITS: u32 = 24;
269
270/// Identifies a node of the accessibility tree: a widget, or a part of one. A widget's id is
271/// never given to another widget, even one in its slot after it is removed, so a request for
272/// a removed widget reaches nothing. Get one from [`Ui::access_id`](crate::Ui::access_id).
273#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
274pub struct AccessId(u64);
275
276impl AccessId {
277    /// The node of the widget numbered `serial`.
278    pub(crate) fn widget(serial: u64) -> Self {
279        Self(serial << PART_BITS)
280    }
281
282    /// The node of part `part` of this widget's node (see [`AccessNode::push_part`]); parts
283    /// past [`MAX_PART`] have no node, and get the widget's.
284    #[must_use]
285    pub fn part(self, part: u32) -> Self {
286        if part > MAX_PART {
287            return self;
288        }
289        // Offset by one: part 0 differs from the widget itself.
290        Self(self.0 & !Self::PART_MASK | u64::from(part + 1))
291    }
292
293    const PART_MASK: u64 = (1 << PART_BITS) - 1;
294
295    /// The id as a number, for platform APIs.
296    pub fn to_raw(self) -> u64 {
297        self.0
298    }
299
300    /// The id a [`to_raw`](Self::to_raw) number came from.
301    pub fn from_raw(raw: u64) -> Self {
302        Self(raw)
303    }
304
305    /// The widget's serial and the part (`None` for the widget itself).
306    pub(crate) fn split(self) -> (u64, Option<u32>) {
307        let part = (self.0 & Self::PART_MASK) as u32;
308        (self.0 >> PART_BITS, part.checked_sub(1))
309    }
310}
311
312/// The accessibility tree of a [`Ui`](crate::Ui): every shown widget (and the parts widgets
313/// add), parents before children.
314#[derive(Debug, Clone, PartialEq)]
315pub struct AccessTree {
316    pub(crate) nodes: Vec<(AccessId, AccessNode)>,
317    pub(crate) root: AccessId,
318    pub(crate) focus: AccessId,
319}
320
321impl AccessTree {
322    /// Every node and its id.
323    pub fn nodes(&self) -> &[(AccessId, AccessNode)] {
324        &self.nodes
325    }
326
327    /// The root's id: the [`Ui`](crate::Ui)'s root widget.
328    pub fn root(&self) -> AccessId {
329        self.root
330    }
331
332    /// The node with keyboard focus (the root if no widget has it).
333    pub fn focus(&self) -> AccessId {
334        self.focus
335    }
336
337    /// The node `id`.
338    pub fn node(&self, id: AccessId) -> Option<&AccessNode> {
339        self.nodes.iter().find(|(i, _)| *i == id).map(|(_, n)| n)
340    }
341}