AccessKit / accesskit

Accessibility infrastructure for UI toolkits
https://accesskit.dev
BSD 3-Clause "New" or "Revised" License
1.05k stars 53 forks source link

feat: Expose the `placeholder` property #417

Closed DataTriny closed 4 months ago

DataTriny commented 4 months ago

The only notable difference with the Chromium implementation is the name of the attribute holding the value on Unix. Chromium uses placeholder as it is common with IAccessible2. I chose to use placeholder-text as that is what Orca checks first, and that is also what GTK uses.

Also worth mentioning that the AT-SPI "specification" says that we should fire attribute-changed events, but Chromium doesn't do it and I think it would be costly to rebuild the hashmap everytime once we'll start adding more attributes.

These changes were tested on all platforms with the example below:

platforms/winit/examples/placeholder.rs

```rust use accesskit::{ Action, DefaultActionVerb, Node, NodeBuilder, NodeId, Rect, Role, Tree, TreeUpdate, }; use accesskit_winit::{Adapter, Event as AccessKitEvent, WindowEvent as AccessKitWindowEvent}; use std::error::Error; use winit::{ application::ApplicationHandler, event::WindowEvent, event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy}, window::{Window, WindowId}, }; const WINDOW_TITLE: &str = "Placeholder test"; const WINDOW_ID: NodeId = NodeId(0); const TEXT_ENTRY_ID: NodeId = NodeId(1); const INITIAL_FOCUS: NodeId = TEXT_ENTRY_ID; const TEXT_ENTRY_RECT: Rect = Rect { x0: 20.0, y0: 20.0, x1: 200.0, y1: 60.0, }; fn build_text_entry(name: &str, placeholder: &str) -> Node { let mut builder = NodeBuilder::new(Role::TextInput); builder.set_bounds(TEXT_ENTRY_RECT); builder.set_name(name); builder.set_placeholder(placeholder); builder.add_action(Action::Focus); builder.set_default_action_verb(DefaultActionVerb::Click); builder.build() } struct UiState { focus: NodeId, } impl UiState { fn new() -> Self { Self { focus: INITIAL_FOCUS, } } fn build_root(&mut self) -> Node { let mut builder = NodeBuilder::new(Role::Window); builder.push_child(TEXT_ENTRY_ID); builder.set_name(WINDOW_TITLE); builder.build() } fn build_initial_tree(&mut self) -> TreeUpdate { let root = self.build_root(); let text_entry = build_text_entry("Enter your name:", "John Smith"); let mut tree = Tree::new(WINDOW_ID); tree.app_name = Some("placeholder_example".to_string()); TreeUpdate { nodes: vec![(WINDOW_ID, root), (TEXT_ENTRY_ID, text_entry)], tree: Some(tree), focus: self.focus, } } } struct WindowState { window: Window, adapter: Adapter, ui: UiState, } impl WindowState { fn new(window: Window, adapter: Adapter, ui: UiState) -> Self { Self { window, adapter, ui, } } } struct Application { event_loop_proxy: EventLoopProxy, window: Option, } impl Application { fn new(event_loop_proxy: EventLoopProxy) -> Self { Self { event_loop_proxy, window: None, } } fn create_window(&mut self, event_loop: &ActiveEventLoop) -> Result<(), Box> { let window_attributes = Window::default_attributes() .with_title(WINDOW_TITLE) .with_visible(false); let window = event_loop.create_window(window_attributes)?; let adapter = Adapter::with_event_loop_proxy(&window, self.event_loop_proxy.clone()); window.set_visible(true); self.window = Some(WindowState::new(window, adapter, UiState::new())); Ok(()) } } impl ApplicationHandler for Application { fn window_event(&mut self, _: &ActiveEventLoop, _: WindowId, event: WindowEvent) { let window = match &mut self.window { Some(window) => window, None => return, }; let adapter = &mut window.adapter; adapter.process_event(&window.window, &event); match event { WindowEvent::CloseRequested => { self.window = None; } _ => (), } } fn user_event(&mut self, _: &ActiveEventLoop, user_event: AccessKitEvent) { let window = match &mut self.window { Some(window) => window, None => return, }; let adapter = &mut window.adapter; let state = &mut window.ui; match user_event.window_event { AccessKitWindowEvent::InitialTreeRequested => { adapter.update_if_active(|| state.build_initial_tree()); } _ => (), } } fn resumed(&mut self, event_loop: &ActiveEventLoop) { self.create_window(event_loop) .expect("failed to create initial window"); } fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { if self.window.is_none() { event_loop.exit(); } } } fn main() -> Result<(), Box> { let event_loop = EventLoop::with_user_event().build()?; let mut state = Application::new(event_loop.create_proxy()); event_loop.run_app(&mut state).map_err(Into::into) } ```