Complete parametric ribbon and constraint tools

This commit is contained in:
Hakan Seven committed 2026-09-14 01:05:39 +03:00
1 parent f3716bb516
commit 7b4cef41f7
49 files changed
+1438 -103

No files matched your search

+16 -7
View File
@@ -848,6 +848,9 @@ impl Ribbon {
RibbonItem::Dropdown {
id, items, default, ..
} => (*id, items, *default),
RibbonItem::LabeledDropdown { id, items, default, .. } => {
(*id, items, *default)
}
RibbonItem::LargeDropdown {
id, items, default, ..
} => (*id, items, *default),
@@ -1356,7 +1359,8 @@ fn render_group<'a>(
for item in &group.tools {
let is_large = match item {
RibbonItem::LargeTool(_) | RibbonItem::LargeDropdown { .. } => !compact,
RibbonItem::LayerComboGroup { .. }
RibbonItem::ToolGrid { .. }
| RibbonItem::LayerComboGroup { .. }
| RibbonItem::PropertiesGroup { .. }
| RibbonItem::StyleComboGroup { .. } => true,
_ => false,
@@ -1400,8 +1404,10 @@ fn render_group<'a>(
/// The top-level command id of a ribbon item, if it has one.
fn item_id(it: &RibbonItem) -> Option<&'static str> {
match it {
RibbonItem::Tool(t) | RibbonItem::LargeTool(t) => Some(t.id),
RibbonItem::Dropdown { id, .. } | RibbonItem::LargeDropdown { id, .. } => Some(*id),
RibbonItem::Tool(t) | RibbonItem::LabeledTool(t) | RibbonItem::LargeTool(t) => Some(t.id),
RibbonItem::Dropdown { id, .. }
| RibbonItem::LabeledDropdown { id, .. }
| RibbonItem::LargeDropdown { id, .. } => Some(*id),
RibbonItem::PropertiesGroup { match_prop } => Some(match_prop.id),
_ => None,
}
@@ -1427,13 +1433,17 @@ fn representative<'g>(group: &'g RibbonGroup, last_used: Option<&str>) -> Option
/// still get a representative icon.
fn first_tool_icon(group: &RibbonGroup) -> Option<IconKind> {
group.tools.iter().find_map(|it| match it {
RibbonItem::Tool(t) | RibbonItem::LargeTool(t) => Some(t.icon),
RibbonItem::Dropdown { icon, .. } | RibbonItem::LargeDropdown { icon, .. } => Some(*icon),
RibbonItem::Tool(t) | RibbonItem::LabeledTool(t) | RibbonItem::LargeTool(t) => Some(t.icon),
RibbonItem::Dropdown { icon, .. }
| RibbonItem::LabeledDropdown { icon, .. }
| RibbonItem::LargeDropdown { icon, .. } => Some(*icon),
RibbonItem::PropertiesGroup { match_prop } => Some(match_prop.icon),
RibbonItem::LayerComboGroup { row2, .. } => row2.first().map(|t| t.icon),
RibbonItem::StyleComboGroup { rows, .. } => {
rows.first().and_then(|r| r.first()).map(|t| t.icon)
}
},
RibbonItem::ToolGrid { columns } => columns.first()
.and_then(|column| column.first()).map(|tool| tool.icon),
})
}
@@ -1611,4 +1621,3 @@ mod tests {
assert_eq!(ribbon.open_dropdown, None);
}
}
+75 -1
View File
@@ -59,6 +59,7 @@ const LARGE_LABEL_LINES: f32 = 2.0;
const LARGE_LABEL_SIZE: f32 = 10.0;
/// Width of a 1-row (small) button.
pub(super) const SMALL_W: f32 = ROW_H;
const LABELED_SMALL_W: f32 = ROW_H * 4.0;
/// Width of the ▾ strip on a small dropdown.
pub(super) const ARROW_W: f32 = ROW_H * 0.4;
/// Height of the ▾ strip at the bottom of a large dropdown.
@@ -524,6 +525,26 @@ pub(super) fn render_small<'a>(
.into()
}
RibbonItem::LabeledTool(t) => {
let active = is_active_tool(t.id, active_tool, &state);
let event = t.event.clone();
let tool_id = t.id.to_string();
let label = t!(t.label).into_owned();
let tip_text = format!("{}\n{} {}", label, t!("Command:"), t.id);
let content = row![
container(make_icon(t.icon, SMALL_ICON)).width(Length::Fixed(SMALL_W)),
text(label).size(10).wrapping(advanced_text::Wrapping::None),
].spacing(3).align_y(iced::Center);
let btn = button(content)
.on_press(Message::RibbonToolClick { tool_id, event })
.style(move |theme: &Theme, status| tool_btn_style(theme, active, status))
.width(Length::Fixed(LABELED_SMALL_W + ARROW_W))
.height(ROW_H)
.padding([3, 4]);
tooltip(btn, make_tip(tip_text), TipPos::Right)
.gap(6.0).delay(Duration::from_millis(400)).style(tip_style).into()
}
RibbonItem::Dropdown {
id,
icon,
@@ -611,6 +632,42 @@ pub(super) fn render_small<'a>(
.into()
}
RibbonItem::LabeledDropdown { id, label, icon, items, default } => {
let active = active_tool.as_deref() == Some(*id)
|| items.iter().any(|(cmd, _, _)| active_tool.as_deref() == Some(*cmd));
let dd_open = open_dd.as_deref() == Some(*id);
let last = last_cmd.get(id).copied().unwrap_or(*default);
let cur_icon = last_cmd.get(id).copied().and_then(|cmd| {
items.iter().find(|(candidate, _, _)| *candidate == cmd)
.map(|(_, _, item_icon)| *item_icon)
}).or_else(|| items.first().map(|(_, _, item_icon)| *item_icon)).unwrap_or(*icon);
let localized_label = t!(*label).into_owned();
let face = row![
container(make_icon(cur_icon, SMALL_ICON)).width(Length::Fixed(SMALL_W)),
text(localized_label.clone()).size(10).wrapping(advanced_text::Wrapping::None),
].spacing(3).align_y(iced::Center);
let face_btn = button(face)
.on_press(Message::RibbonToolClick {
tool_id: last.to_string(),
event: ModuleEvent::Command(last.to_string()),
})
.style(move |theme: &Theme, status| tool_btn_style(theme, active, status))
.width(Length::Fixed(LABELED_SMALL_W)).height(ROW_H).padding([3, 4]);
let arrow = button(container(icons::themed_arrow_down(8.0))
.width(Fill).height(Fill).align_x(iced::Center).align_y(iced::Center))
.on_press(Message::ToggleRibbonDropdown(id.to_string()))
.style(move |theme: &Theme, status| tool_btn_style(theme, dd_open, status))
.width(Length::Fixed(ARROW_W)).height(ROW_H).padding(0);
let face_tip = format!("{}\n{} {}", localized_label, t!("Command:"), last);
let arrow_tip = format!("{} {}", localized_label, t!("options"));
PosReport::new(*id, row![
tooltip(face_btn, make_tip(face_tip), TipPos::Right)
.gap(6.0).delay(Duration::from_millis(400)).style(tip_style),
tooltip(arrow, make_tip(arrow_tip), TipPos::Right)
.gap(6.0).delay(Duration::from_millis(400)).style(tip_style),
].spacing(0).height(ROW_H)).into()
}
_ => text("").into(),
}
}
@@ -802,7 +859,7 @@ pub(super) fn render_large<'a>(
match item {
// A plain Tool renders large too, so a collapsed panel can show its
// representative tool as a big icon.
RibbonItem::LargeTool(t) | RibbonItem::Tool(t) => {
RibbonItem::LargeTool(t) | RibbonItem::Tool(t) | RibbonItem::LabeledTool(t) => {
let active = is_active_tool(t.id, active_tool, &state);
let event = t.event.clone();
let tool_id = t.id.to_string();
@@ -857,6 +914,23 @@ pub(super) fn render_large<'a>(
render_large_dropdown(*id, *icon, Some(*label), items, *default, ctx)
}
RibbonItem::LabeledDropdown { id, label, icon, items, default } => {
render_large_dropdown(*id, *icon, Some(*label), items, *default, ctx)
}
RibbonItem::ToolGrid { columns } => columns.iter().fold(
row![].spacing(2).height(Fill).align_y(iced::Top),
|row, tools| {
let column = tools.iter().fold(
column![].spacing(2).width(Length::Fixed(SMALL_W)),
|column, tool| column.push(render_small(
&RibbonItem::Tool(tool.clone()), active_tool, open_dd, last_cmd, state,
)),
);
row.push(column)
},
).into(),
// A plain Dropdown renders large too (used by a collapsed panel whose
// representative tool is a dropdown).
RibbonItem::Dropdown {