Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:42:09.279Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

a11y.rs

267 lines · 10.5 KB · rust
1#![cfg_attr(target_family = "wasm", no_main)]
2
3//! Accessibility (AccessKit) demo app.
4//!
5//! Run with: `cargo run -p gpui --example a11y`
6//!
7//! Or on Linux: `cargo run -p gpui --features gpui_platform/wayland,gpui_platform/x11 --example a11y`
8//!
9//! This app uses GPUI's accessibility APIs to attach structured information to
10//! the element tree, which allows assistive technology to see and interact with
11//! the UI programmatically.
12//!
13//! The app behaves as follows:
14//! - It opens a single window.
15//! - The window's title is "GPUI Accessibility Demo".
16//! - The window has a sequence of UI elements, stacked vertically:
17//!   - A heading with the text "Accessibility Demo".
18//!   - A row containing two elements:
19//!     - A spin button (role `SpinButton`) labelled "Counter: <n>", where
20//!       `<n>` is the current count. It supports `Increment` and `Decrement`
21//!       accessible actions, and also increments on click. The numeric value
22//!       is clamped to a minimum of 0.
23//!     - A button labelled "Reset counter" that resets the count to 0.
24//!   - A row containing two elements:
25//!     - A switch, that can be toggled, and starts disabled. Toggling the switch
26//!       does nothing.
27//!     - The text "Enable feature".
28//!   - A "to-do" list, with three items, each represented with a `Text` element:
29//!     - "1. Write code"
30//!     - "2. Run tests"
31//!     - "3. Ship it"
32
33use gpui::{
34    AccessibleAction, App, Bounds, Context, FocusHandle, KeyBinding, Role, SharedString, Toggled,
35    Window, WindowBounds, WindowOptions, actions, div, prelude::*, px, rgb, size, text,
36};
37use gpui_platform::application;
38
39actions!(a11y_example, [Tab, TabPrev]);
40
41struct A11yDemo {
42    focus_handle: FocusHandle,
43    count: i32,
44    enabled: bool,
45}
46
47impl A11yDemo {
48    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
49        let focus_handle = cx.focus_handle();
50        window.focus(&focus_handle, cx);
51        Self {
52            focus_handle,
53            count: 0,
54            enabled: false,
55        }
56    }
57}
58
59impl Render for A11yDemo {
60    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
61        div()
62            .id("root")
63            .role(Role::Application)
64            .aria_label("Accessibility Demo")
65            .track_focus(&self.focus_handle)
66            .on_action(cx.listener(|_, _: &Tab, window, cx| window.focus_next(cx)))
67            .on_action(cx.listener(|_, _: &TabPrev, window, cx| window.focus_prev(cx)))
68            .size_full()
69            .flex()
70            .flex_col()
71            .gap_4()
72            .p_4()
73            .bg(rgb(0x1e1e2e))
74            .text_color(rgb(0xcdd6f4))
75            // Heading
76            .child(
77                div()
78                    .id("heading")
79                    .role(Role::Heading)
80                    .aria_level(1)
81                    .aria_label("Accessibility Demo")
82                    .text_xl()
83                    .font_weight(gpui::FontWeight::BOLD)
84                    .child(text!("Accessibility Demo")),
85            )
86            // Counter — uses a SpinButton role with Increment/Decrement
87            // actions so screen readers can adjust the value directly.
88            // Click also works via the built-in handler.
89            .child(
90                div()
91                    .flex()
92                    .items_center()
93                    .gap_3()
94                    .child(
95                        div()
96                            .id("counter")
97                            .focusable()
98                            .tab_stop(true)
99                            .role(Role::SpinButton)
100                            .aria_label(SharedString::from(format!("Counter: {}", self.count)))
101                            .aria_numeric_value(self.count as f64)
102                            .aria_min_numeric_value(0.0)
103                            .on_a11y_action(AccessibleAction::Increment, {
104                                let this = cx.entity().downgrade();
105                                move |_, _, cx| {
106                                    this.update(cx, |this, cx| {
107                                        this.count += 1;
108                                        cx.notify();
109                                    })
110                                    .ok();
111                                }
112                            })
113                            .on_a11y_action(AccessibleAction::Decrement, {
114                                let this = cx.entity().downgrade();
115                                move |_, _, cx| {
116                                    this.update(cx, |this, cx| {
117                                        this.count = (this.count - 1).max(0);
118                                        cx.notify();
119                                    })
120                                    .ok();
121                                }
122                            })
123                            .on_click(cx.listener(|this, _, _, cx| {
124                                this.count += 1;
125                                cx.notify();
126                            }))
127                            .px_3()
128                            .py_1()
129                            .rounded_md()
130                            .bg(rgb(0x89b4fa))
131                            .text_color(rgb(0x1e1e2e))
132                            .cursor_pointer()
133                            .child(text!(format!("Count: {}", self.count))),
134                    )
135                    .child(
136                        div()
137                            .id("reset")
138                            .focusable()
139                            .tab_stop(true)
140                            .role(Role::Button)
141                            .aria_label("Reset counter")
142                            .px_3()
143                            .py_1()
144                            .rounded_md()
145                            .bg(rgb(0x585b70))
146                            .cursor_pointer()
147                            .on_click(cx.listener(|this, _, _, cx| {
148                                this.count = 0;
149                                cx.notify();
150                            }))
151                            .child(text!("Reset")),
152                    ),
153            )
154            // A toggle switch
155            .child(
156                div()
157                    .flex()
158                    .items_center()
159                    .gap_2()
160                    .child(
161                        div()
162                            .id("toggle")
163                            .focusable()
164                            .tab_stop(true)
165                            .role(Role::Switch)
166                            .aria_label("Enable feature")
167                            .aria_toggled(if self.enabled {
168                                Toggled::True
169                            } else {
170                                Toggled::False
171                            })
172                            .w(px(44.))
173                            .h(px(24.))
174                            .rounded_full()
175                            .cursor_pointer()
176                            .when(self.enabled, |el| el.bg(rgb(0x89b4fa)))
177                            .when(!self.enabled, |el| el.bg(rgb(0x585b70)))
178                            .child(
179                                div()
180                                    .size(px(20.))
181                                    .rounded_full()
182                                    .bg(gpui::white())
183                                    .mt(px(2.))
184                                    .when(self.enabled, |el| el.ml(px(22.)))
185                                    .when(!self.enabled, |el| el.ml(px(2.))),
186                            )
187                            .on_click(cx.listener(|this, _, _, cx| {
188                                this.enabled = !this.enabled;
189                                cx.notify();
190                            })),
191                    )
192                    .child(text!("Enable feature")),
193            )
194            // A short list
195            .child(
196                div()
197                    .id("task-list")
198                    .role(Role::List)
199                    .aria_label("Tasks")
200                    .flex()
201                    .flex_col()
202                    .gap_1()
203                    .children(
204                        ["Write code", "Run tests", "Ship it"]
205                            .iter()
206                            .enumerate()
207                            .map(|(i, label)| {
208                                div()
209                                    .id(("task", i))
210                                    .role(Role::ListItem)
211                                    .aria_label(SharedString::from(*label))
212                                    .aria_position_in_set(i + 1)
213                                    .aria_size_of_set(3)
214                                    .py_1()
215                                    .px_2()
216                                    // Note: even though this `text!` macro
217                                    // produces multiple elements, it doesn't
218                                    // need its own unique ID because the parent
219                                    // div has different IDs for each string.
220                                    .child(text!(format!("{}. {}", i + 1, label)))
221                            }),
222                    ),
223            )
224    }
225}
226
227fn run_example() {
228    application().run(|cx: &mut App| {
229        cx.bind_keys([
230            KeyBinding::new("tab", Tab, None),
231            KeyBinding::new("shift-tab", TabPrev, None),
232        ]);
233
234        let bounds = Bounds::centered(None, size(px(500.), px(400.0)), cx);
235        cx.open_window(
236            WindowOptions {
237                window_bounds: Some(WindowBounds::Windowed(bounds)),
238                titlebar: Some(gpui::TitlebarOptions {
239                    title: Some("GPUI Accessibility Demo".into()),
240                    ..Default::default()
241                }),
242                ..Default::default()
243            },
244            |window, cx| cx.new(|cx| A11yDemo::new(window, cx)),
245        )
246        .unwrap();
247
248        cx.activate(true);
249    });
250}
251
252#[cfg(not(target_family = "wasm"))]
253fn main() {
254    env_logger::builder()
255        .filter_level(log::LevelFilter::Warn)
256        .filter_module("gpui", log::LevelFilter::Info)
257        .init();
258    run_example();
259}
260
261#[cfg(target_family = "wasm")]
262#[wasm_bindgen::prelude::wasm_bindgen(start)]
263pub fn start() {
264    gpui_platform::web_init();
265    run_example();
266}
267
Served at tenant.openagents/omega Member data and write actions are omitted.