From 2519d64907d578945e69f8d7fb5f672c8db03489 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Fri, 12 Jun 2026 01:22:59 +0300 Subject: [PATCH] fix(select): box/lasso adds to the selection (#83) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Box selection is a two-click window (corner, corner): the first empty click armed the box AND cleared the selection, and the box completion cleared again before applying its hits — so a second box dropped everything the first one picked. Stop clearing on the arming click and make the completion accumulate like individual picks (#47): a plain box or lasso adds, Shift+box/lasso removes the enclosed entities, an empty box/lasso (or Esc) clears. Fixes #83 Co-Authored-By: Claude Opus 4.8 --- src/app/update.rs | 61 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/src/app/update.rs b/src/app/update.rs index b611235e6..bc1d117a1 100644 --- a/src/app/update.rs +++ b/src/app/update.rs @@ -3320,11 +3320,20 @@ impl OpenCADStudio { vp_mat, bounds, )); - self.tabs[i].scene.deselect_all(); - for h in &handles { - self.tabs[i].scene.select_entity(*h, false); + // Box/lasso accumulates like individual picks + // (issue #83): a plain box adds to the current + // selection, Shift+box removes the boxed + // entities. Esc / empty-space click still clears. + if self.shift_down { + for h in &handles { + self.tabs[i].scene.deselect_entity(*h); + } + } else { + for h in &handles { + self.tabs[i].scene.select_entity(*h, false); + } + self.tabs[i].scene.expand_selection_for_groups(&handles); } - self.tabs[i].scene.expand_selection_for_groups(&handles); self.refresh_properties(); selection_just_completed = true; } @@ -3364,11 +3373,21 @@ impl OpenCADStudio { )); // Selection filter: keep only allowed types. handles.retain(|&h| self.tabs[i].scene.passes_selection_filter(h)); - self.tabs[i].scene.deselect_all(); - for h in &handles { - self.tabs[i].scene.select_entity(*h, false); + // Accumulate like the box path (issue #83): plain + // lasso adds, Shift+lasso removes, an empty lasso + // clears. + if self.shift_down { + for h in &handles { + self.tabs[i].scene.deselect_entity(*h); + } + } else if handles.is_empty() { + self.tabs[i].scene.deselect_all(); + } else { + for h in &handles { + self.tabs[i].scene.select_entity(*h, false); + } + self.tabs[i].scene.expand_selection_for_groups(&handles); } - self.tabs[i].scene.expand_selection_for_groups(&handles); self.refresh_properties(); selection_just_completed = true; } @@ -3451,8 +3470,11 @@ impl OpenCADStudio { self.refresh_properties(); selection_just_completed = true; } else { - self.tabs[i].scene.deselect_all(); - self.refresh_properties(); + // Empty-space click only ARMS a box here; it + // no longer clears the selection, so a box can + // add to it (issue #83). The box completion + // (or Esc) decides what happens to the + // selection. let mut sel = self.tabs[i].scene.selection.borrow_mut(); // Full-canvas space: ViewportMove updates // box_current in canvas coords and the overlay @@ -3488,11 +3510,22 @@ impl OpenCADStudio { )); // Selection filter: keep only allowed types. handles.retain(|&h| self.tabs[i].scene.passes_selection_filter(h)); - self.tabs[i].scene.deselect_all(); - for h in &handles { - self.tabs[i].scene.select_entity(*h, false); + // Accumulate (issue #83): a plain box adds to the + // current selection, Shift+box removes the boxed + // entities, and an empty box clears (the two-click + // analogue of clicking empty space). + if self.shift_down { + for h in &handles { + self.tabs[i].scene.deselect_entity(*h); + } + } else if handles.is_empty() { + self.tabs[i].scene.deselect_all(); + } else { + for h in &handles { + self.tabs[i].scene.select_entity(*h, false); + } + self.tabs[i].scene.expand_selection_for_groups(&handles); } - self.tabs[i].scene.expand_selection_for_groups(&handles); self.refresh_properties(); let mut sel = self.tabs[i].scene.selection.borrow_mut(); sel.box_last = Some((a, p));