Skip to repository content267 lines · 10.5 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T02:37:16.612Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
a11y.rs
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