cranpose_ui/layout/
semantics_labels.rs1use std::borrow::Cow;
2
3use super::{SemanticsNode, SemanticsRole, SemanticsWidgetRole};
4
5impl SemanticsNode {
6 pub fn merges_accessibility_descendants(&self) -> bool {
9 self.merge_descendants
10 || !self.actions.is_empty()
11 || self.editable_text
12 || self.password
13 || self.focusable
14 || !self.custom_actions.is_empty()
15 || self.set_progress.is_some()
16 || self.set_text.is_some()
17 || self.set_selection.is_some()
18 || self.on_long_click.is_some()
19 || self.on_magic_tap.is_some()
20 || self.expand.is_some()
21 || self.collapse.is_some()
22 || self.dismiss.is_some()
23 }
24
25 pub fn is_accessibility_boundary(&self) -> bool {
28 self.merges_accessibility_descendants()
29 || self.vertical_scroll.is_some()
30 || self.horizontal_scroll.is_some()
31 || self.selectable_group
32 || self.pane_title.is_some()
33 || matches!(
34 self.widget_role,
35 Some(
36 SemanticsWidgetRole::Dialog
37 | SemanticsWidgetRole::Toolbar
38 | SemanticsWidgetRole::Menu
39 | SemanticsWidgetRole::TabBar
40 | SemanticsWidgetRole::List
41 | SemanticsWidgetRole::RadioGroup
42 )
43 )
44 }
45
46 pub fn accessibility_children(&self) -> Vec<&Self> {
49 let mut children: Vec<_> = self.children.iter().collect();
50 let index = |node: &&Self| {
51 if node.traversal_index.is_finite() {
52 node.traversal_index
53 } else {
54 0.0
55 }
56 };
57 if children.iter().any(|child| index(child) != 0.0) {
58 children.sort_by(|left, right| {
59 index(left)
60 .partial_cmp(&index(right))
61 .unwrap_or(std::cmp::Ordering::Equal)
62 });
63 }
64 children
65 }
66
67 pub fn accessibility_label(&self) -> Option<Cow<'_, str>> {
72 if self.hidden {
73 return None;
74 }
75 let own = self
76 .description
77 .as_deref()
78 .filter(|label| !label.trim().is_empty())
79 .or_else(|| match &self.role {
80 SemanticsRole::Text { value } if !value.as_str().trim().is_empty() => {
81 Some(value.as_str())
82 }
83 _ => None,
84 });
85 if self.password {
86 return Some(Cow::Borrowed(
87 self.description
88 .as_deref()
89 .filter(|label| {
90 !label.trim().is_empty() && Some(*label) != self.text.as_deref()
91 })
92 .unwrap_or("password"),
93 ));
94 }
95 own.map(Cow::Borrowed)
96 .or_else(|| {
97 if !self.merges_accessibility_descendants() {
98 return None;
99 }
100 let mut words = Vec::new();
101 self.collect_accessibility_words(&mut words);
102 (!words.is_empty()).then(|| Cow::Owned(words.join(", ")))
103 })
104 .or_else(|| self.editable_text.then_some(Cow::Borrowed("")))
105 }
106
107 fn collect_accessibility_words<'a>(&'a self, words: &mut Vec<&'a str>) {
108 for child in self.accessibility_children() {
109 if child.hidden || child.is_accessibility_boundary() {
110 continue;
111 }
112 match child.accessibility_label() {
113 Some(Cow::Borrowed(label)) if !label.trim().is_empty() => words.push(label),
114 _ => child.collect_accessibility_words(words),
115 }
116 }
117 }
118}
119
120#[cfg(test)]
121#[path = "tests/semantics_labels.rs"]
122mod tests;