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:
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 useplaceholder-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)
}
```