fix(select): accumulate selection set, apply on Enter/RMB (#220)
Modify commands invoked with nothing pre-selected (ERASE, MOVE, COPY, ROTATE, SCALE, MIRROR, ARRAY*, STRETCH, EXPLODE, GROUP, BLOCK, the clipboard cuts, LAYOFF/FRZ/LCK/ULK, ...) ran the command on the FIRST picked object and terminated. You could not build up a selection. SelectObjectsCommand now keeps gathering: each pick accumulates into the set (Shift removes), the prompt shows the running count, and the command fires only when the user presses Enter or right-clicks. Reuses the existing JOIN gather pattern, so no viewport/host changes are needed. LAYMCUR acts on a single object's layer, so it keeps the immediate first-pick behaviour via the new SelectObjectsCommand::instant(). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
57136b9eda
commit
0a5d85aa8a
2 changed files with 53 additions and 7 deletions
|
|
@ -301,7 +301,8 @@ impl OpenCADStudio {
|
||||||
let entities = self.tabs[i].scene.selected_entities();
|
let entities = self.tabs[i].scene.selected_entities();
|
||||||
if entities.is_empty() {
|
if entities.is_empty() {
|
||||||
use crate::modules::draw::select::SelectObjectsCommand;
|
use crate::modules::draw::select::SelectObjectsCommand;
|
||||||
let cmd = SelectObjectsCommand::new("LAYMCUR");
|
// LAYMCUR acts on one object's layer; apply on the first pick.
|
||||||
|
let cmd = SelectObjectsCommand::instant("LAYMCUR");
|
||||||
self.command_line.push_info(&cmd.prompt());
|
self.command_line.push_info(&cmd.prompt());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,11 @@
|
||||||
//
|
//
|
||||||
// Used when a modify command is invoked with nothing pre-selected.
|
// Used when a modify command is invoked with nothing pre-selected.
|
||||||
// The user may single-click, box-select, or polygon-select any number of objects.
|
// The user may single-click, box-select, or polygon-select any number of objects.
|
||||||
// After the FIRST completed selection action (regardless of method) the command fires
|
// Picks accumulate into the selection (Shift removes); the set is applied when
|
||||||
// immediately — no Enter required.
|
// the user presses Enter or right-clicks (standard "Select objects:" behaviour).
|
||||||
|
//
|
||||||
|
// Single-object commands (e.g. LAYMCUR) use `instant()` instead: the first
|
||||||
|
// completed selection action fires straight away, no Enter required.
|
||||||
|
|
||||||
use acadrust::Handle;
|
use acadrust::Handle;
|
||||||
use glam::DVec3;
|
use glam::DVec3;
|
||||||
|
|
@ -13,12 +16,31 @@ use crate::scene::model::wire_model::WireModel;
|
||||||
|
|
||||||
pub struct SelectObjectsCommand {
|
pub struct SelectObjectsCommand {
|
||||||
pending_cmd: String,
|
pending_cmd: String,
|
||||||
|
/// Selection accumulated so far (kept in sync with the scene selection by
|
||||||
|
/// each `on_selection_complete` call). Applied on Enter when `commit_on_enter`.
|
||||||
|
handles: Vec<Handle>,
|
||||||
|
/// When true, gathering continues until Enter / right-click commits the set.
|
||||||
|
/// When false, the first completed selection action fires immediately.
|
||||||
|
commit_on_enter: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectObjectsCommand {
|
impl SelectObjectsCommand {
|
||||||
|
/// Standard selection set: accumulate picks, apply on Enter / right-click.
|
||||||
pub fn new(pending_cmd: &str) -> Self {
|
pub fn new(pending_cmd: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
pending_cmd: pending_cmd.to_string(),
|
pending_cmd: pending_cmd.to_string(),
|
||||||
|
handles: Vec::new(),
|
||||||
|
commit_on_enter: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Single-object variant: the first completed selection action applies
|
||||||
|
/// immediately, with no Enter (used by commands that act on one object).
|
||||||
|
pub fn instant(pending_cmd: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
pending_cmd: pending_cmd.to_string(),
|
||||||
|
handles: Vec::new(),
|
||||||
|
commit_on_enter: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -29,7 +51,15 @@ impl CadCommand for SelectObjectsCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prompt(&self) -> String {
|
fn prompt(&self) -> String {
|
||||||
format!("{} Select objects:", self.pending_cmd)
|
if self.commit_on_enter && !self.handles.is_empty() {
|
||||||
|
format!(
|
||||||
|
"{} Select objects ({} selected, Enter to apply):",
|
||||||
|
self.pending_cmd,
|
||||||
|
self.handles.len()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!("{} Select objects:", self.pending_cmd)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Opt into the selection-gathering path; host routes clicks through
|
// Opt into the selection-gathering path; host routes clicks through
|
||||||
|
|
@ -39,20 +69,35 @@ impl CadCommand for SelectObjectsCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_selection_complete(&mut self, handles: Vec<Handle>) -> CmdResult {
|
fn on_selection_complete(&mut self, handles: Vec<Handle>) -> CmdResult {
|
||||||
|
if self.commit_on_enter {
|
||||||
|
// Keep gathering: remember the running set and update the prompt.
|
||||||
|
// The command is applied when the user presses Enter / right-clicks.
|
||||||
|
self.handles = handles;
|
||||||
|
return CmdResult::NeedPoint;
|
||||||
|
}
|
||||||
|
// Single-object variant: apply on the first non-empty selection.
|
||||||
if handles.is_empty() {
|
if handles.is_empty() {
|
||||||
return CmdResult::NeedPoint;
|
return CmdResult::NeedPoint;
|
||||||
}
|
}
|
||||||
CmdResult::Relaunch(std::mem::take(&mut self.pending_cmd), handles)
|
CmdResult::Relaunch(std::mem::take(&mut self.pending_cmd), handles)
|
||||||
}
|
}
|
||||||
|
|
||||||
// These are never called while is_selection_gathering is true, but the
|
|
||||||
// trait requires implementations.
|
|
||||||
fn on_point(&mut self, _pt: DVec3) -> CmdResult {
|
fn on_point(&mut self, _pt: DVec3) -> CmdResult {
|
||||||
CmdResult::NeedPoint
|
CmdResult::NeedPoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enter / right-click ends the gather phase and fires the pending command
|
||||||
|
// with the accumulated selection. Nothing selected → cancel.
|
||||||
fn on_enter(&mut self) -> CmdResult {
|
fn on_enter(&mut self) -> CmdResult {
|
||||||
CmdResult::Cancel
|
if !self.commit_on_enter || self.handles.is_empty() {
|
||||||
|
return CmdResult::Cancel;
|
||||||
|
}
|
||||||
|
CmdResult::Relaunch(
|
||||||
|
std::mem::take(&mut self.pending_cmd),
|
||||||
|
std::mem::take(&mut self.handles),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_escape(&mut self) -> CmdResult {
|
fn on_escape(&mut self) -> CmdResult {
|
||||||
CmdResult::Cancel
|
CmdResult::Cancel
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue