From d995c3fa9032d73b86307cfcd0d638f10f72531c Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Tue, 17 Sep 2024 10:13:40 -0500 Subject: [PATCH 1/5] Support the AccessKit focus and blur actions in textboxes --- masonry/src/widget/textbox.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/masonry/src/widget/textbox.rs b/masonry/src/widget/textbox.rs index 65022b2ca..8be866970 100644 --- a/masonry/src/widget/textbox.rs +++ b/masonry/src/widget/textbox.rs @@ -220,7 +220,22 @@ impl Widget for Textbox { } } - fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) { + fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) { + if event.target == ctx.widget_id() { + match event.action { + accesskit::Action::Focus => { + if !ctx.is_disabled() && !ctx.is_focused() { + ctx.request_focus(); + } + } + accesskit::Action::Blur => { + if ctx.is_focused() { + ctx.resign_focus(); + } + } + _ => {} + } + } // TODO - Handle accesskit::Action::SetTextSelection // TODO - Handle accesskit::Action::ReplaceSelectedText // TODO - Handle accesskit::Action::SetValue @@ -354,6 +369,8 @@ impl Widget for Textbox { } fn accessibility(&mut self, ctx: &mut AccessCtx) { + ctx.current_node().add_action(accesskit::Action::Focus); + ctx.current_node().add_action(accesskit::Action::Blur); // TODO: Replace with full accessibility. ctx.current_node().set_value(self.text()); } From 65a4b3c6ea13e0fcc97ea59e9ae48e6c134265d1 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Wed, 18 Sep 2024 07:39:05 -0500 Subject: [PATCH 2/5] Make this generic to all widgets in the focus chain --- masonry/src/contexts.rs | 6 ++++++ masonry/src/passes/accessibility.rs | 4 ++++ masonry/src/passes/event.rs | 17 +++++++++++++++++ masonry/src/passes/update.rs | 1 + masonry/src/widget/textbox.rs | 19 +------------------ masonry/src/widget/widget_state.rs | 4 ++++ 6 files changed, 33 insertions(+), 18 deletions(-) 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..2c890e4f2 100644 --- a/masonry/src/passes/accessibility.rs +++ b/masonry/src/passes/accessibility.rs @@ -111,6 +111,10 @@ fn build_access_node(widget: &dyn Widget, state: &WidgetState, scale_factor: f64 if state.clip.is_some() { node.set_clips_children(); } + if state.in_focus_chain { + node.add_action(accesskit::Action::Focus); + node.add_action(accesskit::Action::Blur); + } node } diff --git a/masonry/src/passes/event.rs b/masonry/src/passes/event.rs index 38d76dbdb..f47dc07ee 100644 --- a/masonry/src/passes/event.rs +++ b/masonry/src/passes/event.rs @@ -195,6 +195,23 @@ pub(crate) fn root_on_access_event( event, false, |widget, ctx, event| { + 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(); + } + return; + } + accesskit::Action::Blur => { + if ctx.is_focused() { + ctx.resign_focus(); + } + 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/textbox.rs b/masonry/src/widget/textbox.rs index 8be866970..65022b2ca 100644 --- a/masonry/src/widget/textbox.rs +++ b/masonry/src/widget/textbox.rs @@ -220,22 +220,7 @@ impl Widget for Textbox { } } - fn on_access_event(&mut self, ctx: &mut EventCtx, event: &AccessEvent) { - if event.target == ctx.widget_id() { - match event.action { - accesskit::Action::Focus => { - if !ctx.is_disabled() && !ctx.is_focused() { - ctx.request_focus(); - } - } - accesskit::Action::Blur => { - if ctx.is_focused() { - ctx.resign_focus(); - } - } - _ => {} - } - } + fn on_access_event(&mut self, _ctx: &mut EventCtx, _event: &AccessEvent) { // TODO - Handle accesskit::Action::SetTextSelection // TODO - Handle accesskit::Action::ReplaceSelectedText // TODO - Handle accesskit::Action::SetValue @@ -369,8 +354,6 @@ impl Widget for Textbox { } fn accessibility(&mut self, ctx: &mut AccessCtx) { - ctx.current_node().add_action(accesskit::Action::Focus); - ctx.current_node().add_action(accesskit::Action::Blur); // TODO: Replace with full accessibility. ctx.current_node().set_value(self.text()); } 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, From 20653c2da8584a418a03c312a1e66579b69a033f Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Wed, 18 Sep 2024 09:56:50 -0500 Subject: [PATCH 3/5] Refactor AccessKit node building code so code to set common properties has access to AccessCtx, like the widgets themselves --- masonry/src/passes/accessibility.rs | 31 +++++++++++++++++------------ 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/masonry/src/passes/accessibility.rs b/masonry/src/passes/accessibility.rs index 2c890e4f2..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,24 +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 state.in_focus_chain { - node.add_action(accesskit::Action::Focus); - node.add_action(accesskit::Action::Blur); + 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 { From c7e301715da37192ba8d40e077886fab70265a3c Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Wed, 18 Sep 2024 12:29:52 -0500 Subject: [PATCH 4/5] More tweaks to focus action handling --- masonry/src/passes/event.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/masonry/src/passes/event.rs b/masonry/src/passes/event.rs index f47dc07ee..9f0bf8503 100644 --- a/masonry/src/passes/event.rs +++ b/masonry/src/passes/event.rs @@ -200,14 +200,16 @@ pub(crate) fn root_on_access_event( accesskit::Action::Focus => { if ctx.is_in_focus_chain() && !ctx.is_disabled() && !ctx.is_focused() { ctx.request_focus(); + ctx.set_handled(); + return; } - return; } accesskit::Action::Blur => { if ctx.is_focused() { ctx.resign_focus(); + ctx.set_handled(); + return; } - return; } _ => {} } From e2eadf2310c0b0c156e2be4b6792f7c5f02164a1 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Thu, 19 Sep 2024 06:46:18 -0500 Subject: [PATCH 5/5] Add TODO comment suggesting new pass Co-authored-by: Olivier FAURE --- masonry/src/passes/event.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/masonry/src/passes/event.rs b/masonry/src/passes/event.rs index 9f0bf8503..44068c1e0 100644 --- a/masonry/src/passes/event.rs +++ b/masonry/src/passes/event.rs @@ -195,6 +195,7 @@ 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 => {