1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
//! # Accessibility in GPUI
//!
//! "Accessibility" refers to the ability of your application to be used by all
//! users, regardless of disability status. There are many aspects, all important, including:
//! - Ensuring sufficient text contrast.
//! - Providing a mechanism to disable animations.
//! - Providing a mechanism to increase text sizes.
//! - etc.
//!
//! This guide is focused on **programmatic accessibility**. This allows
//! assistive technology, such as screen readers or Braille displays, to inspect
//! and interact with your app.
//!
//! GPUI integrates with [AccessKit] to provide programmatic accessibility
//! features (referred to as simply "accessibility" for the rest of this guide).
//!
//! A minimal example can be found in the `examples/a11y` directory.
//!
//! ## Background
//!
//! Accessibility support is based on two key capabilities:
//! - Exposing information about the current UI state to assistive technology.
//! - Responding to actions requested by assistive technology.
//!
//! For example, a screen reader might want to announce to the user that a new
//! button has appeared. The user may then want to use a voice control program
//! to press that button.
//!
//! ### IDs in GPUI - [`ElementId`] and [`GlobalElementId`]
//!
//! In GPUI, each [`Element`] can have an [`id`][Element::id]:
//! ```rust
//! # use open_gpui::*;
//! let div_with_id = div().id("my-id").child(text!("hello"));
//!
//! // IDs are optional
//! let div_without_id = div().child(text!("hello"));
//! ```
//!
//! [`Element`]s with IDs are also assigned a [`GlobalElementId`]. This global
//! ID is formed by composing all the non-`None` IDs of its ancestors. For
//! example:
//! ```rust
//! # use open_gpui::*;
//! let inner = div().id("inner-id");
//! let middle = div().child(inner); // no ID
//! let outer = div().id("outer-id").child(middle);
//! ```
//! In this example, `inner`s global ID is (roughly speaking) `["outer-id",
//! "inner-id"]`.
//!
//! Since `middle` doesn't have an ID itself, it has no global ID.
//!
//! [`GlobalElementId`]s should be unique per-frame. Duplicate global IDs in the
//! same frame will likely cause bugs.
//!
//! ### IDs and accessibility
//!
//! When GPUI renders a frame, it walks your UI tree, and finds nodes with
//! global IDs, and informs assistive technology about this node.
//!
//! In order for nodes to be reported, they must also have a non-`None`
//! [`role`][Element::a11y_role]. This is used to inform assistive technology
//! what *sort* of node it is (button, label, table, etc.). You can use
//! [`div().id(...).role()`][StatefulInteractiveElement::role] to set the role.
//!
//! Nodes with the same global ID *across frames* are considered to be "the
//! same" node. For example:
//! ```rust
//! # use open_gpui::*;
//! // The UI in frame 1
//! let frame_1 = div()
//! .id("parent")
//! .role(Role::Button)
//! .child(
//! div()
//! .id("id-1")
//! .role(Role::Label)
//! .child(text!("hello"))
//! );
//!
//! // The UI on the next frame
//! let frame_2 = div()
//! .id("parent")
//! .role(Role::Button)
//! .child(
//! div()
//! .id("id-2") // <- different ID
//! .role(Role::Label)
//! .child(text!("hello"))
//! );
//! ```
//! Logically, the UI has not changed. But the screen reader has no way of
//! knowing that both child [`div`]s are "the same". So assistive technology
//! will interpret this as one node being removed, and another node being added.
//! This can be very disorienting for users, since announcements typically only
//! happen when something has *meaningfully* changed.
//!
//! In other words, by controlling the ID of an element, you can control whether
//! a change to a UI element is considered meaningful. You can also control
//! whether elements are reported to assistive technology *at all* by setting
//! the [`role`][Element::a11y_role], since nodes with no role are not reported.
//!
//! #### IDs and text
//!
//! Special care must be taken when dealing with text.
//!
//! GPUI provides the [`text!`] macro, which wraps strings in the [`Text`] type,
//! but automatically derives an ID. Usually, this is what you want. However,
//! the way it generates its ID is subtle and perhaps surprising.
//!
//! The ID of an invocation of the [`text!`] macro is derived from the
//! **location in the source code of that invocation**. For example:
//!
//! ```rust
//! # use open_gpui::*;
//! let a = text!("a");
//! let b = text!("b");
//!
//! // Different source locations, different IDs
//! assert_ne!(a.id(), b.id());
//!
//! // However:
//!
//! fn make_text(s: &str) -> Text { text!(s) }
//!
//! let a = make_text("a");
//! let b = make_text("b");
//!
//! // Both `a` and `b` are produced by the same `text!` invocation, so the IDs
//! // are the same
//! assert_eq!(a.id(), b.id());
//! ```
//! This can produce surprising behaviour. For example, this footgun:
//! ```rust
//! # use open_gpui::*;
//! let todos = vec!["eat lunch", "drink water", "go to gym"];
//! let todo_divs = todos.into_iter().map(|todo| {
//! text!(todo)
//! });
//!
//! div()
//! .id("todo-list")
//! .role(Role::Document)
//! .children(todo_divs); // ERROR: multiple nodes with the same global ID
//! ```
//!
//! Here, when we map the iterator, since we have only written [`text!`] once,
//! there is only one ID. And since they have the same ancestors and the same
//! ID, they will have the same global ID. In release builds, this will mean
//! some nodes get silently dropped!
//!
//! To fix this, you can set an ID:
//! ```rust
//! # use open_gpui::*;
//! let todos = vec!["eat lunch", "drink water", "go to gym"];
//! let todo_divs = todos.into_iter().enumerate().map(|(index, todo)| {
//! text!(todo).with_id(index) // OR `text(id = index, todo)`
//! });
//!
//! div()
//! .id("todo-list")
//! .role(Role::Document)
//! .children(todo_divs);
//! ```
//! Another possible solution is to wrap the [`text!`] in another node that
//! *does* have a unique global ID. For example:
//! ```rust
//! # use open_gpui::*;
//! let todos = vec!["eat lunch", "drink water", "go to gym"];
//! let todo_divs = todos.into_iter().enumerate().map(|(index, todo)| {
//! div().id(index).child(text!(todo))
//! });
//!
//! div()
//! .id("todo-list")
//! .role(Role::Document)
//! .children(todo_divs);
//! ```
//! Since the AccessKit [`NodeId`][accesskit::NodeId] is derived from the global
//! ID, and the global ID takes into account the IDs of all ancestors, this
//! works too.
//!
//! Occasionally, you will need to create a [`Text`] element with *no* ID. You
//! can achieve this with [`Text::new_inaccessible`]. If you are creating a
//! custom UI component (e.g. a button), you may want this so that you can set a
//! label property on a parent [`div`] without duplicating the text in the
//! accessibility tree.
//!
//! ### Handling actions
//!
//! Assistive technology can dispatch actions to the UI. While many users of
//! assistive technology use traditional input devices (e.g. a keyboard), some
//! use more specialized systems. For example, users with limited mobility may
//! use voice control to interact with your app.
//!
//! When a user dispatches an action, it is dispatched *to a specific node*. It
//! is your responsibility to tell the UI elements how they should respond when
//! a request comes in.
//!
//! Note, these actions are **totally unrelated** to GPUI's [`Action`] trait.
//! AccessKit exposes [`accesskit::Action`]. In GPUI, this is re-exported as
//! [`AccessibleAction`].
//!
//! To respond to an accessible action, use
//! [`div().on_a11y_action()`][InteractiveElement::on_a11y_action]:
//! ```rust,ignore
//! div()
//! .id("my-slider")
//! .role(Role::Slider)
//! .on_a11y_action(AccessibleAction::Increment, |_extra, _window, _cx| {
//! position += 1;
//! cx.notify();
//! })
//! .child(my_cool_slider());
//! ```
//!
//! Note that some common actions are automatically registered. For example,
//! [`.on_click()`][StatefulInteractiveElement::on_click] adds an
//! [`AccessibleAction::Click`] handler that calls the click handler.
//!
//! ## Further reading
//!
//! Designing high-quality accessible interfaces can be challenging, in the same
//! way that designing high-quality traditional interfaces can be. The
//! following pages have useful information:
//!
//! - [AccessKit]: The cross-platform accessibility toolkit GPUI uses
//! internally.
//! - [MDN WAI-ARIA basics][mdn-aria]: Introduction to roles, properties, and
//! states.
//! - [ARIA Authoring Practices Guide][apg]: W3C patterns for accessible
//! widgets.
//!
//! Note that, while GPUI mimics web APIs, it doesn't necessarily behave
//! *exactly* as a web browser would with the same attributes.
//!
//! [AccessKit]: https://accesskit.dev/
//! [mdn-aria]: https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Accessibility/WAI-ARIA_basics
//! [apg]: https://www.w3.org/WAI/ARIA/apg/
use crate::*; // so I don't have to qualify every type :)