Skip to content
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
77 changes: 45 additions & 32 deletions platforms/ios/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@ use objc2_foundation::{
};
use objc2_ui_kit::{
UIAccessibilityAnnouncementNotification, UIAccessibilityLayoutChangedNotification,
UIAccessibilityNotifications, UIAccessibilityPostNotification, UIAccessibilityPriority,
UIAccessibilityPriorityHigh, UIAccessibilityPriorityLow,
UIAccessibilityScreenChangedNotification, UIAccessibilitySpeechAttributeAnnouncementPriority,
UIAccessibilitySpeechAttributeQueueAnnouncement,
UIAccessibilityNotifications, UIAccessibilityPostNotification,
UIAccessibilityScreenChangedNotification, UIAccessibilitySpeechAttributeQueueAnnouncement,
};
use std::collections::VecDeque;
use std::rc::Rc;

use crate::{context::Context, filters::filter, node::PlatformNode};
use crate::{
context::Context,
filters::filter,
node::PlatformNode,
util::{announcement_priority_high, announcement_priority_key, announcement_priority_low},
};

pub(crate) enum QueuedEvent {
Generic {
Expand All @@ -29,19 +32,15 @@ pub(crate) enum QueuedEvent {
NodeDestroyed(NodeId),
Announcement {
text: String,
priority: &'static UIAccessibilityPriority,
assertive: bool,
},
}

impl QueuedEvent {
fn live_region_announcement(text: String, priority: Live) -> Self {
Self::Announcement {
text,
priority: if priority == Live::Assertive {
unsafe { UIAccessibilityPriorityHigh }
} else {
unsafe { UIAccessibilityPriorityLow }
},
assertive: priority == Live::Assertive,
}
}

Expand All @@ -61,32 +60,46 @@ impl QueuedEvent {
Self::NodeDestroyed(node_id) => {
context.remove_platform_node(node_id);
}
Self::Announcement { text, priority } => {
Self::raise_announcement(&text, priority);
Self::Announcement { text, assertive } => {
Self::raise_announcement(&text, assertive);
}
}
}

fn raise_announcement(text: &str, priority: &'static UIAccessibilityPriority) {
fn raise_announcement(text: &str, assertive: bool) {
let text = NSString::from_str(text);
let mut attrs: objc2::rc::Retained<NSMutableDictionary<NSAttributedStringKey, AnyObject>> =
NSMutableDictionary::new();
unsafe {
attrs.setObject_forKey(
priority,
ProtocolObject::from_ref(UIAccessibilitySpeechAttributeAnnouncementPriority),
);
attrs.setObject_forKey(
&*NSNumber::new_bool(true),
ProtocolObject::from_ref(UIAccessibilitySpeechAttributeQueueAnnouncement),
);
}
let announcement = unsafe { NSAttributedString::new_with_attributes(&text, &attrs) };
unsafe {
UIAccessibilityPostNotification(
UIAccessibilityAnnouncementNotification,
Some(&announcement),
);
let priority = if assertive {
announcement_priority_high()
} else {
announcement_priority_low()
};
match (priority, announcement_priority_key()) {
(Some(priority), Some(priority_key)) => {
let mut attrs: objc2::rc::Retained<
NSMutableDictionary<NSAttributedStringKey, AnyObject>,
> = NSMutableDictionary::new();
unsafe {
attrs.setObject_forKey(priority, ProtocolObject::from_ref(priority_key));
attrs.setObject_forKey(
&*NSNumber::new_bool(true),
ProtocolObject::from_ref(UIAccessibilitySpeechAttributeQueueAnnouncement),
);
}
let announcement =
unsafe { NSAttributedString::new_with_attributes(&text, &attrs) };
unsafe {
UIAccessibilityPostNotification(
UIAccessibilityAnnouncementNotification,
Some(&announcement),
);
}
}
_ => unsafe {
UIAccessibilityPostNotification(
UIAccessibilityAnnouncementNotification,
Some(&*text),
);
},
}
}
}
Expand Down
16 changes: 8 additions & 8 deletions platforms/ios/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ use objc2_ui_kit::{
UIAccessibilityTraitButton, UIAccessibilityTraitHeader, UIAccessibilityTraitImage,
UIAccessibilityTraitLink, UIAccessibilityTraitNone, UIAccessibilityTraitNotEnabled,
UIAccessibilityTraitSearchField, UIAccessibilityTraitSelected, UIAccessibilityTraitStaticText,
UIAccessibilityTraitToggleButton, UIAccessibilityTraitUpdatesFrequently, UIAccessibilityTraits,
UIAccessibilityTraitUpdatesFrequently, UIAccessibilityTraits,
};
use std::rc::{Rc, Weak};

use crate::{
context::Context,
filters::{filter, filter_for_is_accessibility_element},
util::{UIAccessibilityExpandedStatus, to_cg_rect, to_screen_rect},
util::{UIAccessibilityExpandedStatus, to_cg_rect, to_screen_rect, toggle_button_trait},
};

#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -139,7 +139,7 @@ impl NodeWrapper<'_> {
}

if self.0.toggled().is_some() {
traits |= unsafe { UIAccessibilityTraitToggleButton };
traits |= toggle_button_trait();
}

if self.0.is_selected() == Some(true) {
Expand Down Expand Up @@ -710,7 +710,7 @@ mod tests {
node.set_toggled(Toggled::False);
let t = node_traits(&node);
assert!(t & unsafe { UIAccessibilityTraitButton } != 0);
assert!(t & unsafe { UIAccessibilityTraitToggleButton } != 0);
assert!(t & toggle_button_trait() != 0);
}

#[test]
Expand All @@ -719,7 +719,7 @@ mod tests {
node.set_toggled(Toggled::True);
let t = node_traits(&node);
assert!(t & unsafe { UIAccessibilityTraitButton } != 0);
assert!(t & unsafe { UIAccessibilityTraitToggleButton } != 0);
assert!(t & toggle_button_trait() != 0);
}

#[test]
Expand All @@ -745,7 +745,7 @@ mod tests {
let mut node = NodeBuilder::new(Role::CheckBox);
node.set_toggled(toggled);
assert!(
node_traits(&node) & unsafe { UIAccessibilityTraitToggleButton } != 0,
node_traits(&node) & toggle_button_trait() != 0,
"toggled {toggled:?}",
);
}
Expand Down Expand Up @@ -821,7 +821,7 @@ mod tests {
assert!(t & unsafe { UIAccessibilityTraitButton } != 0);
assert!(t & unsafe { UIAccessibilityTraitNotEnabled } == 0);
assert!(t & unsafe { UIAccessibilityTraitSelected } == 0);
assert!(t & unsafe { UIAccessibilityTraitToggleButton } == 0);
assert!(t & toggle_button_trait() == 0);
assert!(t & unsafe { UIAccessibilityTraitUpdatesFrequently } == 0);
assert!(t & unsafe { UIAccessibilityTraitAllowsDirectInteraction } == 0);
}
Expand Down Expand Up @@ -864,7 +864,7 @@ mod tests {
node.set_disabled();
let t = node_traits(&node);
assert!(t & unsafe { UIAccessibilityTraitButton } != 0);
assert!(t & unsafe { UIAccessibilityTraitToggleButton } != 0);
assert!(t & toggle_button_trait() != 0);
assert!(t & unsafe { UIAccessibilityTraitNotEnabled } != 0);
}

Expand Down
50 changes: 48 additions & 2 deletions platforms/ios/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@
use accesskit::Point;
use accesskit_consumer::Node;
use objc2::encode::{Encode, Encoding, RefEncode};
use objc2_foundation::{CGPoint, CGRect, CGSize, NSInteger};
use objc2_ui_kit::{UIAccessibilityConvertFrameToScreenCoordinates, UICoordinateSpace, UIView};
use objc2_foundation::{CGPoint, CGRect, CGSize, NSAttributedStringKey, NSInteger, NSString};
use objc2_ui_kit::{
UIAccessibilityConvertFrameToScreenCoordinates, UIAccessibilityPriority, UIAccessibilityTraits,
UICoordinateSpace, UIView,
};
use std::ffi::{c_char, c_void};
use std::sync::OnceLock;

// TODO: Remove once we update to objc2 0.6
#[repr(transparent)]
Expand Down Expand Up @@ -56,3 +61,44 @@ pub(crate) fn to_cg_rect(view: &UIView, rect: accesskit::Rect) -> CGRect {
};
to_screen_rect(view, local_rect)
}

const RTLD_DEFAULT: *mut c_void = -2isize as *mut c_void;

unsafe extern "C" {
fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
}

pub(crate) fn toggle_button_trait() -> UIAccessibilityTraits {
static TRAIT: OnceLock<UIAccessibilityTraits> = OnceLock::new();
*TRAIT.get_or_init(|| unsafe {
let symbol = dlsym(RTLD_DEFAULT, c"UIAccessibilityTraitToggleButton".as_ptr());
if symbol.is_null() {
0
} else {
*symbol.cast::<UIAccessibilityTraits>()
}
})
}

fn resolve_nsstring_const(symbol: *const c_char) -> Option<&'static NSString> {
unsafe {
let slot = dlsym(RTLD_DEFAULT, symbol);
if slot.is_null() {
None
} else {
(*slot.cast::<*const NSString>()).as_ref()
}
}
}

pub(crate) fn announcement_priority_high() -> Option<&'static UIAccessibilityPriority> {
resolve_nsstring_const(c"UIAccessibilityPriorityHigh".as_ptr())
}

pub(crate) fn announcement_priority_low() -> Option<&'static UIAccessibilityPriority> {
resolve_nsstring_const(c"UIAccessibilityPriorityLow".as_ptr())
}

pub(crate) fn announcement_priority_key() -> Option<&'static NSAttributedStringKey> {
resolve_nsstring_const(c"UIAccessibilitySpeechAttributeAnnouncementPriority".as_ptr())
}