git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

commit6f3784ee4db842ccb4cb0c180d006215eb9fae81
parenta786f92898
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-08 12:34
refactor(focus): drive focus through UiContext::focus_widget / unfocus_widget

A direct w.focus() / w.unfocus() left the window's record of focus (the Tab
walk, the accessibility tree) behind: on a widget that had let go, or beside
a newly focused one. The UiContext helpers do what the direct call did and
keep the record in step (cce-ui's global-state RFC, phase 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 src/app.rs  | 24 ++++++++++++------------
 src/main.rs |  8 ++++----
 2 files changed, 16 insertions(+), 16 deletions(-)

diff --git a/src/app.rs b/src/app.rs
index b0c10f4..636388e 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -7122,7 +7122,7 @@ impl State {
         if self.focused_pane != LEFT_MENUBAR_IDX {
             self.focused_pane = LEFT_MENUBAR_IDX;
             if let Some(old) = self.focused_widget.take() {
-                self.slots.get_dyn_mut(old).unfocus();
+                self.ui_context.unfocus_widget(self.slots.get_dyn_mut(old));
             }
             self.sync_pane_focus();
         }
@@ -9424,7 +9424,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
         self.slots.param_menubar.set_selected(PARAM_MENUBAR_IDX == f);
         self.slots.spreadsheet_menubar.set_selected(SPREADSHEET_MENUBAR_IDX == f);
         if self.focused_pane != PARAM_MENUBAR_IDX {
-            self.slots.param.unfocus();
+            self.ui_context.unfocus_widget(&mut self.slots.param);
             self.sync_parameters_to_project();
         }
         self.sync_context_dropdowns();
@@ -11150,10 +11150,10 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
                             if on_border {
                                 self.focused_pane = LEFT_MENUBAR_IDX;
                                 if let Some(old) = self.focused_widget {
-                                    self.slots.get_dyn_mut(old).unfocus();
+                                    self.ui_context.unfocus_widget(self.slots.get_dyn_mut(old));
                                     self.focused_widget = None;
                                 }
-                                self.slots.param.unfocus();
+                                self.ui_context.unfocus_widget(&mut self.slots.param);
                                 self.sync_parameters_to_project();
                                 return true;
                             }
@@ -11185,7 +11185,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
                                 });
                                 self.focused_pane = PARAM_MENUBAR_IDX;
                                 if let Some(old) = self.focused_widget {
-                                    self.slots.get_dyn_mut(old).unfocus();
+                                    self.ui_context.unfocus_widget(self.slots.get_dyn_mut(old));
                                     self.focused_widget = None;
                                 }
                                 return true;
@@ -11204,7 +11204,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
                                 });
                                 self.focused_pane = SPREADSHEET_MENUBAR_IDX;
                                 if let Some(old) = self.focused_widget {
-                                    self.slots.get_dyn_mut(old).unfocus();
+                                    self.ui_context.unfocus_widget(self.slots.get_dyn_mut(old));
                                     self.focused_widget = None;
                                 }
                                 return true;
@@ -11253,7 +11253,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
                             self.grid_cursor_drag = Some((col, row));
                             self.focused_pane = LEFT_MENUBAR_IDX;
                             if let Some(old) = self.focused_widget.take() {
-                                self.slots.get_dyn_mut(old).unfocus();
+                                self.ui_context.unfocus_widget(self.slots.get_dyn_mut(old));
                             }
                             self.sync_pane_focus();
                             return true;
@@ -11280,7 +11280,7 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
                             }
                             self.focused_pane = RIGHT_MENUBAR_IDX;
                             if let Some(old) = self.focused_widget {
-                                self.slots.get_dyn_mut(old).unfocus();
+                                self.ui_context.unfocus_widget(self.slots.get_dyn_mut(old));
                                 self.focused_widget = None;
                             }
                             self.sync_pane_focus();
@@ -11419,12 +11419,12 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
 
                         if let Some(old) = self.focused_widget {
                             if click_target != Some(old) && click_target != Some(PARAM_IDX) {
-                                self.slots.get_dyn_mut(old).unfocus();
+                                self.ui_context.unfocus_widget(self.slots.get_dyn_mut(old));
                                 self.focused_widget = None;
                             }
                         }
                         if click_target != Some(PARAM_IDX) {
-                            self.slots.param.unfocus();
+                            self.ui_context.unfocus_widget(&mut self.slots.param);
                             self.sync_parameters_to_project();
                         }
                         if click_target.is_none() && in_circle_network_pane {
@@ -11470,12 +11470,12 @@ pub(crate) fn geometry_to_spreadsheet_columns(geom: &Detail) -> (Vec<String>, Ve
                                 self.drag_press_cursor = Some((self.cursor_x, self.cursor_y));
                             }
                             if i != PARAM_IDX {
-                                self.slots.get_dyn_mut(i).focus();
+                                self.ui_context.focus_widget(self.slots.get_dyn_mut(i));
                                 self.focused_widget = Some(i);
                                 if self.menubar_at(i).map(|m| m.is_menu_bar()).unwrap_or(false)
                                     && !self.slots.get_dyn_mut(i).focused(&self.ui_context)
                                 {
-                                    self.slots.get_dyn_mut(i).unfocus();
+                                    self.ui_context.unfocus_widget(self.slots.get_dyn_mut(i));
                                     self.focused_widget = None;
                                 }
                             }
diff --git a/src/main.rs b/src/main.rs
index b8e8b3b..1ef7d16 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2528,7 +2528,7 @@ mod tests {
         assert!((state.geo_opacity - 0.40).abs() < 1e-6);
 
         // Press on the band's right end: 100%, live; drag back; release.
-        let band = context_menu::CONTEXT_MENU.with(|m| m.borrow().slider_band(i));
+        let band = context_menu::with_state(|m| m.borrow().slider_band(i));
         state.cursor_x = band.x + band.width - 1.0;
         state.handle_event(&WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left });
         assert!((state.geo_opacity - 1.0).abs() < 1e-6, "{}", state.geo_opacity);
@@ -3266,7 +3266,7 @@ mod tests {
         assert!(kdl.contains("wire_width (f64)3") || kdl.contains("wire_width 3"), "persisted: {kdl}");
         assert!(state.viewport_menu_open());
 
-        let band = context_menu::CONTEXT_MENU.with(|m| m.borrow().slider_band(i));
+        let band = context_menu::with_state(|m| m.borrow().slider_band(i));
         state.cursor_x = band.x + 1.0;
         state.handle_event(&WindowEvent::MouseInput { state: ElementState::Pressed, button: MouseButton::Left });
         state.handle_event(&WindowEvent::MouseInput { state: ElementState::Released, button: MouseButton::Left });
@@ -3290,7 +3290,7 @@ mod tests {
         state.cursor_x = 300.0;
         state.cursor_y = 200.0;
         state.open_viewport_context_menu();
-        let full = context_menu::CONTEXT_MENU.with(|m| m.borrow().content_h);
+        let full = context_menu::with_state(|m| m.borrow().content_h);
         context_menu::place(300.0, 0.0, full * 0.5);
 
         // Over the first row, which is Frame All — an action, not a slider.
@@ -3301,7 +3301,7 @@ mod tests {
             position: LocalPosition { x: state.cursor_x as f64, y: state.cursor_y as f64 },
         });
         state.handle_event(&WindowEvent::MouseWheel { delta: MouseScrollDelta::LineDelta(0.0, -3.0) });
-        let scroll = context_menu::CONTEXT_MENU.with(|m| m.borrow().scroll);
+        let scroll = context_menu::with_state(|m| m.borrow().scroll);
         assert_eq!(scroll, 3.0 * context_menu::ROW_H, "three notches down, three rows");
         assert!(state.viewport_menu_open(), "scrolling keeps the menu up");