1use accesskit::{
2 Action, ActionRequest, Live, Node, NodeId, Orientation as AccessOrientation, Rect,
3 Role as AccessRole, Tree, TreeId, TreeUpdate,
4};
5
6use crate::{
7 LiveRegion, Role, SemanticAction, SemanticNode, SemanticNodeId, SemanticPatch, SemanticRequest,
8 SemanticTree, SemanticValue,
9};
10
11impl TryFrom<ActionRequest> for SemanticRequest {
12 type Error = ();
13
14 fn try_from(request: ActionRequest) -> Result<Self, Self::Error> {
15 let action = match request.action {
16 Action::Click => SemanticAction::Click,
17 Action::Focus => SemanticAction::Focus,
18 Action::Blur => SemanticAction::Blur,
19 Action::Increment => SemanticAction::Increment,
20 Action::Decrement => SemanticAction::Decrement,
21 Action::Expand => SemanticAction::Expand,
22 Action::Collapse => SemanticAction::Collapse,
23 Action::SetValue => SemanticAction::SetValue,
24 Action::ScrollIntoView => SemanticAction::ScrollIntoView,
25 _ => return Err(()),
26 };
27 let value = match request.data {
28 Some(accesskit::ActionData::Value(value)) => {
29 Some(SemanticValue::Text(value.into_string()))
30 }
31 Some(accesskit::ActionData::NumericValue(value)) => Some(SemanticValue::Number {
32 value,
33 minimum: None,
34 maximum: None,
35 step: None,
36 }),
37 _ => None,
38 };
39 Ok(Self {
40 target: SemanticNodeId::new(request.target_node.0),
41 action,
42 value,
43 })
44 }
45}
46
47pub struct AccessKitTree;
48
49impl AccessKitTree {
50 #[must_use]
51 pub fn full(tree: &SemanticTree) -> TreeUpdate {
52 let mut metadata = Tree::new(NodeId(tree.root.get()));
53 metadata.toolkit_name = Some("Argui".into());
54 metadata.toolkit_version = Some(env!("CARGO_PKG_VERSION").into());
55 TreeUpdate {
56 nodes: tree.nodes.iter().map(lower_node).collect(),
57 tree: Some(metadata),
58 tree_id: TreeId::ROOT,
59 focus: NodeId(tree.focus.get()),
60 }
61 }
62
63 #[must_use]
64 pub fn patch(patch: &SemanticPatch, tree: &SemanticTree) -> TreeUpdate {
65 TreeUpdate {
66 nodes: patch.upserts.iter().map(lower_node).collect(),
67 tree: patch.root.map(|root| Tree::new(NodeId(root.get()))),
68 tree_id: TreeId::ROOT,
69 focus: NodeId(tree.focus.get()),
70 }
71 }
72}
73
74fn lower_node(node: &SemanticNode) -> (NodeId, Node) {
75 let mut output = Node::new(lower_role(node.semantics.role));
76 output.set_bounds(Rect {
77 x0: f64::from(node.bounds.origin.x),
78 y0: f64::from(node.bounds.origin.y),
79 x1: f64::from(node.bounds.origin.x + node.bounds.size.width),
80 y1: f64::from(node.bounds.origin.y + node.bounds.size.height),
81 });
82 output.set_children(
83 node.children
84 .iter()
85 .map(|id| NodeId(id.get()))
86 .collect::<Vec<_>>(),
87 );
88 if let Some(label) = &node.semantics.label {
89 output.set_label(label.clone());
90 }
91 if let Some(description) = &node.semantics.description {
92 output.set_description(description.clone());
93 }
94 let relations = &node.semantics.relations;
95 output.set_labelled_by(
96 relations
97 .labelled_by
98 .iter()
99 .map(|id| NodeId(id.get()))
100 .collect::<Vec<_>>(),
101 );
102 output.set_described_by(
103 relations
104 .described_by
105 .iter()
106 .map(|id| NodeId(id.get()))
107 .collect::<Vec<_>>(),
108 );
109 output.set_controls(
110 relations
111 .controls
112 .iter()
113 .map(|id| NodeId(id.get()))
114 .collect::<Vec<_>>(),
115 );
116 if let Some(id) = relations.active_descendant {
117 output.set_active_descendant(NodeId(id.get()));
118 }
119 let grid = node.semantics.grid;
120 if let Some(value) = grid.row_count {
121 output.set_row_count(value as usize);
122 }
123 if let Some(value) = grid.column_count {
124 output.set_column_count(value as usize);
125 }
126 if let Some(value) = grid.row_index {
127 output.set_row_index(value.saturating_sub(1) as usize);
128 }
129 if let Some(value) = grid.column_index {
130 output.set_column_index(value.saturating_sub(1) as usize);
131 }
132 if let Some(value) = node.semantics.sort {
133 output.set_sort_direction(match value {
134 crate::SortDirection::Ascending => accesskit::SortDirection::Ascending,
135 crate::SortDirection::Descending => accesskit::SortDirection::Descending,
136 });
137 }
138 if let Some(value) = node.semantics.popup {
139 output.set_has_popup(match value {
140 crate::PopupKind::Menu => accesskit::HasPopup::Menu,
141 crate::PopupKind::ListBox => accesskit::HasPopup::Listbox,
142 crate::PopupKind::Tree => accesskit::HasPopup::Tree,
143 crate::PopupKind::Grid => accesskit::HasPopup::Grid,
144 crate::PopupKind::Dialog => accesskit::HasPopup::Dialog,
145 });
146 }
147 match &node.semantics.value {
148 Some(SemanticValue::Text(value)) => output.set_value(value.clone()),
149 Some(SemanticValue::Number {
150 value,
151 minimum,
152 maximum,
153 step,
154 }) => {
155 output.set_numeric_value(*value);
156 if let Some(value) = minimum {
157 output.set_min_numeric_value(*value);
158 }
159 if let Some(value) = maximum {
160 output.set_max_numeric_value(*value);
161 }
162 if let Some(value) = step {
163 output.set_numeric_value_step(*value);
164 }
165 }
166 None => {}
167 }
168 for action in &node.semantics.actions {
169 output.add_action(lower_action(*action));
170 }
171 if node.semantics.state.disabled {
172 output.set_disabled();
173 }
174 output.set_selected(node.semantics.state.selected);
175 if node.semantics.state.multiselectable {
176 output.set_multiselectable();
177 }
178 if let Some(pressed) = node.semantics.state.pressed {
179 output.set_toggled(if pressed {
180 accesskit::Toggled::True
181 } else {
182 accesskit::Toggled::False
183 });
184 }
185 if let Some(value) = node.semantics.state.checked {
186 output.set_toggled(match value {
187 crate::CheckedState::Unchecked => accesskit::Toggled::False,
188 crate::CheckedState::Checked => accesskit::Toggled::True,
189 crate::CheckedState::Mixed => accesskit::Toggled::Mixed,
190 });
191 }
192 if let Some(value) = node.semantics.state.expanded {
193 output.set_expanded(value);
194 }
195 if node.semantics.state.required {
196 output.set_required();
197 }
198 if node.semantics.state.read_only {
199 output.set_read_only();
200 }
201 if node.semantics.state.protected {
202 output.set_role(accesskit::Role::PasswordInput);
203 }
204 if node.semantics.state.invalid {
205 output.set_invalid(accesskit::Invalid::True);
206 }
207 if node.semantics.state.modal {
208 output.set_modal();
209 }
210 if node.semantics.state.busy {
211 output.set_busy();
212 }
213 match node.semantics.live {
214 LiveRegion::Off => {}
215 LiveRegion::Polite => output.set_live(Live::Polite),
216 LiveRegion::Assertive => output.set_live(Live::Assertive),
217 }
218 if let Some(level) = node.semantics.level {
219 output.set_level(level as usize);
220 }
221 if let Some(position) = node.semantics.position_in_set {
222 output.set_position_in_set(position as usize);
223 }
224 if let Some(size) = node.semantics.set_size {
225 output.set_size_of_set(size as usize);
226 }
227 if let Some(orientation) = node.semantics.orientation {
228 output.set_orientation(match orientation {
229 crate::Orientation::Horizontal => AccessOrientation::Horizontal,
230 crate::Orientation::Vertical => AccessOrientation::Vertical,
231 });
232 }
233 (NodeId(node.id.get()), output)
234}
235
236const fn lower_role(role: Role) -> AccessRole {
237 match role {
238 Role::Generic => AccessRole::GenericContainer,
239 Role::Window => AccessRole::Window,
240 Role::Group => AccessRole::Group,
241 Role::Navigation => AccessRole::Navigation,
242 Role::Text => AccessRole::Label,
243 Role::Heading => AccessRole::Heading,
244 Role::Image => AccessRole::Image,
245 Role::Link => AccessRole::Link,
246 Role::Button => AccessRole::Button,
247 Role::CheckBox => AccessRole::CheckBox,
248 Role::RadioButton => AccessRole::RadioButton,
249 Role::Switch => AccessRole::Switch,
250 Role::TextInput => AccessRole::TextInput,
251 Role::TextArea => AccessRole::MultilineTextInput,
252 Role::SearchInput => AccessRole::SearchInput,
253 Role::Table | Role::Grid => AccessRole::Table,
254 Role::Row => AccessRole::Row,
255 Role::ColumnHeader => AccessRole::ColumnHeader,
256 Role::Cell => AccessRole::Cell,
257 Role::List => AccessRole::List,
258 Role::ListItem => AccessRole::ListItem,
259 Role::ListBox => AccessRole::ListBox,
260 Role::Option => AccessRole::ListBoxOption,
261 Role::Menu => AccessRole::Menu,
262 Role::MenuBar => AccessRole::MenuBar,
263 Role::MenuItemCheckBox => AccessRole::MenuItemCheckBox,
264 Role::MenuItemRadio => AccessRole::MenuItemRadio,
265 Role::ComboBox => AccessRole::ComboBox,
266 Role::Tooltip => AccessRole::Tooltip,
267 Role::Status => AccessRole::Status,
268 Role::AlertDialog => AccessRole::AlertDialog,
269 Role::MenuItem => AccessRole::MenuItem,
270 Role::Slider => AccessRole::Slider,
271 Role::Progress => AccessRole::ProgressIndicator,
272 Role::Tab => AccessRole::Tab,
273 Role::TabList => AccessRole::TabList,
274 Role::TabPanel => AccessRole::TabPanel,
275 Role::Dialog => AccessRole::Dialog,
276 Role::Alert => AccessRole::Alert,
277 Role::Separator => AccessRole::Splitter,
278 Role::Tree => AccessRole::Tree,
279 Role::TreeItem => AccessRole::TreeItem,
280 }
281}
282
283const fn lower_action(action: SemanticAction) -> Action {
284 match action {
285 SemanticAction::Click => Action::Click,
286 SemanticAction::Focus => Action::Focus,
287 SemanticAction::Blur => Action::Blur,
288 SemanticAction::Increment => Action::Increment,
289 SemanticAction::Decrement => Action::Decrement,
290 SemanticAction::Expand => Action::Expand,
291 SemanticAction::Collapse => Action::Collapse,
292 SemanticAction::SetValue => Action::SetValue,
293 SemanticAction::ScrollIntoView => Action::ScrollIntoView,
294 }
295}