Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Mikayla
63e9a7069f
WIP: Start on accesskit integration 2024-02-20 10:58:25 -08:00
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. # It is not intended for manual editing.
version = 3 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]] [[package]]
name = "activity_indicator" name = "activity_indicator"
version = "0.1.0" version = "0.1.0"
@ -1457,6 +1485,25 @@ dependencies = [
"generic-array", "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]] [[package]]
name = "blocking" name = "blocking"
version = "1.3.1" version = "1.3.1"
@ -4070,6 +4117,8 @@ dependencies = [
name = "gpui" name = "gpui"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"accesskit",
"accesskit_macos",
"anyhow", "anyhow",
"as-raw-xcb-connection", "as-raw-xcb-connection",
"ashpd", "ashpd",
@ -4500,6 +4549,16 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "icrate"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e286f4b975ac6c054971a0600a9b76438b332edace54bff79c71c9d3adfc9772"
dependencies = [
"block2",
"objc2",
]
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.4.0" version = "0.4.0"
@ -6101,6 +6160,28 @@ dependencies = [
"objc_exception", "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]] [[package]]
name = "objc_exception" name = "objc_exception"
version = "0.1.2" version = "0.1.2"

View file

@ -68,6 +68,7 @@ usvg = { version = "0.14", features = [] }
util.workspace = true util.workspace = true
uuid = { version = "1.1.2", features = ["v4"] } uuid = { version = "1.1.2", features = ["v4"] }
waker-fn = "1.1.0" waker-fn = "1.1.0"
accesskit = { version = "0.12" }
[dev-dependencies] [dev-dependencies]
backtrace = "0.3" backtrace = "0.3"
@ -93,6 +94,7 @@ log.workspace = true
media.workspace = true media.workspace = true
metal = "0.21.0" metal = "0.21.0"
objc = "0.2" objc = "0.2"
accesskit_macos = "0.11.0"
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
flume = "0.11" 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) quit_observers: SubscriberSet<(), QuitHandler>,
pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests. pub(crate) layout_id_buffer: Vec<LayoutId>, // We recycle this memory across layout requests.
pub(crate) propagate_event: bool, pub(crate) propagate_event: bool,
pub(crate) screen_reader_enabled: bool,
} }
impl AppContext { impl AppContext {
@ -299,6 +300,7 @@ impl AppContext {
quit_observers: SubscriberSet::new(), quit_observers: SubscriberSet::new(),
layout_id_buffer: Default::default(), layout_id_buffer: Default::default(),
propagate_event: true, propagate_event: true,
screen_reader_enabled: false,
}), }),
}); });
@ -314,6 +316,7 @@ impl AppContext {
app app
} }
/// Quit the application gracefully. Handlers registered with [`ModelContext::on_app_quit`] /// Quit the application gracefully. Handlers registered with [`ModelContext::on_app_quit`]
/// will be given 100ms to complete before exiting. /// will be given 100ms to complete before exiting.
pub fn shutdown(&mut self) { pub fn shutdown(&mut self) {

View file

@ -38,9 +38,10 @@ use crate::{
util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, ElementContext, ElementId, LayoutId, util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, ElementContext, ElementId, LayoutId,
Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA, Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA,
}; };
use collections::FxHashSet;
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
pub(crate) use smallvec::SmallVec; 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. /// 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. /// 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. /// A globally unique identifier for an element, used to track state across frames.
#[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)] #[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 { trait ElementObject {
fn element_id(&self) -> Option<ElementId>; fn element_id(&self) -> Option<ElementId>;

View file

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

View file

@ -222,6 +222,22 @@ unsafe fn build_classes() {
accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL, 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() decl.register()
}; };
} }
@ -343,6 +359,7 @@ struct MacWindowState {
input_during_keydown: Option<SmallVec<[ImeInput; 1]>>, input_during_keydown: Option<SmallVec<[ImeInput; 1]>>,
previous_keydown_inserted_text: Option<String>, previous_keydown_inserted_text: Option<String>,
external_files_dragged: bool, external_files_dragged: bool,
accesskit: Option<accesskit_macos::Adapter>
} }
impl MacWindowState { impl MacWindowState {
@ -467,6 +484,30 @@ impl MacWindowState {
msg_send![self.native_window, convertPointToScreen: point] 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 {} unsafe impl Send for MacWindowState {}
@ -541,6 +582,8 @@ impl MacWindow {
let native_view = NSView::init(native_view); let native_view = NSView::init(native_view);
assert!(!native_view.is_null()); 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 { let window = Self(Arc::new(Mutex::new(MacWindowState {
handle, handle,
executor, executor,
@ -570,6 +613,7 @@ impl MacWindow {
input_during_keydown: None, input_during_keydown: None,
previous_keydown_inserted_text: None, previous_keydown_inserted_text: None,
external_files_dragged: false, external_files_dragged: false,
accesskit: None,
}))); })));
(*native_window).set_ivar( (*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 { extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
let window_state = unsafe { get_window_state(this) }; let window_state = unsafe { get_window_state(this) };
if send_new_event(&window_state, { if send_new_event(&window_state, {

View file

@ -1,13 +1,5 @@
use crate::{ use crate::{
px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext, 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
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,
}; };
use anyhow::{anyhow, Context as _, Result}; use anyhow::{anyhow, Context as _, Result};
use collections::FxHashSet; use collections::FxHashSet;
@ -17,20 +9,10 @@ use parking_lot::RwLock;
use slotmap::SlotMap; use slotmap::SlotMap;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{ use std::{
any::{Any, TypeId}, 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::{
borrow::{Borrow, BorrowMut},
cell::{Cell, RefCell},
fmt::{Debug, Display},
future::Future,
hash::{Hash, Hasher},
marker::PhantomData,
mem,
rc::Rc,
sync::{
atomic::{AtomicUsize, Ordering::SeqCst}, atomic::{AtomicUsize, Ordering::SeqCst},
Arc, Arc,
}, }, time::{Duration, Instant}
time::{Duration, Instant},
}; };
use util::{measure, ResultExt}; use util::{measure, ResultExt};
@ -950,6 +932,7 @@ impl<'a> WindowContext<'a> {
} }
let root_view = self.window.root_view.take().unwrap(); let root_view = self.window.root_view.take().unwrap();
self.with_element_context(|cx| { self.with_element_context(|cx| {
cx.with_z_index(0, |cx| { cx.with_z_index(0, |cx| {
cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| { cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| {

View file

@ -29,14 +29,7 @@ use smallvec::SmallVec;
use util::post_inc; use util::post_inc;
use crate::{ use crate::{
prelude::*, size, AnyTooltip, AppContext, AvailableSpace, Bounds, BoxShadow, ContentMask, 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
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>; 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) requested_cursor_style: Option<CursorStyle>,
pub(crate) view_stack: Vec<EntityId>, pub(crate) view_stack: Vec<EntityId>,
pub(crate) reused_views: FxHashSet<EntityId>, pub(crate) reused_views: FxHashSet<EntityId>,
pub(crate) accesskit: Option<AccessKitState>,
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub(crate) debug_bounds: FxHashMap<String, Bounds<Pixels>>, pub(crate) debug_bounds: FxHashMap<String, Bounds<Pixels>>,
@ -96,6 +90,7 @@ impl Frame {
requested_cursor_style: None, requested_cursor_style: None,
view_stack: Vec::new(), view_stack: Vec::new(),
reused_views: FxHashSet::default(), reused_views: FxHashSet::default(),
accesskit: None,
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
debug_bounds: FxHashMap::default(), debug_bounds: FxHashMap::default(),
@ -386,6 +381,7 @@ impl<'a> ElementContext<'a> {
} }
} }
/// Updates the cursor style at the platform level. /// Updates the cursor style at the platform level.
pub fn set_cursor_style(&mut self, style: CursorStyle) { pub fn set_cursor_style(&mut self, style: CursorStyle) {
let view_id = self.parent_view_id(); let view_id = self.parent_view_id();