forked from 1b-agent/opencadstudio-upstream
Merge main into codex/graphic-attributes-cleanup
Resolve conflicts from main's rustfmt pass by taking main's versions of command.rs, style.rs, view/mod.rs, scene/mod.rs, ui/window/mod.rs and ui/properties.rs and re-applying this branch's Graphic Attributes changes. Add the Graphic Attributes strings to the new lo-LA catalog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E1QnME6WJbD5iGFae1eeCb
This commit is contained in:
596 files changed
+69069
-34120
No files matched your search
+57
-56
@@ -15,8 +15,8 @@ use iced::advanced::layout::{self, Layout};
|
||||
use iced::advanced::widget::{self, Widget};
|
||||
use iced::advanced::{mouse, overlay, renderer, Renderer as _, Shell};
|
||||
use iced::{
|
||||
Background, Border, Element, Event, Length, Point, Rectangle, Renderer, Shadow, Size,
|
||||
Theme, Vector,
|
||||
Background, Border, Element, Event, Length, Point, Rectangle, Renderer, Shadow, Size, Theme,
|
||||
Vector,
|
||||
};
|
||||
|
||||
use crate::app::Message;
|
||||
@@ -59,9 +59,7 @@ const MAX_PANEL_SQUEEZE: f32 = 8.0;
|
||||
/// How the ribbon tool panels are sized. `Auto` adapts to the window width (the
|
||||
/// step-by-step degradation); the others pin every panel to one density so the
|
||||
/// user can override the automatic choice. The selection is persisted.
|
||||
#[derive(
|
||||
Clone, Copy, PartialEq, Eq, Debug, Default, serde::Serialize, serde::Deserialize,
|
||||
)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub enum CollapseMode {
|
||||
/// Size panels to the window: degrade from the right as space runs out.
|
||||
#[default]
|
||||
@@ -93,7 +91,6 @@ impl CollapseMode {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The degradation level every panel is pinned to, or `None` for `Auto`.
|
||||
fn forced_level(self) -> Option<Level> {
|
||||
match self {
|
||||
@@ -103,7 +100,6 @@ impl CollapseMode {
|
||||
CollapseMode::Collapsed => Some(Level::Collapsed),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl std::fmt::Display for CollapseMode {
|
||||
@@ -130,11 +126,7 @@ pub(crate) struct Widths {
|
||||
/// panel at a time: first Full -> Compact, then Compact -> Collapsed, each phase
|
||||
/// only while the row still overflows; if even the all-collapsed row overflows,
|
||||
/// every collapsed panel drops to Tight at once. Forced modes pin everything.
|
||||
pub(crate) fn decide_levels(
|
||||
mode: CollapseMode,
|
||||
widths: &[Widths],
|
||||
max_w: f32,
|
||||
) -> Vec<Level> {
|
||||
pub(crate) fn decide_levels(mode: CollapseMode, widths: &[Widths], max_w: f32) -> Vec<Level> {
|
||||
if let Some(level) = mode.forced_level() {
|
||||
return vec![level; widths.len()];
|
||||
}
|
||||
@@ -144,9 +136,8 @@ pub(crate) fn decide_levels(
|
||||
Level::Collapsed => widths[i].button,
|
||||
Level::Tight => widths[i].tight,
|
||||
};
|
||||
let total = |levels: &[Level]| -> f32 {
|
||||
(0..levels.len()).map(|i| width_of(levels[i], i)).sum()
|
||||
};
|
||||
let total =
|
||||
|levels: &[Level]| -> f32 { (0..levels.len()).map(|i| width_of(levels[i], i)).sum() };
|
||||
let mut levels = vec![Level::Full; widths.len()];
|
||||
for degraded in [Level::Compact, Level::Collapsed] {
|
||||
for i in (0..widths.len()).rev() {
|
||||
@@ -264,17 +255,33 @@ impl<'a> Widget<Message, Theme, Renderer> for CollapsePanels<'a> {
|
||||
let auto = self.mode == CollapseMode::Auto;
|
||||
if auto {
|
||||
for i in 0..n {
|
||||
let f = self.panels[i].elements[Level::Full as usize].as_widget_mut()
|
||||
let f = self.panels[i].elements[Level::Full as usize]
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[slot(i, Level::Full)], renderer, &natural)
|
||||
.size();
|
||||
let c = self.panels[i].elements[Level::Compact as usize].as_widget_mut()
|
||||
.layout(&mut tree.children[slot(i, Level::Compact)], renderer, &natural)
|
||||
let c = self.panels[i].elements[Level::Compact as usize]
|
||||
.as_widget_mut()
|
||||
.layout(
|
||||
&mut tree.children[slot(i, Level::Compact)],
|
||||
renderer,
|
||||
&natural,
|
||||
)
|
||||
.size();
|
||||
let b = self.panels[i].elements[Level::Collapsed as usize].as_widget_mut()
|
||||
.layout(&mut tree.children[slot(i, Level::Collapsed)], renderer, &natural)
|
||||
let b = self.panels[i].elements[Level::Collapsed as usize]
|
||||
.as_widget_mut()
|
||||
.layout(
|
||||
&mut tree.children[slot(i, Level::Collapsed)],
|
||||
renderer,
|
||||
&natural,
|
||||
)
|
||||
.size();
|
||||
let t = self.panels[i].elements[Level::Tight as usize].as_widget_mut()
|
||||
.layout(&mut tree.children[slot(i, Level::Tight)], renderer, &natural)
|
||||
let t = self.panels[i].elements[Level::Tight as usize]
|
||||
.as_widget_mut()
|
||||
.layout(
|
||||
&mut tree.children[slot(i, Level::Tight)],
|
||||
renderer,
|
||||
&natural,
|
||||
)
|
||||
.size();
|
||||
widths.push(Widths {
|
||||
full: f.width,
|
||||
@@ -294,10 +301,7 @@ impl<'a> Widget<Message, Theme, Renderer> for CollapsePanels<'a> {
|
||||
// The row is "tight" once any panel has dropped to its small icon — the
|
||||
// last, most cramped state. The tab bar hides its mode selector then.
|
||||
if let Some(out) = &self.tight_out {
|
||||
out.store(
|
||||
levels.iter().any(|&l| l == Level::Tight),
|
||||
Ordering::Relaxed,
|
||||
);
|
||||
out.store(levels.iter().any(|&l| l == Level::Tight), Ordering::Relaxed);
|
||||
}
|
||||
*self.levels.borrow_mut() = levels.clone();
|
||||
|
||||
@@ -357,10 +361,7 @@ impl<'a> Widget<Message, Theme, Renderer> for CollapsePanels<'a> {
|
||||
.fold(0.0f32, f32::max)
|
||||
}
|
||||
} else {
|
||||
placed
|
||||
.iter()
|
||||
.map(|(_, _, h)| *h)
|
||||
.fold(self.row_h, f32::max)
|
||||
placed.iter().map(|(_, _, h)| *h).fold(self.row_h, f32::max)
|
||||
};
|
||||
let children: Vec<layout::Node> = placed
|
||||
.into_iter()
|
||||
@@ -522,8 +523,7 @@ impl<'a> Widget<Message, Theme, Renderer> for CollapsePanels<'a> {
|
||||
{
|
||||
let child_layout = layout.children().nth(p).unwrap();
|
||||
let b = child_layout.bounds();
|
||||
let anchor =
|
||||
Point::new(b.x + translation.x, b.y + b.height + translation.y);
|
||||
let anchor = Point::new(b.x + translation.x, b.y + b.height + translation.y);
|
||||
// The flyout is the panel's own `full` rendering (the row
|
||||
// never shows slot 0 while the panel is button-form, so it
|
||||
// is free to double as the overlay content).
|
||||
@@ -543,8 +543,7 @@ impl<'a> Widget<Message, Theme, Renderer> for CollapsePanels<'a> {
|
||||
// Split borrows so each shown child and its tree slot are disjoint.
|
||||
let mut overlays = Vec::new();
|
||||
let mut tree_rest = tree.children.as_mut_slice();
|
||||
for ((i, panel), child_layout) in
|
||||
self.panels.iter_mut().enumerate().zip(layout.children())
|
||||
for ((i, panel), child_layout) in self.panels.iter_mut().enumerate().zip(layout.children())
|
||||
{
|
||||
if tree_rest.len() < SLOTS {
|
||||
break;
|
||||
@@ -683,9 +682,13 @@ impl overlay::Overlay<Message, Theme, Renderer> for FlyoutOverlay<'_, '_> {
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
let child = layout.children().next().unwrap();
|
||||
self.content
|
||||
.as_widget()
|
||||
.mouse_interaction(self.tree, child, cursor, &child.bounds(), renderer)
|
||||
self.content.as_widget().mouse_interaction(
|
||||
self.tree,
|
||||
child,
|
||||
cursor,
|
||||
&child.bounds(),
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -734,10 +737,7 @@ mod tests {
|
||||
/// compact first, and stops as soon as the row fits.
|
||||
#[test]
|
||||
fn auto_degrades_from_the_right_one_panel_at_a_time() {
|
||||
let widths = [
|
||||
w(50.0, 40.0, 30.0, 20.0),
|
||||
w(200.0, 100.0, 60.0, 30.0),
|
||||
];
|
||||
let widths = [w(50.0, 40.0, 30.0, 20.0), w(200.0, 100.0, 60.0, 30.0)];
|
||||
// Full row = 250 > 240; rightmost compact => 50 + 100 = 150 <= 240.
|
||||
assert_eq!(
|
||||
decide_levels(CollapseMode::Auto, &widths, 240.0),
|
||||
@@ -800,10 +800,7 @@ mod tests {
|
||||
fn no_panels_stays_empty() {
|
||||
assert_eq!(decide_levels(CollapseMode::Auto, &[], 1e9), vec![]);
|
||||
assert_eq!(decide_levels(CollapseMode::Full, &[], 1e9), vec![]);
|
||||
assert_eq!(
|
||||
decide_levels(CollapseMode::Collapsed, &[], 1e9),
|
||||
vec![]
|
||||
);
|
||||
assert_eq!(decide_levels(CollapseMode::Collapsed, &[], 1e9), vec![]);
|
||||
}
|
||||
|
||||
/// A single panel walked the whole ladder as the width shrinks: FULL,
|
||||
@@ -910,9 +907,8 @@ mod tests {
|
||||
2 => widths[i].button,
|
||||
_ => widths[i].tight,
|
||||
};
|
||||
let total = |levels: &[u8]| -> f32 {
|
||||
(0..levels.len()).map(|i| width_of(levels[i], i)).sum()
|
||||
};
|
||||
let total =
|
||||
|levels: &[u8]| -> f32 { (0..levels.len()).map(|i| width_of(levels[i], i)).sum() };
|
||||
let mut levels = vec![0u8; widths.len()];
|
||||
for degraded in [1u8, 2u8] {
|
||||
for i in (0..widths.len()).rev() {
|
||||
@@ -923,10 +919,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
if total(&levels) > max_w {
|
||||
levels
|
||||
.iter_mut()
|
||||
.filter(|l| **l == 2)
|
||||
.for_each(|l| *l = 3);
|
||||
levels.iter_mut().filter(|l| **l == 2).for_each(|l| *l = 3);
|
||||
}
|
||||
levels
|
||||
}
|
||||
@@ -936,13 +929,21 @@ mod tests {
|
||||
for _ in 0..40 {
|
||||
let mut widths = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
|
||||
rng = rng
|
||||
.wrapping_mul(6364136223846793005)
|
||||
.wrapping_add(1442695040888963407);
|
||||
let full = (rng % 2000) as f32 / 10.0;
|
||||
rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
|
||||
rng = rng
|
||||
.wrapping_mul(6364136223846793005)
|
||||
.wrapping_add(1442695040888963407);
|
||||
let compact = (rng % (full as u64 * 10 + 1)) as f32 / 10.0;
|
||||
rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
|
||||
rng = rng
|
||||
.wrapping_mul(6364136223846793005)
|
||||
.wrapping_add(1442695040888963407);
|
||||
let button = (rng % (compact as u64 * 10 + 1)) as f32 / 10.0;
|
||||
rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
|
||||
rng = rng
|
||||
.wrapping_mul(6364136223846793005)
|
||||
.wrapping_add(1442695040888963407);
|
||||
let tight = (rng % (button as u64 * 10 + 1)) as f32 / 10.0;
|
||||
widths.push(Widths {
|
||||
full,
|
||||
|
||||
@@ -134,7 +134,9 @@ fn placeholder_swatch<'a>() -> Element<'a, Message> {
|
||||
.style(|theme: &Theme| {
|
||||
let palette = theme.palette();
|
||||
container::Style {
|
||||
background: Some(Background::Color(palette.background.weak.color.scale_alpha(0.5))),
|
||||
background: Some(Background::Color(
|
||||
palette.background.weak.color.scale_alpha(0.5),
|
||||
)),
|
||||
border: Border {
|
||||
color: palette.background.neutral.color.scale_alpha(0.5),
|
||||
width: 1.0,
|
||||
@@ -147,16 +149,12 @@ fn placeholder_swatch<'a>() -> Element<'a, Message> {
|
||||
}
|
||||
|
||||
fn divider<'a>() -> Element<'a, Message> {
|
||||
container(
|
||||
Space::new()
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fixed(1.0)),
|
||||
)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(theme.palette().background.neutral.color)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
container(Space::new().width(Length::Fill).height(Length::Fixed(1.0)))
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(theme.palette().background.neutral.color)),
|
||||
..Default::default()
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
fn logical_row<'a>(
|
||||
@@ -190,12 +188,9 @@ fn logical_row<'a>(
|
||||
});
|
||||
|
||||
button(
|
||||
row![
|
||||
swatch,
|
||||
text(t!(label)).size(11),
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
row![swatch, text(t!(label)).size(11),]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::RibbonColorChanged(color))
|
||||
.style(popup_row_style)
|
||||
@@ -205,18 +200,15 @@ fn logical_row<'a>(
|
||||
}
|
||||
|
||||
fn more_colors_row<'a>(active_color: AcadColor) -> Element<'a, Message> {
|
||||
button(
|
||||
row![text(t!("Select Color...")).size(11)]
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::OpenColorWindow(
|
||||
crate::app::ColorPickTarget::Ribbon,
|
||||
active_color,
|
||||
))
|
||||
.style(popup_row_style)
|
||||
.padding([4, 4])
|
||||
.width(Length::Fill)
|
||||
.into()
|
||||
button(row![text(t!("Select Color...")).size(11)].align_y(iced::Center))
|
||||
.on_press(Message::OpenColorWindow(
|
||||
crate::app::ColorPickTarget::Ribbon,
|
||||
active_color,
|
||||
))
|
||||
.style(popup_row_style)
|
||||
.padding([4, 4])
|
||||
.width(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Builds the complete 5-section color dropdown panel element.
|
||||
|
||||
@@ -42,7 +42,11 @@ pub fn pdf_underlay_tools(kind: codec::entities::UnderlayType) -> Vec<ToolDef> {
|
||||
"Enable\nSnap",
|
||||
include_bytes!("../../../assets/icons/snap_underlays.svg"),
|
||||
),
|
||||
tool("EXTERNALREFERENCES", "External\nReferences", crate::ui::icons::FOLDER_OPEN),
|
||||
tool(
|
||||
"EXTERNALREFERENCES",
|
||||
"External\nReferences",
|
||||
crate::ui::icons::FOLDER_OPEN,
|
||||
),
|
||||
tool(
|
||||
"ULAYERS",
|
||||
"Edit\nLayers",
|
||||
@@ -73,7 +77,11 @@ pub fn point_cloud_tools() -> Vec<ToolDef> {
|
||||
),
|
||||
tool("_PCCROPINVERT", "Invert\nCropping", CLIP),
|
||||
tool("_PCUNCROP", "Remove All\nCropping", UNCLIP),
|
||||
tool("EXTERNALREFERENCES", "External\nReferences", crate::ui::icons::FOLDER_OPEN),
|
||||
tool(
|
||||
"EXTERNALREFERENCES",
|
||||
"External\nReferences",
|
||||
crate::ui::icons::FOLDER_OPEN,
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -85,10 +93,18 @@ pub fn xref_tools() -> Vec<ToolDef> {
|
||||
"Edit Reference\nIn-Place",
|
||||
include_bytes!("../../../assets/icons/edit_block.svg"),
|
||||
),
|
||||
tool("_XREFOPEN", "Open\nReference", crate::ui::icons::FOLDER_OPEN),
|
||||
tool(
|
||||
"_XREFOPEN",
|
||||
"Open\nReference",
|
||||
crate::ui::icons::FOLDER_OPEN,
|
||||
),
|
||||
tool("_XREFCLIP", "Create Clipping\nBoundary", CLIP),
|
||||
tool("_XREFUNCLIP", "Remove\nClipping", UNCLIP),
|
||||
tool("EXTERNALREFERENCES", "External\nReferences", crate::ui::icons::FOLDER_OPEN),
|
||||
tool(
|
||||
"EXTERNALREFERENCES",
|
||||
"External\nReferences",
|
||||
crate::ui::icons::FOLDER_OPEN,
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
+54
-11
@@ -125,21 +125,50 @@ const REFERENCE_TOOLS: &[Tool] = &[Tool {
|
||||
pub(super) const REFERENCE_PANEL_ID: &str = "reference_extension";
|
||||
|
||||
const fn icon_tool(command: &'static str, label: &'static str, icon: &'static [u8]) -> Tool {
|
||||
Tool { command, label, icon, options: &[] }
|
||||
Tool {
|
||||
command,
|
||||
label,
|
||||
icon,
|
||||
options: &[],
|
||||
}
|
||||
}
|
||||
|
||||
/// Home > Block slide-out.
|
||||
const HOME_BLOCK_TOOLS: &[Tool] = &[
|
||||
icon_tool("ATTDEF", "Define Attributes", include_bytes!("../../../assets/icons/attdef.svg")),
|
||||
icon_tool("ATTMAN", "Manage Attributes", include_bytes!("../../../assets/icons/attman.svg")),
|
||||
icon_tool("ATTSYNC", "Synchronize", include_bytes!("../../../assets/icons/attsync.svg")),
|
||||
icon_tool("BASE", "Set Base Point", include_bytes!("../../../assets/icons/base_point.svg")),
|
||||
icon_tool(
|
||||
"ATTDEF",
|
||||
"Define Attributes",
|
||||
include_bytes!("../../../assets/icons/attdef.svg"),
|
||||
),
|
||||
icon_tool(
|
||||
"ATTMAN",
|
||||
"Manage Attributes",
|
||||
include_bytes!("../../../assets/icons/attman.svg"),
|
||||
),
|
||||
icon_tool(
|
||||
"ATTSYNC",
|
||||
"Synchronize",
|
||||
include_bytes!("../../../assets/icons/attsync.svg"),
|
||||
),
|
||||
icon_tool(
|
||||
"BASE",
|
||||
"Set Base Point",
|
||||
include_bytes!("../../../assets/icons/base_point.svg"),
|
||||
),
|
||||
];
|
||||
|
||||
/// Insert > Block Definition slide-out.
|
||||
const BLOCK_DEFINITION_TOOLS: &[Tool] = &[
|
||||
icon_tool("BASE", "Set Base Point", include_bytes!("../../../assets/icons/base_point.svg")),
|
||||
icon_tool("ATTSYNC", "Synchronize", include_bytes!("../../../assets/icons/attsync.svg")),
|
||||
icon_tool(
|
||||
"BASE",
|
||||
"Set Base Point",
|
||||
include_bytes!("../../../assets/icons/base_point.svg"),
|
||||
),
|
||||
icon_tool(
|
||||
"ATTSYNC",
|
||||
"Synchronize",
|
||||
include_bytes!("../../../assets/icons/attsync.svg"),
|
||||
),
|
||||
];
|
||||
|
||||
const PANELS: &[Panel] = &[
|
||||
@@ -213,7 +242,9 @@ fn panel_for_group(title: &str, group_ids: &[&'static str]) -> Option<Panel> {
|
||||
PANELS.iter().copied().find(|panel| {
|
||||
panel.title == title
|
||||
&& !panel.tools.is_empty()
|
||||
&& panel.anchor.is_none_or(|anchor| group_ids.contains(&anchor))
|
||||
&& panel
|
||||
.anchor
|
||||
.is_none_or(|anchor| group_ids.contains(&anchor))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -252,7 +283,11 @@ pub(super) fn group_title<'a>(
|
||||
let expanded = open.as_deref().and_then(parent_panel) == Some(panel.id);
|
||||
// A command running from the closed slide-out lights its title, the way a
|
||||
// dropdown lights while one of its items runs.
|
||||
let lit = expanded || panel.tools.iter().any(|tool| tool_is_active(tool, active_tool));
|
||||
let lit = expanded
|
||||
|| panel
|
||||
.tools
|
||||
.iter()
|
||||
.any(|tool| tool_is_active(tool, active_tool));
|
||||
let arrow = if expanded {
|
||||
icons::themed_arrow_up(GROUP_TITLE_ARROW_SIZE)
|
||||
} else {
|
||||
@@ -294,7 +329,11 @@ pub(super) fn group_title<'a>(
|
||||
})
|
||||
.style(move |theme: &Theme, status| tool_btn_style(theme, false, status))
|
||||
.padding([0, 3]),
|
||||
make_tip(format!("{}\n{} EXTERNALREFERENCES", t!("External References"), t!("Command:"))),
|
||||
make_tip(format!(
|
||||
"{}\n{} EXTERNALREFERENCES",
|
||||
t!("External References"),
|
||||
t!("Command:")
|
||||
)),
|
||||
tooltip::Position::Bottom,
|
||||
)
|
||||
.delay(Duration::from_millis(400))
|
||||
@@ -377,7 +416,11 @@ fn reference_overlay<'a>(ribbon: &Ribbon, panel: Panel, win: (f32, f32)) -> Elem
|
||||
let fading = row![
|
||||
tooltip(
|
||||
switch,
|
||||
make_tip(format!("{}\n{} XDWGFADECTL", t!("Xref fading"), t!("Command:"))),
|
||||
make_tip(format!(
|
||||
"{}\n{} XDWGFADECTL",
|
||||
t!("Xref fading"),
|
||||
t!("Command:")
|
||||
)),
|
||||
tooltip::Position::Bottom
|
||||
)
|
||||
.delay(Duration::from_millis(400))
|
||||
|
||||
+123
-175
@@ -20,21 +20,21 @@ use crate::modules::{CadModule, IconKind, RibbonGroup, RibbonItem};
|
||||
use crate::plugin::all_ribbon_modules;
|
||||
use crate::ui::properties::{linetype_display_name, lw_options, LinetypeItem};
|
||||
|
||||
pub(crate) mod widgets;
|
||||
mod draw_panel;
|
||||
pub(crate) mod widgets;
|
||||
pub(crate) use draw_panel::tools as panel_tools;
|
||||
mod modify_panel;
|
||||
mod color_dropdown;
|
||||
mod context_tools;
|
||||
mod modify_panel;
|
||||
pub use context_tools::{pdf_underlay_tools, point_cloud_tools, xref_tools, UnderlayContext};
|
||||
use widgets::{StyleContext, *};
|
||||
pub(crate) use widgets::{REDO_HISTORY_ID, UNDO_HISTORY_ID};
|
||||
mod collapse;
|
||||
mod locate;
|
||||
use collapse::{CollapsePanels, Panel};
|
||||
pub use collapse::CollapseMode;
|
||||
use crate::ui::wrap_bar::{PosReport, WrapBar, WrapFlow};
|
||||
use crate::t;
|
||||
use crate::ui::wrap_bar::{PosReport, WrapBar, WrapFlow};
|
||||
pub use collapse::CollapseMode;
|
||||
use collapse::{CollapsePanels, Panel};
|
||||
|
||||
pub(crate) fn tooltip_content(text: String) -> Element<'static, Message> {
|
||||
widgets::make_tip(text)
|
||||
@@ -75,7 +75,6 @@ pub struct Ribbon {
|
||||
/// shows this tool on its button, defaulting to the panel's first tool.
|
||||
last_panel_tool: HashMap<&'static str, &'static str>,
|
||||
last_cmd: HashMap<&'static str, &'static str>,
|
||||
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
|
||||
@@ -192,7 +191,6 @@ impl Ribbon {
|
||||
last_panel_tool: HashMap::default(),
|
||||
last_cmd: HashMap::default(),
|
||||
// Empty until a document is open — populated by sync_ribbon_layers.
|
||||
layer_names: vec![],
|
||||
active_layer: String::new(),
|
||||
layer_infos: vec![],
|
||||
layer_filter: String::new(),
|
||||
@@ -288,7 +286,6 @@ impl Ribbon {
|
||||
|
||||
pub fn set_layers(&mut self, infos: Vec<LayerInfo>, active: &str) {
|
||||
self.active_layer = active.to_string();
|
||||
self.layer_names = infos.iter().map(|l| l.name.clone()).collect();
|
||||
self.layer_infos = infos;
|
||||
}
|
||||
|
||||
@@ -561,36 +558,42 @@ impl Ribbon {
|
||||
let is_contextual = module.id() == "layout";
|
||||
let holds_tool = holding_tab == Some(i);
|
||||
let btn = container(
|
||||
button(text(crate::i18n::ribbon_module_title(module.id(), module.title())).size(12))
|
||||
.on_press(Message::RibbonSelectTab(i))
|
||||
.style(move |theme: &Theme, status| {
|
||||
let palette = theme.palette();
|
||||
let accent = if is_contextual {
|
||||
palette.warning.base
|
||||
} else {
|
||||
palette.primary.base
|
||||
};
|
||||
let pair = match (is_active, status) {
|
||||
(true, _) => palette.background.weakest,
|
||||
(false, _) if holds_tool => palette.primary.weak,
|
||||
(false, button::Status::Hovered) => {
|
||||
if is_contextual {
|
||||
palette.warning.weak
|
||||
} else {
|
||||
palette.background.weak
|
||||
}
|
||||
button(
|
||||
text(crate::i18n::ribbon_module_title(
|
||||
module.id(),
|
||||
module.title(),
|
||||
))
|
||||
.size(12),
|
||||
)
|
||||
.on_press(Message::RibbonSelectTab(i))
|
||||
.style(move |theme: &Theme, status| {
|
||||
let palette = theme.palette();
|
||||
let accent = if is_contextual {
|
||||
palette.warning.base
|
||||
} else {
|
||||
palette.primary.base
|
||||
};
|
||||
let pair = match (is_active, status) {
|
||||
(true, _) => palette.background.weakest,
|
||||
(false, _) if holds_tool => palette.primary.weak,
|
||||
(false, button::Status::Hovered) => {
|
||||
if is_contextual {
|
||||
palette.warning.weak
|
||||
} else {
|
||||
palette.background.weak
|
||||
}
|
||||
_ => palette.background.base,
|
||||
};
|
||||
button::Style {
|
||||
}
|
||||
_ => palette.background.base,
|
||||
};
|
||||
button::Style {
|
||||
background: (is_active
|
||||
|| holds_tool
|
||||
|| matches!(status, button::Status::Hovered))
|
||||
.then_some(Background::Color(if holds_tool && !is_active {
|
||||
pair.color.scale_alpha(0.35)
|
||||
} else {
|
||||
pair.color
|
||||
})),
|
||||
.then_some(Background::Color(if holds_tool && !is_active {
|
||||
pair.color.scale_alpha(0.35)
|
||||
} else {
|
||||
pair.color
|
||||
})),
|
||||
text_color: if is_active {
|
||||
pair.text
|
||||
} else if holds_tool {
|
||||
@@ -611,9 +614,9 @@ impl Ribbon {
|
||||
},
|
||||
shadow: iced::Shadow::default(),
|
||||
snap: false,
|
||||
}
|
||||
})
|
||||
.padding([5, 14]),
|
||||
}
|
||||
})
|
||||
.padding([5, 14]),
|
||||
)
|
||||
.style(move |theme: &Theme| container::Style {
|
||||
border: Border {
|
||||
@@ -636,7 +639,6 @@ impl Ribbon {
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
// Tabs may squeeze their gaps to fit before wrapping: from the normal 6px
|
||||
// down to -12px on a narrow (e.g. phone) tab row, tucking neighbours into
|
||||
// each other's 14px side padding without overlapping the labels. When the
|
||||
@@ -654,9 +656,7 @@ impl Ribbon {
|
||||
let dd_open = self.open_dropdown.as_deref() == Some(COLLAPSE_MODE_ID);
|
||||
let mode_btn = button(crate::ui::icons::themed_arrow_down(10.0))
|
||||
.on_press(Message::ToggleRibbonDropdown(COLLAPSE_MODE_ID.to_string()))
|
||||
.style(move |theme: &Theme, status| {
|
||||
top_hist_btn_style(theme, true, dd_open, status)
|
||||
})
|
||||
.style(move |theme: &Theme, status| top_hist_btn_style(theme, true, dd_open, status))
|
||||
.height(24)
|
||||
.padding([2, 8]);
|
||||
let mode_dd = PosReport::new(COLLAPSE_MODE_ID, mode_btn);
|
||||
@@ -675,9 +675,7 @@ impl Ribbon {
|
||||
|
||||
let tab_bar = container(tab_row)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.base.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.base.color)),
|
||||
..Default::default()
|
||||
})
|
||||
.padding(Padding {
|
||||
@@ -732,9 +730,7 @@ impl Ribbon {
|
||||
|
||||
let tool_bar: Element<'_, Message> = container(tool_area)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme.palette().background.weakest.color,
|
||||
)),
|
||||
background: Some(Background::Color(theme.palette().background.weakest.color)),
|
||||
border: Border {
|
||||
color: theme.palette().background.neutral.color,
|
||||
width: 1.0,
|
||||
@@ -755,12 +751,7 @@ impl Ribbon {
|
||||
.height(Fill)
|
||||
.style(|theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(
|
||||
theme
|
||||
.palette()
|
||||
.background
|
||||
.strongest
|
||||
.color
|
||||
.scale_alpha(0.58),
|
||||
theme.palette().background.strongest.color.scale_alpha(0.58),
|
||||
)),
|
||||
..Default::default()
|
||||
}),
|
||||
@@ -915,9 +906,9 @@ impl Ribbon {
|
||||
RibbonItem::Dropdown {
|
||||
id, items, default, ..
|
||||
} => (*id, items, *default),
|
||||
RibbonItem::LabeledDropdown { id, items, default, .. } => {
|
||||
(*id, items, *default)
|
||||
}
|
||||
RibbonItem::LabeledDropdown {
|
||||
id, items, default, ..
|
||||
} => (*id, items, *default),
|
||||
RibbonItem::LargeDropdown {
|
||||
id, items, default, ..
|
||||
} => (*id, items, *default),
|
||||
@@ -941,24 +932,16 @@ impl Ribbon {
|
||||
let disabled = widgets::is_disabled_item(cmd);
|
||||
let checkmark: Element<'_, Message> =
|
||||
crate::ui::icons::themed_check_cell(is_current);
|
||||
let icon_el: Element<Message> =
|
||||
container(make_icon(*item_icon, 20.0))
|
||||
.width(Length::Fixed(20.0))
|
||||
.into();
|
||||
let label_el =
|
||||
text(t!(*label))
|
||||
.size(11)
|
||||
.wrapping(iced::advanced::text::Wrapping::None)
|
||||
.style(move |theme: &Theme| iced::widget::text::Style {
|
||||
color: (!is_current || disabled).then_some(
|
||||
theme
|
||||
.palette()
|
||||
.background
|
||||
.base
|
||||
.text
|
||||
.scale_alpha(0.72),
|
||||
),
|
||||
});
|
||||
let icon_el: Element<Message> = container(make_icon(*item_icon, 20.0))
|
||||
.width(Length::Fixed(20.0))
|
||||
.into();
|
||||
let label_el = text(t!(*label))
|
||||
.size(11)
|
||||
.wrapping(iced::advanced::text::Wrapping::None)
|
||||
.style(move |theme: &Theme| iced::widget::text::Style {
|
||||
color: (!is_current || disabled)
|
||||
.then_some(theme.palette().background.base.text.scale_alpha(0.72)),
|
||||
});
|
||||
|
||||
button(
|
||||
row![checkmark, icon_el, label_el]
|
||||
@@ -1015,9 +998,7 @@ impl Ribbon {
|
||||
let rows: Vec<Element<Message>> = self
|
||||
.layer_infos
|
||||
.iter()
|
||||
.filter(|info| {
|
||||
filter.is_empty() || info.name.to_lowercase().contains(&filter)
|
||||
})
|
||||
.filter(|info| filter.is_empty() || info.name.to_lowercase().contains(&filter))
|
||||
.map(|info| {
|
||||
let is_active = info.name == self.active_layer;
|
||||
let lc = info.color;
|
||||
@@ -1060,19 +1041,12 @@ impl Ribbon {
|
||||
});
|
||||
let checkmark: Element<'_, Message> =
|
||||
crate::ui::icons::themed_check_cell(is_active);
|
||||
let label =
|
||||
text(&info.name)
|
||||
.size(11)
|
||||
.style(move |theme: &Theme| iced::widget::text::Style {
|
||||
color: (!is_active).then_some(
|
||||
theme
|
||||
.palette()
|
||||
.background
|
||||
.base
|
||||
.text
|
||||
.scale_alpha(0.72),
|
||||
),
|
||||
});
|
||||
let label = text(&info.name).size(11).style(move |theme: &Theme| {
|
||||
iced::widget::text::Style {
|
||||
color: (!is_active)
|
||||
.then_some(theme.palette().background.base.text.scale_alpha(0.72)),
|
||||
}
|
||||
});
|
||||
|
||||
// The swatch + label area selects the layer as active; the
|
||||
// icon buttons above handle their own toggles.
|
||||
@@ -1135,10 +1109,10 @@ impl Ribbon {
|
||||
]
|
||||
.spacing(2),
|
||||
)
|
||||
.style(popup_panel_style)
|
||||
.width(Length::Fixed(220.0));
|
||||
.style(popup_panel_style)
|
||||
.width(Length::Fixed(300.0));
|
||||
|
||||
let (align_right, h_pad, top) = self.dd_anchor(LAYER_COMBO_ID, 220.0, win.0);
|
||||
let (align_right, h_pad, top) = self.dd_anchor(LAYER_COMBO_ID, 300.0, win.0);
|
||||
let positioned = position_ribbon_dropdown(panel.into(), align_right, h_pad, top);
|
||||
|
||||
Some(dropdown_backdrop(positioned))
|
||||
@@ -1190,23 +1164,17 @@ impl Ribbon {
|
||||
.map(|name| {
|
||||
let is_sel = name.as_str() == active.as_str();
|
||||
let n = name.clone();
|
||||
let checkmark: Element<Message> =
|
||||
crate::ui::icons::themed_check_cell(is_sel);
|
||||
let checkmark: Element<Message> = crate::ui::icons::themed_check_cell(is_sel);
|
||||
button(
|
||||
row![
|
||||
checkmark,
|
||||
text(name.clone())
|
||||
.size(11)
|
||||
.style(move |theme: &Theme| iced::widget::text::Style {
|
||||
text(name.clone()).size(11).style(move |theme: &Theme| {
|
||||
iced::widget::text::Style {
|
||||
color: (!is_sel).then_some(
|
||||
theme
|
||||
.palette()
|
||||
.background
|
||||
.base
|
||||
.text
|
||||
.scale_alpha(0.72),
|
||||
theme.palette().background.base.text.scale_alpha(0.72),
|
||||
),
|
||||
}),
|
||||
}
|
||||
}),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(iced::Center),
|
||||
@@ -1286,19 +1254,12 @@ impl Ribbon {
|
||||
.into_iter()
|
||||
.map(|lt| {
|
||||
let is_cur = lt.name == *active_lt;
|
||||
let check: Element<'_, Message> =
|
||||
crate::ui::icons::themed_check_cell(is_cur);
|
||||
let check: Element<'_, Message> = crate::ui::icons::themed_check_cell(is_cur);
|
||||
let name_col = text(linetype_display_name(<.name))
|
||||
.size(11)
|
||||
.style(move |theme: &Theme| iced::widget::text::Style {
|
||||
color: (!is_cur).then_some(
|
||||
theme
|
||||
.palette()
|
||||
.background
|
||||
.base
|
||||
.text
|
||||
.scale_alpha(0.72),
|
||||
),
|
||||
color: (!is_cur)
|
||||
.then_some(theme.palette().background.base.text.scale_alpha(0.72)),
|
||||
})
|
||||
.width(Length::Fixed(90.0));
|
||||
let art_col = text(lt.art.clone()).size(9).style(muted_text_style);
|
||||
@@ -1336,23 +1297,17 @@ impl Ribbon {
|
||||
.map(|item| {
|
||||
let is_cur = item.0 == active_lw;
|
||||
let label = item.to_string();
|
||||
let check: Element<'_, Message> =
|
||||
crate::ui::icons::themed_check_cell(is_cur);
|
||||
let check: Element<'_, Message> = crate::ui::icons::themed_check_cell(is_cur);
|
||||
button(
|
||||
row![
|
||||
check,
|
||||
text(label)
|
||||
.size(11)
|
||||
.style(move |theme: &Theme| iced::widget::text::Style {
|
||||
text(label).size(11).style(move |theme: &Theme| {
|
||||
iced::widget::text::Style {
|
||||
color: (!is_cur).then_some(
|
||||
theme
|
||||
.palette()
|
||||
.background
|
||||
.base
|
||||
.text
|
||||
.scale_alpha(0.72),
|
||||
theme.palette().background.base.text.scale_alpha(0.72),
|
||||
),
|
||||
})
|
||||
}
|
||||
})
|
||||
]
|
||||
.spacing(5)
|
||||
.align_y(iced::Center),
|
||||
@@ -1438,13 +1393,7 @@ fn render_group<'a>(
|
||||
flush_small_col(&mut small_buf, &mut items_row);
|
||||
items_row.push(render_large(item, &ctx));
|
||||
} else {
|
||||
small_buf.push(render_small(
|
||||
item,
|
||||
active_tool,
|
||||
open_dd,
|
||||
last_cmd,
|
||||
state,
|
||||
));
|
||||
small_buf.push(render_small(item, active_tool, open_dd, last_cmd, state));
|
||||
if small_buf.len() == 3 {
|
||||
flush_small_col(&mut small_buf, &mut items_row);
|
||||
}
|
||||
@@ -1514,9 +1463,11 @@ fn first_tool_icon(group: &RibbonGroup) -> Option<IconKind> {
|
||||
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),
|
||||
}
|
||||
RibbonItem::ToolGrid { columns } => columns
|
||||
.first()
|
||||
.and_then(|column| column.first())
|
||||
.map(|tool| tool.icon),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1600,44 +1551,42 @@ fn collapse_button<'a>(
|
||||
// For a Properties panel the representative is its Match button.
|
||||
let rep = representative(group, last_used);
|
||||
// The face lights itself when it is the running tool.
|
||||
let face_lit = rep.zip(active).is_some_and(|(item, id)| locate::item_holds(item, id));
|
||||
let face_lit = rep
|
||||
.zip(active)
|
||||
.is_some_and(|(item, id)| locate::item_holds(item, id));
|
||||
let face: Element<'_, Message> = match rep {
|
||||
Some(RibbonItem::PropertiesGroup { match_prop }) => {
|
||||
render_large(
|
||||
&RibbonItem::LargeTool(match_prop.clone()),
|
||||
&widgets::RenderCtx {
|
||||
active_tool,
|
||||
open_dd,
|
||||
last_cmd,
|
||||
state,
|
||||
layer_infos,
|
||||
active_layer,
|
||||
active_color,
|
||||
active_linetype,
|
||||
active_lineweight,
|
||||
style_ctx,
|
||||
compact: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
Some(item) => {
|
||||
render_large(
|
||||
item,
|
||||
&widgets::RenderCtx {
|
||||
active_tool,
|
||||
open_dd,
|
||||
last_cmd,
|
||||
state,
|
||||
layer_infos,
|
||||
active_layer,
|
||||
active_color,
|
||||
active_linetype,
|
||||
active_lineweight,
|
||||
style_ctx,
|
||||
compact: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
Some(RibbonItem::PropertiesGroup { match_prop }) => render_large(
|
||||
&RibbonItem::LargeTool(match_prop.clone()),
|
||||
&widgets::RenderCtx {
|
||||
active_tool,
|
||||
open_dd,
|
||||
last_cmd,
|
||||
state,
|
||||
layer_infos,
|
||||
active_layer,
|
||||
active_color,
|
||||
active_linetype,
|
||||
active_lineweight,
|
||||
style_ctx,
|
||||
compact: false,
|
||||
},
|
||||
),
|
||||
Some(item) => render_large(
|
||||
item,
|
||||
&widgets::RenderCtx {
|
||||
active_tool,
|
||||
open_dd,
|
||||
last_cmd,
|
||||
state,
|
||||
layer_infos,
|
||||
active_layer,
|
||||
active_color,
|
||||
active_linetype,
|
||||
active_lineweight,
|
||||
style_ctx,
|
||||
compact: false,
|
||||
},
|
||||
),
|
||||
None => text("").into(),
|
||||
};
|
||||
|
||||
@@ -1709,4 +1658,3 @@ mod tests {
|
||||
assert_eq!(ribbon.open_dropdown, None);
|
||||
}
|
||||
}
|
||||
|
||||
+156
-126
@@ -20,10 +20,10 @@ use iced::{
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::modules::{IconKind, ModuleEvent, RibbonItem, StyleKey, ToolDef};
|
||||
use crate::ui::wrap_bar::PosReport;
|
||||
use crate::t;
|
||||
use crate::ui::icons;
|
||||
use crate::ui::properties::{acad_color_display, linetype_display_name, LwItem};
|
||||
use crate::t;
|
||||
use crate::ui::wrap_bar::PosReport;
|
||||
|
||||
use super::LayerInfo;
|
||||
|
||||
@@ -110,8 +110,8 @@ fn ribbon_label_bounds(
|
||||
) -> Size {
|
||||
use advanced_text::{Paragraph as _, Renderer as _};
|
||||
|
||||
let paragraph = <iced::Renderer as advanced_text::Renderer>::Paragraph::with_text(
|
||||
advanced_text::Text {
|
||||
let paragraph =
|
||||
<iced::Renderer as advanced_text::Renderer>::Paragraph::with_text(advanced_text::Text {
|
||||
content: label,
|
||||
bounds: Size::new(width, f32::INFINITY),
|
||||
size: Pixels(LARGE_LABEL_SIZE),
|
||||
@@ -123,8 +123,7 @@ fn ribbon_label_bounds(
|
||||
wrapping,
|
||||
ellipsis: advanced_text::Ellipsis::None,
|
||||
hint_factor: None,
|
||||
},
|
||||
);
|
||||
});
|
||||
paragraph.min_bounds()
|
||||
}
|
||||
|
||||
@@ -139,12 +138,7 @@ fn measure_large_width(renderer: &iced::Renderer, label: &str) -> f32 {
|
||||
.0;
|
||||
let max_label_height = line_height * LARGE_LABEL_LINES + 0.5;
|
||||
let fits = |width: f32| {
|
||||
let bounds = ribbon_label_bounds(
|
||||
renderer,
|
||||
label,
|
||||
width,
|
||||
advanced_text::Wrapping::Word,
|
||||
);
|
||||
let bounds = ribbon_label_bounds(renderer, label, width, advanced_text::Wrapping::Word);
|
||||
bounds.width <= width + 0.5 && bounds.height <= max_label_height
|
||||
};
|
||||
|
||||
@@ -218,11 +212,9 @@ impl Widget<Message, Theme, iced::Renderer> for AutomaticLargeWidth<'_> {
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let width = automatic_large_width(renderer, &self.label);
|
||||
self.content.as_widget_mut().layout(
|
||||
tree,
|
||||
renderer,
|
||||
&limits.width(Length::Fixed(width)),
|
||||
)
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
.layout(tree, renderer, &limits.width(Length::Fixed(width)))
|
||||
}
|
||||
|
||||
fn operate(
|
||||
@@ -247,9 +239,9 @@ impl Widget<Message, Theme, iced::Renderer> for AutomaticLargeWidth<'_> {
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
self.content.as_widget_mut().update(
|
||||
tree, event, layout, cursor, renderer, shell, viewport,
|
||||
);
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
.update(tree, event, layout, cursor, renderer, shell, viewport);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
@@ -371,11 +363,7 @@ pub(super) fn make_icon(icon: IconKind, size: f32) -> Element<'static, Message>
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_active_tool(
|
||||
id: &str,
|
||||
active_tool: &Option<String>,
|
||||
state: &ToggleState,
|
||||
) -> bool {
|
||||
pub(super) fn is_active_tool(id: &str, active_tool: &Option<String>, state: &ToggleState) -> bool {
|
||||
match id {
|
||||
"ORTHO" => state.ortho_mode,
|
||||
"PERSP" => !state.ortho_mode,
|
||||
@@ -409,8 +397,10 @@ pub(super) fn tool_btn_style(
|
||||
button::Style {
|
||||
background: is_active
|
||||
.then_some(Background::Color(pair.color))
|
||||
.or_else(|| matches!(status, button::Status::Hovered | button::Status::Pressed)
|
||||
.then_some(Background::Color(pair.color))),
|
||||
.or_else(|| {
|
||||
matches!(status, button::Status::Hovered | button::Status::Pressed)
|
||||
.then_some(Background::Color(pair.color))
|
||||
}),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
radius: 3.0.into(),
|
||||
@@ -569,7 +559,9 @@ pub(super) fn render_small<'a>(
|
||||
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);
|
||||
]
|
||||
.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))
|
||||
@@ -577,7 +569,10 @@ pub(super) fn render_small<'a>(
|
||||
.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()
|
||||
.gap(6.0)
|
||||
.delay(Duration::from_millis(400))
|
||||
.style(tip_style)
|
||||
.into()
|
||||
}
|
||||
|
||||
RibbonItem::Dropdown {
|
||||
@@ -644,9 +639,7 @@ pub(super) fn render_small<'a>(
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::ToggleRibbonDropdown(id.to_string()))
|
||||
.style(move |theme: &Theme, status| {
|
||||
tool_btn_style(theme, dd_open, status)
|
||||
})
|
||||
.style(move |theme: &Theme, status| tool_btn_style(theme, dd_open, status))
|
||||
.width(Length::Fixed(ARROW_W))
|
||||
.height(ROW_H)
|
||||
.padding(0);
|
||||
@@ -667,15 +660,30 @@ pub(super) fn render_small<'a>(
|
||||
.into()
|
||||
}
|
||||
|
||||
RibbonItem::LabeledDropdown { id, label, icon, items, default } => {
|
||||
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));
|
||||
|| 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 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 = if label.is_empty() {
|
||||
items
|
||||
.iter()
|
||||
@@ -687,8 +695,12 @@ pub(super) fn render_small<'a>(
|
||||
};
|
||||
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);
|
||||
text(localized_label.clone())
|
||||
.size(10)
|
||||
.wrapping(advanced_text::Wrapping::None),
|
||||
]
|
||||
.spacing(3)
|
||||
.align_y(iced::Center);
|
||||
// A state that is only shown (not chosen) opens the list instead.
|
||||
let face_btn = button(face)
|
||||
.on_press(if is_disabled_item(last) {
|
||||
@@ -700,20 +712,39 @@ pub(super) fn render_small<'a>(
|
||||
}
|
||||
})
|
||||
.style(move |theme: &Theme, status| tool_btn_style(theme, active, status))
|
||||
.width(Length::Shrink).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);
|
||||
.width(Length::Shrink)
|
||||
.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 = tool_tip_text(&localized_label, 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()
|
||||
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(),
|
||||
@@ -765,13 +796,23 @@ pub(super) fn render_large_dropdown<'a>(
|
||||
let cur_icon = last_cmd
|
||||
.get(id)
|
||||
.copied()
|
||||
.and_then(|cmd| items.iter().find(|(c, _, _)| *c == cmd).map(|(_, _, ik)| *ik))
|
||||
.and_then(|cmd| {
|
||||
items
|
||||
.iter()
|
||||
.find(|(c, _, _)| *c == cmd)
|
||||
.map(|(_, _, ik)| *ik)
|
||||
})
|
||||
.or_else(|| items.first().map(|(_, _, ik)| *ik))
|
||||
.unwrap_or(icon);
|
||||
let cur_label = last_cmd
|
||||
.get(id)
|
||||
.copied()
|
||||
.and_then(|cmd| items.iter().find(|(c, _, _)| *c == cmd).map(|(_, lbl, _)| *lbl))
|
||||
.and_then(|cmd| {
|
||||
items
|
||||
.iter()
|
||||
.find(|(c, _, _)| *c == cmd)
|
||||
.map(|(_, lbl, _)| *lbl)
|
||||
})
|
||||
.or_else(|| items.first().map(|(_, lbl, _)| *lbl))
|
||||
.unwrap_or(id);
|
||||
let label = t!(explicit_label.unwrap_or(cur_label)).into_owned();
|
||||
@@ -820,9 +861,7 @@ pub(super) fn render_large_dropdown<'a>(
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::ToggleRibbonDropdown(id.to_string()))
|
||||
.style(move |theme: &Theme, status| {
|
||||
tool_btn_style(theme, dd_open, status)
|
||||
})
|
||||
.style(move |theme: &Theme, status| tool_btn_style(theme, dd_open, status))
|
||||
.width(Fill)
|
||||
.height(LARGE_ARR)
|
||||
.padding(0);
|
||||
@@ -841,8 +880,7 @@ pub(super) fn render_large_dropdown<'a>(
|
||||
.width(Fill)
|
||||
.height(Fill);
|
||||
|
||||
PosReport::new(id, automatic_large_button(label, content.into()))
|
||||
.into()
|
||||
PosReport::new(id, automatic_large_button(label, content.into())).into()
|
||||
}
|
||||
|
||||
/// A row of small tool buttons beneath a combo dropdown. Shared by the
|
||||
@@ -860,9 +898,7 @@ fn tool_row<'a>(tools: &[ToolDef], active_tool: &Option<String>) -> Element<'a,
|
||||
tooltip(
|
||||
button(icon_el)
|
||||
.on_press(msg)
|
||||
.style(move |theme: &Theme, status| {
|
||||
tool_btn_style(theme, is_active, status)
|
||||
})
|
||||
.style(move |theme: &Theme, status| tool_btn_style(theme, is_active, status))
|
||||
.padding([2, 5]),
|
||||
make_tip(tip.to_string()),
|
||||
TipPos::Right,
|
||||
@@ -889,10 +925,7 @@ fn combo_panel_col(width: f32, items: Vec<Element<'_, Message>>) -> Element<'_,
|
||||
}
|
||||
|
||||
/// Render a full-height large button (LargeTool, LargeDropdown, LayerCombo, StyleCombo).
|
||||
pub(super) fn render_large<'a>(
|
||||
item: &RibbonItem,
|
||||
ctx: &RenderCtx<'_>,
|
||||
) -> Element<'a, Message> {
|
||||
pub(super) fn render_large<'a>(item: &RibbonItem, ctx: &RenderCtx<'_>) -> Element<'a, Message> {
|
||||
let active_tool = ctx.active_tool;
|
||||
let open_dd = ctx.open_dd;
|
||||
let last_cmd = ctx.last_cmd;
|
||||
@@ -946,10 +979,10 @@ pub(super) fn render_large<'a>(
|
||||
make_tip(tip_text),
|
||||
TipPos::Right,
|
||||
)
|
||||
.gap(6.0)
|
||||
.delay(Duration::from_millis(400))
|
||||
.style(tip_style)
|
||||
.into()
|
||||
.gap(6.0)
|
||||
.delay(Duration::from_millis(400))
|
||||
.style(tip_style)
|
||||
.into()
|
||||
}
|
||||
|
||||
RibbonItem::LargeDropdown {
|
||||
@@ -958,26 +991,37 @@ pub(super) fn render_large<'a>(
|
||||
icon,
|
||||
items,
|
||||
default,
|
||||
} => {
|
||||
render_large_dropdown(*id, *icon, Some(*label), items, *default, ctx)
|
||||
}
|
||||
} => 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::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(),
|
||||
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).
|
||||
@@ -986,9 +1030,7 @@ pub(super) fn render_large<'a>(
|
||||
icon,
|
||||
items,
|
||||
default,
|
||||
} => {
|
||||
render_large_dropdown(*id, *icon, None, items, *default, ctx)
|
||||
}
|
||||
} => render_large_dropdown(*id, *icon, None, items, *default, ctx),
|
||||
|
||||
RibbonItem::LayerComboGroup { row2, row3 } => {
|
||||
const TOOL_BUTTON_W: f32 = 26.0;
|
||||
@@ -998,7 +1040,9 @@ pub(super) fn render_large<'a>(
|
||||
let tools_w = tool_count * TOOL_BUTTON_W
|
||||
+ (tool_count - 1.0).max(0.0) * TOOL_SPACING
|
||||
+ GROUP_PADDING;
|
||||
let combo_w = (LARGE_W * 2.5).max(tools_w);
|
||||
// Wide enough to read a typical layer name (about 25 characters)
|
||||
// in the closed combo.
|
||||
let combo_w = (LARGE_W * 2.5).max(tools_w).max(250.0);
|
||||
|
||||
let info = layer_infos.iter().find(|l| l.name == active_layer);
|
||||
let lc = info.map(|l| l.color).unwrap_or(Color::WHITE);
|
||||
@@ -1023,15 +1067,13 @@ pub(super) fn render_large<'a>(
|
||||
.width(12)
|
||||
.height(12);
|
||||
|
||||
const FIXED_COMBO_W: f32 =
|
||||
14.0 * 3.0 + 12.0 + 9.0 + 4.0 * 4.0 + 8.0 * 2.0;
|
||||
const FIXED_COMBO_W: f32 = 14.0 * 3.0 + 12.0 + 9.0 + 4.0 * 4.0 + 8.0 * 2.0;
|
||||
let name_w = (combo_w - FIXED_COMBO_W).max(24.0);
|
||||
// About 6 px per glyph at 11 px. The dropdown itself keeps the
|
||||
// complete layer name available; only its closed ribbon label is
|
||||
// shortened to preserve the fixed row height.
|
||||
let name_budget = ((name_w / 6.0) as usize).max(4);
|
||||
let active_layer_label =
|
||||
crate::ui::text_util::elide(active_layer, name_budget);
|
||||
let active_layer_label = crate::ui::text_util::elide(active_layer, name_budget);
|
||||
|
||||
let combo_btn = button(
|
||||
row![
|
||||
@@ -1048,16 +1090,16 @@ pub(super) fn render_large<'a>(
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::ToggleRibbonDropdown(LAYER_COMBO_ID.to_string()))
|
||||
.style(move |theme: &Theme, status| {
|
||||
combo_btn_style(theme, is_open, status, 3.0)
|
||||
})
|
||||
.style(move |theme: &Theme, status| combo_btn_style(theme, is_open, status, 3.0))
|
||||
.padding([3, 8])
|
||||
.width(Fill);
|
||||
|
||||
combo_panel_col(
|
||||
combo_w,
|
||||
vec![
|
||||
container(PosReport::new(LAYER_COMBO_ID, combo_btn)).width(Fill).into(),
|
||||
container(PosReport::new(LAYER_COMBO_ID, combo_btn))
|
||||
.width(Fill)
|
||||
.into(),
|
||||
tool_row(row2, active_tool),
|
||||
tool_row(row3, active_tool),
|
||||
],
|
||||
@@ -1109,9 +1151,7 @@ pub(super) fn render_large<'a>(
|
||||
button(
|
||||
row![
|
||||
swatch_el,
|
||||
container(text(label).size(10))
|
||||
.width(Fill)
|
||||
.clip(true),
|
||||
container(text(label).size(10)).width(Fill).clip(true),
|
||||
if is_open {
|
||||
icons::themed_arrow_up(8.0)
|
||||
} else {
|
||||
@@ -1122,9 +1162,7 @@ pub(super) fn render_large<'a>(
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::ToggleRibbonDropdown(dd_id.to_string()))
|
||||
.style(move |theme: &Theme, status| {
|
||||
combo_btn_style(theme, is_open, status, 2.0)
|
||||
})
|
||||
.style(move |theme: &Theme, status| combo_btn_style(theme, is_open, status, 2.0))
|
||||
.padding([3, 8])
|
||||
.width(Length::Fixed(PROP_W))
|
||||
};
|
||||
@@ -1195,9 +1233,7 @@ pub(super) fn render_large<'a>(
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.on_press(Message::ToggleRibbonDropdown(combo_id.to_string()))
|
||||
.style(move |theme: &Theme, status| {
|
||||
combo_btn_style(theme, is_open, status, 3.0)
|
||||
})
|
||||
.style(move |theme: &Theme, status| combo_btn_style(theme, is_open, status, 3.0))
|
||||
.padding([3, 8])
|
||||
.width(Fill);
|
||||
|
||||
@@ -1208,9 +1244,11 @@ pub(super) fn render_large<'a>(
|
||||
iced::widget::Space::new().width(0).height(0).into();
|
||||
|
||||
let mut col_items: Vec<Element<Message>> =
|
||||
vec![container(row![PosReport::new(*combo_id, combo_btn), items_panel].spacing(0))
|
||||
.width(Fill)
|
||||
.into()];
|
||||
vec![
|
||||
container(row![PosReport::new(*combo_id, combo_btn), items_panel].spacing(0))
|
||||
.width(Fill)
|
||||
.into(),
|
||||
];
|
||||
for row_tools in rows {
|
||||
col_items.push(tool_row(row_tools, active_tool));
|
||||
}
|
||||
@@ -1299,9 +1337,7 @@ pub(super) fn render_history_control<'a>(
|
||||
.align_x(iced::Center)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(move |theme: &Theme, status| {
|
||||
top_hist_btn_style(theme, active, dd_open, status)
|
||||
})
|
||||
.style(move |theme: &Theme, status| top_hist_btn_style(theme, active, dd_open, status))
|
||||
.width(Length::Fixed(TOP_HIST_W))
|
||||
.height(24)
|
||||
.padding([2, 0]);
|
||||
@@ -1340,9 +1376,7 @@ pub(super) fn render_history_control<'a>(
|
||||
.align_x(iced::Center)
|
||||
.align_y(iced::Center),
|
||||
)
|
||||
.style(move |theme: &Theme, status| {
|
||||
top_hist_btn_style(theme, active, dd_open, status)
|
||||
})
|
||||
.style(move |theme: &Theme, status| top_hist_btn_style(theme, active, dd_open, status))
|
||||
.width(Length::Fixed(TOP_ARR_W))
|
||||
.height(24)
|
||||
.padding(0);
|
||||
@@ -1353,10 +1387,7 @@ pub(super) fn render_history_control<'a>(
|
||||
};
|
||||
tooltip(
|
||||
btn,
|
||||
make_tip(format!(
|
||||
"{}",
|
||||
t!("%{label} history", label = t!(label))
|
||||
)),
|
||||
make_tip(format!("{}", t!("%{label} history", label = t!(label)))),
|
||||
TipPos::Right,
|
||||
)
|
||||
.gap(6.0)
|
||||
@@ -1382,10 +1413,9 @@ pub(super) fn top_hist_btn_style(
|
||||
_ => palette.background.base,
|
||||
};
|
||||
button::Style {
|
||||
background: (!active || open || matches!(
|
||||
status,
|
||||
button::Status::Hovered | button::Status::Pressed
|
||||
))
|
||||
background: (!active
|
||||
|| open
|
||||
|| matches!(status, button::Status::Hovered | button::Status::Pressed))
|
||||
.then_some(Background::Color(pair.color)),
|
||||
text_color: pair.text,
|
||||
border: Border {
|
||||
|
||||
Reference in new issue
Block a user