refactor(ribbon): simplify draw tool registry

This commit is contained in:
Hakan Seven committed 2026-09-10 17:23:08 +03:00
1 parent 1b3efb84de
commit acba9f87bd
17 files changed
+269 -150

No files matched your search

+180 -26
View File
@@ -5,7 +5,10 @@ use std::time::Duration;
use iced::widget::{button, column, container, row, scrollable, text, tooltip};
use iced::{Element, Fill, Theme};
use super::widgets::{make_icon, make_tip, muted_text_style, popup_panel_style, popup_row_style, tip_style, tool_btn_style};
use super::widgets::{
make_icon, make_tip, muted_text_style, popup_panel_style, popup_row_style, tip_style,
tool_btn_style,
};
use super::{dropdown_backdrop, position_ribbon_dropdown, Ribbon};
use crate::app::Message;
use crate::modules::IconKind;
@@ -23,39 +26,158 @@ struct Tool {
options: &'static [(&'static str, &'static str)],
}
include!(concat!(env!("OUT_DIR"), "/draw_panel_tools.rs"));
const TOOLS: &[Tool] = &[
Tool {
command: "SPLINE",
label: "Spline Fit",
icon: include_bytes!("../../../assets/icons/spline.svg"),
options: &[],
},
Tool {
command: "SPLINECV",
label: "Spline CV",
icon: include_bytes!("../../../assets/icons/spline_cv.svg"),
options: &[],
},
Tool {
command: "XLINE",
label: "Construction Line",
icon: include_bytes!("../../../assets/icons/xline.svg"),
options: &[],
},
Tool {
command: "RAY",
label: "Ray",
icon: include_bytes!("../../../assets/icons/ray.svg"),
options: &[],
},
Tool {
command: "MULTIPOINT",
label: "Multiple Points",
icon: include_bytes!("../../../assets/icons/multipoint.svg"),
options: &[
("POINT", "Single Point"),
("MULTIPOINT", "Multiple Points"),
("DDPTYPE", "Point Style"),
],
},
Tool {
command: "DIVIDE",
label: "Divide",
icon: include_bytes!("../../../assets/icons/divide.svg"),
options: &[],
},
Tool {
command: "MEASURE",
label: "Measure",
icon: include_bytes!("../../../assets/icons/measure.svg"),
options: &[],
},
Tool {
command: "REGION",
label: "Region",
icon: include_bytes!("../../../assets/icons/region.svg"),
options: &[],
},
Tool {
command: "WIPEOUT",
label: "Wipeout",
icon: include_bytes!("../../../assets/icons/wipeout.svg"),
options: &[],
},
Tool {
command: "BOUNDARY",
label: "Boundary",
icon: include_bytes!("../../../assets/icons/boundary.svg"),
options: &[],
},
Tool {
command: "HELIX",
label: "Helix",
icon: include_bytes!("../../../assets/icons/helix.svg"),
options: &[],
},
Tool {
command: "DONUT",
label: "Donut",
icon: include_bytes!("../../../assets/icons/donut.svg"),
options: &[],
},
Tool {
command: "REVCLOUD",
label: "Revision Cloud",
icon: include_bytes!("../../../assets/icons/revcloud.svg"),
options: &[
("REVCLOUD_RECTANGULAR", "Rectangular"),
("REVCLOUD_POLYGONAL", "Polygonal"),
("REVCLOUD_FREEHAND", "Freehand"),
],
},
];
pub(super) fn owns_dropdown(id: &str) -> bool {
id == PANEL_ID || TOOLS.iter().any(|tool| tool.command == id && !tool.options.is_empty())
id == PANEL_ID
|| TOOLS
.iter()
.any(|tool| tool.command == id && !tool.options.is_empty())
}
pub(super) fn group_title<'a>(title: &'static str, open: &Option<String>) -> Element<'a, Message> {
if title != "Draw" || TOOLS.is_empty() {
return container(text(t!(title)).size(9).style(muted_text_style)).padding([1, 4]).into();
return container(text(t!(title)).size(9).style(muted_text_style))
.padding([1, 4])
.into();
}
let expanded = open.as_deref().is_some_and(owns_dropdown);
let arrow = if expanded { icons::themed_arrow_up(7.0) } else { icons::themed_arrow_down(7.0) };
PosReport::new(PANEL_ID, button(row![text(t!(title)).size(9), arrow].spacing(4).align_y(iced::Center))
let arrow = if expanded {
icons::themed_arrow_up(7.0)
} else {
icons::themed_arrow_down(7.0)
};
PosReport::new(
PANEL_ID,
button(
row![text(t!(title)).size(9), arrow]
.spacing(4)
.align_y(iced::Center),
)
.on_press(Message::ToggleRibbonDropdown(PANEL_ID.to_string()))
.style(move |theme: &Theme, status| tool_btn_style(theme, expanded, status))
.padding([1, 4])).into()
.padding([1, 4]),
)
.into()
}
fn tool_button(tool: &Tool, active: bool) -> Element<'static, Message> {
let face = button(make_icon(IconKind::Svg(tool.icon), 23.0))
.on_press(Message::DropdownSelectItem { dropdown_id: PANEL_ID, cmd: tool.command })
.on_press(Message::DropdownSelectItem {
dropdown_id: PANEL_ID,
cmd: tool.command,
})
.style(move |theme: &Theme, status| tool_btn_style(theme, active, status))
.width(if tool.options.is_empty() { CELL } else { CELL - 11.0 })
.width(if tool.options.is_empty() {
CELL
} else {
CELL - 11.0
})
.height(CELL)
.padding(3);
let tip = format!("{}\n{} {}", t!(tool.label), t!("Command:"), tool.command);
let face: Element<'static, Message> = tooltip(face, make_tip(tip), tooltip::Position::Bottom)
.delay(Duration::from_millis(400)).style(tip_style).into();
.delay(Duration::from_millis(400))
.style(tip_style)
.into();
if !tool.options.is_empty() {
row![face, button(icons::themed_arrow_down(6.0))
.on_press(Message::ToggleRibbonDropdown(tool.command.to_string()))
.style(move |theme: &Theme, status| tool_btn_style(theme, false, status))
.width(11).height(CELL).padding(2)].into()
row![
face,
button(icons::themed_arrow_down(6.0))
.on_press(Message::ToggleRibbonDropdown(tool.command.to_string()))
.style(move |theme: &Theme, status| tool_btn_style(theme, false, status))
.width(11)
.height(CELL)
.padding(2)
]
.into()
} else {
face
}
@@ -65,28 +187,60 @@ pub(super) fn overlay<'a>(ribbon: &Ribbon, id: &str, win: (f32, f32)) -> Element
let width = (7.0 * (CELL + GAP) - GAP + 12.0).min((win.0 - 8.0).max(CELL + 12.0));
let (_, x, anchor_top) = ribbon.dd_anchor(PANEL_ID, width, win.0);
let anchor = crate::ui::wrap_bar::dropdown_bounds(PANEL_ID);
let left = anchor.map_or(x, |b| b.x).clamp(0.0, (win.0 - width).max(0.0));
let left = anchor
.map_or(x, |b| b.x)
.clamp(0.0, (win.0 - width).max(0.0));
let top = anchor_top.min((win.1 - 80.0).max(0.0));
let available_height = (win.1 - top - 4.0).max(1.0);
let cols = (((width - 12.0 + GAP) / (CELL + GAP)).floor() as usize).clamp(1, 7);
let option_tool = TOOLS.iter().find(|tool| tool.command == id && !tool.options.is_empty());
let option_tool = TOOLS
.iter()
.find(|tool| tool.command == id && !tool.options.is_empty());
let content_height = option_tool.map_or_else(
|| TOOLS.len().div_ceil(cols) as f32 * (CELL + GAP) - GAP + 12.0,
|tool| tool.options.len() as f32 * 30.0,
);
let contents: Element<'static, Message> = if let Some(tool) = option_tool {
column(tool.options.iter().map(|&(cmd, label)| {
button(text(t!(label)).size(11))
.on_press(Message::DropdownSelectItem { dropdown_id: tool.command, cmd })
.style(popup_row_style).width(Fill).height(30).padding([6, 10]).into()
}).collect::<Vec<Element<'static, Message>>>()).into()
column(
tool.options
.iter()
.map(|&(cmd, label)| {
button(text(t!(label)).size(11))
.on_press(Message::DropdownSelectItem {
dropdown_id: tool.command,
cmd,
})
.style(popup_row_style)
.width(Fill)
.height(30)
.padding([6, 10])
.into()
})
.collect::<Vec<Element<'static, Message>>>(),
)
.into()
} else {
column(TOOLS.chunks(cols).map(|tools| {
row(tools.iter().map(|tool| tool_button(tool, ribbon.active_tool.as_deref() == Some(tool.command)))
.collect::<Vec<_>>()).spacing(GAP).into()
}).collect::<Vec<Element<'static, Message>>>()).spacing(GAP).padding(6).into()
column(
TOOLS
.chunks(cols)
.map(|tools| {
row(tools
.iter()
.map(|tool| {
tool_button(tool, ribbon.active_tool.as_deref() == Some(tool.command))
})
.collect::<Vec<_>>())
.spacing(GAP)
.into()
})
.collect::<Vec<Element<'static, Message>>>(),
)
.spacing(GAP)
.padding(6)
.into()
};
let panel = container(scrollable(contents).height(content_height.min(available_height)))
.width(width).style(popup_panel_style);
.width(width)
.style(popup_panel_style);
dropdown_backdrop(position_ribbon_dropdown(panel.into(), false, left, top))
}
@@ -1,6 +0,0 @@
Tool {
command: "SPLINE",
label: "Spline Fit",
icon: include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/icons/spline.svg")),
options: &[],
}
-6
View File
@@ -1,6 +0,0 @@
Tool {
command: "SPLINECV",
label: "Spline CV",
icon: include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/icons/spline_cv.svg")),
options: &[],
}
-6
View File
@@ -1,6 +0,0 @@
Tool {
command: "XLINE",
label: "Construction Line",
icon: include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/icons/xline.svg")),
options: &[],
}
-6
View File
@@ -1,6 +0,0 @@
Tool {
command: "RAY",
label: "Ray",
icon: include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/icons/ray.svg")),
options: &[],
}
@@ -1,6 +0,0 @@
Tool {
command: "MULTIPOINT",
label: "Multiple Points",
icon: include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/icons/multipoint.svg")),
options: &[("POINT", "Single Point"), ("MULTIPOINT", "Multiple Points"), ("DDPTYPE", "Point Style")],
}
-6
View File
@@ -1,6 +0,0 @@
Tool {
command: "DIVIDE",
label: "Divide",
icon: include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/icons/divide.svg")),
options: &[],
}
-6
View File
@@ -1,6 +0,0 @@
Tool {
command: "MEASURE",
label: "Measure",
icon: include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/icons/measure.svg")),
options: &[],
}
-6
View File
@@ -1,6 +0,0 @@
Tool {
command: "REGION",
label: "Region",
icon: include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/icons/region.svg")),
options: &[],
}
-6
View File
@@ -1,6 +0,0 @@
Tool {
command: "WIPEOUT",
label: "Wipeout",
icon: include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/icons/wipeout.svg")),
options: &[],
}
-6
View File
@@ -1,6 +0,0 @@
Tool {
command: "BOUNDARY",
label: "Boundary",
icon: include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/icons/boundary.svg")),
options: &[],
}
-6
View File
@@ -1,6 +0,0 @@
Tool {
command: "HELIX",
label: "Helix",
icon: include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/icons/helix.svg")),
options: &[],
}
-6
View File
@@ -1,6 +0,0 @@
Tool {
command: "DONUT",
label: "Donut",
icon: include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/icons/donut.svg")),
options: &[],
}
-10
View File
@@ -1,10 +0,0 @@
Tool {
command: "REVCLOUD",
label: "Revision Cloud",
icon: include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/icons/revcloud.svg")),
options: &[
("REVCLOUD_RECTANGULAR", "Rectangular"),
("REVCLOUD_POLYGONAL", "Polygonal"),
("REVCLOUD_FREEHAND", "Freehand"),
],
}