diff --git a/src/app/mod.rs b/src/app/mod.rs index a2cd8ba9a..8cf5f8920 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -202,6 +202,31 @@ pub struct QSelectState { pub error: Option, } +#[derive(Clone, Debug)] +struct QSelectSettings { + scope: QSelectScope, + type_filter: Option, + property_field: Option, + operator: QSelectOp, + value: String, + mode: QSelectMode, + append: bool, +} + +impl From<&QSelectState> for QSelectSettings { + fn from(state: &QSelectState) -> Self { + Self { + scope: state.scope, + type_filter: state.type_filter.clone(), + property_field: state.property.as_ref().map(|property| property.field.clone()), + operator: state.operator, + value: state.value.clone(), + mode: state.mode, + append: state.append, + } + } +} + #[derive(Clone, Debug, Default)] pub(crate) struct FindReplaceState { pub search: String, @@ -578,6 +603,7 @@ pub(super) struct OpenCADStudio { /// applied via `Message::QSelectApply`; the panel is dismissed on /// Apply / Cancel / Esc / outside-click. qselect: Option, + qselect_settings: Option, /// Show the UCS icon in the bottom-left corner of model space (UCSICON). show_ucs_icon: bool, /// Anchor the UCS icon to the projected UCS origin when it is on-screen, @@ -3252,6 +3278,7 @@ impl OpenCADStudio { grip_text_verts: Vec::new(), grip_text_slide: false, qselect: None, + qselect_settings: None, show_ucs_icon: true, ucs_icon_at_origin: true, show_viewcube: true, diff --git a/src/app/update/command.rs b/src/app/update/command.rs index efd4d5b8d..ce1b6256d 100644 --- a/src/app/update/command.rs +++ b/src/app/update/command.rs @@ -826,7 +826,8 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task { } // Cancel layout rename first, then fall through. let i_e = self.active_tab; - if self.qselect.take().is_some() { + if let Some(state) = self.qselect.take() { + self.qselect_settings = Some((&state).into()); self.reset_modal_geometry(); return Task::none(); } @@ -1546,35 +1547,74 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task { pub(super) fn on_qselect_open(&mut self) -> Task { let i = self.active_tab; self.tabs[i].scene.selection.borrow_mut().context_menu = None; - // Seed the type filter from the first selected entity so a - // right-click → Quick Select on a known object opens the - // panel pre-tuned to that entity's type. Property defaults - // to "(Any property)" so the user immediately picks what - // they want to compare. - let mut type_filter: Option = None; - if let Some(&h) = self.tabs[i].scene.selected.iter().next() { - if let Some(e) = self.tabs[i].scene.document.get_entity(h) { - use crate::entities::traits::entity_type_name; - type_filter = Some(entity_type_name(e).to_string()); - } - } - let scope = crate::app::QSelectScope::CurrentSpace; + let remembered = self.qselect_settings.clone(); + let scope = remembered.as_ref().map_or( + crate::app::QSelectScope::CurrentSpace, + |settings| settings.scope, + ); let available_types = self.tabs[i].scene.qselect_entity_type_names(scope); + let type_filter = if let Some(settings) = remembered.as_ref() { + settings + .type_filter + .as_ref() + .filter(|selected| available_types.iter().any(|item| item == *selected)) + .cloned() + } else { + self.tabs[i] + .scene + .selected + .iter() + .next() + .and_then(|handle| self.tabs[i].scene.document.get_entity(*handle)) + .map(crate::entities::traits::entity_type_name) + .map(str::to_string) + }; let available_properties = self.tabs[i] .scene .qselect_properties(type_filter.as_deref(), scope); let candidate_count = self.tabs[i].scene.qselect_candidate_count(scope); + let property = remembered + .as_ref() + .and_then(|settings| settings.property_field.as_ref()) + .and_then(|field| { + available_properties + .iter() + .find(|property| property.field == *field) + .cloned() + }); + let mut operator = remembered + .as_ref() + .map_or(crate::app::QSelectOp::Eq, |settings| settings.operator); + if matches!(operator, crate::app::QSelectOp::Gt | crate::app::QSelectOp::Lt) + && !property.as_ref().is_some_and(|property| { + matches!(property.editor, crate::app::QSelectValueEditor::Number) + }) + { + operator = crate::app::QSelectOp::Eq; + } + let value = remembered.as_ref().map_or_else(String::new, |settings| { + if settings.property_field.is_none() || property.is_some() { + settings.value.clone() + } else { + String::new() + } + }); + let mode = remembered + .as_ref() + .map_or(crate::app::QSelectMode::Include, |settings| settings.mode); + let append = matches!(scope, crate::app::QSelectScope::CurrentSpace) + && remembered.as_ref().is_some_and(|settings| settings.append); self.qselect = Some(crate::app::QSelectState { scope, available_types, available_properties, candidate_count, type_filter, - property: None, - operator: crate::app::QSelectOp::Eq, - value: String::new(), - mode: crate::app::QSelectMode::Include, - append: false, + property, + operator, + value, + mode, + append, error: None, }); self.reset_modal_geometry(); diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 4dbe55bcb..4c2e9305c 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -4349,7 +4349,9 @@ impl OpenCADStudio { Message::QSelectOpen => self.on_qselect_open(), Message::QSelectClose => { - self.qselect = None; + if let Some(state) = self.qselect.take() { + self.qselect_settings = Some((&state).into()); + } self.reset_modal_geometry(); Task::none() } @@ -4530,6 +4532,7 @@ impl OpenCADStudio { let Some(state) = self.qselect.take() else { return Task::none(); }; + self.qselect_settings = Some((&state).into()); self.reset_modal_geometry(); let i = self.active_tab; let matched = self.tabs[i].scene.qselect(