Skip to content

Commit

Permalink
fix: Add list box support to the platform adapters
Browse files Browse the repository at this point in the history
  • Loading branch information
DataTriny committed Dec 21, 2024
1 parent 058b714 commit e25af98
Show file tree
Hide file tree
Showing 9 changed files with 746 additions and 27 deletions.
47 changes: 46 additions & 1 deletion platforms/macos/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ pub(crate) struct EventGenerator {
context: Rc<Context>,
events: Vec<QueuedEvent>,
text_changed: HashSet<NodeId>,
selected_rows_changed: HashSet<NodeId>,
}

impl EventGenerator {
Expand All @@ -145,6 +146,7 @@ impl EventGenerator {
context,
events: Vec::new(),
text_changed: HashSet::new(),
selected_rows_changed: HashSet::new(),
}
}

Expand Down Expand Up @@ -181,6 +183,30 @@ impl EventGenerator {
self.insert_text_change_if_needed_parent(node);
}
}

fn enqueue_selected_rows_change_if_needed_parent(&mut self, node: Node) {
if !node.is_container_with_selectable_children() {
return;
}
let id = node.id();
if self.selected_rows_changed.contains(&id) {
return;
}
self.events.push(QueuedEvent::Generic {
node_id: id,
notification: unsafe { NSAccessibilitySelectedRowsChangedNotification },
});
self.selected_rows_changed.insert(id);
}

fn enqueue_selected_rows_change_if_needed(&mut self, node: &Node) {
if !node.is_item_like() {
return;
}
if let Some(node) = node.selection_container(&filter) {
self.enqueue_selected_rows_change_if_needed_parent(node);
}
}
}

impl TreeChangeHandler for EventGenerator {
Expand All @@ -189,6 +215,9 @@ impl TreeChangeHandler for EventGenerator {
if filter(node) != FilterResult::Include {
return;
}
if let Some(true) = node.is_selected() {
self.enqueue_selected_rows_change_if_needed(node);
}
if node.value().is_some() && node.live() != Live::Off {
self.events
.push(QueuedEvent::live_region_announcement(node));
Expand All @@ -199,7 +228,14 @@ impl TreeChangeHandler for EventGenerator {
if old_node.raw_value() != new_node.raw_value() {
self.insert_text_change_if_needed(new_node);
}
let old_node_was_filtered_out = filter(old_node) != FilterResult::Include;
if filter(new_node) != FilterResult::Include {
if !old_node_was_filtered_out
&& old_node.is_item_like()
&& old_node.is_selected() == Some(true)
{
self.enqueue_selected_rows_change_if_needed(old_node);
}
return;
}
let node_id = new_node.id();
Expand Down Expand Up @@ -230,11 +266,17 @@ impl TreeChangeHandler for EventGenerator {
&& new_node.live() != Live::Off
&& (new_node.value() != old_node.value()
|| new_node.live() != old_node.live()
|| filter(old_node) != FilterResult::Include)
|| old_node_was_filtered_out)
{
self.events
.push(QueuedEvent::live_region_announcement(new_node));
}
if new_node.is_item_like()
&& (new_node.is_selected() != old_node.is_selected()
|| (old_node_was_filtered_out && new_node.is_selected() == Some(true)))
{
self.enqueue_selected_rows_change_if_needed(new_node);
}
}

fn focus_moved(&mut self, _old_node: Option<&Node>, new_node: Option<&Node>) {
Expand All @@ -248,6 +290,9 @@ impl TreeChangeHandler for EventGenerator {

fn node_removed(&mut self, node: &Node) {
self.insert_text_change_if_needed(node);
if let Some(true) = node.is_selected() {
self.enqueue_selected_rows_change_if_needed(node);
}
self.events.push(QueuedEvent::NodeDestroyed(node.id()));
}
}
97 changes: 97 additions & 0 deletions platforms/macos/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,22 @@ declare_class!(
self.children_internal()
}

#[method_id(accessibilitySelectedChildren)]
fn selected_children(&self) -> Option<Id<NSArray<PlatformNode>>> {
self.resolve_with_context(|node, context| {
if !node.is_container_with_selectable_children() {
return None;
}
let platform_nodes = node
.items(filter)
.filter(|item| item.is_selected() == Some(true))
.map(|child| context.get_or_create_platform_node(child.id()))
.collect::<Vec<Id<PlatformNode>>>();
Some(NSArray::from_vec(platform_nodes))
})
.flatten()
}

#[method(accessibilityFrame)]
fn frame(&self) -> NSRect {
self.resolve_with_context(|node, context| {
Expand Down Expand Up @@ -813,6 +829,75 @@ declare_class!(
.unwrap_or(false)
}

#[method(isAccessibilitySelected)]
fn is_selected(&self) -> bool {
self.resolve(|node| node.is_selected()).flatten().unwrap_or(false)
}

#[method(setAccessibilitySelected:)]
fn set_selected(&self, selected: bool) {
self.resolve_with_context(|node, context| {
if !node.is_clickable() || !node.is_selectable() {
return;
}
if node.is_selected() == Some(selected) {
return;
}
context.do_action(ActionRequest {
action: Action::Click,
target: node.id(),
data: None,
});
});
}

#[method_id(accessibilityRows)]
fn rows(&self) -> Option<Id<NSArray<PlatformNode>>> {
self.resolve_with_context(|node, context| {
if !node.is_container_with_selectable_children() {
return None;
}
let platform_nodes = node
.items(filter)
.map(|child| context.get_or_create_platform_node(child.id()))
.collect::<Vec<Id<PlatformNode>>>();
Some(NSArray::from_vec(platform_nodes))
})
.flatten()
}

#[method_id(accessibilitySelectedRows)]
fn selected_rows(&self) -> Option<Id<NSArray<PlatformNode>>> {
self.resolve_with_context(|node, context| {
if !node.is_container_with_selectable_children() {
return None;
}
let platform_nodes = node
.items(filter)
.filter(|item| item.is_selected() == Some(true))
.map(|child| context.get_or_create_platform_node(child.id()))
.collect::<Vec<Id<PlatformNode>>>();
Some(NSArray::from_vec(platform_nodes))
})
.flatten()
}

#[method(accessibilityPerformPick)]
fn pick(&self) -> bool {
self.resolve_with_context(|node, context| {
let selectable = node.is_clickable() && node.is_selectable();
if selectable {
context.do_action(ActionRequest {
action: Action::Click,
target: node.id(),
data: None,
});
}
selectable
})
.unwrap_or(false)
}

#[method(isAccessibilitySelectorAllowed:)]
fn is_selector_allowed(&self, selector: Sel) -> bool {
self.resolve(|node| {
Expand Down Expand Up @@ -849,9 +934,21 @@ declare_class!(
// the expected VoiceOver behavior.
return node.supports_text_ranges() && !node.is_read_only();
}
if selector == sel!(isAccessibilitySelected) {
return node.is_selectable();
}
if selector == sel!(accessibilityRows)
|| selector == sel!(accessibilitySelectedRows)
{
return node.is_container_with_selectable_children()
}
if selector == sel!(accessibilityPerformPick) {
return node.is_clickable() && node.is_selectable();
}
selector == sel!(accessibilityParent)
|| selector == sel!(accessibilityChildren)
|| selector == sel!(accessibilityChildrenInNavigationOrder)
|| selector == sel!(accessibilitySelectedChildren)
|| selector == sel!(accessibilityFrame)
|| selector == sel!(accessibilityRole)
|| selector == sel!(accessibilitySubrole)
Expand Down
27 changes: 27 additions & 0 deletions platforms/unix/src/atspi/bus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ impl Bus {
)
.await?;
}
if new_interfaces.contains(Interface::Selection) {
self.register_interface(
&path,
SelectionInterface::new(bus_name.clone(), node.clone()),
)
.await?;
}
if new_interfaces.contains(Interface::Text) {
self.register_interface(&path, TextInterface::new(node.clone()))
.await?;
Expand Down Expand Up @@ -164,6 +171,10 @@ impl Bus {
self.unregister_interface::<ComponentInterface>(&path)
.await?;
}
if old_interfaces.contains(Interface::Selection) {
self.unregister_interface::<SelectionInterface>(&path)
.await?;
}
if old_interfaces.contains(Interface::Text) {
self.unregister_interface::<TextInterface>(&path).await?;
}
Expand Down Expand Up @@ -206,6 +217,7 @@ impl Bus {
ObjectEvent::CaretMoved(_) => "TextCaretMoved",
ObjectEvent::ChildAdded(_, _) | ObjectEvent::ChildRemoved(_) => "ChildrenChanged",
ObjectEvent::PropertyChanged(_) => "PropertyChange",
ObjectEvent::SelectionChanged => "SelectionChanged",
ObjectEvent::StateChanged(_, _) => "StateChanged",
ObjectEvent::TextInserted { .. } | ObjectEvent::TextRemoved { .. } => "TextChanged",
ObjectEvent::TextSelectionChanged => "TextSelectionChanged",
Expand Down Expand Up @@ -350,6 +362,21 @@ impl Bus {
)
.await
}
ObjectEvent::SelectionChanged => {
self.emit_event(
target,
interface,
signal,
EventBody {
kind: "",
detail1: 0,
detail2: 0,
any_data: 0i32.into(),
properties,
},
)
.await
}
ObjectEvent::StateChanged(state, value) => {
self.emit_event(
target,
Expand Down
2 changes: 2 additions & 0 deletions platforms/unix/src/atspi/interfaces/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod accessible;
mod action;
mod application;
mod component;
mod selection;
mod text;
mod value;

Expand All @@ -31,5 +32,6 @@ pub(crate) use accessible::*;
pub(crate) use action::*;
pub(crate) use application::*;
pub(crate) use component::*;
pub(crate) use selection::*;
pub(crate) use text::*;
pub(crate) use value::*;
82 changes: 82 additions & 0 deletions platforms/unix/src/atspi/interfaces/selection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2024 The AccessKit Authors. All rights reserved.
// Licensed under the Apache License, Version 2.0 (found in
// the LICENSE-APACHE file) or the MIT license (found in
// the LICENSE-MIT file), at your option.

use accesskit_atspi_common::PlatformNode;
use zbus::{fdo, interface, names::OwnedUniqueName};

use crate::atspi::{ObjectId, OwnedObjectAddress};

pub(crate) struct SelectionInterface {
bus_name: OwnedUniqueName,
node: PlatformNode,
}

impl SelectionInterface {
pub fn new(bus_name: OwnedUniqueName, node: PlatformNode) -> Self {
Self { bus_name, node }
}

fn map_error(&self) -> impl '_ + FnOnce(accesskit_atspi_common::Error) -> fdo::Error {
|error| crate::util::map_error_from_node(&self.node, error)
}
}

#[interface(name = "org.a11y.atspi.Selection")]
impl SelectionInterface {
#[zbus(property)]
fn n_selected_children(&self) -> fdo::Result<i32> {
self.node.n_selected_children().map_err(self.map_error())
}

fn get_selected_child(&self, selected_child_index: i32) -> fdo::Result<(OwnedObjectAddress,)> {
let child = self
.node
.selected_child(map_child_index(selected_child_index)?)
.map_err(self.map_error())?
.map(|child| ObjectId::Node {
adapter: self.node.adapter_id(),
node: child,
});
Ok(super::optional_object_address(&self.bus_name, child))
}

fn select_child(&self, child_index: i32) -> fdo::Result<bool> {
self.node
.select_child(map_child_index(child_index)?)
.map_err(self.map_error())
}

fn deselect_selected_child(&self, selected_child_index: i32) -> fdo::Result<bool> {
self.node
.deselect_selected_child(map_child_index(selected_child_index)?)
.map_err(self.map_error())
}

fn is_child_selected(&self, child_index: i32) -> fdo::Result<bool> {
self.node
.is_child_selected(map_child_index(child_index)?)
.map_err(self.map_error())
}

fn select_all(&self) -> fdo::Result<bool> {
self.node.select_all().map_err(self.map_error())
}

fn clear_selection(&self) -> fdo::Result<bool> {
self.node.clear_selection().map_err(self.map_error())
}

fn deselect_child(&self, child_index: i32) -> fdo::Result<bool> {
self.node
.deselect_child(map_child_index(child_index)?)
.map_err(self.map_error())
}
}

fn map_child_index(index: i32) -> fdo::Result<usize> {
index
.try_into()
.map_err(|_| fdo::Error::InvalidArgs("Index can't be negative.".into()))
}
Loading

0 comments on commit e25af98

Please sign in to comment.