zng_wgt_access/
meta.rs

1use zng_app::widget::info::access::WidgetAccessInfoBuilder;
2use zng_ext_l10n::Lang;
3use zng_wgt::prelude::*;
4
5use std::num::NonZeroU32;
6
7pub use zng_view_api::access::{
8    AccessCmdName, AccessRole, AutoComplete, CurrentKind, Invalid, LiveIndicator, Orientation, Popup, SortDirection,
9};
10
11/// Sets the widget kind for accessibility services.
12///
13/// Note that the widget role must be implemented, this property only sets the metadata.
14#[property(CONTEXT)]
15pub fn access_role(child: impl UiNode, role: impl IntoVar<AccessRole>) -> impl UiNode {
16    with_access_state(child, role, |b, v| b.set_role(*v))
17}
18
19/// Append supported access commands.
20#[property(CONTEXT)]
21pub fn access_commands(child: impl UiNode, commands: impl IntoVar<Vec<AccessCmdName>>) -> impl UiNode {
22    with_access_state(child, commands, |b, v| {
23        for cmd in v {
24            b.push_command(*cmd);
25        }
26    })
27}
28
29/// Defines if the widget and descendants can be present in the accessibility info tree.
30///
31/// If set to `false` the widget and descendants is not included in accessibility info send to screen readers,
32/// if set to `true` the widget and descendants can be accessible if they set any accessibility metadata, the
33/// same as if this property is not set.
34///
35/// Note that not accessible widgets will still collect accessibility info, the info is just no send
36/// to the view-process and screen readers. Also note that hidden or collapsed widgets are not accessible
37/// by default.
38#[property(WIDGET, default(true))]
39pub fn accessible(child: impl UiNode, accessible: impl IntoVar<bool>) -> impl UiNode {
40    with_access_state(child, accessible, |b, v| {
41        if !*v {
42            b.flag_inaccessible();
43        }
44    })
45}
46
47/// Set how input text triggers display of one or more predictions of the user's intended
48/// value for a [`ComboBox`], [`SearchBox`], or [`TextInput`].
49///
50/// [`ComboBox`]: AccessRole::ComboBox
51/// [`SearchBox`]: AccessRole::SearchBox
52/// [`TextInput`]: AccessRole::TextInput
53#[property(CONTEXT)]
54pub fn auto_complete(child: impl UiNode, auto_complete: impl IntoVar<AutoComplete>) -> impl UiNode {
55    with_access_state(child, auto_complete, |b, v| b.set_auto_complete(*v))
56}
57
58/// If the widget is checked (`Some(true)`), unchecked (`Some(false)`), or if the checked status is indeterminate (`None`).
59#[property(CONTEXT)]
60pub fn checked(child: impl UiNode, checked: impl IntoVar<Option<bool>>) -> impl UiNode {
61    with_access_state(child, checked, |b, v| b.set_checked(*v))
62}
63
64/// Indicates that the widget represents the current item of a [kind](CurrentKind).
65#[property(CONTEXT)]
66pub fn current(child: impl UiNode, kind: impl IntoVar<CurrentKind>) -> impl UiNode {
67    with_access_state(child, kind, |b, v| b.set_current(*v))
68}
69
70/// Indicates that the widget is an error message for the `invalid_wgt`.
71///
72/// The other widget must [`invalid`].
73///
74/// [`invalid`]: fn@invalid
75#[property(CONTEXT)]
76pub fn error_message(child: impl UiNode, invalid_wgt: impl IntoVar<WidgetId>) -> impl UiNode {
77    with_access_state(child, invalid_wgt, |b, v| b.set_error_message(*v))
78}
79
80/// Identifies the currently active widget when focus is on a composite widget.
81#[property(CONTEXT)]
82pub fn active_descendant(child: impl UiNode, descendant: impl IntoVar<WidgetId>) -> impl UiNode {
83    with_access_state(child, descendant, |b, v| b.set_active_descendant(*v))
84}
85
86/// Indicate that the widget toggles the visibility of related widgets.
87///
88/// Use [`controls`], or [`owns`] to indicate the widgets that change visibility based on
89/// this value.
90///
91/// [`controls`]: fn@controls
92/// [`owns`]: fn@owns
93#[property(CONTEXT)]
94pub fn expanded(child: impl UiNode, expanded: impl IntoVar<bool>) -> impl UiNode {
95    with_access_state(child, expanded, |b, v| b.set_expanded(*v))
96}
97
98/// Indicates the availability and type of interactive popup widget.
99#[property(CONTEXT)]
100pub fn popup(child: impl UiNode, popup: impl IntoVar<Popup>) -> impl UiNode {
101    with_access_state(child, popup, |b, v| b.set_popup(*v))
102}
103
104/// Sets a custom name for the widget in accessibility info.
105///
106/// See also [`labelled_by`] and [`labelled_by_child`].
107///
108/// [`labelled_by`]: fn@labelled_by
109/// [`labelled_by_child`]: fn@labelled_by_child
110#[property(CONTEXT)]
111pub fn label(child: impl UiNode, label: impl IntoVar<Txt>) -> impl UiNode {
112    with_access_state(child, label, |b, v| b.set_label(v.clone()))
113}
114
115/// Uses the accessible children as [`labelled_by`].
116///
117/// [`labelled_by`]: fn@labelled_by
118#[property(CONTEXT)]
119pub fn labelled_by_child(child: impl UiNode, enabled: impl IntoVar<bool>) -> impl UiNode {
120    with_access_state(child, enabled, |b, v| {
121        if *v {
122            b.flag_labelled_by_child();
123        }
124    })
125}
126
127/// Sets the hierarchical level of the widget within a parent scope.
128#[property(CONTEXT)]
129pub fn level(child: impl UiNode, hierarchical_level: impl IntoVar<NonZeroU32>) -> impl UiNode {
130    with_access_state(child, hierarchical_level, |b, v| b.set_level(*v))
131}
132
133/// Indicates that the user may select more than one item from the current selectable descendants.
134#[property(CONTEXT)]
135pub fn multi_selectable(child: impl UiNode, multi_selectable: impl IntoVar<bool>) -> impl UiNode {
136    with_access_state(child, multi_selectable, |b, v| {
137        if *v {
138            b.flag_multi_selectable()
139        }
140    })
141}
142
143/// Indicates whether the widget's orientation is horizontal, vertical, or unknown/ambiguous.
144#[property(CONTEXT)]
145pub fn orientation(child: impl UiNode, orientation: impl IntoVar<Orientation>) -> impl UiNode {
146    with_access_state(child, orientation, |b, v| b.set_orientation(*v))
147}
148
149/// Short hint (a word or short phrase) intended to help the user with data entry when a form control has no value.
150#[property(CONTEXT)]
151pub fn placeholder(child: impl UiNode, placeholder: impl IntoVar<Txt>) -> impl UiNode {
152    with_access_state(child, placeholder, |b, v| b.set_placeholder(v.clone()))
153}
154
155/// Indicates that the widget is not editable, but is otherwise operable.
156#[property(CONTEXT)]
157pub fn read_only(child: impl UiNode, read_only: impl IntoVar<bool>) -> impl UiNode {
158    with_access_state(child, read_only, |b, v| {
159        if *v {
160            b.flag_read_only()
161        }
162    })
163}
164
165/// Indicates that user input is required on the widget before a form may be submitted.
166#[property(CONTEXT)]
167pub fn required(child: impl UiNode, required: impl IntoVar<bool>) -> impl UiNode {
168    with_access_state(child, required, |b, v| {
169        if *v {
170            b.flag_required()
171        }
172    })
173}
174
175/// Indicates that the widget is selected.
176#[property(CONTEXT)]
177pub fn selected(child: impl UiNode, selected: impl IntoVar<bool>) -> impl UiNode {
178    with_access_state(child, selected, |b, v| {
179        if *v {
180            b.flag_selected()
181        }
182    })
183}
184
185/// Sets the sort direction for the table or grid items.
186#[property(CONTEXT)]
187pub fn sort(child: impl UiNode, direction: impl IntoVar<SortDirection>) -> impl UiNode {
188    with_access_state(child, direction, |b, v| b.set_sort(*v))
189}
190
191/// Set the maximum value (inclusive).
192#[property(CONTEXT)]
193pub fn value_max(child: impl UiNode, max: impl IntoVar<f64>) -> impl UiNode {
194    with_access_state(child, max, |b, v| b.set_value_max(*v))
195}
196
197/// Set the minimum value (inclusive).
198#[property(CONTEXT)]
199pub fn value_min(child: impl UiNode, min: impl IntoVar<f64>) -> impl UiNode {
200    with_access_state(child, min, |b, v| b.set_value_min(*v))
201}
202
203/// Set the current value.
204#[property(CONTEXT)]
205pub fn value(child: impl UiNode, value: impl IntoVar<f64>) -> impl UiNode {
206    with_access_state(child, value, |b, v| b.set_value(*v))
207}
208
209/// Set a text that is a readable version of the current value.
210#[property(CONTEXT)]
211pub fn value_text(child: impl UiNode, value: impl IntoVar<Txt>) -> impl UiNode {
212    with_access_state(child, value, |b, v| b.set_value_text(v.clone()))
213}
214
215/// Sets the total number of columns in a [`Table`], [`Grid`], or [`TreeGrid`] when not all columns are present in tree.
216///
217/// The value `0` indicates that not all columns are in the widget and the application cannot determinate the exact number.
218///
219/// [`Table`]: AccessRole::Table
220/// [`Grid`]: AccessRole::Grid
221/// [`TreeGrid`]: AccessRole::TreeGrid
222#[property(CONTEXT)]
223pub fn col_count(child: impl UiNode, count: impl IntoVar<usize>) -> impl UiNode {
224    with_access_state(child, count, |b, v| b.set_col_count(*v))
225}
226
227/// Sets the widget's column index in the parent table or grid.
228#[property(CONTEXT)]
229pub fn col_index(child: impl UiNode, index: impl IntoVar<usize>) -> impl UiNode {
230    with_access_state(child, index, |b, v| b.set_col_index(*v))
231}
232
233/// Sets the number of columns spanned by the widget in the parent table or grid.
234#[property(CONTEXT)]
235pub fn col_span(child: impl UiNode, span: impl IntoVar<usize>) -> impl UiNode {
236    with_access_state(child, span, |b, v| b.set_col_span(*v))
237}
238
239/// Sets the total number of rows in a [`Table`], [`Grid`], or [`TreeGrid`] when not all rows are present in the tree.
240///
241/// The value `0` indicates that not all rows are in the widget and the application cannot determinate the exact number.
242///
243/// [`Table`]: AccessRole::Table
244/// [`Grid`]: AccessRole::Grid
245/// [`TreeGrid`]: AccessRole::TreeGrid
246#[property(CONTEXT)]
247pub fn row_count(child: impl UiNode, count: impl IntoVar<usize>) -> impl UiNode {
248    with_access_state(child, count, |b, v| b.set_row_count(*v))
249}
250
251/// Sets the widget's row index in the parent table or grid.
252#[property(CONTEXT)]
253pub fn row_index(child: impl UiNode, index: impl IntoVar<usize>) -> impl UiNode {
254    with_access_state(child, index, |b, v| b.set_row_index(*v))
255}
256
257/// Sets the number of rows spanned by the widget in the parent table or grid.
258#[property(CONTEXT)]
259pub fn row_span(child: impl UiNode, span: impl IntoVar<usize>) -> impl UiNode {
260    with_access_state(child, span, |b, v| b.set_row_span(*v))
261}
262
263/// Sets the number of items in the current set of list items or tree items when not all items in the set are present in the tree.
264#[property(CONTEXT)]
265pub fn item_count(child: impl UiNode, count: impl IntoVar<usize>) -> impl UiNode {
266    with_access_state(child, count, |b, v| b.set_item_count(*v))
267}
268
269/// Sets the widget's number or position in the current set of list items or tree items when not all items are present in the tree.
270#[property(CONTEXT)]
271pub fn item_index(child: impl UiNode, index: impl IntoVar<usize>) -> impl UiNode {
272    with_access_state(child, index, |b, v| b.set_item_index(*v))
273}
274
275/// Sets if the widget is modal when displayed.
276#[property(CONTEXT)]
277pub fn modal(child: impl UiNode, modal: impl IntoVar<bool>) -> impl UiNode {
278    with_access_state(child, modal, |b, v| {
279        if *v {
280            b.flag_modal()
281        }
282    })
283}
284
285/// Append widgets whose contents or presence are controlled by this widget to the controlled list.
286#[property(CONTEXT)]
287pub fn controls(child: impl UiNode, controlled: impl IntoVar<Vec<WidgetId>>) -> impl UiNode {
288    with_access_state(child, controlled, |b, v| {
289        for id in v {
290            b.push_controls(*id);
291        }
292    })
293}
294
295/// Append widgets that describes this widget to the descriptors list.
296#[property(CONTEXT)]
297pub fn described_by(child: impl UiNode, descriptors: impl IntoVar<Vec<WidgetId>>) -> impl UiNode {
298    with_access_state(child, descriptors, |b, v| {
299        for id in v {
300            b.push_described_by(*id);
301        }
302    })
303}
304
305/// Append widgets that provide additional information related to this widget to the details list.
306#[property(CONTEXT)]
307pub fn details(child: impl UiNode, details: impl IntoVar<Vec<WidgetId>>) -> impl UiNode {
308    with_access_state(child, details, |b, v| {
309        for id in v {
310            b.push_details(*id);
311        }
312    })
313}
314
315/// Append widgets that provide additional information related to this widget.
316#[property(CONTEXT)]
317pub fn labelled_by(child: impl UiNode, labels: impl IntoVar<Vec<WidgetId>>) -> impl UiNode {
318    with_access_state(child, labels, |b, v| {
319        for id in v {
320            b.push_labelled_by(*id);
321        }
322    })
323}
324
325/// Append `owned` widgets that are *children* of this widget, but are not already children in the info tree.
326#[property(CONTEXT)]
327pub fn owns(child: impl UiNode, owned: impl IntoVar<Vec<WidgetId>>) -> impl UiNode {
328    with_access_state(child, owned, |b, v| {
329        for id in v {
330            b.push_owns(*id);
331        }
332    })
333}
334
335/// Append options for next widget to be read by screen readers.
336#[property(CONTEXT)]
337pub fn flows_to(child: impl UiNode, next_options: impl IntoVar<Vec<WidgetId>>) -> impl UiNode {
338    with_access_state(child, next_options, |b, v| {
339        for id in v {
340            b.push_flows_to(*id);
341        }
342    })
343}
344
345/// Indicates that the widget's data is invalid with optional kinds of errors.
346#[property(CONTEXT)]
347pub fn invalid(child: impl UiNode, error: impl IntoVar<Invalid>) -> impl UiNode {
348    with_access_state(child, error, |b, v| b.set_invalid(*v))
349}
350
351/// Defines the language used by screen-readers to read text in this widget and descendants.
352#[property(CONTEXT)]
353pub fn lang(child: impl UiNode, lang: impl IntoVar<Lang>) -> impl UiNode {
354    with_access_state(child, lang, |b, v| b.set_lang(v.0.clone()))
355}
356
357/// Sets the amount scrolled horizontally if allowed.
358///
359/// The `normal_x` value can be a read-only variable, the variable can be updated without needing to rebuild
360/// info for every pixel scrolled, if the view-process requires access info the value is updated every render
361/// together with the widget bounds updates.
362///
363/// The value must be normalized in the 0..=1 range, 0 is showing the content leftmost edge, 1 is showing
364/// the content the rightmost edge.
365#[property(CONTEXT)]
366pub fn scroll_horizontal(child: impl UiNode, normal_x: impl IntoVar<Factor>) -> impl UiNode {
367    with_access_state_var(child, normal_x, |b, v| b.set_scroll_horizontal(v.clone()))
368}
369
370/// Sets the amount scrolled vertically if allowed.
371///
372/// The `normal_y` value can be a read-only variable, the variable can be updated without needing to rebuild
373/// info for every pixel scrolled, if the view-process requires access info the value is updated every render
374/// together with the widget bounds updates.
375///
376/// The value must be normalized in the 0..=1 range, 0 is showing the content topmost edge, 1 is showing
377/// the content the bottommost edge.
378#[property(CONTEXT)]
379pub fn scroll_vertical(child: impl UiNode, normal_y: impl IntoVar<Factor>) -> impl UiNode {
380    with_access_state_var(child, normal_y, |b, v| b.set_scroll_vertical(v.clone()))
381}
382
383/// Indicate that the widget can change, how the change can be announced, if `atomic`
384/// the entire widget must be re-read, if `busy` the screen reader must wait until the change completes.
385#[property(CONTEXT)]
386pub fn live(
387    child: impl UiNode,
388    indicator: impl IntoVar<LiveIndicator>,
389    atomic: impl IntoVar<bool>,
390    busy: impl IntoVar<bool>,
391) -> impl UiNode {
392    let indicator = indicator.into_var();
393    let atomic = atomic.into_var();
394    let busy = busy.into_var();
395    let mut handles = VarHandles::dummy();
396    match_node(child, move |c, op| match op {
397        UiNodeOp::Deinit => {
398            handles.clear();
399        }
400        UiNodeOp::Info { info } => {
401            c.info(info);
402            if let Some(mut builder) = info.access() {
403                if handles.is_dummy() {
404                    handles.push(indicator.subscribe(UpdateOp::Info, WIDGET.id()));
405                    handles.push(atomic.subscribe(UpdateOp::Info, WIDGET.id()));
406                    handles.push(busy.subscribe(UpdateOp::Info, WIDGET.id()));
407                }
408                builder.set_live(indicator.get(), atomic.get(), busy.get());
409            }
410        }
411        _ => {}
412    })
413}
414
415fn with_access_state<T: VarValue>(
416    child: impl UiNode,
417    state: impl IntoVar<T>,
418    set_info: impl Fn(&mut WidgetAccessInfoBuilder, &T) + Send + 'static,
419) -> impl UiNode {
420    with_access_state_var(child, state, move |b, v| v.with(|v| set_info(b, v)))
421}
422
423fn with_access_state_var<T: VarValue, I: IntoVar<T>>(
424    child: impl UiNode,
425    state: I,
426    set_info: impl Fn(&mut WidgetAccessInfoBuilder, &I::Var) + Send + 'static,
427) -> impl UiNode {
428    let state = state.into_var();
429    let mut handle = VarHandle::dummy();
430    match_node(child, move |c, op| match op {
431        UiNodeOp::Deinit => {
432            handle = VarHandle::dummy();
433        }
434        UiNodeOp::Info { info } => {
435            c.info(info);
436            if let Some(mut builder) = info.access() {
437                if handle.is_dummy() {
438                    handle = state.subscribe(UpdateOp::Info, WIDGET.id());
439                }
440                set_info(&mut builder, &state)
441            }
442        }
443        _ => {}
444    })
445}