forked from 1b-agent/opencadstudio-upstream
feat(layers): search boxes in the layer dropdown and Layer Manager (#343)
The ribbon layer dropdown gets a live search field above the list (cleared when the dropdown closes, panel height tracks the filtered count), and the Layer Manager toolbar gets one too. Both filter by case-insensitive substring while toggle/rename messages keep the original row indices, so acting on a filtered row hits the right layer. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
87f40e7544
commit
3dd27c6789
5 files changed
+106
-4
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
+49
-2
@@ -55,6 +55,9 @@ pub struct Ribbon {
|
||||
pub layer_names: Vec<String>,
|
||||
pub active_layer: String,
|
||||
pub layer_infos: Vec<LayerInfo>,
|
||||
/// 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<Element<Message>> = 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 {
|
||||
|
||||
+43
-1
@@ -106,6 +106,8 @@ pub struct LayerPanel {
|
||||
pub sort_col: Option<LayerSortCol>,
|
||||
/// 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);
|
||||
|
||||
Reference in new issue
Block a user