Disabled widgets are now also disabled in the accesskit output (#4750)
Marking widgets as disabled was not reflected in the accesskit output, now the disabled status should match. --------- Co-authored-by: Wybe Westra <w.westra@kwantcontrols.nl>
This commit is contained in:
parent
fcec84ca74
commit
fa8d535fe7
|
|
@ -540,8 +540,9 @@ impl CollapsingHeader {
|
|||
header_response.mark_changed();
|
||||
}
|
||||
|
||||
header_response
|
||||
.widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, galley.text()));
|
||||
header_response.widget_info(|| {
|
||||
WidgetInfo::labeled(WidgetType::CollapsingHeader, ui.is_enabled(), galley.text())
|
||||
});
|
||||
|
||||
let openness = state.openness(ui.ctx());
|
||||
|
||||
|
|
|
|||
|
|
@ -213,12 +213,13 @@ impl ComboBox {
|
|||
(width, height),
|
||||
);
|
||||
if let Some(label) = label {
|
||||
ir.response
|
||||
.widget_info(|| WidgetInfo::labeled(WidgetType::ComboBox, label.text()));
|
||||
ir.response.widget_info(|| {
|
||||
WidgetInfo::labeled(WidgetType::ComboBox, ui.is_enabled(), label.text())
|
||||
});
|
||||
ir.response |= ui.label(label);
|
||||
} else {
|
||||
ir.response
|
||||
.widget_info(|| WidgetInfo::labeled(WidgetType::ComboBox, ""));
|
||||
.widget_info(|| WidgetInfo::labeled(WidgetType::ComboBox, ui.is_enabled(), ""));
|
||||
}
|
||||
ir
|
||||
})
|
||||
|
|
|
|||
|
|
@ -547,8 +547,9 @@ impl WidgetInfo {
|
|||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn labeled(typ: WidgetType, label: impl ToString) -> Self {
|
||||
pub fn labeled(typ: WidgetType, enabled: bool, label: impl ToString) -> Self {
|
||||
Self {
|
||||
enabled,
|
||||
label: Some(label.to_string()),
|
||||
..Self::new(typ)
|
||||
}
|
||||
|
|
@ -556,25 +557,28 @@ impl WidgetInfo {
|
|||
|
||||
/// checkboxes, radio-buttons etc
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn selected(typ: WidgetType, selected: bool, label: impl ToString) -> Self {
|
||||
pub fn selected(typ: WidgetType, enabled: bool, selected: bool, label: impl ToString) -> Self {
|
||||
Self {
|
||||
enabled,
|
||||
label: Some(label.to_string()),
|
||||
selected: Some(selected),
|
||||
..Self::new(typ)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drag_value(value: f64) -> Self {
|
||||
pub fn drag_value(enabled: bool, value: f64) -> Self {
|
||||
Self {
|
||||
enabled,
|
||||
value: Some(value),
|
||||
..Self::new(WidgetType::DragValue)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn slider(value: f64, label: impl ToString) -> Self {
|
||||
pub fn slider(enabled: bool, value: f64, label: impl ToString) -> Self {
|
||||
let label = label.to_string();
|
||||
Self {
|
||||
enabled,
|
||||
label: if label.is_empty() { None } else { Some(label) },
|
||||
value: Some(value),
|
||||
..Self::new(WidgetType::Slider)
|
||||
|
|
@ -582,7 +586,11 @@ impl WidgetInfo {
|
|||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn text_edit(prev_text_value: impl ToString, text_value: impl ToString) -> Self {
|
||||
pub fn text_edit(
|
||||
enabled: bool,
|
||||
prev_text_value: impl ToString,
|
||||
text_value: impl ToString,
|
||||
) -> Self {
|
||||
let text_value = text_value.to_string();
|
||||
let prev_text_value = prev_text_value.to_string();
|
||||
let prev_text_value = if text_value == prev_text_value {
|
||||
|
|
@ -591,6 +599,7 @@ impl WidgetInfo {
|
|||
Some(prev_text_value)
|
||||
};
|
||||
Self {
|
||||
enabled,
|
||||
current_text_value: Some(text_value),
|
||||
prev_text_value,
|
||||
..Self::new(WidgetType::TextEdit)
|
||||
|
|
@ -599,10 +608,12 @@ impl WidgetInfo {
|
|||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn text_selection_changed(
|
||||
enabled: bool,
|
||||
text_selection: std::ops::RangeInclusive<usize>,
|
||||
current_text_value: impl ToString,
|
||||
) -> Self {
|
||||
Self {
|
||||
enabled,
|
||||
text_selection: Some(text_selection),
|
||||
current_text_value: Some(current_text_value.to_string()),
|
||||
..Self::new(WidgetType::TextEdit)
|
||||
|
|
|
|||
|
|
@ -524,7 +524,11 @@ impl SubMenuButton {
|
|||
|
||||
let (rect, response) = ui.allocate_at_least(desired_size, sense);
|
||||
response.widget_info(|| {
|
||||
crate::WidgetInfo::labeled(crate::WidgetType::Button, text_galley.text())
|
||||
crate::WidgetInfo::labeled(
|
||||
crate::WidgetType::Button,
|
||||
ui.is_enabled(),
|
||||
text_galley.text(),
|
||||
)
|
||||
});
|
||||
|
||||
if ui.is_rect_visible(rect) {
|
||||
|
|
|
|||
|
|
@ -880,6 +880,9 @@ impl Response {
|
|||
|
||||
#[cfg(feature = "accesskit")]
|
||||
pub(crate) fn fill_accesskit_node_common(&self, builder: &mut accesskit::NodeBuilder) {
|
||||
if !self.enabled {
|
||||
builder.set_disabled();
|
||||
}
|
||||
builder.set_bounds(accesskit::Rect {
|
||||
x0: self.rect.min.x.into(),
|
||||
y0: self.rect.min.y.into(),
|
||||
|
|
@ -921,6 +924,9 @@ impl Response {
|
|||
WidgetType::ProgressIndicator => Role::ProgressIndicator,
|
||||
WidgetType::Other => Role::Unknown,
|
||||
});
|
||||
if !info.enabled {
|
||||
builder.set_disabled();
|
||||
}
|
||||
if let Some(label) = info.label {
|
||||
builder.set_name(label);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ impl Widget for Button<'_> {
|
|||
let (rect, mut response) = ui.allocate_at_least(desired_size, sense);
|
||||
response.widget_info(|| {
|
||||
if let Some(galley) = &galley {
|
||||
WidgetInfo::labeled(WidgetType::Button, galley.text())
|
||||
WidgetInfo::labeled(WidgetType::Button, ui.is_enabled(), galley.text())
|
||||
} else {
|
||||
WidgetInfo::new(WidgetType::Button)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,11 +82,13 @@ impl<'a> Widget for Checkbox<'a> {
|
|||
if indeterminate {
|
||||
WidgetInfo::labeled(
|
||||
WidgetType::Checkbox,
|
||||
ui.is_enabled(),
|
||||
galley.as_ref().map_or("", |x| x.text()),
|
||||
)
|
||||
} else {
|
||||
WidgetInfo::selected(
|
||||
WidgetType::Checkbox,
|
||||
ui.is_enabled(),
|
||||
*checked,
|
||||
galley.as_ref().map_or("", |x| x.text()),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -621,7 +621,7 @@ impl<'a> Widget for DragValue<'a> {
|
|||
|
||||
response.changed = get(&mut get_set_value) != old_value;
|
||||
|
||||
response.widget_info(|| WidgetInfo::drag_value(value));
|
||||
response.widget_info(|| WidgetInfo::drag_value(ui.is_enabled(), value));
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
ui.ctx().accesskit_node_builder(response.id, |builder| {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ impl Widget for Link {
|
|||
let label = Label::new(text).sense(Sense::click());
|
||||
|
||||
let (galley_pos, galley, response) = label.layout_in_ui(ui);
|
||||
response.widget_info(|| WidgetInfo::labeled(WidgetType::Link, galley.text()));
|
||||
response
|
||||
.widget_info(|| WidgetInfo::labeled(WidgetType::Link, ui.is_enabled(), galley.text()));
|
||||
|
||||
if ui.is_rect_visible(response.rect) {
|
||||
let color = ui.visuals().hyperlink_color;
|
||||
|
|
|
|||
|
|
@ -229,7 +229,8 @@ impl Widget for Label {
|
|||
let selectable = self.selectable;
|
||||
|
||||
let (galley_pos, galley, mut response) = self.layout_in_ui(ui);
|
||||
response.widget_info(|| WidgetInfo::labeled(WidgetType::Label, galley.text()));
|
||||
response
|
||||
.widget_info(|| WidgetInfo::labeled(WidgetType::Label, ui.is_enabled(), galley.text()));
|
||||
|
||||
if ui.is_rect_visible(response.rect) {
|
||||
if galley.elided {
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ impl Widget for ProgressBar {
|
|||
|
||||
response.widget_info(|| {
|
||||
let mut info = if let Some(ProgressBarText::Custom(text)) = &text {
|
||||
WidgetInfo::labeled(WidgetType::ProgressIndicator, text.text())
|
||||
WidgetInfo::labeled(WidgetType::ProgressIndicator, ui.is_enabled(), text.text())
|
||||
} else {
|
||||
WidgetInfo::new(WidgetType::ProgressIndicator)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ impl Widget for RadioButton {
|
|||
response.widget_info(|| {
|
||||
WidgetInfo::selected(
|
||||
WidgetType::RadioButton,
|
||||
ui.is_enabled(),
|
||||
checked,
|
||||
galley.as_ref().map_or("", |x| x.text()),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -50,7 +50,12 @@ impl Widget for SelectableLabel {
|
|||
desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y);
|
||||
let (rect, response) = ui.allocate_at_least(desired_size, Sense::click());
|
||||
response.widget_info(|| {
|
||||
WidgetInfo::selected(WidgetType::SelectableLabel, selected, galley.text())
|
||||
WidgetInfo::selected(
|
||||
WidgetType::SelectableLabel,
|
||||
ui.is_enabled(),
|
||||
selected,
|
||||
galley.text(),
|
||||
)
|
||||
});
|
||||
|
||||
if ui.is_rect_visible(response.rect) {
|
||||
|
|
|
|||
|
|
@ -853,7 +853,7 @@ impl<'a> Slider<'a> {
|
|||
|
||||
let value = self.get_value();
|
||||
response.changed = value != old_value;
|
||||
response.widget_info(|| WidgetInfo::slider(value, self.text.text()));
|
||||
response.widget_info(|| WidgetInfo::slider(ui.is_enabled(), value, self.text.text()));
|
||||
|
||||
#[cfg(feature = "accesskit")]
|
||||
ui.ctx().accesskit_node_builder(response.id, |builder| {
|
||||
|
|
|
|||
|
|
@ -744,6 +744,7 @@ impl<'t> TextEdit<'t> {
|
|||
if response.changed {
|
||||
response.widget_info(|| {
|
||||
WidgetInfo::text_edit(
|
||||
ui.is_enabled(),
|
||||
mask_if_password(password, prev_text.as_str()),
|
||||
mask_if_password(password, text.as_str()),
|
||||
)
|
||||
|
|
@ -753,6 +754,7 @@ impl<'t> TextEdit<'t> {
|
|||
let char_range =
|
||||
cursor_range.primary.ccursor.index..=cursor_range.secondary.ccursor.index;
|
||||
let info = WidgetInfo::text_selection_changed(
|
||||
ui.is_enabled(),
|
||||
char_range,
|
||||
mask_if_password(password, text.as_str()),
|
||||
);
|
||||
|
|
@ -760,6 +762,7 @@ impl<'t> TextEdit<'t> {
|
|||
} else {
|
||||
response.widget_info(|| {
|
||||
WidgetInfo::text_edit(
|
||||
ui.is_enabled(),
|
||||
mask_if_password(password, prev_text.as_str()),
|
||||
mask_if_password(password, text.as_str()),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
//! Tests the accesskit accessibility output of egui.
|
||||
#![cfg(feature = "accesskit")]
|
||||
|
||||
use accesskit::Role;
|
||||
use egui::{Context, RawInput};
|
||||
use accesskit::{Role, TreeUpdate};
|
||||
use egui::{CentralPanel, Context, RawInput};
|
||||
|
||||
/// Baseline test that asserts there are no spurious nodes in the
|
||||
/// accesskit output when the ui is empty.
|
||||
|
|
@ -11,86 +11,133 @@ use egui::{Context, RawInput};
|
|||
/// are put there because of the widgets rendered.
|
||||
#[test]
|
||||
fn empty_ui_should_return_tree_with_only_root_window() {
|
||||
let ctx = Context::default();
|
||||
ctx.enable_accesskit();
|
||||
|
||||
let output = ctx.run(RawInput::default(), |ctx| {
|
||||
egui::CentralPanel::default().show(ctx, |_| {});
|
||||
let output = accesskit_output_single_egui_frame(|ctx| {
|
||||
CentralPanel::default().show(ctx, |_| {});
|
||||
});
|
||||
|
||||
let tree_update = output
|
||||
.platform_output
|
||||
.accesskit_update
|
||||
.expect("Missing accesskit update");
|
||||
|
||||
let tree = tree_update.tree.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
tree_update.nodes.len(),
|
||||
output.nodes.len(),
|
||||
1,
|
||||
"Empty ui should produce only the root window."
|
||||
);
|
||||
let (id, root) = &tree_update.nodes[0];
|
||||
let (id, root) = &output.nodes[0];
|
||||
|
||||
assert_eq!(*id, tree.root);
|
||||
assert_eq!(*id, output.tree.unwrap().root);
|
||||
assert_eq!(root.role(), Role::Window);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn button_text() {
|
||||
fn button_node() {
|
||||
let button_text = "This is a test button!";
|
||||
|
||||
let ctx = Context::default();
|
||||
ctx.enable_accesskit();
|
||||
|
||||
let output = ctx.run(RawInput::default(), |ctx| {
|
||||
egui::CentralPanel::default().show(ctx, |ui| ui.button(button_text));
|
||||
let output = accesskit_output_single_egui_frame(|ctx| {
|
||||
CentralPanel::default().show(ctx, |ui| ui.button(button_text));
|
||||
});
|
||||
|
||||
let nodes = output
|
||||
.platform_output
|
||||
.accesskit_update
|
||||
.expect("Missing accesskit update")
|
||||
.nodes;
|
||||
|
||||
assert_eq!(
|
||||
nodes.len(),
|
||||
output.nodes.len(),
|
||||
2,
|
||||
"Expected only the root node and the button."
|
||||
);
|
||||
|
||||
nodes
|
||||
let (_, button) = output
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|(_, node)| node.role() == Role::Button && node.name() == Some(button_text))
|
||||
.find(|(_, node)| node.role() == Role::Button)
|
||||
.expect("Button should exist in the accesskit output");
|
||||
|
||||
assert_eq!(button.name(), Some(button_text));
|
||||
assert!(!button.is_disabled());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn toggle_button_text() {
|
||||
let button_text = "A toggle button";
|
||||
fn disabled_button_node() {
|
||||
let button_text = "This is a test button!";
|
||||
|
||||
let ctx = Context::default();
|
||||
ctx.enable_accesskit();
|
||||
|
||||
let mut selected = false;
|
||||
let output = ctx.run(RawInput::default(), |ctx| {
|
||||
egui::CentralPanel::default().show(ctx, |ui| ui.toggle_value(&mut selected, button_text));
|
||||
let output = accesskit_output_single_egui_frame(|ctx| {
|
||||
CentralPanel::default().show(ctx, |ui| {
|
||||
ui.add_enabled(false, egui::Button::new(button_text))
|
||||
});
|
||||
});
|
||||
|
||||
let nodes = output
|
||||
.platform_output
|
||||
.accesskit_update
|
||||
.expect("Missing accesskit update")
|
||||
.nodes;
|
||||
|
||||
assert_eq!(
|
||||
nodes.len(),
|
||||
output.nodes.len(),
|
||||
2,
|
||||
"Expected only the root node and the button."
|
||||
);
|
||||
|
||||
nodes
|
||||
let (_, button) = output
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|(_, node)| node.role() == Role::ToggleButton && node.name() == Some(button_text))
|
||||
.expect("Toggle button should exist in the accesskit output");
|
||||
.find(|(_, node)| node.role() == Role::Button)
|
||||
.expect("Button should exist in the accesskit output");
|
||||
|
||||
assert_eq!(button.name(), Some(button_text));
|
||||
assert!(button.is_disabled());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn toggle_button_node() {
|
||||
let button_text = "A toggle button";
|
||||
|
||||
let mut selected = false;
|
||||
let output = accesskit_output_single_egui_frame(|ctx| {
|
||||
CentralPanel::default().show(ctx, |ui| ui.toggle_value(&mut selected, button_text));
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
output.nodes.len(),
|
||||
2,
|
||||
"Expected only the root node and the button."
|
||||
);
|
||||
|
||||
let (_, toggle) = output
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|(_, node)| node.role() == Role::ToggleButton)
|
||||
.expect("Toggle button should exist in the accesskit output");
|
||||
|
||||
assert_eq!(toggle.name(), Some(button_text));
|
||||
assert!(!toggle.is_disabled());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_disabled_widgets() {
|
||||
let output = accesskit_output_single_egui_frame(|ctx| {
|
||||
CentralPanel::default().show(ctx, |ui| {
|
||||
ui.add_enabled_ui(false, |ui| {
|
||||
let _ = ui.button("Button 1");
|
||||
let _ = ui.button("Button 2");
|
||||
let _ = ui.button("Button 3");
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
output.nodes.len(),
|
||||
4,
|
||||
"Expected the root node and all the child widgets."
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
output
|
||||
.nodes
|
||||
.iter()
|
||||
.filter(|(_, node)| node.is_disabled())
|
||||
.count(),
|
||||
3,
|
||||
"All widgets should be disabled."
|
||||
);
|
||||
}
|
||||
|
||||
fn accesskit_output_single_egui_frame(run_ui: impl FnOnce(&Context)) -> TreeUpdate {
|
||||
let ctx = Context::default();
|
||||
ctx.enable_accesskit();
|
||||
|
||||
let output = ctx.run(RawInput::default(), run_ui);
|
||||
|
||||
output
|
||||
.platform_output
|
||||
.accesskit_update
|
||||
.expect("Missing accesskit update")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@ pub fn toggle_ui(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
|||
}
|
||||
|
||||
// Attach some meta-data to the response which can be used by screen readers:
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, *on, ""));
|
||||
response.widget_info(|| {
|
||||
egui::WidgetInfo::selected(egui::WidgetType::Checkbox, ui.is_enabled(), *on, "")
|
||||
});
|
||||
|
||||
// 4. Paint!
|
||||
// Make sure we need to paint:
|
||||
|
|
@ -77,7 +79,9 @@ fn toggle_ui_compact(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
|
|||
*on = !*on;
|
||||
response.mark_changed();
|
||||
}
|
||||
response.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::Checkbox, *on, ""));
|
||||
response.widget_info(|| {
|
||||
egui::WidgetInfo::selected(egui::WidgetType::Checkbox, ui.is_enabled(), *on, "")
|
||||
});
|
||||
|
||||
if ui.is_rect_visible(rect) {
|
||||
let how_on = ui.ctx().animate_bool_responsive(response.id, *on);
|
||||
|
|
|
|||
|
|
@ -117,8 +117,14 @@ impl LegendEntry {
|
|||
let desired_size = total_extra + galley.size();
|
||||
let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click());
|
||||
|
||||
response
|
||||
.widget_info(|| WidgetInfo::selected(WidgetType::Checkbox, *checked, galley.text()));
|
||||
response.widget_info(|| {
|
||||
WidgetInfo::selected(
|
||||
WidgetType::Checkbox,
|
||||
ui.is_enabled(),
|
||||
*checked,
|
||||
galley.text(),
|
||||
)
|
||||
});
|
||||
|
||||
let visuals = ui.style().interact(&response);
|
||||
let label_on_the_left = ui.layout().horizontal_placement() == Align::RIGHT;
|
||||
|
|
|
|||
Loading…
Reference in New Issue