diff --git a/bindings/c/examples/sdl/hello_world.c b/bindings/c/examples/sdl/hello_world.c index b1431b53..8406cc3c 100644 --- a/bindings/c/examples/sdl/hello_world.c +++ b/bindings/c/examples/sdl/hello_world.c @@ -76,8 +76,8 @@ void accesskit_sdl_adapter_init(struct accesskit_sdl_adapter *adapter, adapter->adapter = accesskit_macos_subclassing_adapter_for_window( (void *)wmInfo.info.cocoa.window, source, source_userdata, handler); #elif defined(UNIX) - adapter->adapter = accesskit_unix_adapter_new(app_name, "SDL", "2.0", source, - source_userdata, handler); + adapter->adapter = accesskit_unix_adapter_new( + app_name, "SDL", "2.0", source, source_userdata, false, handler); #elif defined(_WIN32) SDL_SysWMinfo wmInfo; SDL_VERSION(&wmInfo.version); @@ -145,6 +145,24 @@ void accesskit_sdl_adapter_update_if_active( #endif } +void accesskit_sdl_adapter_update_window_focus_state( + const struct accesskit_sdl_adapter *adapter, bool is_focused) { +#if defined(__APPLE__) + accesskit_macos_queued_events *events = + accesskit_macos_subclassing_adapter_update_view_focus_state( + adapter->adapter, is_focused); + if (events != NULL) { + accesskit_macos_queued_events_raise(events); + } +#elif defined(UNIX) + if (adapter->adapter != NULL) { + accesskit_unix_adapter_update_window_focus_state(adapter->adapter, + is_focused); + } +#endif + /* On Windows, the subclassing adapter takes care of this. */ +} + void accesskit_sdl_adapter_update_root_window_bounds( const struct accesskit_sdl_adapter *adapter, SDL_Window *window) { #if defined(UNIX) @@ -165,7 +183,6 @@ void accesskit_sdl_adapter_update_root_window_bounds( struct window_state { accesskit_node_id focus; - bool is_window_focused; const char *announcement; accesskit_node_class_set *node_classes; SDL_mutex *mutex; @@ -173,7 +190,6 @@ struct window_state { void window_state_init(struct window_state *state) { state->focus = INITIAL_FOCUS; - state->is_window_focused = false; state->announcement = NULL; state->node_classes = accesskit_node_class_set_new(); state->mutex = SDL_CreateMutex(); @@ -192,13 +208,6 @@ void window_state_unlock(struct window_state *state) { SDL_UnlockMutex(state->mutex); } -void window_state_set_tree_update_focus(const struct window_state *state, - accesskit_tree_update *update) { - if (state->is_window_focused) { - accesskit_tree_update_set_focus(update, state->focus); - } -} - accesskit_node *window_state_build_root(const struct window_state *state) { accesskit_node_builder *builder = accesskit_node_builder_new(ACCESSKIT_ROLE_WINDOW); @@ -218,10 +227,9 @@ accesskit_tree_update *window_state_build_initial_tree( build_button(BUTTON_1_ID, "Button 1", state->node_classes); accesskit_node *button_2 = build_button(BUTTON_2_ID, "Button 2", state->node_classes); - accesskit_tree_update *result = accesskit_tree_update_with_capacity( - (state->announcement != NULL) ? 4 : 3); + accesskit_tree_update *result = accesskit_tree_update_with_capacity_and_focus( + (state->announcement != NULL) ? 4 : 3, state->focus); accesskit_tree_update_set_tree(result, accesskit_tree_new(WINDOW_ID)); - window_state_set_tree_update_focus(state, result); accesskit_tree_update_push_node(result, WINDOW_ID, root); accesskit_tree_update_push_node(result, BUTTON_1_ID, button_1); accesskit_tree_update_push_node(result, BUTTON_2_ID, button_2); @@ -238,10 +246,10 @@ accesskit_tree_update *build_tree_update_for_button_press(void *userdata) { accesskit_node *announcement = build_announcement(state->announcement, state->node_classes); accesskit_node *root = window_state_build_root(state); - accesskit_tree_update *update = accesskit_tree_update_with_capacity(2); + accesskit_tree_update *update = + accesskit_tree_update_with_capacity_and_focus(2, state->focus); accesskit_tree_update_push_node(update, ANNOUNCEMENT_ID, announcement); accesskit_tree_update_push_node(update, WINDOW_ID, root); - accesskit_tree_update_set_focus(update, state->focus); return update; } @@ -261,13 +269,15 @@ void window_state_press_button(struct window_state *state, accesskit_tree_update *build_tree_update_for_focus_update(void *userdata) { struct window_state *state = userdata; - accesskit_tree_update *update = accesskit_tree_update_new(); - accesskit_tree_update_set_focus(update, state->focus); + accesskit_tree_update *update = + accesskit_tree_update_with_focus(state->focus); return update; } -void window_state_update_focus(struct window_state *state, - const struct accesskit_sdl_adapter *adapter) { +void window_state_set_focus(struct window_state *state, + const struct accesskit_sdl_adapter *adapter, + accesskit_node_id focus) { + state->focus = focus; accesskit_sdl_adapter_update_if_active( adapter, build_tree_update_for_focus_update, state); } @@ -345,17 +355,10 @@ int main(int argc, char *argv[]) { event.window.windowID == window_id) { switch (event.window.event) { case SDL_WINDOWEVENT_FOCUS_GAINED: - window_state_lock(&state); - state.is_window_focused = true; - window_state_update_focus(&state, &adapter); - window_state_unlock(&state); - continue; + accesskit_sdl_adapter_update_window_focus_state(&adapter, true); break; case SDL_WINDOWEVENT_FOCUS_LOST: - window_state_lock(&state); - state.is_window_focused = false; - window_state_update_focus(&state, &adapter); - window_state_unlock(&state); + accesskit_sdl_adapter_update_window_focus_state(&adapter, false); break; case SDL_WINDOWEVENT_MAXIMIZED: case SDL_WINDOWEVENT_MOVED: @@ -370,12 +373,9 @@ int main(int argc, char *argv[]) { switch (event.key.keysym.sym) { case SDLK_TAB: window_state_lock(&state); - if (state.focus == BUTTON_1_ID) { - state.focus = BUTTON_2_ID; - } else { - state.focus = BUTTON_1_ID; - } - window_state_update_focus(&state, &adapter); + accesskit_node_id new_focus = + (state.focus == BUTTON_1_ID) ? BUTTON_2_ID : BUTTON_1_ID; + window_state_set_focus(&state, &adapter, new_focus); window_state_unlock(&state); break; case SDLK_SPACE: @@ -391,8 +391,7 @@ int main(int argc, char *argv[]) { if (target == BUTTON_1_ID || target == BUTTON_2_ID) { window_state_lock(&state); if (event.user.code == SET_FOCUS_MSG) { - state.focus = target; - window_state_update_focus(&state, &adapter); + window_state_set_focus(&state, &adapter, target); } else if (event.user.code == DO_DEFAULT_ACTION_MSG) { window_state_press_button(&state, &adapter, target); } diff --git a/bindings/c/examples/windows/hello_world.c b/bindings/c/examples/windows/hello_world.c index c79386f9..d76a2ff0 100644 --- a/bindings/c/examples/windows/hello_world.c +++ b/bindings/c/examples/windows/hello_world.c @@ -68,13 +68,6 @@ void window_state_free(struct window_state *state) { free(state); } -void window_state_set_tree_update_focus(struct window_state *state, - accesskit_tree_update *update) { - if (state->is_window_focused) { - accesskit_tree_update_set_focus(update, state->focus); - } -} - accesskit_node *window_state_build_root(struct window_state *state) { accesskit_node_builder *builder = accesskit_node_builder_new(ACCESSKIT_ROLE_WINDOW); @@ -93,10 +86,9 @@ accesskit_tree_update *window_state_build_initial_tree( build_button(BUTTON_1_ID, "Button 1", state->node_classes); accesskit_node *button_2 = build_button(BUTTON_2_ID, "Button 2", state->node_classes); - accesskit_tree_update *result = accesskit_tree_update_with_capacity( - (state->announcement != NULL) ? 4 : 3); + accesskit_tree_update *result = accesskit_tree_update_with_capacity_and_focus( + (state->announcement != NULL) ? 4 : 3, state->focus); accesskit_tree_update_set_tree(result, accesskit_tree_new(WINDOW_ID)); - window_state_set_tree_update_focus(state, result); accesskit_tree_update_push_node(result, WINDOW_ID, root); accesskit_tree_update_push_node(result, BUTTON_1_ID, button_1); accesskit_tree_update_push_node(result, BUTTON_2_ID, button_2); @@ -129,12 +121,24 @@ accesskit_windows_adapter *window_state_get_or_init_accesskit_adapter( accesskit_action_handler *action_handler = accesskit_action_handler_new(do_action, (void *)window); state->adapter = accesskit_windows_adapter_new( - window, initial_tree, action_handler, state->uia_init_marker); + window, initial_tree, state->is_window_focused, action_handler, + state->uia_init_marker); state->uia_init_marker = NULL; return state->adapter; } } +void window_state_set_focus(struct window_state *state, + accesskit_node_id focus) { + state->focus = focus; + if (state->adapter != NULL) { + accesskit_tree_update *update = accesskit_tree_update_with_focus(focus); + accesskit_windows_queued_events *events = + accesskit_windows_adapter_update(state->adapter, update); + accesskit_windows_queued_events_raise(events); + } +} + void window_state_press_button(struct window_state *state, accesskit_node_id id) { const char *text; @@ -148,10 +152,10 @@ void window_state_press_button(struct window_state *state, accesskit_node *announcement = build_announcement(text, state->node_classes); accesskit_node *root = window_state_build_root(state); - accesskit_tree_update *update = accesskit_tree_update_with_capacity(2); + accesskit_tree_update *update = + accesskit_tree_update_with_capacity_and_focus(2, state->focus); accesskit_tree_update_push_node(update, ANNOUNCEMENT_ID, announcement); accesskit_tree_update_push_node(update, WINDOW_ID, root); - window_state_set_tree_update_focus(state, update); accesskit_windows_queued_events *events = accesskit_windows_adapter_update(state->adapter, update); accesskit_windows_queued_events_raise(events); @@ -162,14 +166,13 @@ struct window_state *get_window_state(HWND window) { return (struct window_state *)(GetWindowLongPtr(window, GWLP_USERDATA)); } -void update_focus(HWND window, bool is_window_focused) { +void update_window_focus_state(HWND window, bool is_focused) { struct window_state *state = get_window_state(window); - state->is_window_focused = is_window_focused; + state->is_window_focused = is_focused; if (state->adapter != NULL) { - accesskit_tree_update *update = accesskit_tree_update_new(); - window_state_set_tree_update_focus(state, update); accesskit_windows_queued_events *events = - accesskit_windows_adapter_update(state->adapter, update); + accesskit_windows_adapter_update_window_focus_state(state->adapter, + is_focused); accesskit_windows_queued_events_raise(events); } } @@ -220,19 +223,16 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { } } else if (msg == WM_SETFOCUS || msg == WM_EXITMENULOOP || msg == WM_EXITSIZEMOVE) { - update_focus(hwnd, true); + update_window_focus_state(hwnd, true); } else if (msg == WM_KILLFOCUS || msg == WM_ENTERMENULOOP || msg == WM_ENTERSIZEMOVE) { - update_focus(hwnd, false); + update_window_focus_state(hwnd, false); } else if (msg == WM_KEYDOWN) { if (wParam == VK_TAB) { struct window_state *state = get_window_state(hwnd); - if (state->focus == BUTTON_1_ID) { - state->focus = BUTTON_2_ID; - } else { - state->focus = BUTTON_1_ID; - } - update_focus(hwnd, true); + accesskit_node_id new_focus = + (state->focus == BUTTON_1_ID) ? BUTTON_2_ID : BUTTON_1_ID; + window_state_set_focus(state, new_focus); } else if (wParam == VK_SPACE) { struct window_state *window_state = get_window_state(hwnd); accesskit_node_id id = window_state->focus; @@ -244,9 +244,7 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { accesskit_node_id id = (accesskit_node_id)lParam; if (id == BUTTON_1_ID || id == BUTTON_2_ID) { struct window_state *state = get_window_state(hwnd); - state->focus = id; - bool is_window_focused = state->is_window_focused; - update_focus(hwnd, is_window_focused); + window_state_set_focus(state, id); } } else if (msg == DO_DEFAULT_ACTION_MSG) { accesskit_node_id id = (accesskit_node_id)lParam; diff --git a/bindings/c/src/common.rs b/bindings/c/src/common.rs index 0fe923ad..e6278a51 100644 --- a/bindings/c/src/common.rs +++ b/bindings/c/src/common.rs @@ -907,16 +907,24 @@ impl BoxCastPtr for tree_update {} impl tree_update { #[no_mangle] - pub extern "C" fn accesskit_tree_update_new() -> *mut tree_update { - let update = TreeUpdate::default(); + pub extern "C" fn accesskit_tree_update_with_focus(focus: node_id) -> *mut tree_update { + let update = TreeUpdate { + nodes: vec![], + tree: None, + focus: focus.into(), + }; BoxCastPtr::to_mut_ptr(update) } #[no_mangle] - pub extern "C" fn accesskit_tree_update_with_capacity(capacity: usize) -> *mut tree_update { + pub extern "C" fn accesskit_tree_update_with_capacity_and_focus( + capacity: usize, + focus: node_id, + ) -> *mut tree_update { let update = TreeUpdate { nodes: Vec::with_capacity(capacity), - ..Default::default() + tree: None, + focus: focus.into(), }; BoxCastPtr::to_mut_ptr(update) } @@ -954,13 +962,7 @@ impl tree_update { #[no_mangle] pub extern "C" fn accesskit_tree_update_set_focus(update: *mut tree_update, focus: node_id) { let update = mut_from_ptr(update); - update.focus = Some(focus.into()); - } - - #[no_mangle] - pub extern "C" fn accesskit_tree_update_clear_focus(update: *mut tree_update) { - let update = mut_from_ptr(update); - update.focus = None; + update.focus = focus.into(); } } diff --git a/bindings/c/src/macos.rs b/bindings/c/src/macos.rs index 729ecce2..d54d53ea 100644 --- a/bindings/c/src/macos.rs +++ b/bindings/c/src/macos.rs @@ -55,11 +55,12 @@ impl macos_adapter { pub unsafe extern "C" fn accesskit_macos_adapter_new( view: *mut c_void, initial_state: *mut tree_update, + is_view_focused: bool, handler: *mut action_handler, ) -> *mut macos_adapter { let initial_state = box_from_ptr(initial_state); let handler = box_from_ptr(handler); - let adapter = Adapter::new(view, *initial_state, handler); + let adapter = Adapter::new(view, *initial_state, is_view_focused, handler); BoxCastPtr::to_mut_ptr(adapter) } @@ -69,7 +70,7 @@ impl macos_adapter { } /// This function takes ownership of `update`. - /// You must call `accesskit_macos_queued_events_raise` on the returned pointer. It can be null in case of error. + /// You must call `accesskit_macos_queued_events_raise` on the returned pointer. #[no_mangle] pub extern "C" fn accesskit_macos_adapter_update( adapter: *const macos_adapter, @@ -81,6 +82,19 @@ impl macos_adapter { BoxCastPtr::to_mut_ptr(events) } + /// Update the tree state based on whether the window is focused. + /// + /// You must call `accesskit_macos_queued_events_raise` on the returned pointer. + #[no_mangle] + pub extern "C" fn accesskit_macos_adapter_update_view_focus_state( + adapter: *const macos_adapter, + is_focused: bool, + ) -> *mut macos_queued_events { + let adapter = ref_from_ptr(adapter); + let events = adapter.update_view_focus_state(is_focused); + BoxCastPtr::to_mut_ptr(events) + } + /// Returns a pointer to an `NSArray`. Ownership of the pointer is not transfered. #[no_mangle] pub extern "C" fn accesskit_macos_adapter_view_children( @@ -177,7 +191,7 @@ impl macos_subclassing_adapter { } /// This function takes ownership of `update`. - /// You must call `accesskit_macos_queued_events_raise` on the returned pointer. It can be null in case of error. + /// You must call `accesskit_macos_queued_events_raise` on the returned pointer. #[no_mangle] pub extern "C" fn accesskit_macos_subclassing_adapter_update( adapter: *const macos_subclassing_adapter, @@ -189,7 +203,7 @@ impl macos_subclassing_adapter { BoxCastPtr::to_mut_ptr(events) } - /// You must call `accesskit_macos_queued_events_raise` on the returned pointer. It can be null in case of error or if the window is not active. + /// You must call `accesskit_macos_queued_events_raise` on the returned pointer. It can be null if the adapter is not active. #[no_mangle] pub extern "C" fn accesskit_macos_subclassing_adapter_update_if_active( adapter: *const macos_subclassing_adapter, @@ -205,6 +219,22 @@ impl macos_subclassing_adapter { None => ptr::null_mut(), } } + + /// Update the tree state based on whether the window is focused. + /// + /// You must call `accesskit_macos_queued_events_raise` on the returned pointer. It can be null if the adapter is not active. + #[no_mangle] + pub extern "C" fn accesskit_macos_subclassing_adapter_update_view_focus_state( + adapter: *const macos_subclassing_adapter, + is_focused: bool, + ) -> *mut macos_queued_events { + let adapter = ref_from_ptr(adapter); + let events = adapter.update_view_focus_state(is_focused); + match events { + Some(events) => BoxCastPtr::to_mut_ptr(events), + None => ptr::null_mut(), + } + } } /// Modifies the specified class, which must be a subclass of `NSWindow`, diff --git a/bindings/c/src/unix.rs b/bindings/c/src/unix.rs index 23372681..48a27639 100644 --- a/bindings/c/src/unix.rs +++ b/bindings/c/src/unix.rs @@ -35,6 +35,7 @@ impl unix_adapter { toolkit_version: *const c_char, initial_state: tree_update_factory, initial_state_userdata: *mut c_void, + is_window_focused: bool, handler: *mut action_handler, ) -> *mut unix_adapter { let app_name = unsafe { CStr::from_ptr(app_name).to_string_lossy().into() }; @@ -47,6 +48,7 @@ impl unix_adapter { toolkit_name, toolkit_version, move || *box_from_ptr(initial_state(initial_state_userdata)), + is_window_focused, handler, ); adapter.map_or_else(ptr::null_mut, BoxCastPtr::to_mut_ptr) @@ -77,4 +79,14 @@ impl unix_adapter { let update = box_from_ptr(update); adapter.update(*update); } + + /// Update the tree state based on whether the window is focused. + #[no_mangle] + pub extern "C" fn accesskit_unix_adapter_update_window_focus_state( + adapter: *const unix_adapter, + is_focused: bool, + ) { + let adapter = ref_from_ptr(adapter); + adapter.update_window_focus_state(is_focused); + } } diff --git a/bindings/c/src/windows.rs b/bindings/c/src/windows.rs index 3b376263..60a270ff 100644 --- a/bindings/c/src/windows.rs +++ b/bindings/c/src/windows.rs @@ -71,13 +71,20 @@ impl windows_adapter { pub extern "C" fn accesskit_windows_adapter_new( hwnd: HWND, initial_state: *mut tree_update, + is_window_focused: bool, handler: *mut action_handler, uia_init_marker: *mut windows_uia_init_marker, ) -> *mut windows_adapter { let initial_state = box_from_ptr(initial_state); let handler = box_from_ptr(handler); let uia_init_marker = *box_from_ptr(uia_init_marker); - let adapter = Adapter::new(hwnd, *initial_state, handler, uia_init_marker); + let adapter = Adapter::new( + hwnd, + *initial_state, + is_window_focused, + handler, + uia_init_marker, + ); BoxCastPtr::to_mut_ptr(adapter) } @@ -87,7 +94,7 @@ impl windows_adapter { } /// This function takes ownership of `update`. - /// You must call `accesskit_windows_queued_events_raise` on the returned pointer. It can be null in case of error. + /// You must call `accesskit_windows_queued_events_raise` on the returned pointer. #[no_mangle] pub extern "C" fn accesskit_windows_adapter_update( adapter: *const windows_adapter, @@ -99,6 +106,19 @@ impl windows_adapter { BoxCastPtr::to_mut_ptr(events) } + /// Update the tree state based on whether the window is focused. + /// + /// You must call `accesskit_windows_queued_events_raise` on the returned pointer. + #[no_mangle] + pub extern "C" fn accesskit_windows_adapter_update_window_focus_state( + adapter: *const windows_adapter, + is_focused: bool, + ) -> *mut windows_queued_events { + let adapter = ref_from_ptr(adapter); + let events = adapter.update_window_focus_state(is_focused); + BoxCastPtr::to_mut_ptr(events) + } + #[no_mangle] pub extern "C" fn accesskit_windows_adapter_handle_wm_getobject( adapter: *mut windows_adapter, @@ -148,7 +168,7 @@ impl windows_subclassing_adapter { } /// This function takes ownership of `update`. - /// You must call `accesskit_windows_queued_events_raise` on the returned pointer. It can be null in case of error. + /// You must call `accesskit_windows_queued_events_raise` on the returned pointer. #[no_mangle] pub extern "C" fn accesskit_windows_subclassing_adapter_update( adapter: *const windows_subclassing_adapter, @@ -160,7 +180,7 @@ impl windows_subclassing_adapter { BoxCastPtr::to_mut_ptr(events) } - /// You must call `accesskit_windows_queued_events_raise` on the returned pointer. It can be null in case of error or if the window is not active. + /// You must call `accesskit_windows_queued_events_raise` on the returned pointer. It can be null if the adapter is not active. #[no_mangle] pub extern "C" fn accesskit_windows_subclassing_adapter_update_if_active( adapter: *const windows_subclassing_adapter, diff --git a/common/src/lib.rs b/common/src/lib.rs index dc233f71..5919e943 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -2364,7 +2364,7 @@ impl Tree { /// events for nodes that have not changed since the previous update, /// but there is still a cost in processing these nodes and replacing /// the previous instances. -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "schemars", derive(JsonSchema))] #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] @@ -2398,18 +2398,12 @@ pub struct TreeUpdate { /// a tree. pub tree: Option, - /// The node with keyboard focus within this tree, if any. - /// The most recent focus, if any,must be provided with every tree update. - /// - /// This field must contain a value if and only if the native host - /// (e.g. window) currently has the keyboard focus. This implies - /// that the AccessKit provider must track the native focus state - /// and send matching tree updates. Rationale: A robust GUI toolkit - /// must do this native focus tracking anyway in order to correctly - /// render widgets (e.g. to draw or not draw a focus rectangle), - /// so this focus tracking should not be duplicated between the toolkit - /// and the AccessKit platform adapters. - pub focus: Option, + /// The node within this tree that has keyboard focus when the native + /// host (e.g. window) has focus. If no specific node within the tree + /// has keyboard focus, this must be set to the root. The latest focus state + /// must be provided with every tree update, even if the focus state + /// didn't change in a given update. + pub focus: NodeId, } impl TreeUpdate> From for TreeUpdate { diff --git a/consumer/src/lib.rs b/consumer/src/lib.rs index 618ba686..eeae4671 100644 --- a/consumer/src/lib.rs +++ b/consumer/src/lib.rs @@ -142,9 +142,9 @@ mod tests { (EMPTY_CONTAINER_3_3_IGNORED_ID, empty_container_3_3_ignored), ], tree: Some(Tree::new(ROOT_ID)), - focus: None, + focus: ROOT_ID, }; - crate::tree::Tree::new(initial_update) + crate::tree::Tree::new(initial_update, false) } pub fn test_tree_filter(node: &crate::Node) -> FilterResult { diff --git a/consumer/src/node.rs b/consumer/src/node.rs index 90fbd9e2..0a298369 100644 --- a/consumer/src/node.rs +++ b/consumer/src/node.rs @@ -57,7 +57,7 @@ impl<'a> Node<'a> { } pub fn is_focused(&self) -> bool { - self.tree_state.focus == Some(self.id()) + self.tree_state.focus_id() == Some(self.id()) } } @@ -1021,9 +1021,9 @@ mod tests { ), ], tree: Some(Tree::new(NodeId(0))), - focus: None, + focus: NodeId(0), }; - let tree = crate::Tree::new(update); + let tree = crate::Tree::new(update, false); assert_eq!(None, tree.state().node_by_id(NodeId(1)).unwrap().name()); } @@ -1064,9 +1064,9 @@ mod tests { }), ], tree: Some(Tree::new(NodeId(0))), - focus: None, + focus: NodeId(0), }; - let tree = crate::Tree::new(update); + let tree = crate::Tree::new(update, false); assert_eq!( Some([LABEL_1, LABEL_2].join(" ")), tree.state().node_by_id(NodeId(1)).unwrap().name() @@ -1117,9 +1117,9 @@ mod tests { }), ], tree: Some(Tree::new(NodeId(0))), - focus: None, + focus: NodeId(0), }; - let tree = crate::Tree::new(update); + let tree = crate::Tree::new(update, false); assert_eq!( Some(BUTTON_LABEL.into()), tree.state().node_by_id(NodeId(1)).unwrap().name() diff --git a/consumer/src/text.rs b/consumer/src/text.rs index 12433632..8c479ba8 100644 --- a/consumer/src/text.rs +++ b/consumer/src/text.rs @@ -1213,10 +1213,10 @@ mod tests { }), ], tree: Some(Tree::new(NodeId(0))), - focus: Some(NodeId(1)), + focus: NodeId(1), }; - crate::Tree::new(update) + crate::Tree::new(update, true) } fn multiline_end_selection() -> TextSelection { diff --git a/consumer/src/tree.rs b/consumer/src/tree.rs index f11f53fe..ad05edf4 100644 --- a/consumer/src/tree.rs +++ b/consumer/src/tree.rs @@ -12,7 +12,8 @@ use crate::node::{DetachedNode, Node, NodeState, ParentAndIndex}; pub struct State { pub(crate) nodes: HashMap, pub(crate) data: TreeData, - pub(crate) focus: Option, + focus: NodeId, + is_host_focused: bool, } struct InternalFocusChange { @@ -31,15 +32,18 @@ struct InternalChanges { impl State { fn validate_global(&self) { assert!(self.nodes.contains_key(&self.data.root)); - if let Some(id) = self.focus { - assert!(self.nodes.contains_key(&id)); - } + assert!(self.nodes.contains_key(&self.focus)); if let Some(id) = self.data.root_scroller { assert!(self.nodes.contains_key(&id)); } } - fn update(&mut self, update: TreeUpdate, mut changes: Option<&mut InternalChanges>) { + fn update( + &mut self, + update: TreeUpdate, + is_host_focused: bool, + mut changes: Option<&mut InternalChanges>, + ) { // First, if we're collecting changes, get the accurate state // of any updated nodes. if let Some(changes) = &mut changes { @@ -52,8 +56,7 @@ impl State { } let mut orphans = HashSet::new(); - let old_focus_id = self.focus; - let old_focus = self.focus.map(|id| self.node_by_id(id).unwrap().detached()); + let old_focus_id = self.is_host_focused.then_some(self.focus); let old_root_id = self.data.root; if let Some(tree) = update.tree { @@ -139,12 +142,13 @@ impl State { assert_eq!(pending_nodes.len(), 0); assert_eq!(pending_children.len(), 0); - if update.focus != self.focus { + if update.focus != self.focus || is_host_focused != self.is_host_focused { + let old_focus = old_focus_id.map(|id| self.node_by_id(id).unwrap().detached()); + let new_focus = is_host_focused.then_some(update.focus); if let Some(changes) = &mut changes { changes.focus_change = Some(InternalFocusChange { old_focus, - new_focus_old_node: update - .focus + new_focus_old_node: new_focus .and_then(|id| { (!changes.updated_nodes.contains_key(&id)) .then(|| self.node_by_id(id).map(|node| node.detached())) @@ -153,6 +157,7 @@ impl State { }); } self.focus = update.focus; + self.is_host_focused = is_host_focused; } if !orphans.is_empty() { @@ -195,6 +200,19 @@ impl State { self.validate_global(); } + fn update_host_focus_state( + &mut self, + is_host_focused: bool, + changes: Option<&mut InternalChanges>, + ) { + let update = TreeUpdate { + nodes: vec![], + tree: None, + focus: self.focus, + }; + self.update(update, is_host_focused, changes); + } + pub fn serialize(&self) -> TreeUpdate { let mut nodes = Vec::new(); @@ -237,11 +255,11 @@ impl State { } pub fn focus_id(&self) -> Option { - self.focus + self.is_host_focused.then_some(self.focus) } pub fn focus(&self) -> Option> { - self.focus.map(|id| self.node_by_id(id).unwrap()) + self.focus_id().map(|id| self.node_by_id(id).unwrap()) } } @@ -269,18 +287,19 @@ pub struct Tree { } impl Tree { - pub fn new(mut initial_state: TreeUpdate) -> Self { + pub fn new(mut initial_state: TreeUpdate, is_host_focused: bool) -> Self { let mut state = State { nodes: HashMap::new(), data: initial_state.tree.take().unwrap(), - focus: None, + focus: initial_state.focus, + is_host_focused, }; - state.update(initial_state, None); + state.update(initial_state, is_host_focused, None); Self { state } } pub fn update(&mut self, update: TreeUpdate) { - self.state.update(update, None); + self.state.update(update, self.state.is_host_focused, None); } pub fn update_and_process_changes( @@ -289,7 +308,27 @@ impl Tree { handler: &mut impl ChangeHandler, ) { let mut changes = InternalChanges::default(); - self.state.update(update, Some(&mut changes)); + self.state + .update(update, self.state.is_host_focused, Some(&mut changes)); + self.process_changes(changes, handler); + } + + pub fn update_host_focus_state(&mut self, is_host_focused: bool) { + self.state.update_host_focus_state(is_host_focused, None); + } + + pub fn update_host_focus_state_and_process_changes( + &mut self, + is_host_focused: bool, + handler: &mut impl ChangeHandler, + ) { + let mut changes = InternalChanges::default(); + self.state + .update_host_focus_state(is_host_focused, Some(&mut changes)); + self.process_changes(changes, handler); + } + + fn process_changes(&self, changes: InternalChanges, handler: &mut impl ChangeHandler) { for id in &changes.added_node_ids { let node = self.state.node_by_id(*id).unwrap(); handler.node_added(&node); @@ -348,9 +387,9 @@ mod tests { NodeBuilder::new(Role::Window).build(&mut classes), )], tree: Some(Tree::new(NodeId(0))), - focus: None, + focus: NodeId(0), }; - let tree = super::Tree::new(update); + let tree = super::Tree::new(update, false); assert_eq!(NodeId(0), tree.state().root().id()); assert_eq!(Role::Window, tree.state().root().role()); assert!(tree.state().root().parent().is_none()); @@ -376,9 +415,9 @@ mod tests { ), ], tree: Some(Tree::new(NodeId(0))), - focus: None, + focus: NodeId(0), }; - let tree = super::Tree::new(update); + let tree = super::Tree::new(update, false); let state = tree.state(); assert_eq!( NodeId(0), @@ -398,9 +437,9 @@ mod tests { let first_update = TreeUpdate { nodes: vec![(NodeId(0), root_builder.clone().build(&mut classes))], tree: Some(Tree::new(NodeId(0))), - focus: None, + focus: NodeId(0), }; - let mut tree = super::Tree::new(first_update); + let mut tree = super::Tree::new(first_update, false); assert_eq!(0, tree.state().root().children().count()); let second_update = TreeUpdate { nodes: vec![ @@ -415,7 +454,7 @@ mod tests { ), ], tree: None, - focus: None, + focus: NodeId(0), }; struct Handler { got_new_child_node: bool, @@ -491,14 +530,14 @@ mod tests { ), ], tree: Some(Tree::new(NodeId(0))), - focus: None, + focus: NodeId(0), }; - let mut tree = super::Tree::new(first_update); + let mut tree = super::Tree::new(first_update, false); assert_eq!(1, tree.state().root().children().count()); let second_update = TreeUpdate { nodes: vec![(NodeId(0), root_builder.build(&mut classes))], tree: None, - focus: None, + focus: NodeId(0), }; struct Handler { got_updated_root_node: bool, @@ -572,14 +611,14 @@ mod tests { ), ], tree: Some(Tree::new(NodeId(0))), - focus: Some(NodeId(1)), + focus: NodeId(1), }; - let mut tree = super::Tree::new(first_update); + let mut tree = super::Tree::new(first_update, true); assert!(tree.state().node_by_id(NodeId(1)).unwrap().is_focused()); let second_update = TreeUpdate { nodes: vec![], tree: None, - focus: Some(NodeId(2)), + focus: NodeId(2), }; struct Handler { got_old_focus_node_update: bool, @@ -665,9 +704,9 @@ mod tests { }), ], tree: Some(Tree::new(NodeId(0))), - focus: None, + focus: NodeId(0), }; - let mut tree = super::Tree::new(first_update); + let mut tree = super::Tree::new(first_update, false); assert_eq!( Some("foo".into()), tree.state().node_by_id(NodeId(1)).unwrap().name() @@ -679,7 +718,7 @@ mod tests { builder.build(&mut classes) })], tree: None, - focus: None, + focus: NodeId(0), }; struct Handler { got_updated_child_node: bool, diff --git a/platforms/macos/src/adapter.rs b/platforms/macos/src/adapter.rs index 2cdd8120..e4da04c5 100644 --- a/platforms/macos/src/adapter.rs +++ b/platforms/macos/src/adapter.rs @@ -35,11 +35,12 @@ impl Adapter { pub unsafe fn new( view: *mut c_void, initial_state: TreeUpdate, + is_view_focused: bool, action_handler: Box, ) -> Self { let view = unsafe { Id::retain(view as *mut NSView) }.unwrap(); let view = WeakId::new(&view); - let tree = Tree::new(initial_state); + let tree = Tree::new(initial_state, is_view_focused); let mtm = MainThreadMarker::new().unwrap(); Self { context: Context::new(view, tree, action_handler, mtm), @@ -56,6 +57,16 @@ impl Adapter { event_generator.into_result() } + /// Update the tree state based on whether the window is focused. + /// + /// The caller must call [`QueuedEvents::raise`] on the return value. + pub fn update_view_focus_state(&self, is_focused: bool) -> QueuedEvents { + let mut event_generator = EventGenerator::new(self.context.clone()); + let mut tree = self.context.tree.borrow_mut(); + tree.update_host_focus_state_and_process_changes(is_focused, &mut event_generator); + event_generator.into_result() + } + pub fn view_children(&self) -> *mut NSArray { let tree = self.context.tree.borrow(); let state = tree.state(); diff --git a/platforms/macos/src/subclass.rs b/platforms/macos/src/subclass.rs index 744f78ea..57c4477d 100644 --- a/platforms/macos/src/subclass.rs +++ b/platforms/macos/src/subclass.rs @@ -18,7 +18,7 @@ use objc2::{ sel, ClassType, }; use once_cell::{sync::Lazy as SyncLazy, unsync::Lazy}; -use std::{collections::HashMap, ffi::c_void, sync::Mutex}; +use std::{cell::Cell, collections::HashMap, ffi::c_void, rc::Rc, sync::Mutex}; use crate::{ appkit::{NSView, NSWindow}, @@ -102,11 +102,14 @@ unsafe extern "C" fn hit_test(this: &NSView, _cmd: Sel, point: NSPoint) -> *mut /// accessibility methods when normal subclassing isn't an option. pub struct SubclassingAdapter { view: Id, + is_view_focused: Rc>, associated: Id, } impl SubclassingAdapter { /// Create an adapter that dynamically subclasses the specified view. + /// This must be done before the view is shown or focused for + /// the first time. /// /// The action handler will always be called on the main thread. /// @@ -128,11 +131,13 @@ impl SubclassingAdapter { source: impl 'static + FnOnce() -> TreeUpdate, action_handler: Box, ) -> Self { + let is_view_focused = Rc::new(Cell::new(false)); let adapter: LazyAdapter = { let retained_view = retained_view.clone(); + let is_view_focused = Rc::clone(&is_view_focused); Lazy::new(Box::new(move || { let view = Id::as_ptr(&retained_view) as *mut c_void; - unsafe { Adapter::new(view, source(), action_handler) } + unsafe { Adapter::new(view, source(), is_view_focused.get(), action_handler) } })) }; let view = Id::as_ptr(&retained_view) as *mut NSView; @@ -180,6 +185,7 @@ impl SubclassingAdapter { unsafe { object_setClass(view as *mut _, (*subclass as *const Class).cast()) }; Self { view: retained_view, + is_view_focused, associated, } } @@ -227,6 +233,16 @@ impl SubclassingAdapter { ) -> Option { Lazy::get(&self.associated.adapter).map(|adapter| adapter.update(update_factory())) } + + /// Update the tree state based on whether the window is focused. + /// + /// If a [`QueuedEvents`] instance is returned, the caller must call + /// [`QueuedEvents::raise`] on it. + pub fn update_view_focus_state(&self, is_focused: bool) -> Option { + self.is_view_focused.set(is_focused); + Lazy::get(&self.associated.adapter) + .map(|adapter| adapter.update_view_focus_state(is_focused)) + } } impl Drop for SubclassingAdapter { diff --git a/platforms/unix/src/adapter.rs b/platforms/unix/src/adapter.rs index a549d7fe..62407f6f 100644 --- a/platforms/unix/src/adapter.rs +++ b/platforms/unix/src/adapter.rs @@ -23,6 +23,111 @@ use futures_lite::StreamExt; use std::sync::Arc; use zbus::Task; +struct AdapterChangeHandler<'a> { + adapter: &'a Adapter, +} + +impl AdapterChangeHandler<'_> { + fn add_node(&mut self, node: &Node) { + let interfaces = NodeWrapper::Node(node).interfaces(); + self.adapter + .register_interfaces(node.id(), interfaces) + .unwrap(); + } + + fn remove_node(&mut self, node: &DetachedNode) { + let node = NodeWrapper::DetachedNode(node); + self.adapter + .events + .send_blocking(Event::Object { + target: node.id(), + event: ObjectEvent::StateChanged(State::Defunct, true), + }) + .unwrap(); + self.adapter + .unregister_interfaces(&node.id(), node.interfaces()) + .unwrap(); + } +} + +impl TreeChangeHandler for AdapterChangeHandler<'_> { + fn node_added(&mut self, node: &Node) { + if filter(node) == FilterResult::Include { + self.add_node(node); + } + } + + fn node_updated(&mut self, old_node: &DetachedNode, new_node: &Node) { + let filter_old = filter_detached(old_node); + let filter_new = filter(new_node); + if filter_new != filter_old { + if filter_new == FilterResult::Include { + self.add_node(new_node); + } else if filter_old == FilterResult::Include { + self.remove_node(old_node); + } + } else if filter_new == FilterResult::Include { + let old_wrapper = NodeWrapper::DetachedNode(old_node); + let new_wrapper = NodeWrapper::Node(new_node); + let old_interfaces = old_wrapper.interfaces(); + let new_interfaces = new_wrapper.interfaces(); + let kept_interfaces = old_interfaces & new_interfaces; + self.adapter + .unregister_interfaces(&new_wrapper.id(), old_interfaces ^ kept_interfaces) + .unwrap(); + self.adapter + .register_interfaces(new_node.id(), new_interfaces ^ kept_interfaces) + .unwrap(); + new_wrapper.notify_changes( + &self.adapter.context.read_root_window_bounds(), + &self.adapter.events, + &old_wrapper, + ); + } + } + + fn focus_moved( + &mut self, + old_node: Option<&DetachedNode>, + new_node: Option<&Node>, + current_state: &TreeState, + ) { + if let Some(root_window) = root_window(current_state) { + if old_node.is_none() && new_node.is_some() { + self.adapter + .window_activated(&NodeWrapper::Node(&root_window), &self.adapter.events); + } else if old_node.is_some() && new_node.is_none() { + self.adapter + .window_deactivated(&NodeWrapper::Node(&root_window), &self.adapter.events); + } + } + if let Some(node) = new_node.map(NodeWrapper::Node) { + self.adapter + .events + .send_blocking(Event::Object { + target: node.id(), + event: ObjectEvent::StateChanged(State::Focused, true), + }) + .unwrap(); + } + if let Some(node) = old_node.map(NodeWrapper::DetachedNode) { + self.adapter + .events + .send_blocking(Event::Object { + target: node.id(), + event: ObjectEvent::StateChanged(State::Focused, false), + }) + .unwrap(); + } + } + + fn node_removed(&mut self, node: &DetachedNode, _: &TreeState) { + if filter_detached(node) == FilterResult::Include { + self.remove_node(node); + } + } +} + pub struct Adapter { atspi_bus: Bus, _event_task: Task<()>, @@ -37,6 +142,7 @@ impl Adapter { toolkit_name: String, toolkit_version: String, initial_state: impl 'static + FnOnce() -> TreeUpdate, + is_window_focused: bool, action_handler: Box, ) -> Option { let mut atspi_bus = block_on(async { Bus::a11y_bus().await })?; @@ -50,7 +156,7 @@ impl Adapter { }, "accesskit_event_task", ); - let tree = Tree::new(initial_state()); + let tree = Tree::new(initial_state(), is_window_focused); let app_context = AppContext::new(app_name, toolkit_name, toolkit_version); let context = Context::new(tree, action_handler, app_context); block_on(async { atspi_bus.register_root_node(&context).await.ok() })?; @@ -171,113 +277,18 @@ impl Adapter { /// Apply the provided update to the tree. pub fn update(&self, update: TreeUpdate) { - struct Handler<'a> { - adapter: &'a Adapter, - } - impl Handler<'_> { - fn add_node(&mut self, node: &Node) { - let interfaces = NodeWrapper::Node(node).interfaces(); - self.adapter - .register_interfaces(node.id(), interfaces) - .unwrap(); - } - fn remove_node(&mut self, node: &DetachedNode) { - let node = NodeWrapper::DetachedNode(node); - self.adapter - .events - .send_blocking(Event::Object { - target: node.id(), - event: ObjectEvent::StateChanged(State::Defunct, true), - }) - .unwrap(); - self.adapter - .unregister_interfaces(&node.id(), node.interfaces()) - .unwrap(); - } - } - impl TreeChangeHandler for Handler<'_> { - fn node_added(&mut self, node: &Node) { - if filter(node) == FilterResult::Include { - self.add_node(node); - } - } - fn node_updated(&mut self, old_node: &DetachedNode, new_node: &Node) { - let filter_old = filter_detached(old_node); - let filter_new = filter(new_node); - if filter_new != filter_old { - if filter_new == FilterResult::Include { - self.add_node(new_node); - } else if filter_old == FilterResult::Include { - self.remove_node(old_node); - } - } else if filter_new == FilterResult::Include { - let old_wrapper = NodeWrapper::DetachedNode(old_node); - let new_wrapper = NodeWrapper::Node(new_node); - let old_interfaces = old_wrapper.interfaces(); - let new_interfaces = new_wrapper.interfaces(); - let kept_interfaces = old_interfaces & new_interfaces; - self.adapter - .unregister_interfaces(&new_wrapper.id(), old_interfaces ^ kept_interfaces) - .unwrap(); - self.adapter - .register_interfaces(new_node.id(), new_interfaces ^ kept_interfaces) - .unwrap(); - new_wrapper.notify_changes( - &self.adapter.context.read_root_window_bounds(), - &self.adapter.events, - &old_wrapper, - ); - } - } - fn focus_moved( - &mut self, - old_node: Option<&DetachedNode>, - new_node: Option<&Node>, - current_state: &TreeState, - ) { - if let Some(root_window) = root_window(current_state) { - if old_node.is_none() && new_node.is_some() { - self.adapter.window_activated( - &NodeWrapper::Node(&root_window), - &self.adapter.events, - ); - } else if old_node.is_some() && new_node.is_none() { - self.adapter.window_deactivated( - &NodeWrapper::Node(&root_window), - &self.adapter.events, - ); - } - } - if let Some(node) = new_node.map(NodeWrapper::Node) { - self.adapter - .events - .send_blocking(Event::Object { - target: node.id(), - event: ObjectEvent::StateChanged(State::Focused, true), - }) - .unwrap(); - } - if let Some(node) = old_node.map(NodeWrapper::DetachedNode) { - self.adapter - .events - .send_blocking(Event::Object { - target: node.id(), - event: ObjectEvent::StateChanged(State::Focused, false), - }) - .unwrap(); - } - } - fn node_removed(&mut self, node: &DetachedNode, _: &TreeState) { - if filter_detached(node) == FilterResult::Include { - self.remove_node(node); - } - } - } - let mut handler = Handler { adapter: self }; + let mut handler = AdapterChangeHandler { adapter: self }; let mut tree = self.context.tree.write().unwrap(); tree.update_and_process_changes(update, &mut handler); } + /// Update the tree state based on whether the window is focused. + pub fn update_window_focus_state(&self, is_focused: bool) { + let mut handler = AdapterChangeHandler { adapter: self }; + let mut tree = self.context.tree.write().unwrap(); + tree.update_host_focus_state_and_process_changes(is_focused, &mut handler); + } + fn window_activated(&self, window: &NodeWrapper, events: &Sender) { events .send_blocking(Event::Window { diff --git a/platforms/windows/examples/hello_world.rs b/platforms/windows/examples/hello_world.rs index dd39d7b8..d07f2a4e 100644 --- a/platforms/windows/examples/hello_world.rs +++ b/platforms/windows/examples/hello_world.rs @@ -92,10 +92,6 @@ struct InnerWindowState { } impl InnerWindowState { - fn focus(&self) -> Option { - self.is_window_focused.then_some(self.focus) - } - fn build_root(&mut self) -> Node { let mut builder = NodeBuilder::new(Role::Window); builder.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); @@ -116,7 +112,7 @@ impl InnerWindowState { (BUTTON_2_ID, button_2), ], tree: Some(Tree::new(WINDOW_ID)), - focus: self.focus(), + focus: self.focus, }; if let Some(announcement) = &self.announcement { result.nodes.push(( @@ -139,17 +135,33 @@ impl WindowState { self.adapter.get_or_init(|| { let mut inner_state = self.inner_state.borrow_mut(); let initial_tree = inner_state.build_initial_tree(); + let is_window_focused = inner_state.is_window_focused; drop(inner_state); let action_handler = Box::new(SimpleActionHandler { window }); accesskit_windows::Adapter::new( window, initial_tree, + is_window_focused, action_handler, self.uia_init_marker, ) }) } + fn set_focus(&self, focus: NodeId) { + let mut inner_state = self.inner_state.borrow_mut(); + inner_state.focus = focus; + if let Some(adapter) = self.adapter.get() { + let update = TreeUpdate { + nodes: vec![], + tree: None, + focus, + }; + let events = adapter.update(update); + events.raise(); + } + } + fn press_button(&self, id: NodeId) { let mut inner_state = self.inner_state.borrow_mut(); let text = if id == BUTTON_1_ID { @@ -164,7 +176,7 @@ impl WindowState { let update = TreeUpdate { nodes: vec![(ANNOUNCEMENT_ID, announcement), (WINDOW_ID, root)], tree: None, - focus: inner_state.focus(), + focus: inner_state.focus, }; let events = adapter.update(update); events.raise(); @@ -176,18 +188,13 @@ unsafe fn get_window_state(window: HWND) -> *const WindowState { GetWindowLongPtrW(window, GWLP_USERDATA) as _ } -fn update_focus(window: HWND, is_window_focused: bool) { +fn update_window_focus_state(window: HWND, is_window_focused: bool) { let window_state = unsafe { &*get_window_state(window) }; let mut inner_state = window_state.inner_state.borrow_mut(); inner_state.is_window_focused = is_window_focused; - let focus = inner_state.focus(); drop(inner_state); if let Some(adapter) = window_state.adapter.get() { - let events = adapter.update(TreeUpdate { - nodes: vec![], - tree: None, - focus, - }); + let events = adapter.update_window_focus_state(is_window_focused); events.raise(); } } @@ -276,24 +283,23 @@ extern "system" fn wndproc(window: HWND, message: u32, wparam: WPARAM, lparam: L ) } WM_SETFOCUS | WM_EXITMENULOOP | WM_EXITSIZEMOVE => { - update_focus(window, true); + update_window_focus_state(window, true); LRESULT(0) } WM_KILLFOCUS | WM_ENTERMENULOOP | WM_ENTERSIZEMOVE => { - update_focus(window, false); + update_window_focus_state(window, false); LRESULT(0) } WM_KEYDOWN => match VIRTUAL_KEY(wparam.0 as u16) { VK_TAB => { let window_state = unsafe { &*get_window_state(window) }; - let mut inner_state = window_state.inner_state.borrow_mut(); - inner_state.focus = if inner_state.focus == BUTTON_1_ID { + let old_focus = window_state.inner_state.borrow().focus; + let new_focus = if old_focus == BUTTON_1_ID { BUTTON_2_ID } else { BUTTON_1_ID }; - drop(inner_state); - update_focus(window, true); + window_state.set_focus(new_focus); LRESULT(0) } VK_SPACE => { @@ -308,11 +314,7 @@ extern "system" fn wndproc(window: HWND, message: u32, wparam: WPARAM, lparam: L let id = NodeId(lparam.0 as _); if id == BUTTON_1_ID || id == BUTTON_2_ID { let window_state = unsafe { &*get_window_state(window) }; - let mut inner_state = window_state.inner_state.borrow_mut(); - inner_state.focus = id; - let is_window_focused = inner_state.is_window_focused; - drop(inner_state); - update_focus(window, is_window_focused); + window_state.set_focus(id); } LRESULT(0) } diff --git a/platforms/windows/src/adapter.rs b/platforms/windows/src/adapter.rs index d32a182a..fb3601f9 100644 --- a/platforms/windows/src/adapter.rs +++ b/platforms/windows/src/adapter.rs @@ -18,6 +18,125 @@ use crate::{ util::QueuedEvent, }; +struct AdapterChangeHandler<'a> { + context: &'a Arc, + queue: Vec, + text_changed: HashSet, +} + +impl AdapterChangeHandler<'_> { + fn insert_text_change_if_needed_parent(&mut self, node: Node) { + if !node.supports_text_ranges() { + return; + } + let id = node.id(); + if self.text_changed.contains(&id) { + return; + } + let platform_node = PlatformNode::new(self.context, node.id()); + let element: IRawElementProviderSimple = platform_node.into(); + // Text change events must come before selection change + // events. It doesn't matter if text change events come + // before other events. + self.queue.insert( + 0, + QueuedEvent::Simple { + element, + event_id: UIA_Text_TextChangedEventId, + }, + ); + self.text_changed.insert(id); + } + + fn insert_text_change_if_needed(&mut self, node: &Node) { + if node.role() != Role::InlineTextBox { + return; + } + if let Some(node) = node.filtered_parent(&filter) { + self.insert_text_change_if_needed_parent(node); + } + } + + fn insert_text_change_if_needed_for_removed_node( + &mut self, + node: &DetachedNode, + current_state: &TreeState, + ) { + if node.role() != Role::InlineTextBox { + return; + } + if let Some(id) = node.parent_id() { + if let Some(node) = current_state.node_by_id(id) { + self.insert_text_change_if_needed_parent(node); + } + } + } +} + +impl TreeChangeHandler for AdapterChangeHandler<'_> { + fn node_added(&mut self, node: &Node) { + self.insert_text_change_if_needed(node); + if filter(node) != FilterResult::Include { + return; + } + if node.name().is_some() && node.live() != Live::Off { + let platform_node = PlatformNode::new(self.context, node.id()); + let element: IRawElementProviderSimple = platform_node.into(); + self.queue.push(QueuedEvent::Simple { + element, + event_id: UIA_LiveRegionChangedEventId, + }); + } + } + + fn node_updated(&mut self, old_node: &DetachedNode, new_node: &Node) { + if old_node.raw_value() != new_node.raw_value() { + self.insert_text_change_if_needed(new_node); + } + if filter(new_node) != FilterResult::Include { + return; + } + let platform_node = PlatformNode::new(self.context, new_node.id()); + let element: IRawElementProviderSimple = platform_node.into(); + let old_wrapper = NodeWrapper::DetachedNode(old_node); + let new_wrapper = NodeWrapper::Node(new_node); + new_wrapper.enqueue_property_changes(&mut self.queue, &element, &old_wrapper); + if new_node.name().is_some() + && new_node.live() != Live::Off + && (new_node.name() != old_node.name() + || new_node.live() != old_node.live() + || filter_detached(old_node) != FilterResult::Include) + { + self.queue.push(QueuedEvent::Simple { + element, + event_id: UIA_LiveRegionChangedEventId, + }); + } + } + + fn focus_moved( + &mut self, + _old_node: Option<&DetachedNode>, + new_node: Option<&Node>, + _current_state: &TreeState, + ) { + if let Some(new_node) = new_node { + let platform_node = PlatformNode::new(self.context, new_node.id()); + let element: IRawElementProviderSimple = platform_node.into(); + self.queue.push(QueuedEvent::Simple { + element, + event_id: UIA_AutomationFocusChangedEventId, + }); + } + } + + fn node_removed(&mut self, node: &DetachedNode, current_state: &TreeState) { + self.insert_text_change_if_needed_for_removed_node(node, current_state); + } + + // TODO: handle other events (#20) +} + pub struct Adapter { context: Arc, } @@ -30,13 +149,26 @@ impl Adapter { pub fn new( hwnd: HWND, initial_state: TreeUpdate, + is_window_focused: bool, action_handler: Box, _uia_init_marker: UiaInitMarker, ) -> Self { - let context = Context::new(hwnd, Tree::new(initial_state), action_handler); + let context = Context::new( + hwnd, + Tree::new(initial_state, is_window_focused), + action_handler, + ); Self { context } } + fn change_handler(&self) -> AdapterChangeHandler { + AdapterChangeHandler { + context: &self.context, + queue: Vec::new(), + text_changed: HashSet::new(), + } + } + /// Apply the provided update to the tree. /// /// The caller must call [`QueuedEvents::raise`] on the return value. @@ -45,126 +177,26 @@ impl Adapter { /// [`QueuedEvents::raise`] for restrictions on the context in which /// it should be called. pub fn update(&self, update: TreeUpdate) -> QueuedEvents { - struct Handler<'a> { - context: &'a Arc, - queue: Vec, - text_changed: HashSet, - } - impl Handler<'_> { - fn insert_text_change_if_needed_parent(&mut self, node: Node) { - if !node.supports_text_ranges() { - return; - } - let id = node.id(); - if self.text_changed.contains(&id) { - return; - } - let platform_node = PlatformNode::new(self.context, node.id()); - let element: IRawElementProviderSimple = platform_node.into(); - // Text change events must come before selection change - // events. It doesn't matter if text change events come - // before other events. - self.queue.insert( - 0, - QueuedEvent::Simple { - element, - event_id: UIA_Text_TextChangedEventId, - }, - ); - self.text_changed.insert(id); - } - fn insert_text_change_if_needed(&mut self, node: &Node) { - if node.role() != Role::InlineTextBox { - return; - } - if let Some(node) = node.filtered_parent(&filter) { - self.insert_text_change_if_needed_parent(node); - } - } - fn insert_text_change_if_needed_for_removed_node( - &mut self, - node: &DetachedNode, - current_state: &TreeState, - ) { - if node.role() != Role::InlineTextBox { - return; - } - if let Some(id) = node.parent_id() { - if let Some(node) = current_state.node_by_id(id) { - self.insert_text_change_if_needed_parent(node); - } - } - } - } - impl TreeChangeHandler for Handler<'_> { - fn node_added(&mut self, node: &Node) { - self.insert_text_change_if_needed(node); - if filter(node) != FilterResult::Include { - return; - } - if node.name().is_some() && node.live() != Live::Off { - let platform_node = PlatformNode::new(self.context, node.id()); - let element: IRawElementProviderSimple = platform_node.into(); - self.queue.push(QueuedEvent::Simple { - element, - event_id: UIA_LiveRegionChangedEventId, - }); - } - } - fn node_updated(&mut self, old_node: &DetachedNode, new_node: &Node) { - if old_node.raw_value() != new_node.raw_value() { - self.insert_text_change_if_needed(new_node); - } - if filter(new_node) != FilterResult::Include { - return; - } - let platform_node = PlatformNode::new(self.context, new_node.id()); - let element: IRawElementProviderSimple = platform_node.into(); - let old_wrapper = NodeWrapper::DetachedNode(old_node); - let new_wrapper = NodeWrapper::Node(new_node); - new_wrapper.enqueue_property_changes(&mut self.queue, &element, &old_wrapper); - if new_node.name().is_some() - && new_node.live() != Live::Off - && (new_node.name() != old_node.name() - || new_node.live() != old_node.live() - || filter_detached(old_node) != FilterResult::Include) - { - self.queue.push(QueuedEvent::Simple { - element, - event_id: UIA_LiveRegionChangedEventId, - }); - } - } - fn focus_moved( - &mut self, - _old_node: Option<&DetachedNode>, - new_node: Option<&Node>, - _current_state: &TreeState, - ) { - if let Some(new_node) = new_node { - let platform_node = PlatformNode::new(self.context, new_node.id()); - let element: IRawElementProviderSimple = platform_node.into(); - self.queue.push(QueuedEvent::Simple { - element, - event_id: UIA_AutomationFocusChangedEventId, - }); - } - } - fn node_removed(&mut self, node: &DetachedNode, current_state: &TreeState) { - self.insert_text_change_if_needed_for_removed_node(node, current_state); - } - // TODO: handle other events (#20) - } - let mut handler = Handler { - context: &self.context, - queue: Vec::new(), - text_changed: HashSet::new(), - }; + let mut handler = self.change_handler(); let mut tree = self.context.tree.write().unwrap(); tree.update_and_process_changes(update, &mut handler); QueuedEvents(handler.queue) } + /// Update the tree state based on whether the window is focused. + /// + /// The caller must call [`QueuedEvents::raise`] on the return value. + /// + /// This method may be safely called on any thread, but refer to + /// [`QueuedEvents::raise`] for restrictions on the context in which + /// it should be called. + pub fn update_window_focus_state(&self, is_focused: bool) -> QueuedEvents { + let mut handler = self.change_handler(); + let mut tree = self.context.tree.write().unwrap(); + tree.update_host_focus_state_and_process_changes(is_focused, &mut handler); + QueuedEvents(handler.queue) + } + fn root_platform_node(&self) -> PlatformNode { let tree = self.context.read_tree(); let node_id = tree.state().root_id(); diff --git a/platforms/windows/src/subclass.rs b/platforms/windows/src/subclass.rs index 287413fa..21a286ae 100644 --- a/platforms/windows/src/subclass.rs +++ b/platforms/windows/src/subclass.rs @@ -5,7 +5,7 @@ use accesskit::{ActionHandler, TreeUpdate}; use once_cell::unsync::Lazy; -use std::{cell::Cell, ffi::c_void, mem::transmute}; +use std::{cell::Cell, ffi::c_void, mem::transmute, rc::Rc}; use windows::{ core::*, Win32::{Foundation::*, UI::WindowsAndMessaging::*}, @@ -26,6 +26,7 @@ type LazyAdapter = Lazy Adapter>>; struct SubclassImpl { hwnd: HWND, + is_window_focused: Rc>, adapter: LazyAdapter, prev_wnd_proc: WNDPROC, window_destroyed: Cell, @@ -36,26 +37,50 @@ extern "system" fn wnd_proc(window: HWND, message: u32, wparam: WPARAM, lparam: let impl_ptr = handle.0 as *const SubclassImpl; assert!(!impl_ptr.is_null()); let r#impl = unsafe { &*impl_ptr }; - if message == WM_GETOBJECT { - let adapter = Lazy::force(&r#impl.adapter); - if let Some(result) = adapter.handle_wm_getobject(wparam, lparam) { - return result.into(); + match message { + WM_GETOBJECT => { + let adapter = Lazy::force(&r#impl.adapter); + if let Some(result) = adapter.handle_wm_getobject(wparam, lparam) { + return result.into(); + } } - } - if message == WM_NCDESTROY { - r#impl.window_destroyed.set(true); + WM_SETFOCUS | WM_EXITMENULOOP | WM_EXITSIZEMOVE => { + r#impl.update_window_focus_state(true); + } + WM_KILLFOCUS | WM_ENTERMENULOOP | WM_ENTERSIZEMOVE => { + r#impl.update_window_focus_state(false); + } + WM_NCDESTROY => { + r#impl.window_destroyed.set(true); + } + _ => (), } unsafe { CallWindowProcW(r#impl.prev_wnd_proc, window, message, wparam, lparam) } } impl SubclassImpl { - /// Creates a new Windows platform adapter using window subclassing. - /// - /// The action handler may or may not be called on the thread that owns - /// the window. - fn new(hwnd: HWND, adapter: LazyAdapter) -> Box { + fn new( + hwnd: HWND, + source: impl 'static + FnOnce() -> TreeUpdate, + action_handler: Box, + ) -> Box { + let is_window_focused = Rc::new(Cell::new(false)); + let uia_init_marker = UiaInitMarker::new(); + let adapter: LazyAdapter = Lazy::new(Box::new({ + let is_window_focused = Rc::clone(&is_window_focused); + move || { + Adapter::new( + hwnd, + source(), + is_window_focused.get(), + action_handler, + uia_init_marker, + ) + } + })); Box::new(Self { hwnd, + is_window_focused, adapter, prev_wnd_proc: None, window_destroyed: Cell::new(false), @@ -73,6 +98,7 @@ impl SubclassImpl { .unwrap(); let result = unsafe { SetWindowLongPtrW(self.hwnd, GWLP_WNDPROC, wnd_proc as *const c_void as _) }; + #[allow(clippy::unnecessary_literal_unwrap)] if result == 0 { let result: Result<()> = Err(Error::from_win32()); result.unwrap(); @@ -80,6 +106,14 @@ impl SubclassImpl { self.prev_wnd_proc = unsafe { transmute::(result) }; } + fn update_window_focus_state(&self, is_focused: bool) { + self.is_window_focused.set(is_focused); + if let Some(adapter) = Lazy::get(&self.adapter) { + let events = adapter.update_window_focus_state(is_focused); + events.raise(); + } + } + fn uninstall(&self) { if self.window_destroyed.get() { return; @@ -91,6 +125,7 @@ impl SubclassImpl { transmute::(self.prev_wnd_proc), ) }; + #[allow(clippy::unnecessary_literal_unwrap)] if result == 0 { let result: Result<()> = Err(Error::from_win32()); result.unwrap(); @@ -106,16 +141,18 @@ impl SubclassImpl { pub struct SubclassingAdapter(Box); impl SubclassingAdapter { + /// Creates a new Windows platform adapter using window subclassing. + /// This must be done before the window is shown or focused + /// for the first time. + /// + /// The action handler may or may not be called on the thread that owns + /// the window. pub fn new( hwnd: HWND, source: impl 'static + FnOnce() -> TreeUpdate, action_handler: Box, ) -> Self { - let uia_init_marker = UiaInitMarker::new(); - let adapter: LazyAdapter = Lazy::new(Box::new(move || { - Adapter::new(hwnd, source(), action_handler, uia_init_marker) - })); - let mut r#impl = SubclassImpl::new(hwnd, adapter); + let mut r#impl = SubclassImpl::new(hwnd, source, action_handler); r#impl.install(); Self(r#impl) } diff --git a/platforms/windows/src/tests/mod.rs b/platforms/windows/src/tests/mod.rs index 22cce900..12cad981 100644 --- a/platforms/windows/src/tests/mod.rs +++ b/platforms/windows/src/tests/mod.rs @@ -3,7 +3,7 @@ // the LICENSE-APACHE file) or the MIT license (found in // the LICENSE-MIT file), at your option. -use accesskit::{ActionHandler, NodeId, TreeUpdate}; +use accesskit::{ActionHandler, TreeUpdate}; use once_cell::{sync::Lazy as SyncLazy, unsync::Lazy}; use std::{ cell::RefCell, @@ -48,7 +48,6 @@ static WINDOW_CLASS_ATOM: SyncLazy = SyncLazy::new(|| { }); struct InnerWindowState { - focus: NodeId, is_window_focused: bool, } @@ -63,23 +62,18 @@ unsafe fn get_window_state(window: HWND) -> *const WindowState { GetWindowLongPtrW(window, GWLP_USERDATA) as _ } -fn update_focus(window: HWND, is_window_focused: bool) { +fn update_window_focus_state(window: HWND, is_window_focused: bool) { let window_state = unsafe { &*get_window_state(window) }; let mut inner_state = window_state.inner_state.borrow_mut(); inner_state.is_window_focused = is_window_focused; - let focus = inner_state.focus; drop(inner_state); if let Some(adapter) = Lazy::get(&window_state.adapter) { - let events = adapter.update(TreeUpdate { - nodes: vec![], - tree: None, - focus: is_window_focused.then_some(focus), - }); + let events = adapter.update_window_focus_state(is_window_focused); events.raise(); } } -struct WindowCreateParams(TreeUpdate, NodeId, Box); +struct WindowCreateParams(TreeUpdate, Box); extern "system" fn wndproc(window: HWND, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { match message { @@ -87,19 +81,22 @@ extern "system" fn wndproc(window: HWND, message: u32, wparam: WPARAM, lparam: L let create_struct: &CREATESTRUCTW = unsafe { &mut *(lparam.0 as *mut _) }; let create_params: Box = unsafe { Box::from_raw(create_struct.lpCreateParams as _) }; - let WindowCreateParams(initial_state, initial_focus, action_handler) = *create_params; + let WindowCreateParams(initial_state, action_handler) = *create_params; let inner_state = Rc::new(RefCell::new(InnerWindowState { - focus: initial_focus, is_window_focused: false, })); let inner_state_for_tree_init = inner_state.clone(); let uia_init_marker = UiaInitMarker::new(); let state = Box::new(WindowState { adapter: Lazy::new(Box::new(move || { - let mut initial_tree = initial_state; let state = inner_state_for_tree_init.borrow(); - initial_tree.focus = state.is_window_focused.then_some(state.focus); - Adapter::new(window, initial_tree, action_handler, uia_init_marker) + Adapter::new( + window, + initial_state, + state.is_window_focused, + action_handler, + uia_init_marker, + ) })), inner_state, }); @@ -135,11 +132,11 @@ extern "system" fn wndproc(window: HWND, message: u32, wparam: WPARAM, lparam: L ) } WM_SETFOCUS | WM_EXITMENULOOP | WM_EXITSIZEMOVE => { - update_focus(window, true); + update_window_focus_state(window, true); LRESULT(0) } WM_KILLFOCUS | WM_ENTERMENULOOP | WM_ENTERSIZEMOVE => { - update_focus(window, false); + update_window_focus_state(window, false); LRESULT(0) } _ => unsafe { DefWindowProcW(window, message, wparam, lparam) }, @@ -149,14 +146,9 @@ extern "system" fn wndproc(window: HWND, message: u32, wparam: WPARAM, lparam: L fn create_window( title: &str, initial_state: TreeUpdate, - initial_focus: NodeId, action_handler: Box, ) -> Result { - let create_params = Box::new(WindowCreateParams( - initial_state, - initial_focus, - action_handler, - )); + let create_params = Box::new(WindowCreateParams(initial_state, action_handler)); let window = unsafe { CreateWindowExW( @@ -199,7 +191,6 @@ static MUTEX: Mutex<()> = Mutex::new(()); pub(crate) fn scope( window_title: &str, initial_state: TreeUpdate, - initial_focus: NodeId, action_handler: Box, f: F, ) -> Result<()> @@ -220,8 +211,7 @@ where // initialized after the window is shown (as is the case, // at least on some Windows 10 machines, due to IME support). - let window = - create_window(window_title, initial_state, initial_focus, action_handler).unwrap(); + let window = create_window(window_title, initial_state, action_handler).unwrap(); { let mut state = window_mutex.lock().unwrap(); diff --git a/platforms/windows/src/tests/simple.rs b/platforms/windows/src/tests/simple.rs index 4c6230e7..ceedfcc9 100644 --- a/platforms/windows/src/tests/simple.rs +++ b/platforms/windows/src/tests/simple.rs @@ -40,7 +40,7 @@ fn get_initial_state() -> TreeUpdate { (BUTTON_2_ID, button_2), ], tree: Some(Tree::new(WINDOW_ID)), - focus: None, + focus: BUTTON_1_ID, } } @@ -57,7 +57,6 @@ where super::scope( WINDOW_TITLE, get_initial_state(), - BUTTON_1_ID, Box::new(NullActionHandler {}), f, ) diff --git a/platforms/windows/src/tests/subclassed.rs b/platforms/windows/src/tests/subclassed.rs index ecde22e3..843b8288 100644 --- a/platforms/windows/src/tests/subclassed.rs +++ b/platforms/windows/src/tests/subclassed.rs @@ -46,7 +46,7 @@ fn get_initial_state() -> TreeUpdate { (BUTTON_2_ID, button_2), ], tree: Some(Tree::new(WINDOW_ID)), - focus: None, + focus: BUTTON_1_ID, } } diff --git a/platforms/winit/examples/simple.rs b/platforms/winit/examples/simple.rs index 0ff964b6..146cf8cb 100644 --- a/platforms/winit/examples/simple.rs +++ b/platforms/winit/examples/simple.rs @@ -56,7 +56,6 @@ fn build_announcement(text: &str, classes: &mut NodeClassSet) -> Node { struct State { focus: NodeId, - is_window_focused: bool, announcement: Option, node_classes: NodeClassSet, } @@ -65,16 +64,11 @@ impl State { fn new() -> Arc> { Arc::new(Mutex::new(Self { focus: INITIAL_FOCUS, - is_window_focused: false, announcement: None, node_classes: NodeClassSet::new(), })) } - fn focus(&self) -> Option { - self.is_window_focused.then_some(self.focus) - } - fn build_root(&mut self) -> Node { let mut builder = NodeBuilder::new(Role::Window); builder.set_children(vec![BUTTON_1_ID, BUTTON_2_ID]); @@ -96,7 +90,7 @@ impl State { (BUTTON_2_ID, button_2), ], tree: Some(Tree::new(WINDOW_ID)), - focus: self.focus(), + focus: self.focus, }; if let Some(announcement) = &self.announcement { result.nodes.push(( @@ -107,11 +101,12 @@ impl State { result } - fn update_focus(&mut self, adapter: &Adapter) { + fn set_focus(&mut self, adapter: &Adapter, focus: NodeId) { + self.focus = focus; adapter.update_if_active(|| TreeUpdate { nodes: vec![], tree: None, - focus: self.focus(), + focus, }); } @@ -128,7 +123,7 @@ impl State { TreeUpdate { nodes: vec![(ANNOUNCEMENT_ID, announcement), (WINDOW_ID, root)], tree: None, - focus: self.focus(), + focus: self.focus, } }); } @@ -184,11 +179,6 @@ fn main() { WindowEvent::CloseRequested => { *control_flow = ControlFlow::ExitWithCode(0); } - WindowEvent::Focused(is_window_focused) => { - let mut state = state.lock().unwrap(); - state.is_window_focused = is_window_focused; - state.update_focus(&adapter); - } WindowEvent::KeyboardInput { input: KeyboardInput { @@ -200,12 +190,12 @@ fn main() { } => match virtual_code { VirtualKeyCode::Tab => { let mut state = state.lock().unwrap(); - state.focus = if state.focus == BUTTON_1_ID { + let new_focus = if state.focus == BUTTON_1_ID { BUTTON_2_ID } else { BUTTON_1_ID }; - state.update_focus(&adapter); + state.set_focus(&adapter, new_focus); } VirtualKeyCode::Space => { let mut state = state.lock().unwrap(); @@ -228,8 +218,7 @@ fn main() { let mut state = state.lock().unwrap(); match action { Action::Focus => { - state.focus = target; - state.update_focus(&adapter); + state.set_focus(&adapter, target); } Action::Default => { state.press_button(&adapter, target); diff --git a/platforms/winit/src/lib.rs b/platforms/winit/src/lib.rs index 236802a6..c6f4c95f 100644 --- a/platforms/winit/src/lib.rs +++ b/platforms/winit/src/lib.rs @@ -149,6 +149,10 @@ pub struct Adapter { } impl Adapter { + /// Creates a new AccessKit adapter for a winit window. This must be done + /// before the window is shown for the first time. This means that you must + /// use [`winit::window::WindowBuilder::with_visible`] to make the window + /// initially invisible, then create the adapter, then show the window. pub fn new + Send + 'static>( window: &Window, source: impl 'static + FnOnce() -> TreeUpdate + Send, @@ -158,6 +162,11 @@ impl Adapter { Self::with_action_handler(window, source, Box::new(action_handler)) } + /// Creates a new AccessKit adapter for a winit window. This must be done + /// before the window is shown for the first time. This means that you must + /// use [`winit::window::WindowBuilder::with_visible`] to make the window + /// initially invisible, then create the adapter, then show the window. + /// /// Use this if you need to provide your own AccessKit action handler /// rather than dispatching action requests through the winit event loop. /// Remember that an AccessKit action handler can be called on any thread, @@ -171,70 +180,9 @@ impl Adapter { Self { adapter } } - #[cfg(not(all( - feature = "accesskit_unix", - any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ) - )))] - #[must_use] - pub fn on_event(&self, _window: &Window, _event: &WindowEvent) -> bool { - true - } - #[cfg(all( - feature = "accesskit_unix", - any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" - ) - ))] #[must_use] pub fn on_event(&self, window: &Window, event: &WindowEvent) -> bool { - use accesskit::Rect; - - match event { - WindowEvent::Moved(outer_position) => { - let outer_position: (_, _) = outer_position.cast::().into(); - let outer_size: (_, _) = window.outer_size().cast::().into(); - let inner_position: (_, _) = window - .inner_position() - .unwrap_or_default() - .cast::() - .into(); - let inner_size: (_, _) = window.inner_size().cast::().into(); - self.adapter.set_root_window_bounds( - Rect::from_origin_size(outer_position, outer_size), - Rect::from_origin_size(inner_position, inner_size), - ) - } - WindowEvent::Resized(outer_size) => { - let outer_position: (_, _) = window - .outer_position() - .unwrap_or_default() - .cast::() - .into(); - let outer_size: (_, _) = outer_size.cast::().into(); - let inner_position: (_, _) = window - .inner_position() - .unwrap_or_default() - .cast::() - .into(); - let inner_size: (_, _) = window.inner_size().cast::().into(); - self.adapter.set_root_window_bounds( - Rect::from_origin_size(outer_position, outer_size), - Rect::from_origin_size(inner_position, inner_size), - ) - } - _ => (), - } - true + self.adapter.on_event(window, event) } pub fn update(&self, update: TreeUpdate) { diff --git a/platforms/winit/src/platform_impl/macos.rs b/platforms/winit/src/platform_impl/macos.rs index 5037b086..2f313640 100644 --- a/platforms/winit/src/platform_impl/macos.rs +++ b/platforms/winit/src/platform_impl/macos.rs @@ -4,7 +4,7 @@ use accesskit::{ActionHandler, TreeUpdate}; use accesskit_macos::SubclassingAdapter; -use winit::{platform::macos::WindowExtMacOS, window::Window}; +use winit::{event::WindowEvent, platform::macos::WindowExtMacOS, window::Window}; pub type ActionHandlerBox = Box; @@ -33,4 +33,14 @@ impl Adapter { events.raise(); } } + + pub fn on_event(&self, _window: &Window, event: &WindowEvent) -> bool { + if let WindowEvent::Focused(is_focused) = event { + if let Some(events) = self.adapter.update_view_focus_state(*is_focused) { + events.raise(); + } + } + + true + } } diff --git a/platforms/winit/src/platform_impl/null.rs b/platforms/winit/src/platform_impl/null.rs index 26ec48a0..2b462b7d 100644 --- a/platforms/winit/src/platform_impl/null.rs +++ b/platforms/winit/src/platform_impl/null.rs @@ -3,7 +3,7 @@ // the LICENSE-APACHE file). use accesskit::{ActionHandler, TreeUpdate}; -use winit::window::Window; +use winit::{event::WindowEvent, window::Window}; pub type ActionHandlerBox = Box; @@ -21,4 +21,8 @@ impl Adapter { pub fn update(&self, _update: TreeUpdate) {} pub fn update_if_active(&self, _updater: impl FnOnce() -> TreeUpdate) {} + + pub fn on_event(&self, _window: &Window, _event: &WindowEvent) -> bool { + true + } } diff --git a/platforms/winit/src/platform_impl/unix.rs b/platforms/winit/src/platform_impl/unix.rs index 3027832c..49b6114d 100644 --- a/platforms/winit/src/platform_impl/unix.rs +++ b/platforms/winit/src/platform_impl/unix.rs @@ -4,7 +4,7 @@ use accesskit::{ActionHandler, Rect, TreeUpdate}; use accesskit_unix::Adapter as UnixAdapter; -use winit::window::Window; +use winit::{event::WindowEvent, window::Window}; pub type ActionHandlerBox = Box; @@ -23,12 +23,13 @@ impl Adapter { String::new(), String::new(), source, + false, action_handler, ); Self { adapter } } - pub fn set_root_window_bounds(&self, outer: Rect, inner: Rect) { + fn set_root_window_bounds(&self, outer: Rect, inner: Rect) { if let Some(adapter) = &self.adapter { adapter.set_root_window_bounds(outer, inner); } @@ -45,4 +46,53 @@ impl Adapter { adapter.update(updater()); } } + + fn update_window_focus_state(&self, is_focused: bool) { + if let Some(adapter) = &self.adapter { + adapter.update_window_focus_state(is_focused); + } + } + + pub fn on_event(&self, window: &Window, event: &WindowEvent) -> bool { + match event { + WindowEvent::Moved(outer_position) => { + let outer_position: (_, _) = outer_position.cast::().into(); + let outer_size: (_, _) = window.outer_size().cast::().into(); + let inner_position: (_, _) = window + .inner_position() + .unwrap_or_default() + .cast::() + .into(); + let inner_size: (_, _) = window.inner_size().cast::().into(); + self.set_root_window_bounds( + Rect::from_origin_size(outer_position, outer_size), + Rect::from_origin_size(inner_position, inner_size), + ) + } + WindowEvent::Resized(outer_size) => { + let outer_position: (_, _) = window + .outer_position() + .unwrap_or_default() + .cast::() + .into(); + let outer_size: (_, _) = outer_size.cast::().into(); + let inner_position: (_, _) = window + .inner_position() + .unwrap_or_default() + .cast::() + .into(); + let inner_size: (_, _) = window.inner_size().cast::().into(); + self.set_root_window_bounds( + Rect::from_origin_size(outer_position, outer_size), + Rect::from_origin_size(inner_position, inner_size), + ) + } + WindowEvent::Focused(is_focused) => { + self.update_window_focus_state(*is_focused); + } + _ => (), + } + + true + } } diff --git a/platforms/winit/src/platform_impl/windows.rs b/platforms/winit/src/platform_impl/windows.rs index 81006b6a..56a9fd36 100644 --- a/platforms/winit/src/platform_impl/windows.rs +++ b/platforms/winit/src/platform_impl/windows.rs @@ -4,7 +4,7 @@ use accesskit::{ActionHandler, TreeUpdate}; use accesskit_windows::{SubclassingAdapter, HWND}; -use winit::{platform::windows::WindowExtWindows, window::Window}; +use winit::{event::WindowEvent, platform::windows::WindowExtWindows, window::Window}; pub type ActionHandlerBox = Box; @@ -32,4 +32,8 @@ impl Adapter { events.raise(); } } + + pub fn on_event(&self, _window: &Window, _event: &WindowEvent) -> bool { + true + } }