use accesskit::{
Action, ActionRequest, Node, NodeId, Rect as AkRect, Role as AkRole, Toggled, Tree, TreeId,
TreeUpdate,
};
use platform_core::{AccessNode, Role};
const ROOT: NodeId = NodeId(0);
pub(crate) fn tree_update(nodes: &[AccessNode], title: &str) -> TreeUpdate {
let mut root = Node::new(AkRole::Window);
root.set_label(title.to_string());
let mut updates: Vec<(NodeId, Node)> = Vec::with_capacity(nodes.len() + 1);
let mut children = Vec::with_capacity(nodes.len());
let mut focus = ROOT;
for (index, node) in nodes.iter().enumerate() {
let id = match node.id {
Some(focus_id) => NodeId(focus_id.wrapping_add(1 << 32)),
None => NodeId(index as u64 + 1),
};
let mut ak = Node::new(role_of(node.role));
ak.set_label(node.name.clone());
ak.set_bounds(AkRect {
x0: node.rect.x as f64,
y0: node.rect.y as f64,
x1: (node.rect.x + node.rect.width) as f64,
y1: (node.rect.y + node.rect.height) as f64,
});
if node.id.is_some() {
ak.add_action(Action::Focus);
ak.add_action(Action::Click);
}
if !node.enabled {
ak.set_disabled();
}
if node.focused {
focus = id;
}
if let Some(on) = node.toggled {
ak.set_toggled(if on { Toggled::True } else { Toggled::False });
}
if let Some(v) = node.value {
ak.set_numeric_value(v.now);
ak.set_min_numeric_value(v.min);
ak.set_max_numeric_value(v.max);
}
children.push(id);
updates.push((id, ak));
}
root.set_children(children);
updates.push((ROOT, root));
TreeUpdate {
nodes: updates,
tree: Some(Tree::new(ROOT)),
tree_id: TreeId::ROOT,
focus,
}
}
fn role_of(role: Role) -> AkRole {
match role {
Role::Button => AkRole::Button,
Role::CheckBox => AkRole::CheckBox,
Role::Radio => AkRole::RadioButton,
Role::Switch => AkRole::Switch,
Role::Tab => AkRole::Tab,
Role::MenuItem => AkRole::MenuItem,
Role::Slider => AkRole::Slider,
Role::SpinButton => AkRole::SpinButton,
Role::TextInput => AkRole::TextInput,
Role::MultilineTextInput => AkRole::MultilineTextInput,
Role::ComboBox => AkRole::ComboBox,
Role::Disclosure => AkRole::DisclosureTriangle,
Role::Label => AkRole::Label,
}
}
pub(crate) fn requested_focus_id(
request: &ActionRequest,
nodes: &[AccessNode],
) -> Option<(u64, bool)> {
let target = request.target_node.0;
let id = target.checked_sub(1 << 32)?;
nodes.iter().find(|n| n.id == Some(id))?;
let activate = matches!(request.action, Action::Click);
Some((id, activate))
}
#[cfg(test)]
mod tests {
use geometry_core::Rect;
use super::*;
fn node(id: Option<u64>, role: Role, name: &str) -> AccessNode {
AccessNode {
id,
role,
name: name.to_string(),
rect: Rect::new(0.0, 0.0, 10.0, 10.0),
focused: false,
enabled: true,
toggled: None,
value: None,
}
}
#[test]
fn the_window_is_the_root_and_everything_hangs_from_it() {
let nodes = vec![
node(Some(7), Role::Button, "Save"),
node(None, Role::Label, "Unsaved changes"),
];
let update = tree_update(&nodes, "Editor");
assert_eq!(update.nodes.len(), 3, "two nodes plus the window");
let (root_id, root) = update.nodes.last().unwrap();
assert_eq!(*root_id, ROOT);
assert_eq!(root.role(), AkRole::Window);
assert_eq!(root.children().len(), 2);
assert_eq!(update.nodes[0].1.role(), AkRole::Button);
assert_eq!(update.nodes[1].1.role(), AkRole::Label);
}
#[test]
fn a_control_keeps_the_same_node_id_between_updates() {
let first = tree_update(&[node(Some(7), Role::Button, "Save")], "Editor");
let with_extra = tree_update(
&[
node(None, Role::Label, "Heading"),
node(Some(7), Role::Button, "Save"),
],
"Editor",
);
let button = |u: &TreeUpdate| {
u.nodes
.iter()
.find(|(_, n)| n.role() == AkRole::Button)
.unwrap()
.0
};
assert_eq!(button(&first), button(&with_extra));
}
#[test]
fn focus_points_at_the_focused_control_or_at_the_window() {
assert_eq!(tree_update(&[], "Editor").focus, ROOT);
let mut focused = node(Some(7), Role::Button, "Save");
focused.focused = true;
let update = tree_update(&[focused], "Editor");
assert_ne!(update.focus, ROOT);
assert_eq!(update.focus, update.nodes[0].0);
}
#[test]
fn a_request_maps_back_to_the_control_that_claimed_it() {
let nodes = vec![node(Some(7), Role::Button, "Save")];
let update = tree_update(&nodes, "Editor");
let target = update.nodes[0].0;
let request = ActionRequest {
action: Action::Click,
target_tree: TreeId::ROOT,
target_node: target,
data: None,
};
assert_eq!(requested_focus_id(&request, &nodes), Some((7, true)));
let stale = ActionRequest {
action: Action::Focus,
target_tree: TreeId::ROOT,
target_node: NodeId(target.0 + 1),
data: None,
};
assert_eq!(requested_focus_id(&stale, &nodes), None);
assert_eq!(
requested_focus_id(
&ActionRequest {
action: Action::Focus,
target_tree: TreeId::ROOT,
target_node: ROOT,
data: None
},
&nodes
),
None,
"the window itself is not a control"
);
}
}