Files

365 lines
11 KiB
Rust

// Based on the create_window sample in windows-samples-rs.
use accesskit::{
Action, ActionHandler, ActionRequest, ActivationHandler, Live, Node, NodeId, Rect, Role, Tree,
TreeUpdate,
};
use accesskit_windows::Adapter;
use once_cell::sync::Lazy;
use std::cell::RefCell;
use windows::{
core::*,
Win32::{
Foundation::*,
Graphics::Gdi::ValidateRect,
System::LibraryLoader::GetModuleHandleW,
UI::{Input::KeyboardAndMouse::*, WindowsAndMessaging::*},
},
};
static WINDOW_CLASS_ATOM: Lazy<u16> = Lazy::new(|| {
let class_name = w!("AccessKitTest");
let wc = WNDCLASSW {
hCursor: unsafe { LoadCursorW(None, IDC_ARROW) }.unwrap(),
hInstance: unsafe { GetModuleHandleW(None) }.unwrap().into(),
lpszClassName: class_name,
style: CS_HREDRAW | CS_VREDRAW,
lpfnWndProc: Some(wndproc),
..Default::default()
};
let atom = unsafe { RegisterClassW(&wc) };
if atom == 0 {
panic!("{}", Error::from_win32());
}
atom
});
const WINDOW_TITLE: &str = "Hello world";
const WINDOW_ID: NodeId = NodeId(0);
const BUTTON_1_ID: NodeId = NodeId(1);
const BUTTON_2_ID: NodeId = NodeId(2);
const ANNOUNCEMENT_ID: NodeId = NodeId(3);
const INITIAL_FOCUS: NodeId = BUTTON_1_ID;
const BUTTON_1_RECT: Rect = Rect {
x0: 20.0,
y0: 20.0,
x1: 100.0,
y1: 60.0,
};
const BUTTON_2_RECT: Rect = Rect {
x0: 20.0,
y0: 60.0,
x1: 100.0,
y1: 100.0,
};
const SET_FOCUS_MSG: u32 = WM_USER;
const CLICK_MSG: u32 = WM_USER + 1;
fn build_button(id: NodeId, label: &str) -> Node {
let rect = match id {
BUTTON_1_ID => BUTTON_1_RECT,
BUTTON_2_ID => BUTTON_2_RECT,
_ => unreachable!(),
};
let mut node = Node::new(Role::Button);
node.set_bounds(rect);
node.set_label(label);
node.add_action(Action::Focus);
node.add_action(Action::Click);
node
}
fn build_announcement(text: &str) -> Node {
let mut node = Node::new(Role::Label);
node.set_value(text);
node.set_live(Live::Polite);
node
}
struct InnerWindowState {
focus: NodeId,
announcement: Option<String>,
}
impl InnerWindowState {
fn build_root(&mut self) -> Node {
let mut node = Node::new(Role::Window);
node.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]);
if self.announcement.is_some() {
node.push_child(ANNOUNCEMENT_ID);
}
node
}
}
impl ActivationHandler for InnerWindowState {
fn request_initial_tree(&mut self) -> Option<TreeUpdate> {
println!("Initial tree requested");
let root = self.build_root();
let button_1 = build_button(BUTTON_1_ID, "Button 1");
let button_2 = build_button(BUTTON_2_ID, "Button 2");
let tree = Tree::new(WINDOW_ID);
let mut result = TreeUpdate {
nodes: vec![
(WINDOW_ID, root),
(BUTTON_1_ID, button_1),
(BUTTON_2_ID, button_2),
],
tree: Some(tree),
focus: self.focus,
};
if let Some(announcement) = &self.announcement {
result
.nodes
.push((ANNOUNCEMENT_ID, build_announcement(announcement)));
}
Some(result)
}
}
struct WindowState {
adapter: RefCell<Adapter>,
inner_state: RefCell<InnerWindowState>,
}
impl WindowState {
fn set_focus(&self, focus: NodeId) {
self.inner_state.borrow_mut().focus = focus;
let mut adapter = self.adapter.borrow_mut();
if let Some(events) = adapter.update_if_active(|| TreeUpdate {
nodes: vec![],
tree: None,
focus,
}) {
drop(adapter);
events.raise();
}
}
fn press_button(&self, id: NodeId) {
let mut inner_state = self.inner_state.borrow_mut();
let text = if id == BUTTON_1_ID {
"You pressed button 1"
} else {
"You pressed button 2"
};
inner_state.announcement = Some(text.into());
let mut adapter = self.adapter.borrow_mut();
if let Some(events) = adapter.update_if_active(|| {
let announcement = build_announcement(text);
let root = inner_state.build_root();
TreeUpdate {
nodes: vec![(ANNOUNCEMENT_ID, announcement), (WINDOW_ID, root)],
tree: None,
focus: inner_state.focus,
}
}) {
drop(adapter);
drop(inner_state);
events.raise();
}
}
}
unsafe fn get_window_state(window: HWND) -> *const WindowState {
GetWindowLongPtrW(window, GWLP_USERDATA) as _
}
fn update_window_focus_state(window: HWND, is_focused: bool) {
let state = unsafe { &*get_window_state(window) };
let mut adapter = state.adapter.borrow_mut();
if let Some(events) = adapter.update_window_focus_state(is_focused) {
drop(adapter);
events.raise();
}
}
struct WindowCreateParams(NodeId);
struct SimpleActionHandler {
window: HWND,
}
unsafe impl Send for SimpleActionHandler {}
unsafe impl Sync for SimpleActionHandler {}
impl ActionHandler for SimpleActionHandler {
fn do_action(&mut self, request: ActionRequest) {
match request.action {
Action::Focus => {
unsafe {
PostMessageW(
self.window,
SET_FOCUS_MSG,
WPARAM(0),
LPARAM(request.target.0 as _),
)
}
.unwrap();
}
Action::Click => {
unsafe {
PostMessageW(
self.window,
CLICK_MSG,
WPARAM(0),
LPARAM(request.target.0 as _),
)
}
.unwrap();
}
_ => (),
}
}
}
extern "system" fn wndproc(window: HWND, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
match message {
WM_NCCREATE => {
let create_struct: &CREATESTRUCTW = unsafe { &mut *(lparam.0 as *mut _) };
let create_params: Box<WindowCreateParams> =
unsafe { Box::from_raw(create_struct.lpCreateParams as _) };
let WindowCreateParams(initial_focus) = *create_params;
let inner_state = RefCell::new(InnerWindowState {
focus: initial_focus,
announcement: None,
});
let adapter = Adapter::new(window, false, SimpleActionHandler { window });
let state = Box::new(WindowState {
adapter: RefCell::new(adapter),
inner_state,
});
unsafe { SetWindowLongPtrW(window, GWLP_USERDATA, Box::into_raw(state) as _) };
unsafe { DefWindowProcW(window, message, wparam, lparam) }
}
WM_PAINT => {
unsafe { ValidateRect(window, None) }.unwrap();
LRESULT(0)
}
WM_DESTROY => {
let ptr = unsafe { SetWindowLongPtrW(window, GWLP_USERDATA, 0) };
if ptr != 0 {
drop(unsafe { Box::<WindowState>::from_raw(ptr as _) });
}
unsafe { PostQuitMessage(0) };
LRESULT(0)
}
WM_GETOBJECT => {
let state_ptr = unsafe { get_window_state(window) };
if state_ptr.is_null() {
// We need to be prepared to gracefully handle WM_GETOBJECT
// while the window is being destroyed; this can happen if
// the thread is using a COM STA.
return unsafe { DefWindowProcW(window, message, wparam, lparam) };
}
let state = unsafe { &*state_ptr };
let mut adapter = state.adapter.borrow_mut();
let mut inner_state = state.inner_state.borrow_mut();
let result = adapter.handle_wm_getobject(wparam, lparam, &mut *inner_state);
drop(inner_state);
drop(adapter);
result.map_or_else(
|| unsafe { DefWindowProcW(window, message, wparam, lparam) },
|result| result.into(),
)
}
WM_SETFOCUS | WM_EXITMENULOOP | WM_EXITSIZEMOVE => {
update_window_focus_state(window, true);
LRESULT(0)
}
WM_KILLFOCUS | WM_ENTERMENULOOP | WM_ENTERSIZEMOVE => {
update_window_focus_state(window, false);
LRESULT(0)
}
WM_KEYDOWN => match VIRTUAL_KEY(wparam.0 as u16) {
VK_TAB => {
let state = unsafe { &*get_window_state(window) };
let old_focus = state.inner_state.borrow().focus;
let new_focus = if old_focus == BUTTON_1_ID {
BUTTON_2_ID
} else {
BUTTON_1_ID
};
state.set_focus(new_focus);
LRESULT(0)
}
VK_SPACE => {
let state = unsafe { &*get_window_state(window) };
let id = state.inner_state.borrow().focus;
state.press_button(id);
LRESULT(0)
}
_ => unsafe { DefWindowProcW(window, message, wparam, lparam) },
},
SET_FOCUS_MSG => {
let id = NodeId(lparam.0 as _);
if id == BUTTON_1_ID || id == BUTTON_2_ID {
let state = unsafe { &*get_window_state(window) };
state.set_focus(id);
}
LRESULT(0)
}
CLICK_MSG => {
let id = NodeId(lparam.0 as _);
if id == BUTTON_1_ID || id == BUTTON_2_ID {
let state = unsafe { &*get_window_state(window) };
state.press_button(id);
}
LRESULT(0)
}
_ => unsafe { DefWindowProcW(window, message, wparam, lparam) },
}
}
fn create_window(title: &str, initial_focus: NodeId) -> Result<HWND> {
let create_params = Box::new(WindowCreateParams(initial_focus));
let window = unsafe {
CreateWindowExW(
Default::default(),
PCWSTR(*WINDOW_CLASS_ATOM as usize as _),
&HSTRING::from(title),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
None,
None,
GetModuleHandleW(None).unwrap(),
Some(Box::into_raw(create_params) as _),
)?
};
if window.is_invalid() {
return Err(Error::from_win32());
}
Ok(window)
}
fn main() -> Result<()> {
println!("This example has no visible GUI, and a keyboard interface:");
println!("- [Tab] switches focus between two logical buttons.");
println!("- [Space] 'presses' the button, adding static text in a live region announcing that it was pressed.");
println!("Enable Narrator with [Win]+[Ctrl]+[Enter] (or [Win]+[Enter] on older versions of Windows).");
let window = create_window(WINDOW_TITLE, INITIAL_FOCUS)?;
let _ = unsafe { ShowWindow(window, SW_SHOW) };
let mut message = MSG::default();
while unsafe { GetMessageW(&mut message, HWND::default(), 0, 0) }.into() {
let _ = unsafe { TranslateMessage(&message) };
unsafe { DispatchMessageW(&message) };
}
Ok(())
}