diff --git a/masonry/src/contexts.rs b/masonry/src/contexts.rs index 6de05b446..b0c816bce 100644 --- a/masonry/src/contexts.rs +++ b/masonry/src/contexts.rs @@ -301,6 +301,11 @@ impl_context_method!( self.widget_state.has_focus } + /// Whether this specific widget is in the focus chain. + pub fn is_in_focus_chain(&self) -> bool { + self.widget_state.in_focus_chain + } + /// The disabled state of a widget. /// /// Returns `true` if this widget or any of its ancestors is explicitly disabled. @@ -715,6 +720,7 @@ impl LifeCycleCtx<'_> { pub fn register_for_focus(&mut self) { trace!("register_for_focus"); self.widget_state.focus_chain.push(self.widget_id()); + self.widget_state.in_focus_chain = true; } /// Register this widget as accepting text input. diff --git a/masonry/src/passes/accessibility.rs b/masonry/src/passes/accessibility.rs index 7b407651b..4c7168e5c 100644 --- a/masonry/src/passes/accessibility.rs +++ b/masonry/src/passes/accessibility.rs @@ -46,6 +46,7 @@ fn build_accessibility_tree( rebuild_all, scale_factor, }; + set_common_properties(&mut ctx); widget.item.accessibility(&mut ctx); let id: NodeId = ctx.widget_state.id.into(); @@ -99,20 +100,28 @@ fn build_access_node(widget: &dyn Widget, state: &WidgetState, scale_factor: f64 .collect::>(), ); - if state.is_hot { - node.set_hovered(); + node +} + +fn set_common_properties(ctx: &mut AccessCtx) { + if ctx.is_hot() { + ctx.current_node().set_hovered(); } - if state.is_disabled { - node.set_disabled(); + if ctx.is_disabled() { + ctx.current_node().set_disabled(); } - if state.is_stashed { - node.set_hidden(); + if ctx.is_stashed() { + ctx.current_node().set_hidden(); } - if state.clip.is_some() { - node.set_clips_children(); + if ctx.widget_state.clip.is_some() { + ctx.current_node().set_clips_children(); + } + if ctx.is_in_focus_chain() && !ctx.is_disabled() { + ctx.current_node().add_action(accesskit::Action::Focus); + } + if ctx.is_focused() { + ctx.current_node().add_action(accesskit::Action::Blur); } - - node } fn to_accesskit_rect(r: Rect, scale_factor: f64) -> accesskit::Rect { diff --git a/masonry/src/passes/event.rs b/masonry/src/passes/event.rs index 38d76dbdb..44068c1e0 100644 --- a/masonry/src/passes/event.rs +++ b/masonry/src/passes/event.rs @@ -195,6 +195,26 @@ pub(crate) fn root_on_access_event( event, false, |widget, ctx, event| { + // TODO - Split into "access_event_focus" pass or something similar. + if event.target == ctx.widget_id() { + match event.action { + accesskit::Action::Focus => { + if ctx.is_in_focus_chain() && !ctx.is_disabled() && !ctx.is_focused() { + ctx.request_focus(); + ctx.set_handled(); + return; + } + } + accesskit::Action::Blur => { + if ctx.is_focused() { + ctx.resign_focus(); + ctx.set_handled(); + return; + } + } + _ => {} + } + } widget.on_access_event(ctx, event); }, ); diff --git a/masonry/src/passes/update.rs b/masonry/src/passes/update.rs index b7381ff1b..d06b97812 100644 --- a/masonry/src/passes/update.rs +++ b/masonry/src/passes/update.rs @@ -502,6 +502,7 @@ fn update_focus_chain_for_widget( state.item.has_focus = global_state.focused_widget == Some(id); let had_focus = state.item.has_focus; + state.item.in_focus_chain = false; state.item.focus_chain.clear(); { let mut ctx = LifeCycleCtx { diff --git a/masonry/src/widget/widget_state.rs b/masonry/src/widget/widget_state.rs index 8a16bbb27..4e868a28b 100644 --- a/masonry/src/widget/widget_state.rs +++ b/masonry/src/widget/widget_state.rs @@ -133,6 +133,9 @@ pub struct WidgetState { /// Descendants of the focused widget are not in the focused path. pub(crate) has_focus: bool, + /// Whether this specific widget is in the focus chain. + pub(crate) in_focus_chain: bool, + // TODO - document pub(crate) is_stashed: bool, @@ -179,6 +182,7 @@ impl WidgetState { request_accessibility: true, needs_accessibility: true, has_focus: false, + in_focus_chain: false, request_anim: true, needs_anim: true, needs_update_disabled: true,