Skip to main content

argui_accessibility/
schema.rs

1#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
2pub enum Role {
3    #[default]
4    Generic,
5    Window,
6    Group,
7    Navigation,
8    Text,
9    Heading,
10    Image,
11    Link,
12    Button,
13    CheckBox,
14    RadioButton,
15    Switch,
16    TextInput,
17    TextArea,
18    SearchInput,
19    Table,
20    Grid,
21    Row,
22    ColumnHeader,
23    Cell,
24    List,
25    ListItem,
26    Tree,
27    TreeItem,
28    ListBox,
29    Option,
30    Menu,
31    MenuItem,
32    MenuBar,
33    MenuItemCheckBox,
34    MenuItemRadio,
35    ComboBox,
36    Tooltip,
37    Status,
38    AlertDialog,
39    Slider,
40    Progress,
41    Tab,
42    TabList,
43    TabPanel,
44    Dialog,
45    Alert,
46    Separator,
47}
48
49#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
50pub enum SemanticAction {
51    Click,
52    Focus,
53    Blur,
54    Increment,
55    Decrement,
56    Expand,
57    Collapse,
58    SetValue,
59    ScrollIntoView,
60}
61
62#[derive(Clone, Debug, PartialEq)]
63pub enum SemanticValue {
64    Text(String),
65    Number {
66        value: f64,
67        minimum: Option<f64>,
68        maximum: Option<f64>,
69        step: Option<f64>,
70    },
71}
72
73#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
74pub enum LiveRegion {
75    #[default]
76    Off,
77    Polite,
78    Assertive,
79}
80
81#[derive(Clone, Copy, Debug, Eq, PartialEq)]
82pub enum Orientation {
83    Horizontal,
84    Vertical,
85}
86
87#[derive(Clone, Debug, Default, Eq, PartialEq)]
88pub struct SemanticState {
89    pub protected: bool,
90    pub disabled: bool,
91    pub selected: bool,
92    pub multiselectable: bool,
93    pub checked: Option<CheckedState>,
94    /// Persistent pressed state of a toggle button, independent from pointer press.
95    pub pressed: Option<bool>,
96    pub expanded: Option<bool>,
97    pub required: bool,
98    pub read_only: bool,
99    pub invalid: bool,
100    pub modal: bool,
101    pub busy: bool,
102}
103
104#[derive(Clone, Debug, Default, PartialEq)]
105pub struct Semantics {
106    pub role: Role,
107    pub label: Option<String>,
108    pub description: Option<String>,
109    pub value: Option<SemanticValue>,
110    pub state: SemanticState,
111    pub actions: Vec<SemanticAction>,
112    pub live: LiveRegion,
113    pub orientation: Option<Orientation>,
114    pub level: Option<u32>,
115    pub position_in_set: Option<u32>,
116    pub set_size: Option<u32>,
117    pub relations: crate::SemanticRelations,
118    pub grid: crate::GridPosition,
119    pub sort: Option<crate::SortDirection>,
120    pub popup: Option<crate::PopupKind>,
121    pub focus_policy: FocusPolicy,
122}
123
124#[derive(Clone, Debug, PartialEq)]
125pub struct SemanticRequest {
126    pub target: crate::SemanticNodeId,
127    pub action: SemanticAction,
128    pub value: Option<SemanticValue>,
129}
130
131impl Semantics {
132    #[must_use]
133    pub const fn new(role: Role) -> Self {
134        Self {
135            role,
136            label: None,
137            description: None,
138            value: None,
139            state: SemanticState {
140                protected: false,
141                disabled: false,
142                selected: false,
143                multiselectable: false,
144                checked: None,
145                pressed: None,
146                expanded: None,
147                required: false,
148                read_only: false,
149                invalid: false,
150                modal: false,
151                busy: false,
152            },
153            actions: Vec::new(),
154            live: LiveRegion::Off,
155            orientation: None,
156            level: None,
157            position_in_set: None,
158            set_size: None,
159            relations: crate::SemanticRelations::new(),
160            grid: crate::GridPosition {
161                row_count: None,
162                column_count: None,
163                row_index: None,
164                column_index: None,
165            },
166            sort: None,
167            popup: None,
168            focus_policy: FocusPolicy::None,
169        }
170    }
171
172    #[must_use]
173    pub fn label(mut self, label: impl Into<String>) -> Self {
174        self.label = Some(label.into());
175        self
176    }
177
178    #[must_use]
179    pub fn description(mut self, description: impl Into<String>) -> Self {
180        self.description = Some(description.into());
181        self
182    }
183
184    #[must_use]
185    pub fn value(mut self, value: SemanticValue) -> Self {
186        self.value = Some(value);
187        self
188    }
189
190    #[must_use]
191    pub fn action(mut self, action: SemanticAction) -> Self {
192        if !self.actions.contains(&action) {
193            self.actions.push(action);
194        }
195        self
196    }
197
198    #[must_use]
199    pub fn state(mut self, state: SemanticState) -> Self {
200        self.state = state;
201        self
202    }
203
204    #[must_use]
205    pub const fn live(mut self, live: LiveRegion) -> Self {
206        self.live = live;
207        self
208    }
209
210    #[must_use]
211    pub const fn orientation(mut self, orientation: Orientation) -> Self {
212        self.orientation = Some(orientation);
213        self
214    }
215
216    #[must_use]
217    pub const fn level(mut self, level: u32) -> Self {
218        self.level = Some(level);
219        self
220    }
221
222    #[must_use]
223    pub const fn position_in_set(mut self, position: u32, size: u32) -> Self {
224        self.position_in_set = Some(position);
225        self.set_size = Some(size);
226        self
227    }
228}
229
230/// An absent checked value is distinct from a present mixed selection.
231#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
232pub enum CheckedState {
233    #[default]
234    Unchecked,
235    Checked,
236    Mixed,
237}
238
239impl CheckedState {
240    #[must_use]
241    pub const fn toggled(self) -> Self {
242        match self {
243            Self::Checked => Self::Unchecked,
244            Self::Unchecked | Self::Mixed => Self::Checked,
245        }
246    }
247}
248
249/// Whether an element can receive focus and participate in sequential navigation.
250#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
251pub enum FocusPolicy {
252    #[default]
253    None,
254    Programmatic,
255    TabStop,
256}
257
258impl FocusPolicy {
259    #[must_use]
260    pub const fn is_focusable(self) -> bool {
261        !matches!(self, Self::None)
262    }
263
264    #[must_use]
265    pub const fn is_tab_stop(self) -> bool {
266        matches!(self, Self::TabStop)
267    }
268}