diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index 33f3b2e5a..b9fdc9c38 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -1578,7 +1578,7 @@ impl OpenCADStudio { // window — the same points the grips expose — then // refresh the stored measurement so the value tracks // the stretched geometry. - let mut mv = |p: &mut acadrust::types::Vector3| { + let mv = |p: &mut acadrust::types::Vector3| { if in_win(p.x, p.y) { p.x += dx; p.y += dy; diff --git a/src/app/mod.rs b/src/app/mod.rs index d7b5e136b..f14418145 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1625,6 +1625,10 @@ pub enum Message { // ── Ribbon ──────────────────────────────────────────────────────────── /// User selected a layer from the layer combobox in the ribbon. RibbonLayerChanged(String), + /// Live text of the ribbon layer dropdown's search box (#343). + RibbonLayerFilterChanged(String), + /// Live text of the Layer Manager's search box (#343). + LayerManagerFilterChanged(String), /// User changed the active color in the Properties toolbar. RibbonColorChanged(AcadColor), /// Toggle the full ACI palette inside the ribbon color picker. diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 213a962be..afd950489 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -3246,6 +3246,15 @@ impl OpenCADStudio { self.modal_drag_last = None; Task::none() } + Message::RibbonLayerFilterChanged(f) => { + self.ribbon.layer_filter = f; + Task::none() + } + Message::LayerManagerFilterChanged(f) => { + let i = self.active_tab; + self.tabs[i].layers.filter = f; + Task::none() + } Message::LayerNameColGrab => { // Start a Name-column divider drag; rides ModalDragMove. self.layer_col_dragging = true; diff --git a/src/ui/ribbon/mod.rs b/src/ui/ribbon/mod.rs index 620663041..bb634e113 100644 --- a/src/ui/ribbon/mod.rs +++ b/src/ui/ribbon/mod.rs @@ -55,6 +55,9 @@ pub struct Ribbon { pub layer_names: Vec, pub active_layer: String, pub layer_infos: Vec, + /// Live filter for the layer dropdown's search box (#343). Cleared on + /// close so the next open starts unfiltered. + pub layer_filter: String, /// Active object color — ACI / ByLayer / ByBlock. pub active_color: AcadColor, /// Active linetype override ("ByLayer", "Continuous", …). @@ -156,6 +159,7 @@ impl Ribbon { layer_names: vec![], active_layer: String::new(), layer_infos: vec![], + layer_filter: String::new(), active_color: AcadColor::ByLayer, active_linetype: "ByLayer".to_string(), active_lineweight: LineWeight::ByLayer, @@ -328,6 +332,7 @@ impl Ribbon { pub fn close_dropdown(&mut self) { self.open_dropdown = None; self.collapsed_open = None; + self.layer_filter.clear(); } /// Toggle the flyout of a collapsed ribbon panel (identified by its title). @@ -927,10 +932,14 @@ impl Ribbon { .padding([2, 4]) .into() }; + let filter = self.layer_filter.to_lowercase(); let rows: Vec> = self .layer_infos .iter() .enumerate() + .filter(|(_, info)| { + filter.is_empty() || info.name.to_lowercase().contains(&filter) + }) .map(|(index, info)| { let is_active = info.name == self.active_layer; let lc = info.color; @@ -1003,9 +1012,47 @@ impl Ribbon { // Cap the panel height and make the list scrollable so a long layer // list stays reachable instead of running off the bottom of the // screen (#227). Short lists shrink to fit; longer ones scroll. - let row_count = self.layer_infos.len().max(1); + let row_count = rows.len().max(1); let list_h = (row_count as f32 * 26.0).min(420.0); - let panel = container(scrollable(column(rows)).height(Length::Fixed(list_h))) + // Search box (#343): filters the list live as the user types. + let search = iced::widget::text_input("Search layers…", &self.layer_filter) + .on_input(Message::RibbonLayerFilterChanged) + .size(11) + .padding([4, 6]) + .style(|_: &Theme, _| iced::widget::text_input::Style { + background: Background::Color(Color { + r: 0.10, + g: 0.10, + b: 0.10, + a: 1.0, + }), + border: Border { + color: PANEL_BORDER, + width: 1.0, + radius: 2.0.into(), + }, + icon: Color::WHITE, + placeholder: Color { + r: 0.45, + g: 0.45, + b: 0.45, + a: 1.0, + }, + value: Color::WHITE, + selection: Color { + r: 0.20, + g: 0.44, + b: 0.72, + a: 0.5, + }, + }); + let panel = container( + column![ + container(search).padding([4, 4]), + scrollable(column(rows)).height(Length::Fixed(list_h)), + ] + .spacing(2), + ) .style(|_: &Theme| container::Style { background: Some(Background::Color(PANEL_BG)), border: Border { diff --git a/src/ui/window/layers.rs b/src/ui/window/layers.rs index b0adbb37f..1fcdc27ba 100644 --- a/src/ui/window/layers.rs +++ b/src/ui/window/layers.rs @@ -106,6 +106,8 @@ pub struct LayerPanel { pub sort_col: Option, /// Sort direction; `true` = ascending. pub sort_asc: bool, + /// Live name filter from the search box; empty shows every layer (#343). + pub filter: String, } impl Default for LayerPanel { @@ -135,6 +137,7 @@ impl Default for LayerPanel { // click still re-sorts by any other column. sort_col: Some(LayerSortCol::Name), sort_asc: true, + filter: String::new(), } } } @@ -303,8 +306,43 @@ impl LayerPanel { Message::LayerSetCurrent, has_sel, ), + iced::widget::Space::new().width(Fill), + // Search box: filters rows by name as the user types (#343). + text_input("Search…", &self.filter) + .on_input(Message::LayerManagerFilterChanged) + .size(FONT_SZ) + .padding([3, 6]) + .width(Length::Fixed(180.0)) + .style(|_: &Theme, _| iced::widget::text_input::Style { + background: Background::Color(Color { + r: 0.10, + g: 0.10, + b: 0.10, + a: 1.0, + }), + border: Border { + color: BORDER_COLOR, + width: 1.0, + radius: 2.0.into(), + }, + icon: Color::WHITE, + placeholder: Color { + r: 0.45, + g: 0.45, + b: 0.45, + a: 1.0, + }, + value: Color::WHITE, + selection: Color { + r: 0.20, + g: 0.44, + b: 0.72, + a: 0.5, + }, + }), ] - .spacing(2), + .spacing(2) + .align_y(iced::Center), ) .style(|_: &Theme| container::Style { background: Some(Background::Color(TOOLBAR_BG)), @@ -372,7 +410,11 @@ impl LayerPanel { // ── Layer rows ──────────────────────────────────────────────────── let mut rows_col = column![].spacing(0); + let filter = self.filter.to_lowercase(); for (i, layer) in self.layers.iter().enumerate() { + if !filter.is_empty() && !layer.name.to_lowercase().contains(&filter) { + continue; + } // Highlight every selected row; show the editable combos only on the // anchor (a single shared combo state can't drive several rows). let is_anchor = self.selected == Some(i);