use accesskit::{
Action, DefaultActionVerb, Node, NodeBuilder, NodeClassSet, NodeId as AccessibilityId, Rect,
Role, Tree, TreeUpdate,
};
use dioxus_native_core::{
prelude::{NodeType, TextNode},
real_dom::NodeImmutable,
NodeId,
};
use freya_dom::prelude::{DioxusDOM, DioxusNode};
use freya_layout::Layers;
use freya_node_state::AccessibilityState;
use std::slice::Iter;
use tokio::sync::watch;
use torin::{prelude::NodeAreas, torin::Torin};
#[derive(PartialEq)]
pub enum AccessibilityFocusDirection {
Forward,
Backward,
}
pub trait AccessibilityProvider {
fn add_node(
&mut self,
dioxus_node: &DioxusNode,
node_areas: &NodeAreas,
accessibility_id: AccessibilityId,
node_accessibility: &AccessibilityState,
) {
let mut builder = NodeBuilder::new(Role::Unknown);
let children = dioxus_node.get_accessibility_children();
if !children.is_empty() {
builder.set_children(children);
}
if let Some(alt) = &node_accessibility.alt {
builder.set_value(alt.to_owned());
} else if let Some(value) = dioxus_node.get_inner_texts() {
builder.set_value(value);
}
if let Some(name) = &node_accessibility.name {
builder.set_name(name.to_owned());
}
if let Some(role) = node_accessibility.role {
builder.set_role(role);
}
let area = node_areas.area.to_f64();
builder.set_bounds(Rect {
x0: area.min_x(),
x1: area.max_x(),
y0: area.min_y(),
y1: area.max_y(),
});
if node_accessibility.focusable {
builder.add_action(Action::Focus);
} else {
builder.add_action(Action::Default);
builder.set_default_action_verb(DefaultActionVerb::Focus);
}
let node = builder.build(self.node_classes());
self.push_node(accessibility_id, node);
}
fn push_node(&mut self, id: AccessibilityId, node: Node);
fn node_classes(&mut self) -> &mut NodeClassSet;
fn nodes(&self) -> Iter<(AccessibilityId, Node)>;
fn focus_id(&self) -> Option<AccessibilityId>;
fn set_focus(&mut self, new_focus_id: Option<AccessibilityId>);
fn set_focus_with_update(
&mut self,
new_focus_id: Option<AccessibilityId>,
) -> Option<TreeUpdate> {
self.set_focus(new_focus_id);
let node_focused_exists = self.nodes().any(|node| Some(node.0) == new_focus_id);
if node_focused_exists {
Some(TreeUpdate {
nodes: Vec::new(),
tree: None,
focus: self.focus_id(),
})
} else {
None
}
}
fn build_root(&mut self, root_name: &str) -> Node {
let mut builder = NodeBuilder::new(Role::Window);
builder.set_name(root_name.to_string());
builder.set_children(
self.nodes()
.map(|(id, _)| *id)
.collect::<Vec<AccessibilityId>>(),
);
builder.build(self.node_classes())
}
fn process(&mut self, root_id: AccessibilityId, root_name: &str) -> TreeUpdate {
let root = self.build_root(root_name);
let mut nodes = vec![(root_id, root)];
nodes.extend(self.nodes().cloned());
nodes.reverse();
let focus = self.nodes().find_map(|node| {
if Some(node.0) == self.focus_id() {
Some(node.0)
} else {
None
}
});
TreeUpdate {
nodes,
tree: Some(Tree::new(root_id)),
focus,
}
}
fn set_focus_on_next_node(
&mut self,
direction: AccessibilityFocusDirection,
focus_sender: &watch::Sender<Option<AccessibilityId>>,
) -> Option<TreeUpdate> {
if let Some(focused_node_id) = self.focus_id() {
let current_node = self
.nodes()
.enumerate()
.find(|(_, node)| node.0 == focused_node_id)
.map(|(i, _)| i);
if let Some(node_index) = current_node {
let target_node_index = if direction == AccessibilityFocusDirection::Forward {
if node_index == self.nodes().len() - 1 {
0
} else {
node_index + 1
}
} else {
if node_index == 0 {
self.nodes().len() - 1
} else {
node_index - 1
}
};
let target_node = self
.nodes()
.enumerate()
.find(|(i, _)| *i == target_node_index)
.map(|(_, node)| node.0);
self.set_focus(target_node);
} else {
self.set_focus(self.nodes().next().map(|(id, _)| *id))
}
focus_sender.send(self.focus_id()).ok();
Some(TreeUpdate {
nodes: Vec::new(),
tree: None,
focus: self.focus_id(),
})
} else {
None
}
}
}
trait NodeAccessibility {
fn get_inner_texts(&self) -> Option<String>;
fn get_accessibility_children(&self) -> Vec<AccessibilityId>;
}
impl NodeAccessibility for DioxusNode<'_> {
fn get_inner_texts(&self) -> Option<String> {
let children = self.children();
let first_child = children.first()?;
let node_type = first_child.node_type();
if let NodeType::Text(TextNode { text, .. }) = &*node_type {
Some(text.to_owned())
} else {
None
}
}
fn get_accessibility_children(&self) -> Vec<AccessibilityId> {
self.children()
.iter()
.filter_map(|child| {
let node_accessibility = &*child.get::<AccessibilityState>().unwrap();
node_accessibility.focus_id
})
.collect::<Vec<AccessibilityId>>()
}
}
pub fn process_accessibility(
layers: &Layers,
layout: &Torin<NodeId>,
rdom: &DioxusDOM,
access_provider: &mut impl AccessibilityProvider,
) {
for layer in layers.layers.values() {
for node_id in layer {
let node_areas = layout.get(*node_id).unwrap();
let dioxus_node = rdom.get(*node_id);
if let Some(dioxus_node) = dioxus_node {
let node_accessibility = &*dioxus_node.get::<AccessibilityState>().unwrap();
if let Some(accessibility_id) = node_accessibility.focus_id {
access_provider.add_node(
&dioxus_node,
node_areas,
accessibility_id,
node_accessibility,
);
}
}
}
}
}