Skip to main content

argui_accessibility/
tree.rs

1use std::collections::{HashMap, HashSet};
2
3use argui_core::Rect;
4
5use crate::Semantics;
6
7#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
8pub struct SemanticNodeId(u64);
9
10impl SemanticNodeId {
11    #[must_use]
12    pub const fn new(value: u64) -> Self {
13        Self(value)
14    }
15
16    #[must_use]
17    pub const fn get(self) -> u64 {
18        self.0
19    }
20}
21
22#[derive(Clone, Debug, PartialEq)]
23pub struct SemanticNode {
24    pub id: SemanticNodeId,
25    pub bounds: Rect,
26    pub semantics: Semantics,
27    pub children: Vec<SemanticNodeId>,
28}
29
30#[derive(Clone, Debug, PartialEq)]
31pub struct SemanticTree {
32    pub root: SemanticNodeId,
33    pub focus: SemanticNodeId,
34    pub nodes: Vec<SemanticNode>,
35}
36
37impl SemanticTree {
38    /// Converts surface-relative bounds for hosts whose accessible nodes are
39    /// positioned relative to their semantic parent (for example nested DOM).
40    /// Root and unattached nodes retain surface coordinates. Recompute after
41    /// parent movement/reparenting even when a child's surface bounds are unchanged.
42    #[must_use]
43    pub fn parent_relative_bounds(&self) -> HashMap<SemanticNodeId, Rect> {
44        let mut bounds = self
45            .nodes
46            .iter()
47            .map(|node| (node.id, node.bounds))
48            .collect::<HashMap<_, _>>();
49        for parent in &self.nodes {
50            for child in &parent.children {
51                if let Some(bounds) = bounds.get_mut(child) {
52                    bounds.origin.x -= parent.bounds.origin.x;
53                    bounds.origin.y -= parent.bounds.origin.y;
54                }
55            }
56        }
57        bounds
58    }
59
60    #[must_use]
61    pub fn node(&self, id: SemanticNodeId) -> Option<&SemanticNode> {
62        self.nodes.iter().find(|node| node.id == id)
63    }
64
65    #[must_use]
66    pub fn diff(&self, next: &Self) -> SemanticPatch {
67        let old = self
68            .nodes
69            .iter()
70            .map(|node| (node.id, node))
71            .collect::<HashMap<_, _>>();
72        let next_ids = next
73            .nodes
74            .iter()
75            .map(|node| node.id)
76            .collect::<HashSet<_>>();
77        SemanticPatch {
78            root: (self.root != next.root).then_some(next.root),
79            focus: (self.focus != next.focus).then_some(next.focus),
80            upserts: next
81                .nodes
82                .iter()
83                .filter(|node| old.get(&node.id).copied() != Some(*node))
84                .cloned()
85                .collect(),
86            removed: self
87                .nodes
88                .iter()
89                .filter(|node| !next_ids.contains(&node.id))
90                .map(|node| node.id)
91                .collect(),
92        }
93    }
94}
95
96#[derive(Clone, Debug, Default, PartialEq)]
97pub struct SemanticPatch {
98    pub root: Option<SemanticNodeId>,
99    pub focus: Option<SemanticNodeId>,
100    pub upserts: Vec<SemanticNode>,
101    pub removed: Vec<SemanticNodeId>,
102}
103
104impl SemanticPatch {
105    #[must_use]
106    pub fn is_empty(&self) -> bool {
107        self.root.is_none()
108            && self.focus.is_none()
109            && self.upserts.is_empty()
110            && self.removed.is_empty()
111    }
112}