refactor(selection): split 31-field SelectionState into five sub-structs

SelectionState now holds view, gesture, input, menu and orbit sub-structs; all 450 access sites updated mechanically with no behavior change. Deletes three write-only dead fields (box_last, box_last_crossing, poly_last_crossing). Hand-rolled clears in commands and dim_viewport route through the new clear methods; bench fixture updated. Full suite: 1976 passed / 0 failed / 25 ignored.
This commit is contained in:
Karim Jerbi committed 2026-10-06 20:22:04 +01:00
1 parent 8c2cfa2c8f
commit 7aafd9c710
27 files changed
+512 -469

No files matched your search

+20 -20
View File
@@ -19,7 +19,7 @@ impl OpenCADStudio {
pub(in crate::app) fn context_menu_open(&self) -> bool {
self.tabs
.get(self.active_tab)
.is_some_and(|tab| tab.scene.selection.borrow().context_menu.is_some())
.is_some_and(|tab| tab.scene.selection.borrow().menu.open_at.is_some())
}
/// Everything the menu builder needs, read from the active tab. Called by
@@ -71,7 +71,7 @@ impl OpenCADStudio {
.scene
.selection
.borrow()
.context_menu_ui
.menu.ui
.open_submenu;
build_context_menu(&self.context_menu_context(), open)
}
@@ -80,10 +80,10 @@ impl OpenCADStudio {
pub(in crate::app) fn close_context_menu(&mut self) -> Task<Message> {
let i = self.active_tab;
let mut sel = self.tabs[i].scene.selection.borrow_mut();
if sel.context_menu.is_none() {
if sel.menu.open_at.is_none() {
return Task::none();
}
sel.close_context_menu();
sel.menu.close();
drop(sel);
self.focus_cmd_input()
}
@@ -91,7 +91,7 @@ impl OpenCADStudio {
pub(in crate::app) fn on_context_menu_submenu_toggle(&mut self, id: SubmenuId) -> Task<Message> {
let i = self.active_tab;
let mut sel = self.tabs[i].scene.selection.borrow_mut();
let ui = &mut sel.context_menu_ui;
let ui = &mut sel.menu.ui;
ui.open_submenu = if ui.open_submenu == Some(id) { None } else { Some(id) };
drop(sel);
// Keep the keyboard highlight on the header that was toggled.
@@ -101,8 +101,8 @@ impl OpenCADStudio {
.iter()
.position(|s| s.header == Some(id));
let mut sel = self.tabs[i].scene.selection.borrow_mut();
if sel.context_menu_ui.highlighted.is_some() {
sel.context_menu_ui.highlighted = header;
if sel.menu.ui.highlighted.is_some() {
sel.menu.ui.highlighted = header;
}
Task::none()
}
@@ -118,10 +118,10 @@ impl OpenCADStudio {
let i = self.active_tab;
{
let mut sel = self.tabs[i].scene.selection.borrow_mut();
sel.close_context_menu();
sel.menu.close();
// A pick counts as "another interaction" for the Enter-first
// right-click cycle: the next right-click acts as Enter again.
sel.right_click_entered = false;
sel.input.right_click_entered = false;
}
let focus = self.focus_cmd_input();
let task = match action {
@@ -174,7 +174,7 @@ impl OpenCADStudio {
if rows.is_empty() {
return Task::none();
}
let current = self.tabs[i].scene.selection.borrow().context_menu_ui.highlighted;
let current = self.tabs[i].scene.selection.borrow().menu.ui.highlighted;
let next_enabled = |from: usize, step: isize| -> usize {
let n = rows.len() as isize;
let mut idx = from as isize;
@@ -193,7 +193,7 @@ impl OpenCADStudio {
None => menu.default_index(),
Some(cur) => next_enabled(cur, 1),
};
self.tabs[i].scene.selection.borrow_mut().context_menu_ui.highlighted = Some(idx);
self.tabs[i].scene.selection.borrow_mut().menu.ui.highlighted = Some(idx);
Task::none()
}
ContextMenuNav::Up => {
@@ -201,7 +201,7 @@ impl OpenCADStudio {
None => menu.default_index(),
Some(cur) => next_enabled(cur, -1),
};
self.tabs[i].scene.selection.borrow_mut().context_menu_ui.highlighted = Some(idx);
self.tabs[i].scene.selection.borrow_mut().menu.ui.highlighted = Some(idx);
Task::none()
}
ContextMenuNav::Enter => {
@@ -220,7 +220,7 @@ impl OpenCADStudio {
if unique {
self.on_context_menu_pick(rows[idx].action.clone())
} else {
self.tabs[i].scene.selection.borrow_mut().context_menu_ui.highlighted =
self.tabs[i].scene.selection.borrow_mut().menu.ui.highlighted =
Some(idx);
Task::none()
}
@@ -244,7 +244,7 @@ impl OpenCADStudio {
ArrowKey::Left | ArrowKey::Right => {
// On a submenu header, Right expands and Left collapses.
let i = self.active_tab;
let ui = self.tabs[i].scene.selection.borrow().context_menu_ui.clone();
let ui = self.tabs[i].scene.selection.borrow().menu.ui.clone();
let menu = self.current_context_menu();
let rows = menu.selectable();
let header = ui.highlighted.and_then(|c| rows.get(c)).and_then(|r| r.header);
@@ -410,7 +410,7 @@ mod tests {
fn app() -> OpenCADStudio {
let mut app = OpenCADStudio::new_for_test();
app.automation_op(r#"{"op":"new"}"#);
app.tabs[0].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
app.tabs[0].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
let _ = app.update(Message::ViewportMove(Point::new(200.0, 150.0)));
app
}
@@ -428,7 +428,7 @@ mod tests {
}
fn menu_open(app: &OpenCADStudio) -> bool {
app.tabs[0].scene.selection.borrow().context_menu.is_some()
app.tabs[0].scene.selection.borrow().menu.open_at.is_some()
}
fn active(app: &OpenCADStudio) -> Option<&'static str> {
@@ -491,7 +491,7 @@ mod tests {
let _ = app.update(Message::ViewportRightPress);
{
let mut sel = app.tabs[0].scene.selection.borrow_mut();
sel.right_press_time = iced::time::Instant::now()
sel.input.right_press_time = iced::time::Instant::now()
.checked_sub(std::time::Duration::from_millis(400));
}
let _ = app.update(Message::ViewportRightRelease);
@@ -558,7 +558,7 @@ mod tests {
let _ = app.update(Message::ContextMenuPick(MenuAction::Command("LINE".into())));
assert!(!menu_open(&app));
assert_eq!(active(&app), Some("LINE"));
assert!(!app.tabs[0].scene.selection.borrow().right_click_entered);
assert!(!app.tabs[0].scene.selection.borrow().input.right_click_entered);
});
}
@@ -582,7 +582,7 @@ mod tests {
right_click(&mut app);
let _ = app.update(Message::ContextMenuNavigate(ContextMenuNav::Down)); // Enter row
assert_eq!(
app.tabs[0].scene.selection.borrow().context_menu_ui.highlighted,
app.tabs[0].scene.selection.borrow().menu.ui.highlighted,
Some(0)
);
let _ = app.update(Message::ContextMenuNavigate(ContextMenuNav::Down)); // Cancel
@@ -853,7 +853,7 @@ mod transparent_tests {
fn line_app() -> OpenCADStudio {
let mut app = OpenCADStudio::new_for_test();
app.automation_op(r#"{"op":"new"}"#);
app.tabs[0].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
app.tabs[0].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
let _ = app.dispatch_command("LINE");
let _ = app.feed_command(StepInput::Point(DVec3::ZERO));
app