From 7764b6cbf9b4860a6f5f537938fcddfebcd5e7cb Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Wed, 1 Jul 2026 09:47:13 +0300 Subject: [PATCH] feat(statusbar): Select All/Clear All in filter popup, drop osnap settings row MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The selection-filter popup now has the same Select All / Clear All header as the OSNAP popup: Select All clears every exclusion (all types selectable), Clear All excludes every present type. Each button disables when it would be a no-op. Also remove the "Object Snap Settings…" footer from the OSNAP popup. Co-Authored-By: Claude Opus 4.8 --- src/app/mod.rs | 4 ++ src/app/update/mod.rs | 13 +++++ src/ui/popup/selection_filter_popup.rs | 68 +++++++++++++++++++++++++- src/ui/popup/snap_popup.rs | 28 +---------- 4 files changed, 84 insertions(+), 29 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index dbafe7aaa..1af85ecdc 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1317,6 +1317,10 @@ pub enum Message { CloseSelectionFilterPopup, /// Include/exclude an entity type from interactive selection. ToggleSelectionFilterType(String), + /// Make every entity type selectable again (clear the filter). + SelectionFilterSelectAll, + /// Exclude every present entity type from selection. + SelectionFilterClearAll, /// Toggle the drawing-units picker open/closed. ToggleUnitsPopup, /// Close the drawing-units picker. diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 5db14ac2b..32b60fb2c 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -1486,6 +1486,19 @@ impl OpenCADStudio { } Task::none() } + Message::SelectionFilterSelectAll => { + self.tabs[self.active_tab].scene.selection_filter.clear(); + Task::none() + } + Message::SelectionFilterClearAll => { + let i = self.active_tab; + let types = self.tabs[i].scene.entity_type_names_in_layout(); + let f = &mut self.tabs[i].scene.selection_filter; + for t in types { + f.insert(t.to_string()); + } + Task::none() + } Message::ToggleUnitsPopup => { self.units_popup_open ^= true; Task::none() diff --git a/src/ui/popup/selection_filter_popup.rs b/src/ui/popup/selection_filter_popup.rs index f497619ae..f0e15b468 100644 --- a/src/ui/popup/selection_filter_popup.rs +++ b/src/ui/popup/selection_filter_popup.rs @@ -18,6 +18,34 @@ pub fn selection_filter_popup_overlay( types: Vec, excluded: &HashSet, ) -> Element<'static, Message> { + // "Select All / Clear All" header, mirroring the OSNAP popup: Select All + // clears every exclusion, Clear All excludes every present type. + let has_types = !types.is_empty(); + let all_included = excluded.is_empty(); + let all_excluded = has_types && types.iter().all(|t| excluded.contains(t)); + let header = row![ + header_btn( + "Select All", + Message::SelectionFilterSelectAll, + has_types && !all_included, + ), + header_btn( + "Clear All", + Message::SelectionFilterClearAll, + has_types && !all_excluded, + ), + ] + .spacing(1) + .padding([4u16, 8]); + + let divider = container(iced::widget::Space::new().height(1)) + .style(|_: &Theme| container::Style { + background: Some(Background::Color(DIVIDER)), + ..Default::default() + }) + .width(Fill) + .padding([0, 4]); + let rows: Vec> = if types.is_empty() { vec![empty_row()] } else { @@ -30,7 +58,7 @@ pub fn selection_filter_popup_overlay( .collect() }; - let panel = container(column(rows)) + let panel = container(column![header, divider, column(rows)]) .style(|_: &Theme| container::Style { background: Some(Background::Color(PANEL_BG)), border: Border { @@ -40,7 +68,7 @@ pub fn selection_filter_popup_overlay( }, ..Default::default() }) - .width(Length::Fixed(160.0)); + .width(Length::Fixed(180.0)); let positioned = container(panel) .align_right(Fill) @@ -88,6 +116,30 @@ fn empty_row() -> Element<'static, Message> { .into() } +fn header_btn(label: &str, msg: Message, enabled: bool) -> Element<'_, Message> { + let color = if enabled { + Color { r: 0.70, g: 0.70, b: 0.70, a: 1.0 } + } else { + Color { r: 0.38, g: 0.38, b: 0.38, a: 1.0 } + }; + let b = button(text(label).size(10).color(color)); + let b = if enabled { b.on_press(msg) } else { b }; + b.style(|_: &Theme, status| button::Style { + background: Some(Background::Color(match status { + button::Status::Hovered => ROW_HOVER, + _ => BTN_BG, + })), + border: Border { + color: PANEL_BORDER, + width: 1.0, + radius: 2.0.into(), + }, + ..Default::default() + }) + .padding([3, 8]) + .into() +} + // ── Colours ─────────────────────────────────────────────────────────────── const PANEL_BG: Color = Color { @@ -108,6 +160,18 @@ const ROW_HOVER: Color = Color { b: 0.22, a: 1.0, }; +const DIVIDER: Color = Color { + r: 0.28, + g: 0.28, + b: 0.28, + a: 1.0, +}; +const BTN_BG: Color = Color { + r: 0.20, + g: 0.20, + b: 0.20, + a: 1.0, +}; const CHECK_COLOR: Color = Color { r: 0.35, g: 0.75, diff --git a/src/ui/popup/snap_popup.rs b/src/ui/popup/snap_popup.rs index 700a8eef3..4c865829b 100644 --- a/src/ui/popup/snap_popup.rs +++ b/src/ui/popup/snap_popup.rs @@ -40,33 +40,7 @@ pub fn snap_popup_overlay<'a>(snapper: &'a Snapper, right_offset: f32) -> Elemen rows.push(snap_row(snap_type, label, snapper.is_on(snap_type))); } - // "Object Snap Settings…" footer - let footer_divider = container(iced::widget::Space::new().height(1)) - .style(|_: &Theme| container::Style { - background: Some(Background::Color(DIVIDER)), - ..Default::default() - }) - .width(Fill) - .padding([0, 4]); - - let footer = button(text("Object Snap Settings…").size(11).color(Color { - r: 0.75, - g: 0.75, - b: 0.75, - a: 1.0, - })) - .on_press(Message::Command("DSETTINGS".into())) - .style(|_: &Theme, status| button::Style { - background: Some(Background::Color(match status { - button::Status::Hovered => ROW_HOVER, - _ => Color::TRANSPARENT, - })), - ..Default::default() - }) - .width(Fill) - .padding([5, 12]); - - let panel_content = column![header, divider, column(rows), footer_divider, footer,]; + let panel_content = column![header, divider, column(rows)]; let panel = container(panel_content) .style(|_: &Theme| container::Style {