WIP: Start on accesskit integration

This commit is contained in:
Mikayla 2024-02-20 10:13:57 -08:00
parent 1c361ac579
commit 63e9a7069f
No known key found for this signature in database
9 changed files with 231 additions and 31 deletions

81
Cargo.lock generated
View File

@ -2,6 +2,34 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "accesskit"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cb10ed32c63247e4e39a8f42e8e30fb9442fbf7878c8e4a9849e7e381619bea"
[[package]]
name = "accesskit_consumer"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9846654e84f26a31d269c0a8ea44fd8848319ee9cdf8908de18f51356f61f598"
dependencies = [
"accesskit",
]
[[package]]
name = "accesskit_macos"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee4354e6f1a880a3a368fe6339fb4c1b94ccd2c8a053f6752abb09f4497758b3"
dependencies = [
"accesskit",
"accesskit_consumer",
"icrate",
"objc2",
"once_cell",
]
[[package]]
name = "activity_indicator"
version = "0.1.0"
@ -1457,6 +1485,25 @@ dependencies = [
"generic-array",
]
[[package]]
name = "block-sys"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7"
dependencies = [
"objc-sys",
]
[[package]]
name = "block2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e58aa60e59d8dbfcc36138f5f18be5f24394d33b38b24f7fd0b1caa33095f22f"
dependencies = [
"block-sys",
"objc2",
]
[[package]]
name = "blocking"
version = "1.3.1"
@ -4070,6 +4117,8 @@ dependencies = [
name = "gpui"
version = "0.1.0"
dependencies = [
"accesskit",
"accesskit_macos",
"anyhow",
"as-raw-xcb-connection",
"ashpd",
@ -4500,6 +4549,16 @@ dependencies = [
"cc",
]
[[package]]
name = "icrate"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e286f4b975ac6c054971a0600a9b76438b332edace54bff79c71c9d3adfc9772"
dependencies = [
"block2",
"objc2",
]
[[package]]
name = "idna"
version = "0.4.0"
@ -6101,6 +6160,28 @@ dependencies = [
"objc_exception",
]
[[package]]
name = "objc-sys"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7c71324e4180d0899963fc83d9d241ac39e699609fc1025a850aadac8257459"
[[package]]
name = "objc2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a9c7f0d511a4ce26b078183179dca908171cfc69f88986fe36c5138e1834476"
dependencies = [
"objc-sys",
"objc2-encode",
]
[[package]]
name = "objc2-encode"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ff06a6505cde0766484f38d8479ac8e6d31c66fbc2d5492f65ca8c091456379"
[[package]]
name = "objc_exception"
version = "0.1.2"

View File

@ -68,6 +68,7 @@ usvg = { version = "0.14", features = [] }
util.workspace = true
uuid = { version = "1.1.2", features = ["v4"] }
waker-fn = "1.1.0"
accesskit = { version = "0.12" }
[dev-dependencies]
backtrace = "0.3"
@ -93,6 +94,7 @@ log.workspace = true
media.workspace = true
metal = "0.21.0"
objc = "0.2"
accesskit_macos = "0.11.0"
[target.'cfg(target_os = "linux")'.dependencies]
flume = "0.11"

View File

@ -0,0 +1,58 @@
use std::hash::{Hash, Hasher};
use collections::{hash_map::Entry, HashMap};
use crate::{BorrowWindow, ElementContext, ElementId, GlobalElementId, WindowContext};
pub type AccessKitState = HashMap<accesskit::NodeId, accesskit::NodeBuilder>;
impl From<&GlobalElementId> for accesskit::NodeId {
fn from(value: &GlobalElementId) -> Self {
let mut hasher = std::hash::DefaultHasher::new();
value.0.hash(&mut hasher);
accesskit::NodeId(hasher.finish())
}
}
impl<'a> ElementContext<'a> {
// TODO: What's a good, useful signature for this? Need to expose this from the div as well.
fn accesskit_action(&mut self, id: impl Into<ElementId>, action: accesskit::Action, f: impl FnOnce(accesskit::ActionRequest)) {
self.with_element_id(Some(id), |cx| {
// Get the access kit actions from somewhere
// call f with the action request and cx
// egui impl:
// let accesskit_id = id.accesskit_id();
// self.events.iter().filter_map(move |event| {
// if let Event::AccessKitActionRequest(request) = event {
// if request.target == accesskit_id && request.action == action {
// return Some(request);
// }
// }
// None
// })
})
}
// TODO: Expose this through the div API
fn with_accesskit_node(&mut self, id: impl Into<ElementId>, f: impl FnOnce(&mut accesskit::NodeBuilder)) {
let id = id.into();
let window = self.window_mut();
let parent_id: accesskit::NodeId = (&window.element_id_stack).into();
self.with_element_id(Some(id), |cx| {
let window = cx.window_mut();
let this_id: accesskit::NodeId = (&window.element_id_stack).into();
window.next_frame.accesskit.as_mut().map(|nodes| {
if let Entry::Vacant(entry) = nodes.entry(this_id) {
entry.insert(Default::default());
let parent = nodes.get_mut(&parent_id).unwrap();
parent.push_child(this_id);
}
f(nodes.get_mut(&this_id).unwrap());
})
});
}
}

View File

@ -241,6 +241,7 @@ pub struct AppContext {
pub(crate) quit_observers: SubscriberSet<(), QuitHandler>,
pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests.
pub(crate) propagate_event: bool,
pub(crate) screen_reader_enabled: bool,
}
impl AppContext {
@ -299,6 +300,7 @@ impl AppContext {
quit_observers: SubscriberSet::new(),
layout_id_buffer: Default::default(),
propagate_event: true,
screen_reader_enabled: false,
}),
});
@ -314,6 +316,7 @@ impl AppContext {
app
}
/// Quit the application gracefully. Handlers registered with [`ModelContext::on_app_quit`]
/// will be given 100ms to complete before exiting.
pub fn shutdown(&mut self) {

View File

@ -38,9 +38,10 @@ use crate::{
util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, ElementContext, ElementId, LayoutId,
Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA,
};
use collections::FxHashSet;
use derive_more::{Deref, DerefMut};
pub(crate) use smallvec::SmallVec;
use std::{any::Any, fmt::Debug, ops::DerefMut};
use std::{any::Any, fmt::Debug, hash::{Hash, Hasher, SipHasher}, ops::DerefMut};
/// Implemented by types that participate in laying out and painting the contents of a window.
/// Elements form a tree and are laid out according to web-based layout rules, as implemented by Taffy.
@ -222,7 +223,8 @@ impl<C: RenderOnce> IntoElement for Component<C> {
/// A globally unique identifier for an element, used to track state across frames.
#[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]
pub(crate) struct GlobalElementId(SmallVec<[ElementId; 32]>);
pub(crate) struct GlobalElementId(pub(crate) SmallVec<[ElementId; 32]>);
trait ElementObject {
fn element_id(&self) -> Option<ElementId>;

View File

@ -67,6 +67,7 @@
mod action;
mod app;
mod access_kit;
mod arena;
mod assets;
mod color;

View File

@ -222,6 +222,22 @@ unsafe fn build_classes() {
accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL,
);
// AccesKit integration poitns
decl.add_method(
sel!(accessibilityChildren),
accessibility_children as extern "C" fn(&mut Object, Sel) -> id,
);
decl.add_method(
sel!(accessibilityFocusedUIElement),
accessibility_focused as extern "C" fn(&mut Object, Sel) -> id,
);
decl.add_method(
sel!(accessibilityHitTest:),
accessibility_hit_test as extern "C" fn(&mut Object, Sel, NSPoint) -> id,
);
decl.register()
};
}
@ -343,6 +359,7 @@ struct MacWindowState {
input_during_keydown: Option<SmallVec<[ImeInput; 1]>>,
previous_keydown_inserted_text: Option<String>,
external_files_dragged: bool,
accesskit: Option<accesskit_macos::Adapter>
}
impl MacWindowState {
@ -467,6 +484,30 @@ impl MacWindowState {
msg_send![self.native_window, convertPointToScreen: point]
}
}
fn get_accesskit_adapter(&mut self) -> &accesskit_macos::Adapter {
self.accesskit.get_or_insert_with(|| {
fn handler() -> (accesskit::TreeUpdate, bool) {
todo!()
}
let (initial_state, focused) = handler();// self.handler.accesskit_tree();
struct Handler {}
impl accesskit::ActionHandler for Handler {
fn do_action(&mut self, _action: accesskit::ActionRequest) {
todo!();
}
}
let action_handler = Box::new(Handler {});
// SAFETY: The view pointer is based on a valid borrowed reference
// to the view.
unsafe { accesskit_macos::Adapter::new(self.native_view.as_ptr() as *mut _, initial_state, self.is_active, action_handler) }
})
}
}
unsafe impl Send for MacWindowState {}
@ -541,6 +582,8 @@ impl MacWindow {
let native_view = NSView::init(native_view);
assert!(!native_view.is_null());
// let accesskit_adapter = accesskit_macos::Adapter::new(native_view, accesskit::TreeUpdate { nodes: Default::default(), tree: Default::default(), focus: accesskit::NodeId(0) }, false, action_handler);
let window = Self(Arc::new(Mutex::new(MacWindowState {
handle,
executor,
@ -570,6 +613,7 @@ impl MacWindow {
input_during_keydown: None,
previous_keydown_inserted_text: None,
external_files_dragged: false,
accesskit: None,
})));
(*native_window).set_ivar(
@ -1737,6 +1781,36 @@ extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL {
}
}
extern "C" fn accessibility_children(this: &mut Object, _: Sel) -> id {
unsafe {
let state = get_window_state(this);
let mut lock = state.as_ref().lock();
let adapter = lock.get_accesskit_adapter();
adapter.view_children() as *mut _
}
}
extern "C" fn accessibility_focused(this: &mut Object, _: Sel) -> id {
unsafe {
let state = get_window_state(this);
let mut lock = state.as_ref().lock();
let adapter = lock.get_accesskit_adapter();
adapter.focus() as *mut _
}
}
extern "C" fn accessibility_hit_test(this: &mut Object, _: Sel, point: NSPoint) -> id {
unsafe {
let state = get_window_state(this);
let mut lock = state.as_ref().lock();
let adapter = lock.get_accesskit_adapter();
let point = accesskit_macos::NSPoint {
x: point.x,
y: point.y,
};
adapter.hit_test(point) as *mut _
}
}
extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
let window_state = unsafe { get_window_state(this) };
if send_new_event(&window_state, {

View File

@ -1,13 +1,5 @@
use crate::{
px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext,
AvailableSpace, Bounds, Context, Corners, CursorStyle, DispatchActionListener, DispatchNodeId,
DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten,
Global, GlobalElementId, Hsla, KeyBinding, KeyContext, KeyDownEvent, KeyMatch, KeymatchResult,
Keystroke, KeystrokeEvent, Model, ModelContext, Modifiers, MouseButton, MouseMoveEvent,
MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point,
PromptLevel, Render, ScaledPixels, SharedString, Size, SubscriberSet, Subscription,
TaffyLayoutEngine, Task, View, VisualContext, WeakView, WindowAppearance, WindowBounds,
WindowOptions, WindowTextSystem,
px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext, AvailableSpace, Bounds, Context, Corners, CursorStyle, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, Global, GlobalElementId, Hsla, IntoElement, KeyBinding, KeyContext, KeyDownEvent, KeyMatch, KeymatchResult, Keystroke, KeystrokeEvent, Model, ModelContext, Modifiers, MouseButton, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel, Render, ScaledPixels, SharedString, Size, SubscriberSet, Subscription, TaffyLayoutEngine, Task, View, VisualContext, WeakView, WindowAppearance, WindowBounds, WindowOptions, WindowTextSystem
};
use anyhow::{anyhow, Context as _, Result};
use collections::FxHashSet;
@ -17,20 +9,10 @@ use parking_lot::RwLock;
use slotmap::SlotMap;
use smallvec::SmallVec;
use std::{
any::{Any, TypeId},
borrow::{Borrow, BorrowMut},
cell::{Cell, RefCell},
fmt::{Debug, Display},
future::Future,
hash::{Hash, Hasher},
marker::PhantomData,
mem,
rc::Rc,
sync::{
any::{Any, TypeId}, borrow::{Borrow, BorrowMut}, cell::{Cell, RefCell}, collections::HashMap, fmt::{Debug, Display}, future::Future, hash::{Hash, Hasher}, marker::PhantomData, mem, rc::Rc, sync::{
atomic::{AtomicUsize, Ordering::SeqCst},
Arc,
},
time::{Duration, Instant},
}, time::{Duration, Instant}
};
use util::{measure, ResultExt};
@ -950,6 +932,7 @@ impl<'a> WindowContext<'a> {
}
let root_view = self.window.root_view.take().unwrap();
self.with_element_context(|cx| {
cx.with_z_index(0, |cx| {
cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| {

View File

@ -29,14 +29,7 @@ use smallvec::SmallVec;
use util::post_inc;
use crate::{
prelude::*, size, AnyTooltip, AppContext, AvailableSpace, Bounds, BoxShadow, ContentMask,
Corners, CursorStyle, DevicePixels, DispatchPhase, DispatchTree, ElementId, ElementStateBox,
EntityId, FocusHandle, FocusId, FontId, GlobalElementId, GlyphId, Hsla, ImageData,
InputHandler, IsZero, KeyContext, KeyEvent, LayoutId, MonochromeSprite, MouseEvent, PaintQuad,
Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad, RenderGlyphParams,
RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size, StackingContext,
StackingOrder, StrikethroughStyle, Style, TextStyleRefinement, Underline, UnderlineStyle,
Window, WindowContext, SUBPIXEL_VARIANTS,
access_kit::AccessKitState, prelude::*, size, AnyTooltip, AppContext, AvailableSpace, Bounds, BoxShadow, ContentMask, Corners, CursorStyle, DevicePixels, DispatchPhase, DispatchTree, ElementId, ElementStateBox, EntityId, FocusHandle, FocusId, FontId, GlobalElementId, GlyphId, Hsla, ImageData, InputHandler, IsZero, KeyContext, KeyEvent, LayoutId, MonochromeSprite, MouseEvent, PaintQuad, Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad, RenderGlyphParams, RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size, StackingContext, StackingOrder, StrikethroughStyle, Style, TextStyleRefinement, Underline, UnderlineStyle, Window, WindowContext, SUBPIXEL_VARIANTS
};
type AnyMouseListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut ElementContext) + 'static>;
@ -70,6 +63,7 @@ pub(crate) struct Frame {
pub(crate) requested_cursor_style: Option<CursorStyle>,
pub(crate) view_stack: Vec<EntityId>,
pub(crate) reused_views: FxHashSet<EntityId>,
pub(crate) accesskit: Option<AccessKitState>,
#[cfg(any(test, feature = "test-support"))]
pub(crate) debug_bounds: FxHashMap<String, Bounds<Pixels>>,
@ -96,6 +90,7 @@ impl Frame {
requested_cursor_style: None,
view_stack: Vec::new(),
reused_views: FxHashSet::default(),
accesskit: None,
#[cfg(any(test, feature = "test-support"))]
debug_bounds: FxHashMap::default(),
@ -386,6 +381,7 @@ impl<'a> ElementContext<'a> {
}
}
/// Updates the cursor style at the platform level.
pub fn set_cursor_style(&mut self, style: CursorStyle) {
let view_id = self.parent_view_id();