Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve accessibility of ComboBox widgets #7514

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions api/cpp/include/slint-testing.h
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,52 @@ class ElementHandle
return std::nullopt;
}

/// Returns the accessible-expanded of that element, if any.
std::optional<bool> accessible_expanded() const
{
if (auto result = get_accessible_string_property(
cbindgen_private::AccessibleStringProperty::Expanded)) {
if (*result == "true")
return true;
else if (*result == "false")
return false;
}
return std::nullopt;
}
/// Returns the accessible-expandable of that element, if any.
std::optional<bool> accessible_expandable() const
{
if (auto result = get_accessible_string_property(
cbindgen_private::AccessibleStringProperty::Expandable)) {
if (*result == "true")
return true;
else if (*result == "false")
return false;
}
return std::nullopt;
}

/// Invokes the expand accessibility action of that element
/// (`accessible-action-expand`).
void invoke_accessible_expand_action() const
{
if (inner.element_index != 0)
return;
if (auto item = private_api::upgrade_item_weak(inner.item)) {
union ExpandActionHelper {
cbindgen_private::AccessibilityAction action;
ExpandActionHelper()
{
action.tag = cbindgen_private::AccessibilityAction::Tag::Expand;
}
~ExpandActionHelper() { }

} action;
item->item_tree.vtable()->accessibility_action(item->item_tree.borrow(), item->index,
&action.action);
}
}

/// Sets the accessible-value of that element.
///
/// Setting the value will invoke the `accessible-action-set-value` callback.
Expand Down
14 changes: 14 additions & 0 deletions docs/astro/src/content/docs/reference/common.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,17 @@ The description for the current element.
Whether the element is enabled or not. This maps to the "enabled" state of most widgets. (default value: `true`)
</SlintProperty>

### accessible-expandable
<SlintProperty typeName="bool" propName="accessible-expandable" default="false">
Whether the element can be expanded or not.
</SlintProperty>

### accessible-expanded
<SlintProperty typeName="bool" propName="accessible-expanded" default="false">
Whether the element is expanded or not. Applies to combo boxes, menu items,
tree view items and other widgets.
</SlintProperty>

### accessible-label
<SlintProperty typeName="string" propName="accessible-label" default='""'>
The label for an interactive element. (default value: empty for most elements, or the value of the `text` property for Text elements)
Expand Down Expand Up @@ -272,3 +283,6 @@ Invoked when the user requests to increment the value.

### accessible-action-decrement()
Invoked when the user requests to decrement the value.

### accessible-action-expand()
Invoked when the user requests to expand the widget (eg: disclose the list of available choices for a combo box).
25 changes: 24 additions & 1 deletion internal/backends/qt/qt_accessible.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const VALUE_MINIMUM: u32 = CHECKED + 1;
const VALUE_MAXIMUM: u32 = VALUE_MINIMUM + 1;
const VALUE_STEP: u32 = VALUE_MAXIMUM + 1;
const CHECKABLE: u32 = VALUE_STEP + 1;
const EXPANDABLE: u32 = CHECKABLE + 1;
const EXPANDED: u32 = EXPANDABLE + 1;

pub struct AccessibleItemPropertiesTracker {
obj: *mut c_void,
Expand Down Expand Up @@ -208,6 +210,8 @@ impl SlintAccessibleItemData {
if let Some(item_rc) = item.upgrade() {
item_rc.accessible_string_property(AccessibleStringProperty::Checkable);
item_rc.accessible_string_property(AccessibleStringProperty::Checked);
item_rc.accessible_string_property(AccessibleStringProperty::Expandable);
item_rc.accessible_string_property(AccessibleStringProperty::Expanded);
}
});
}
Expand Down Expand Up @@ -267,6 +271,8 @@ cpp! {{
const uint32_t VALUE_MAXIMUM { VALUE_MINIMUM + 1 };
const uint32_t VALUE_STEP { VALUE_MAXIMUM + 1 };
const uint32_t CHECKABLE { VALUE_STEP + 1 };
const uint32_t EXPANDABLE { CHECKABLE + 1 };
const uint32_t EXPANDED { EXPANDABLE + 1 };

// ------------------------------------------------------------------------------
// Helper:
Expand Down Expand Up @@ -362,6 +368,8 @@ cpp! {{
VALUE_MAXIMUM => item.accessible_string_property(AccessibleStringProperty::ValueMaximum),
VALUE_STEP => item.accessible_string_property(AccessibleStringProperty::ValueStep),
CHECKABLE => item.accessible_string_property(AccessibleStringProperty::Checkable),
EXPANDABLE => item.accessible_string_property(AccessibleStringProperty::Expandable),
EXPANDED => item.accessible_string_property(AccessibleStringProperty::Expanded),
_ => None,
};
if let Some(string) = string {
Expand Down Expand Up @@ -621,6 +629,14 @@ cpp! {{
state.focused = has_focus_delegation;
state.checked = (checked == "true") ? 1 : 0;
state.checkable = (item_string_property(m_data, CHECKABLE) == "true") ? 1 : 0;
if (item_string_property(m_data, EXPANDABLE) == "true") {
state.expandable = 1;
if (item_string_property(m_data, EXPANDED) == "true") {
state.expanded = 1;
} else {
state.collapsed = 1;
}
}
return state; /* FIXME */
}

Expand Down Expand Up @@ -669,14 +685,21 @@ cpp! {{
actions << QAccessibleActionInterface::increaseAction();
if (supported & rust!(Slint_accessible_item_an3 [] -> SupportedAccessibilityAction as "uint" { SupportedAccessibilityAction::Decrement }))
actions << QAccessibleActionInterface::decreaseAction();
if (supported & rust!(Slint_accessible_item_an4 [] -> SupportedAccessibilityAction as "uint" { SupportedAccessibilityAction::Expand }))
actions << QAccessibleActionInterface::pressAction();
return actions;
}

void doAction(const QString &actionName) override {
if (actionName == QAccessibleActionInterface::pressAction()) {
rust!(Slint_accessible_item_do_action1 [m_data: Pin<&SlintAccessibleItemData> as "void*"] {
let Some(item) = m_data.item.upgrade() else {return};
item.accessible_action(&AccessibilityAction::Default);
let supported_actions = item.supported_accessibility_actions();
if supported_actions.contains(SupportedAccessibilityAction::Expand) {
item.accessible_action(&AccessibilityAction::Expand);
} else {
item.accessible_action(&AccessibilityAction::Default);
}
});
} else if (actionName == QAccessibleActionInterface::increaseAction()) {
rust!(Slint_accessible_item_do_action2 [m_data: Pin<&SlintAccessibleItemData> as "void*"] {
Expand Down
33 changes: 33 additions & 0 deletions internal/backends/testing/search_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,28 @@ impl ElementHandle {
})
}

/// Returns the value of the `accessible-expanded` property, if present
pub fn accessible_expanded(&self) -> Option<bool> {
if self.element_index != 0 {
return None;
}
self.item
.upgrade()
.and_then(|item| item.accessible_string_property(AccessibleStringProperty::Expanded))
.and_then(|item| item.parse().ok())
}

/// Returns the value of the `accessible-expandable` property, if present
pub fn accessible_expandable(&self) -> Option<bool> {
if self.element_index != 0 {
return None;
}
self.item
.upgrade()
.and_then(|item| item.accessible_string_property(AccessibleStringProperty::Expandable))
.and_then(|item| item.parse().ok())
}

/// Returns the size of the element in logical pixels. This corresponds to the value of the `width` and
/// `height` properties in Slint code. Returns a zero size if the element is not valid.
pub fn size(&self) -> i_slint_core::api::LogicalSize {
Expand Down Expand Up @@ -715,6 +737,17 @@ impl ElementHandle {
}
}

/// Invokes the element's `accessible-action-expand` callback, if declared. On widgets such as combo boxes, this
/// typically discloses the list of available choices.
pub fn invoke_accessible_expand_action(&self) {
if self.element_index != 0 {
return;
}
if let Some(item) = self.item.upgrade() {
item.accessible_action(&AccessibilityAction::Expand)
}
}

/// Simulates a single click (or touch tap) on the element at its center point with the
/// specified button.
pub async fn single_click(&self, button: i_slint_core::platform::PointerEventButton) {
Expand Down
1 change: 1 addition & 0 deletions internal/backends/testing/slint_systest.proto
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ enum ElementAccessibilityAction {
Default_ = 0;
Increment = 1;
Decrement = 2;
Expand = 3;
}

enum PointerEventButton {
Expand Down
1 change: 1 addition & 0 deletions internal/backends/testing/systest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ impl TestingClient {
proto::ElementAccessibilityAction::Decrement => {
element.invoke_accessible_decrement_action()
}
proto::ElementAccessibilityAction::Expand => element.invoke_accessible_expand_action(),
}
Ok(())
}
Expand Down
14 changes: 14 additions & 0 deletions internal/backends/winit/accesskit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ impl AccessKitAdapter {
}
_ => return None,
},
Action::Expand => AccessibilityAction::Expand,
_ => return None,
};
self.nodes
Expand Down Expand Up @@ -520,6 +521,16 @@ impl NodeCollection {
node.set_description(description.to_string());
}

if item
.accessible_string_property(AccessibleStringProperty::Expandable)
.is_some_and(|x| x == "true")
{
node.set_expanded(
item.accessible_string_property(AccessibleStringProperty::Expanded)
.is_some_and(|x| x == "true"),
);
}

if matches!(
role,
Role::Button
Expand Down Expand Up @@ -614,6 +625,9 @@ impl NodeCollection {
if supported.contains(SupportedAccessibilityAction::ReplaceSelectedText) {
node.add_action(accesskit::Action::ReplaceSelectedText);
}
if supported.contains(SupportedAccessibilityAction::Expand) {
node.add_action(accesskit::Action::Expand);
}

node
}
Expand Down
3 changes: 3 additions & 0 deletions internal/compiler/typeregister.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ pub fn reserved_accessibility_properties() -> impl Iterator<Item = (&'static str
("accessible-delegate-focus", Type::Int32),
("accessible-description", Type::String),
("accessible-enabled", Type::Bool),
("accessible-expandable", Type::Bool),
("accessible-expanded", Type::Bool),
("accessible-label", Type::String),
("accessible-value", Type::String),
("accessible-value-maximum", Type::Float32),
Expand All @@ -207,6 +209,7 @@ pub fn reserved_accessibility_properties() -> impl Iterator<Item = (&'static str
("accessible-action-increment", noarg_callback_type()),
("accessible-action-decrement", noarg_callback_type()),
("accessible-action-set-value", strarg_callback_type()),
("accessible-action-expand", noarg_callback_type()),
("accessible-item-selectable", Type::Bool),
("accessible-item-selected", Type::Bool),
("accessible-item-index", Type::Int32),
Expand Down
5 changes: 5 additions & 0 deletions internal/compiler/widgets/cosmic/combobox.slint
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export component ComboBox {
forward-focus: base;
accessible-role: combobox;
accessible-enabled: root.enabled;
accessible-expandable: true;
accessible-expanded: base.popup-has-focus;
accessible-value <=> root.current-value;
accessible-action-expand => { base.show-popup(); }

states [
disabled when !root.enabled : {
Expand Down Expand Up @@ -63,6 +67,7 @@ export component ComboBox {
font-weight: CosmicFontSettings.body.font-weight;
color: CosmicPalette.control-foreground;
text: root.current-value;
accessible-role: none;
}

Image {
Expand Down
5 changes: 5 additions & 0 deletions internal/compiler/widgets/cupertino/combobox.slint
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export component ComboBox {
forward-focus: base;
accessible-role: combobox;
accessible-enabled: root.enabled;
accessible-expandable: true;
accessible-expanded: base.popup-has-focus;
accessible-value <=> root.current-value;
accessible-action-expand => { base.show-popup(); }

states [
disabled when !root.enabled : {
Expand Down Expand Up @@ -95,6 +99,7 @@ export component ComboBox {
font-weight: CupertinoFontSettings.body.font-weight;
color: CupertinoPalette.foreground;
text: root.current-value;
accessible-role: none;
}

VerticalLayout {
Expand Down
5 changes: 5 additions & 0 deletions internal/compiler/widgets/fluent/combobox.slint
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export component ComboBox {

accessible-role: combobox;
accessible-enabled: root.enabled;
accessible-expandable: true;
accessible-expanded: base.popup-has-focus;
accessible-value <=> root.current-value;
accessible-action-expand => { base.show-popup(); }

states [
disabled when !root.enabled : {
Expand Down Expand Up @@ -77,6 +81,7 @@ export component ComboBox {
font-weight: FluentFontSettings.body.font-weight;
color: FluentPalette.control-foreground;
text: root.current-value;
accessible-role: none;
}

icon := Image {
Expand Down
5 changes: 5 additions & 0 deletions internal/compiler/widgets/material/combobox.slint
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export component ComboBox {
forward-focus: base;
accessible-role: combobox;
accessible-enabled: root.enabled;
accessible-expandable: true;
accessible-expanded: base.popup-has-focus;
accessible-value <=> root.current-value;
accessible-action-expand => { base.show-popup(); }

states [
disabled when !root.enabled : {
Expand Down Expand Up @@ -74,6 +78,7 @@ export component ComboBox {
// font-family: MaterialFontSettings.body-large.font;
font-size: MaterialFontSettings.body-large.font-size;
font-weight: MaterialFontSettings.body-large.font-weight;
accessible-role: none;
}

icon := Image {
Expand Down
5 changes: 5 additions & 0 deletions internal/compiler/widgets/qt/combobox.slint
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ export component ComboBox {

accessible-role: combobox;
accessible-enabled: root.enabled;
accessible-expandable: true;
accessible-expanded: base.popup-has-focus;
accessible-value <=> root.current-value;
accessible-action-expand => {
base.show-popup();
}
forward-focus: base;

HorizontalLayout {
Expand Down
8 changes: 6 additions & 2 deletions internal/core/accessibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub enum AccessibleStringProperty {
DelegateFocus,
Description,
Enabled,
Expandable,
Expanded,
ItemCount,
ItemIndex,
ItemSelectable,
Expand All @@ -36,6 +38,7 @@ pub enum AccessibilityAction {
Default,
Decrement,
Increment,
Expand,
/// This is currently unused
ReplaceSelectedText(SharedString),
SetValue(SharedString),
Expand All @@ -49,8 +52,9 @@ bitflags! {
const Default = 1;
const Decrement = 1 << 1;
const Increment = 1 << 2;
const ReplaceSelectedText = 1 << 3;
const SetValue = 1 << 4;
const Expand = 1 << 3;
const ReplaceSelectedText = 1 << 4;
const SetValue = 1 << 5;
}
}

Expand Down
1 change: 1 addition & 0 deletions internal/interpreter/dynamic_item_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2113,6 +2113,7 @@ extern "C" fn accessibility_action(
AccessibilityAction::Default => perform("accessible-action-default", &[]),
AccessibilityAction::Decrement => perform("accessible-action-decrement", &[]),
AccessibilityAction::Increment => perform("accessible-action-increment", &[]),
AccessibilityAction::Expand => perform("accessible-action-expand", &[]),
AccessibilityAction::ReplaceSelectedText(_a) => {
//perform("accessible-action-replace-selected-text", &[Value::String(a.clone())])
i_slint_core::debug_log!("AccessibilityAction::ReplaceSelectedText not implemented in interpreter's accessibility_action");
Expand Down
Loading
Loading