zng_app/
access.rs

1//! Accessibility/automation events.
2
3use zng_txt::Txt;
4use zng_view_api::access::AccessCmd;
5
6pub use zng_view_api::access::ScrollCmd;
7
8use crate::{
9    event::{event, event_args},
10    update::EventUpdate,
11    widget::WidgetId,
12    window::WindowId,
13};
14
15pub(super) fn on_access_init(window_id: WindowId) -> EventUpdate {
16    let args = AccessInitedArgs::now(window_id);
17    ACCESS_INITED_EVENT.new_update(args)
18}
19
20pub(super) fn on_access_deinit(window_id: WindowId) -> EventUpdate {
21    let args = AccessDeinitedArgs::now(window_id);
22    ACCESS_DEINITED_EVENT.new_update(args)
23}
24
25pub(super) fn on_access_command(window_id: WindowId, widget_id: WidgetId, command: AccessCmd) -> Option<EventUpdate> {
26    match command {
27        AccessCmd::Click(primary) => {
28            let args = AccessClickArgs::now(window_id, widget_id, primary);
29            Some(ACCESS_CLICK_EVENT.new_update(args))
30        }
31        AccessCmd::Focus(focus) => {
32            let args = AccessFocusArgs::now(window_id, widget_id, focus);
33            Some(ACCESS_FOCUS_EVENT.new_update(args))
34        }
35        AccessCmd::FocusNavOrigin => {
36            let args = AccessFocusNavOriginArgs::now(window_id, widget_id);
37            Some(ACCESS_FOCUS_NAV_ORIGIN_EVENT.new_update(args))
38        }
39        AccessCmd::SetExpanded(expanded) => {
40            let args = AccessExpanderArgs::now(window_id, widget_id, expanded);
41            Some(ACCESS_EXPANDER_EVENT.new_update(args))
42        }
43        AccessCmd::Increment(inc) => {
44            let args = AccessIncrementArgs::now(window_id, widget_id, inc);
45            Some(ACCESS_INCREMENT_EVENT.new_update(args))
46        }
47        AccessCmd::SetToolTipVis(vis) => {
48            let args = AccessToolTipArgs::now(window_id, widget_id, vis);
49            Some(ACCESS_TOOLTIP_EVENT.new_update(args))
50        }
51        AccessCmd::ReplaceSelectedText(s) => {
52            let args = AccessTextArgs::now(window_id, widget_id, s, true);
53            Some(ACCESS_TEXT_EVENT.new_update(args))
54        }
55        AccessCmd::Scroll(s) => {
56            let args = AccessScrollArgs::now(window_id, widget_id, s);
57            Some(ACCESS_SCROLL_EVENT.new_update(args))
58        }
59        AccessCmd::SelectText {
60            start: (start_wgt, start_idx),
61            caret: (caret_wgt, caret_idx),
62        } => {
63            let start_wgt = WidgetId::from_raw(start_wgt.0);
64            let caret_wgt = WidgetId::from_raw(caret_wgt.0);
65            let args = AccessSelectionArgs::now(window_id, (start_wgt, start_idx), (caret_wgt, caret_idx));
66            Some(ACCESS_SELECTION_EVENT.new_update(args))
67        }
68        AccessCmd::SetString(s) => {
69            let args = AccessTextArgs::now(window_id, widget_id, s, false);
70            Some(ACCESS_TEXT_EVENT.new_update(args))
71        }
72        AccessCmd::SetNumber(n) => {
73            let args = AccessNumberArgs::now(window_id, widget_id, n);
74            Some(ACCESS_NUMBER_EVENT.new_update(args))
75        }
76        a => {
77            tracing::warn!("access command `{a:?}` not implemented");
78            None
79        }
80    }
81}
82
83event_args! {
84    /// Arguments for the [`ACCESS_INITED_EVENT`].
85    pub struct AccessInitedArgs {
86        /// Target window.
87        pub window_id: WindowId,
88
89        ..
90
91        /// Event is broadcast.
92        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
93            list.search_all()
94        }
95    }
96
97    /// Arguments for the [`ACCESS_DEINITED_EVENT`].
98    pub struct AccessDeinitedArgs {
99        /// Target window.
100        pub window_id: WindowId,
101
102        ..
103
104        /// Event is broadcast.
105        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
106            list.search_all()
107        }
108    }
109
110    /// Arguments for the [`ACCESS_CLICK_EVENT`].
111    pub struct AccessClickArgs {
112        /// Target window.
113        pub window_id: WindowId,
114
115        /// Target widget.
116        pub widget_id: WidgetId,
117
118        /// Is primary click (default action).
119        ///
120        /// If `false` is context click.
121        pub is_primary: bool,
122
123        ..
124
125        /// Target the widget.
126        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
127            list.search_widget(self.widget_id);
128        }
129    }
130
131    /// Arguments for the [`ACCESS_FOCUS_EVENT`].
132    pub struct AccessFocusArgs {
133        /// Target window.
134        pub window_id: WindowId,
135
136        /// Target widget.
137        pub widget_id: WidgetId,
138
139        /// If the widget must be focused.
140        ///
141        /// If `true` the widget is focused, if `false` and the widget is focused, does ESC.
142        pub focus: bool,
143
144        ..
145
146        /// Target the widget.
147        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
148            list.search_widget(self.widget_id)
149        }
150    }
151
152    /// Arguments for the [`ACCESS_FOCUS_NAV_ORIGIN_EVENT`].
153    pub struct AccessFocusNavOriginArgs {
154        /// Target window.
155        pub window_id: WindowId,
156
157        /// Target widget.
158        pub widget_id: WidgetId,
159
160        ..
161
162        /// Target the widget.
163        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
164            list.search_widget(self.widget_id)
165        }
166    }
167
168    /// Arguments for the [`ACCESS_EXPANDER_EVENT`].
169    pub struct AccessExpanderArgs {
170        /// Target window.
171        pub window_id: WindowId,
172
173        /// Target widget.
174        pub widget_id: WidgetId,
175
176        /// New expanded value.
177        pub expanded: bool,
178
179        ..
180
181        /// Target the widget.
182        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
183            list.search_widget(self.widget_id)
184        }
185    }
186
187    /// Arguments for the [`ACCESS_INCREMENT_EVENT`].
188    pub struct AccessIncrementArgs {
189        /// Target window.
190        pub window_id: WindowId,
191
192        /// Target widget.
193        pub widget_id: WidgetId,
194
195        /// Increment steps.
196        ///
197        /// Usually is -1 or 1.
198        pub delta: i32,
199
200        ..
201
202        /// Target the widget.
203        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
204            list.search_widget(self.widget_id)
205        }
206    }
207
208    /// Arguments for the [`ACCESS_TOOLTIP_EVENT`].
209    pub struct AccessToolTipArgs {
210        /// Target window.
211        pub window_id: WindowId,
212
213        /// Target widget.
214        pub widget_id: WidgetId,
215
216        /// New tooltip visibility.
217        pub visible: bool,
218
219        ..
220
221        /// Target the widget.
222        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
223            list.search_widget(self.widget_id)
224        }
225    }
226
227    /// Arguments for the [`ACCESS_SCROLL_EVENT`].
228    pub struct AccessScrollArgs {
229        /// Target window.
230        pub window_id: WindowId,
231
232        /// Target widget.
233        pub widget_id: WidgetId,
234
235        /// Scroll command.
236        pub command: ScrollCmd,
237
238        ..
239
240        /// Target the widget.
241        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
242            list.search_widget(self.widget_id)
243        }
244    }
245
246    /// Arguments for the [`ACCESS_TEXT_EVENT`].
247    pub struct AccessTextArgs {
248        /// Target window.
249        pub window_id: WindowId,
250
251        /// Target widget.
252        pub widget_id: WidgetId,
253
254        /// Replacement text.
255        pub txt: Txt,
256
257        /// If only the selected text is replaced.
258        ///
259        /// Note that if the selection is empty the text is just inserted at the caret position, or is appended if there
260        /// is no caret.
261        pub selection_only: bool,
262
263        ..
264
265        /// Target the widget.
266        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
267            list.search_widget(self.widget_id)
268        }
269    }
270
271    /// Arguments for the [`ACCESS_NUMBER_EVENT`].
272    pub struct AccessNumberArgs {
273        /// Target window.
274        pub window_id: WindowId,
275
276        /// Target widget.
277        pub widget_id: WidgetId,
278
279        /// Replacement number.
280        pub num: f64,
281
282        ..
283
284        /// Target the widget.
285        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
286            list.search_widget(self.widget_id)
287        }
288    }
289
290    /// Arguments for the [`ACCESS_SELECTION_EVENT`].
291    pub struct AccessSelectionArgs {
292        /// Target window.
293        pub window_id: WindowId,
294
295        /// Selection start.
296        ///
297        /// Text widget and character index where the selection *starts*.
298        pub start: (WidgetId, usize),
299        /// Selection end.
300        ///
301        /// This is where the caret is placed, it does not need to be greater than the start.
302        pub caret: (WidgetId, usize),
303
304        ..
305
306        /// Target both widgets.
307        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
308            list.search_widget(self.start.0);
309            list.search_widget(self.caret.0);
310        }
311    }
312}
313impl AccessClickArgs {
314    /// Is context click.
315    pub fn is_context(&self) -> bool {
316        !self.is_primary
317    }
318}
319
320event! {
321    /// Accessibility info is now required for the window.
322    pub static ACCESS_INITED_EVENT: AccessInitedArgs;
323
324    /// Accessibility info is no longer required for the window.
325    pub static ACCESS_DEINITED_EVENT: AccessDeinitedArgs;
326
327    /// Run the primary or context click action.
328    pub static ACCESS_CLICK_EVENT: AccessClickArgs;
329
330    /// Focus or escape focus on a widget.
331    pub static ACCESS_FOCUS_EVENT: AccessFocusArgs;
332
333    /// Sets the focus navigation origin.
334    pub static ACCESS_FOCUS_NAV_ORIGIN_EVENT: AccessFocusNavOriginArgs;
335
336    /// Expand or collapse the widget content.
337    pub static ACCESS_EXPANDER_EVENT: AccessExpanderArgs;
338
339    /// Increment or decrement the widget value by steps.
340    pub static ACCESS_INCREMENT_EVENT: AccessIncrementArgs;
341
342    /// Show or hide the widget's tooltip.
343    pub static ACCESS_TOOLTIP_EVENT: AccessToolTipArgs;
344
345    /// Run a scroll command.
346    pub static ACCESS_SCROLL_EVENT: AccessScrollArgs;
347
348    /// Replace the text content.
349    pub static ACCESS_TEXT_EVENT: AccessTextArgs;
350
351    /// Replace the number value.
352    pub static ACCESS_NUMBER_EVENT: AccessNumberArgs;
353
354    /// Select text.
355    pub static ACCESS_SELECTION_EVENT: AccessSelectionArgs;
356}
357
358/// Accessibility service.
359pub struct ACCESS;
360
361impl ACCESS {
362    /// Click the widget in the window.
363    ///
364    /// If `is_primary` is `true` a primary click is generated, if it is `false` a context click is generated.
365    pub fn click(&self, window_id: impl Into<WindowId>, widget_id: impl Into<WidgetId>, is_primary: bool) {
366        let win = window_id.into();
367        let wgt = widget_id.into();
368        ACCESS_CLICK_EVENT.notify(AccessClickArgs::now(win, wgt, is_primary));
369    }
370
371    /// Show tooltip for widget in the window, if it has any tooltip.
372    ///
373    /// The tooltip can auto-hide following the same rules as tooltips shown by hover.
374    pub fn show_tooltip(&self, window_id: impl Into<WindowId>, widget_id: impl Into<WidgetId>) {
375        let win = window_id.into();
376        let wgt = widget_id.into();
377        ACCESS_TOOLTIP_EVENT.notify(AccessToolTipArgs::now(win, wgt, true));
378    }
379
380    /// Hide tooltip for the widget in the window, if it has any tooltip showing.
381    pub fn hide_tooltip(&self, window_id: impl Into<WindowId>, widget_id: impl Into<WidgetId>) {
382        let win = window_id.into();
383        let wgt = widget_id.into();
384        ACCESS_TOOLTIP_EVENT.notify(AccessToolTipArgs::now(win, wgt, false));
385    }
386}