Skip to main content

lgui_core/core/
semantics.rs

1use std::ops::Range;
2
3use super::{InteractionRole, UiId, UiNode, UiNodeKind, UiRect};
4
5#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
6pub enum SemanticRole {
7    #[default]
8    Generic,
9    Window,
10    Group,
11    Label,
12    Text,
13    Image,
14    Button,
15    Link,
16    Navigation,
17    List,
18    ListItem,
19    Table,
20    Row,
21    Cell,
22    CheckBox,
23    Switch,
24    RadioButton,
25    TextInput,
26    PasswordInput,
27    SearchInput,
28    ComboBox,
29    ListBoxOption,
30    Slider,
31    ProgressIndicator,
32    Dialog,
33    Alert,
34    Tab,
35    TabList,
36    TabPanel,
37    Menu,
38    MenuItem,
39    ScrollView,
40    Custom,
41}
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
44pub enum SemanticAction {
45    Click,
46    Focus,
47    Blur,
48    SetValue,
49    Increment,
50    Decrement,
51    ScrollIntoView,
52    ScrollUp,
53    ScrollDown,
54    ScrollLeft,
55    ScrollRight,
56    SetTextSelection,
57}
58
59#[derive(Clone, Debug, Default, PartialEq)]
60pub struct SemanticState {
61    pub disabled: bool,
62    pub selected: bool,
63    pub checked: Option<bool>,
64    pub expanded: Option<bool>,
65    pub read_only: bool,
66    pub required: bool,
67    pub busy: bool,
68    pub invalid: bool,
69    pub hidden: bool,
70    pub multiline: bool,
71    pub password: bool,
72}
73
74#[derive(Clone, Debug, Default, PartialEq, Eq)]
75pub struct SemanticRelationships {
76    pub labelled_by: Vec<UiId>,
77    pub described_by: Vec<UiId>,
78    pub controls: Vec<UiId>,
79    pub owns: Vec<UiId>,
80}
81
82#[derive(Clone, Debug, Default, PartialEq, Eq)]
83pub struct SemanticText {
84    pub selection: Option<Range<usize>>,
85    pub character_count: Option<usize>,
86}
87
88#[derive(Clone, Debug, PartialEq)]
89pub struct Semantics {
90    pub role: SemanticRole,
91    pub name: Option<String>,
92    pub description: Option<String>,
93    pub value: Option<String>,
94    pub numeric_value: Option<f64>,
95    pub numeric_min: Option<f64>,
96    pub numeric_max: Option<f64>,
97    pub numeric_step: Option<f64>,
98    pub state: SemanticState,
99    pub actions: Vec<SemanticAction>,
100    pub relationships: SemanticRelationships,
101    pub text: SemanticText,
102}
103
104impl Semantics {
105    pub fn new(role: SemanticRole) -> Self {
106        Self {
107            role,
108            name: None,
109            description: None,
110            value: None,
111            numeric_value: None,
112            numeric_min: None,
113            numeric_max: None,
114            numeric_step: None,
115            state: SemanticState::default(),
116            actions: Vec::new(),
117            relationships: SemanticRelationships::default(),
118            text: SemanticText::default(),
119        }
120    }
121
122    pub fn name(mut self, value: impl Into<String>) -> Self {
123        self.name = Some(value.into());
124        self
125    }
126
127    pub fn description(mut self, value: impl Into<String>) -> Self {
128        self.description = Some(value.into());
129        self
130    }
131
132    pub fn value(mut self, value: impl Into<String>) -> Self {
133        self.value = Some(value.into());
134        self
135    }
136
137    pub fn action(mut self, action: SemanticAction) -> Self {
138        if !self.actions.contains(&action) {
139            self.actions.push(action);
140        }
141        self
142    }
143}
144
145#[derive(Clone, Debug, PartialEq)]
146pub struct SemanticNode {
147    pub id: UiId,
148    pub parent: Option<UiId>,
149    pub children: Vec<UiId>,
150    pub bounds: UiRect,
151    pub semantics: Semantics,
152}
153
154impl SemanticNode {
155    pub(crate) fn from_ui_node(node: &UiNode) -> Self {
156        let mut semantics = node
157            .semantics
158            .clone()
159            .unwrap_or_else(|| inferred_semantics(node));
160        if node.event_policy.focus && !semantics.actions.contains(&SemanticAction::Focus) {
161            semantics.actions.push(SemanticAction::Focus);
162        }
163        if (node.click_action.is_some() || node.click_handler.is_some())
164            && !semantics.actions.contains(&SemanticAction::Click)
165        {
166            semantics.actions.push(SemanticAction::Click);
167        }
168        Self {
169            id: node.id.clone(),
170            parent: node.parent.clone(),
171            children: node.children.clone(),
172            bounds: node.hit_rect,
173            semantics,
174        }
175    }
176}
177
178#[derive(Clone, Debug, Default, PartialEq)]
179pub struct SemanticUpdate {
180    pub nodes: Vec<SemanticNode>,
181    pub removed: Vec<UiId>,
182    pub focus: Option<UiId>,
183    pub full: bool,
184}
185
186impl SemanticUpdate {
187    #[cfg(feature = "accessibility")]
188    pub(crate) fn full_from_tree(tree: &super::HostTree, focus: Option<UiId>) -> Self {
189        Self {
190            nodes: tree
191                .nodes()
192                .iter()
193                .map(|node| SemanticNode::from_ui_node(node))
194                .collect(),
195            removed: Vec::new(),
196            focus,
197            full: true,
198        }
199    }
200}
201
202fn inferred_semantics(node: &UiNode) -> Semantics {
203    let role = match (node.kind, node.interaction) {
204        (UiNodeKind::Root, _) => SemanticRole::Window,
205        (UiNodeKind::Text, _) => SemanticRole::Text,
206        (UiNodeKind::Image | UiNodeKind::Icon, _) => SemanticRole::Image,
207        (UiNodeKind::Button, _) | (_, InteractionRole::Button) => SemanticRole::Button,
208        (UiNodeKind::Table, _) => SemanticRole::Table,
209        (UiNodeKind::TableRow, _) | (_, InteractionRole::Row) => SemanticRole::Row,
210        (_, InteractionRole::Navigation) => SemanticRole::Navigation,
211        (_, InteractionRole::DragHandle) => SemanticRole::Slider,
212        (UiNodeKind::Group | UiNodeKind::Panel, _) => SemanticRole::Group,
213        (_, InteractionRole::Custom(_)) => SemanticRole::Custom,
214        _ => SemanticRole::Generic,
215    };
216    let mut semantics = Semantics::new(role);
217    if let Some(text) = node.text.as_ref() {
218        if matches!(role, SemanticRole::Text | SemanticRole::Label) {
219            semantics.value = Some(text.to_string());
220            semantics.text.character_count = Some(text.chars().count());
221        } else {
222            semantics.name = Some(text.to_string());
223        }
224    }
225    semantics
226}