diff --git a/src/app/commands/pdf_underlay.rs b/src/app/commands/pdf_underlay.rs index 3a4606199..fce43efc6 100644 --- a/src/app/commands/pdf_underlay.rs +++ b/src/app/commands/pdf_underlay.rs @@ -1,9 +1,9 @@ -//! The contextual PDF Underlay ribbon tab: its values come from the selected -//! underlays, its buttons edit them. +//! The right-edge PDF underlay and xref tools: their state comes from the +//! selection, their buttons edit it. use super::*; use codec::entities::{Underlay, UnderlayDisplayFlags, UnderlayType}; -use crate::ui::ribbon::{UnderlayContext, UnderlayTabMsg}; +use crate::ui::ribbon::UnderlayContext; impl OpenCADStudio { /// The selected PDF underlays, or nothing when anything else is selected. @@ -40,8 +40,9 @@ impl OpenCADStudio { out } - /// Show the PDF Underlay tab while only PDF underlays are selected, the - /// External Reference tab while only xrefs are. + /// The selection context for the right-edge tools: the selected PDF + /// underlay's switches while only underlays are selected, or that only + /// xrefs are. pub(in crate::app) fn sync_underlay_tab(&mut self) { let i = self.active_tab; let xref = !self.tabs[i].is_start && !self.selected_xrefs(i).is_empty(); @@ -49,13 +50,11 @@ impl OpenCADStudio { None } else { self.selected_pdf_underlays(i).first().map(|(_, u)| { - UnderlayContext::new( - u.contrast, - u.fade, - u.flags.contains(UnderlayDisplayFlags::MONOCHROME), - u.flags.contains(UnderlayDisplayFlags::ON), - crate::scene::model::pdf_vector::pdf_osnap(), - ) + UnderlayContext { + monochrome: u.flags.contains(UnderlayDisplayFlags::MONOCHROME), + shown: u.flags.contains(UnderlayDisplayFlags::ON), + snap: crate::scene::model::pdf_vector::pdf_osnap(), + } }) }; self.ribbon.set_underlay_context(context, xref); @@ -90,43 +89,7 @@ impl OpenCADStudio { self.refresh_properties(); } - pub(in crate::app) fn update_underlay_tab(&mut self, message: UnderlayTabMsg) -> Task { - let i = self.active_tab; - let Some(ctx) = self.ribbon.underlay_context_mut() else { - return Task::none(); - }; - match message { - UnderlayTabMsg::Contrast(v) => { - ctx.contrast = v; - ctx.contrast_text = v.to_string(); - } - UnderlayTabMsg::Fade(v) => { - ctx.fade = v; - ctx.fade_text = v.to_string(); - } - UnderlayTabMsg::ContrastText(s) => ctx.contrast_text = s, - UnderlayTabMsg::FadeText(s) => ctx.fade_text = s, - UnderlayTabMsg::Commit => { - // A typed value outside 0–100 or not a number keeps the value. - let parse = |s: &str, old: u8| { - s.trim().parse::().ok().filter(|v| *v <= 100).unwrap_or(old) - }; - let (contrast, fade) = ( - parse(&ctx.contrast_text, ctx.contrast), - parse(&ctx.fade_text, ctx.fade), - ); - ctx.contrast_text = contrast.to_string(); - ctx.fade_text = fade.to_string(); - self.edit_selected_underlays(i, "PDFADJUST", |u| { - u.set_contrast(contrast); - u.set_fade(fade); - }); - } - } - Task::none() - } - - /// The tab's buttons (host-only command names). + /// The right-edge tools (host-only command names). pub(super) fn dispatch_pdf_underlay(&mut self, cmd: &str, i: usize) -> Option> { match cmd { "_PDFULMONO" => { diff --git a/src/app/control/actions.rs b/src/app/control/actions.rs index 47cbd028e..b4e73a9f7 100644 --- a/src/app/control/actions.rs +++ b/src/app/control/actions.rs @@ -700,8 +700,7 @@ impl OpenCADStudio { state.selected = pages; return Ok(Task::none()); } - // Bring a ribbon tab forward by module id ("pdf_underlay" for the - // contextual underlay tab). + // Bring a ribbon tab forward by module id. "ribbon_tab" => { let id = string(req, "value")?; if !self.ribbon.select_by_id(id) { diff --git a/src/app/mod.rs b/src/app/mod.rs index ae52c9b1c..a32338a72 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -3827,11 +3827,8 @@ pub enum Message { PdfAttachPick, /// PDFIMPORT File: pick the PDF to import. PdfImportPick, - /// The contextual PDF Underlay tab. - RibbonSelectUnderlayTab, /// An edit in one of the PDF dialogs. PdfDialog(crate::ui::window::pdf_dialogs::PdfDialogMsg), - UnderlayTab(crate::ui::ribbon::UnderlayTabMsg), PdfImportPickResult(Result<(std::path::PathBuf, std::sync::Arc>), String>), /// Result of the PDFATTACH file picker. PdfAttachPickResult(Result<(std::path::PathBuf, std::sync::Arc>), String>), diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 8da7408a8..16b7654a7 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -1784,11 +1784,6 @@ impl OpenCADStudio { Task::none() } - Message::RibbonSelectUnderlayTab => { - self.ribbon.select_underlay_tab(); - Task::none() - } - Message::UnderlayTab(message) => self.update_underlay_tab(message), Message::PdfDialog(message) => self.update_pdf_dialog(message), Message::RibbonSelectTab(idx) => { self.ribbon.select(idx); diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index 00ee82af7..b022fb0e9 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -1601,9 +1601,32 @@ bg={bg_ms:.1}ms n={view_count}" } } - // Paper-space context actions: a right-edge vertical toolbar - // (viewport / page setup / plot) instead of a contextual ribbon tab. - if is_paper && !tab.is_start { + // Selection actions: with only PDF underlays or only xrefs + // selected, their tools take the right edge (over the paper-space + // tools, which come back when the selection changes). + // ponytail: one right-edge toolbar at a time; stack them if both + // are ever needed together. + let selection_tools = if tab.is_start { + None + } else if self.ribbon.xref_context() { + crate::ui::side_toolbar::view(&crate::ui::ribbon::xref_tools()) + } else if let Some(ctx) = self.ribbon.underlay_context() { + let ctx = ctx.clone(); + crate::ui::side_toolbar::view_with_active( + &crate::ui::ribbon::pdf_underlay_tools(), + &move |id| match id { + "_PDFULMONO" => ctx.monochrome, + "_PDFULSHOW" => ctx.shown, + "_PDFULSNAP" => ctx.snap, + _ => false, + }, + ) + } else { + None + }; + if let Some(tb) = selection_tools { + viewport_stack = viewport_stack.push(tb); + } else if is_paper && !tab.is_start { if let Some(tb) = crate::ui::side_toolbar::view(&crate::modules::layout::paper_space_tools()) { diff --git a/src/ui/ribbon/context_tools.rs b/src/ui/ribbon/context_tools.rs new file mode 100644 index 000000000..c5f69819f --- /dev/null +++ b/src/ui/ribbon/context_tools.rs @@ -0,0 +1,96 @@ +//! Selection-driven tools: while only PDF underlays (or only xrefs) are +//! selected their actions show on the right-edge toolbar; they go when the +//! selection changes. Contrast and fade stay in the Properties panel. + +use super::*; +use crate::modules::{ModuleEvent, ToolDef}; + +/// The switches of the selected underlay (the first one) the toolbar shows +/// as on. +#[derive(Clone, Debug, PartialEq)] +pub struct UnderlayContext { + pub monochrome: bool, + pub shown: bool, + pub snap: bool, +} + +fn tool(id: &'static str, label: &'static str, icon: &'static [u8]) -> ToolDef { + ToolDef { + id, + label, + icon: IconKind::Svg(icon), + event: ModuleEvent::Command(id.to_string()), + } +} + +const CLIP: &[u8] = include_bytes!("../../../assets/icons/xclip.svg"); + +/// PDF underlay tools: display, clipping, options, layers and import. +pub fn pdf_underlay_tools() -> Vec { + const FRAMES: &[u8] = include_bytes!("../../../assets/icons/underlay_frames.svg"); + vec![ + tool("_PDFULMONO", "Display in\nMonochrome", FRAMES), + tool("_PDFULCLIP", "Create Clipping\nBoundary", CLIP), + tool("_PDFULUNCLIP", "Remove\nClipping", CLIP), + tool("_PDFULSHOW", "Show\nUnderlay", FRAMES), + tool( + "_PDFULSNAP", + "Enable\nSnap", + include_bytes!("../../../assets/icons/snap_underlays.svg"), + ), + tool("EXTERNALREFERENCES", "External\nReferences", crate::ui::icons::FOLDER_OPEN), + tool( + "ULAYERS", + "Edit\nLayers", + include_bytes!("../../../assets/icons/underlay_layers.svg"), + ), + tool( + "_PDFULIMPORT", + "Import As\nObjects", + include_bytes!("../../../assets/icons/cui_import.svg"), + ), + ] +} + +/// External reference tools: edit, open, clipping and the palette. +pub fn xref_tools() -> Vec { + vec![ + tool( + "_XREFEDIT", + "Edit Reference\nIn-Place", + include_bytes!("../../../assets/icons/edit_block.svg"), + ), + tool("_XREFOPEN", "Open\nReference", crate::ui::icons::FOLDER_OPEN), + tool("_XREFCLIP", "Create Clipping\nBoundary", CLIP), + tool("_XREFUNCLIP", "Remove\nClipping", CLIP), + tool("EXTERNALREFERENCES", "External\nReferences", crate::ui::icons::FOLDER_OPEN), + ] +} + +impl Ribbon { + /// The selection context: the selected underlay's switches, or that + /// only xrefs are selected, or neither. + pub fn set_underlay_context(&mut self, context: Option, xref: bool) { + self.underlay_ctx = context; + self.xref_ctx = xref; + } + + pub fn underlay_context(&self) -> Option<&UnderlayContext> { + self.underlay_ctx.as_ref() + } + + pub fn xref_context(&self) -> bool { + self.xref_ctx + } + + /// Select a tab by its module id. + pub fn select_by_id(&mut self, id: &str) -> bool { + match self.modules.iter().position(|m| m.id() == id) { + Some(index) => { + self.select(index); + true + } + None => false, + } + } +} diff --git a/src/ui/ribbon/mod.rs b/src/ui/ribbon/mod.rs index d1bc827c4..b5b8e6f6f 100644 --- a/src/ui/ribbon/mod.rs +++ b/src/ui/ribbon/mod.rs @@ -25,8 +25,8 @@ mod draw_panel; pub(crate) use draw_panel::tools as panel_tools; mod modify_panel; mod color_dropdown; -mod underlay_tab; -pub use underlay_tab::{UnderlayContext, UnderlayTabMsg}; +mod context_tools; +pub use context_tools::{pdf_underlay_tools, xref_tools, UnderlayContext}; use widgets::{StyleContext, *}; pub(crate) use widgets::{REDO_HISTORY_ID, UNDO_HISTORY_ID}; mod collapse; @@ -105,11 +105,9 @@ pub struct Ribbon { /// Set by `CollapsePanels` when the tool row is in its tight state; the mode /// selector hides itself then to give the cramped tab row its space back. collapse_tight: Arc, - /// The selected PDF underlay's values while the contextual tab exists. + /// The selected PDF underlay's switches while only underlays are selected. underlay_ctx: Option, - /// The contextual underlay tab is the one shown. - underlay_tab_active: bool, - /// Only xrefs are selected: the External Reference tab is offered. + /// Only xrefs are selected. xref_ctx: bool, /// XDWGFADECTL as the Reference slide-out shows it (negative = off). pub xref_fade: i32, @@ -207,7 +205,6 @@ impl Ribbon { collapse_mode: CollapseMode::default(), collapse_tight: Arc::new(AtomicBool::new(false)), underlay_ctx: None, - underlay_tab_active: false, xref_ctx: false, xref_fade: 50, } @@ -287,7 +284,6 @@ impl Ribbon { pub fn select(&mut self, index: usize) { if index < self.modules.len() { self.active = index; - self.underlay_tab_active = false; } } @@ -351,9 +347,6 @@ impl Ribbon { show_block_palette, show_file_tabs: self.show_file_tabs, show_layout_tabs: self.show_layout_tabs, - underlay_mono: self.underlay_ctx.as_ref().is_some_and(|c| c.monochrome), - underlay_shown: self.underlay_ctx.as_ref().is_some_and(|c| c.shown), - underlay_snap: self.underlay_ctx.as_ref().is_some_and(|c| c.snap), } } @@ -521,7 +514,7 @@ impl Ribbon { return acc; } - let is_active = i == self.active && !self.underlay_tab_shown(); + let is_active = i == self.active; let is_contextual = module.id() == "layout"; let btn = container( button(text(crate::i18n::ribbon_module_title(module.id(), module.title())).size(12)) @@ -591,10 +584,6 @@ impl Ribbon { }, ); - let mut tab_items = tab_items; - if let Some(title) = self.contextual_tab() { - tab_items.push(underlay_tab_button(title, self.underlay_tab_shown())); - } // 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 @@ -676,14 +665,10 @@ impl Ribbon { // shrink to compact icon columns, then it collapses to a ▾ flyout // button. See `CollapsePanels`. let ts = self.toggle_state(show_block_palette); - let panels: Vec> = if self.underlay_tab_shown() { - self.underlay_panels(ts, &style_ctx) - } else { - groups - .iter() - .map(|g| self.panel(g, ts, &style_ctx, &|_| Vec::new())) - .collect() - }; + let panels: Vec> = groups + .iter() + .map(|g| self.panel(g, ts, &style_ctx, &|_| Vec::new())) + .collect(); CollapsePanels::new(panels, self.collapsed_open.clone(), TOOL_BAR_H) .report_height(self.tool_bar_h.clone()) .report_tight(self.collapse_tight.clone()) @@ -1630,30 +1615,3 @@ mod tests { } } -/// The contextual tab's button: framed in the accent colour. -fn underlay_tab_button<'a>(title: &'static str, active: bool) -> Element<'a, Message> { - let btn = button(text(t!(title)).size(12)) - .on_press(Message::RibbonSelectUnderlayTab) - .style(move |theme: &Theme, status| { - let palette = theme.palette(); - let pair = match (active, status) { - (true, _) => palette.background.weakest, - (false, button::Status::Hovered) => palette.background.weak, - _ => palette.background.base, - }; - button::Style { - background: (active || matches!(status, button::Status::Hovered)) - .then_some(Background::Color(pair.color)), - text_color: if active { pair.text } else { palette.primary.base.color }, - border: Border { - color: palette.primary.base.color, - width: if active { 2.0 } else { 1.0 }, - radius: 0.0.into(), - }, - shadow: iced::Shadow::default(), - snap: false, - } - }) - .padding([5, 14]); - container(btn).into() -} diff --git a/src/ui/ribbon/underlay_tab.rs b/src/ui/ribbon/underlay_tab.rs deleted file mode 100644 index 485e21342..000000000 --- a/src/ui/ribbon/underlay_tab.rs +++ /dev/null @@ -1,289 +0,0 @@ -//! The contextual tabs: "PDF Underlay" while only PDF underlays are -//! selected, "External Reference" while only xrefs are; shown and brought -//! forward with the selection, gone when it changes. - -use iced::widget::{column, row, slider, text, text_input}; -use iced::{Element, Length}; - -use super::*; -use crate::modules::{ModuleEvent, ToolDef}; - -/// What the tab shows for the selected underlay (the first one). -#[derive(Clone, Debug, PartialEq)] -pub struct UnderlayContext { - pub contrast: u8, - pub fade: u8, - pub monochrome: bool, - pub shown: bool, - pub snap: bool, - pub contrast_text: String, - pub fade_text: String, -} - -impl UnderlayContext { - pub fn new(contrast: u8, fade: u8, monochrome: bool, shown: bool, snap: bool) -> Self { - Self { - contrast, - fade, - monochrome, - shown, - snap, - contrast_text: contrast.to_string(), - fade_text: fade.to_string(), - } - } -} - -/// The tab's edits; the host writes them to the selected underlays. -#[derive(Clone, Debug)] -pub enum UnderlayTabMsg { - Contrast(u8), - Fade(u8), - ContrastText(String), - FadeText(String), - /// Slider released or value submitted: write contrast and fade. - Commit, -} - -pub(super) const ADJUST: &str = "Adjust"; - -fn tool(id: &'static str, label: &'static str, icon: &'static [u8]) -> ToolDef { - ToolDef { - id, - label, - icon: IconKind::Svg(icon), - event: ModuleEvent::Command(id.to_string()), - } -} - -fn groups() -> &'static [RibbonGroup] { - static GROUPS: std::sync::OnceLock> = std::sync::OnceLock::new(); - const FRAMES: &[u8] = include_bytes!("../../../assets/icons/underlay_frames.svg"); - const CLIP: &[u8] = include_bytes!("../../../assets/icons/xclip.svg"); - GROUPS.get_or_init(|| { - vec![ - RibbonGroup { - title: ADJUST, - tools: vec![RibbonItem::LargeTool(tool( - "_PDFULMONO", - "Display in\nMonochrome", - FRAMES, - ))], - }, - RibbonGroup { - title: "Clipping", - tools: vec![ - RibbonItem::LargeTool(tool("_PDFULCLIP", "Create Clipping\nBoundary", CLIP)), - RibbonItem::LargeTool(tool("_PDFULUNCLIP", "Remove\nClipping", CLIP)), - ], - }, - RibbonGroup { - title: "Options", - tools: vec![ - RibbonItem::LargeTool(tool("_PDFULSHOW", "Show\nUnderlay", FRAMES)), - RibbonItem::LargeTool(tool( - "_PDFULSNAP", - "Enable\nSnap", - include_bytes!("../../../assets/icons/snap_underlays.svg"), - )), - RibbonItem::LargeTool(tool( - "EXTERNALREFERENCES", - "External\nReferences", - crate::ui::icons::FOLDER_OPEN, - )), - ], - }, - RibbonGroup { - title: "PDF Layers", - tools: vec![RibbonItem::LargeTool(tool( - "ULAYERS", - "Edit\nLayers", - include_bytes!("../../../assets/icons/underlay_layers.svg"), - ))], - }, - RibbonGroup { - title: "PDF Import", - tools: vec![RibbonItem::LargeTool(tool( - "_PDFULIMPORT", - "Import As\nObjects", - include_bytes!("../../../assets/icons/cui_import.svg"), - ))], - }, - ] - }) -} - -/// The External Reference tab: edit or open the reference, clip it, and the -/// External References palette. -fn xref_groups() -> &'static [RibbonGroup] { - static GROUPS: std::sync::OnceLock> = std::sync::OnceLock::new(); - const CLIP: &[u8] = include_bytes!("../../../assets/icons/xclip.svg"); - GROUPS.get_or_init(|| { - vec![ - RibbonGroup { - title: "Edit", - tools: vec![ - RibbonItem::LargeTool(tool( - "_XREFEDIT", - "Edit Reference\nIn-Place", - include_bytes!("../../../assets/icons/edit_block.svg"), - )), - RibbonItem::LargeTool(tool( - "_XREFOPEN", - "Open\nReference", - crate::ui::icons::FOLDER_OPEN, - )), - ], - }, - RibbonGroup { - title: "Clipping", - tools: vec![ - RibbonItem::LargeTool(tool("_XREFCLIP", "Create Clipping\nBoundary", CLIP)), - RibbonItem::LargeTool(tool("_XREFUNCLIP", "Remove\nClipping", CLIP)), - ], - }, - RibbonGroup { - title: "Options", - tools: vec![RibbonItem::LargeTool(tool( - "EXTERNALREFERENCES", - "External\nReferences", - crate::ui::icons::FOLDER_OPEN, - ))], - }, - ] - }) -} - -/// Contrast / Fade: a label, a 0–100 slider and the value box. -fn adjust_rows<'a>(ctx: &'a UnderlayContext) -> Element<'a, Message> { - let line = |label: &'static str, - value: u8, - buffer: &'a str, - on_slide: fn(u8) -> UnderlayTabMsg, - on_text: fn(String) -> UnderlayTabMsg| { - row![ - text(t!(label)).size(11).width(Length::Fixed(64.0)), - slider(0..=100u8, value, move |v| Message::UnderlayTab(on_slide(v))) - .on_release(Message::UnderlayTab(UnderlayTabMsg::Commit)) - .width(Length::Fixed(110.0)), - text_input("", buffer) - .on_input(move |s| Message::UnderlayTab(on_text(s))) - .on_submit(Message::UnderlayTab(UnderlayTabMsg::Commit)) - .size(11) - .padding([2, 4]) - .width(Length::Fixed(44.0)), - ] - .spacing(6) - .align_y(iced::Center) - }; - column![ - line( - "Contrast", - ctx.contrast, - &ctx.contrast_text, - UnderlayTabMsg::Contrast, - UnderlayTabMsg::ContrastText, - ), - line( - "Fade", - ctx.fade, - &ctx.fade_text, - UnderlayTabMsg::Fade, - UnderlayTabMsg::FadeText, - ), - ] - .spacing(8) - .padding([6, 4]) - .into() -} - -impl Ribbon { - /// Show the tab for the selected underlay (`context`) or the selected - /// xrefs (`xref`), or drop it. A tab that appears comes to the front; - /// one that goes returns the tab that was active before it. - pub fn set_underlay_context(&mut self, context: Option, xref: bool) { - let before = (self.underlay_ctx.is_some(), self.xref_ctx); - let after = (context.is_some(), xref); - if before != after { - self.underlay_tab_active = after.0 || after.1; - } - self.xref_ctx = xref; - // A value being typed survives a refresh that leaves the value as is. - let mut context = context; - if let (Some(old), Some(new)) = (&self.underlay_ctx, &mut context) { - if (old.contrast, old.fade) == (new.contrast, new.fade) { - new.contrast_text = old.contrast_text.clone(); - new.fade_text = old.fade_text.clone(); - } - } - self.underlay_ctx = context; - } - - pub fn underlay_context_mut(&mut self) -> Option<&mut UnderlayContext> { - self.underlay_ctx.as_mut() - } - - /// Select a tab by its module id; "pdf_underlay" and "xref" are the - /// contextual tabs. - pub fn select_by_id(&mut self, id: &str) -> bool { - if (id == "pdf_underlay" && self.underlay_ctx.is_some()) || (id == "xref" && self.xref_ctx) { - self.select_underlay_tab(); - return self.underlay_tab_active; - } - match self.modules.iter().position(|m| m.id() == id) { - Some(index) => { - self.select(index); - true - } - None => false, - } - } - - pub fn select_underlay_tab(&mut self) { - if self.contextual_tab().is_some() { - self.underlay_tab_active = true; - } - } - - pub(super) fn underlay_tab_shown(&self) -> bool { - self.underlay_tab_active && self.contextual_tab().is_some() - } - - /// The contextual tab's title, while one is offered. - pub(super) fn contextual_tab(&self) -> Option<&'static str> { - if self.xref_ctx { - Some("External Reference") - } else { - self.underlay_ctx.as_ref().map(|_| "PDF Underlay") - } - } - - pub(super) fn underlay_panels<'a>( - &'a self, - ts: widgets::ToggleState, - style_ctx: &StyleContext<'_>, - ) -> Vec> { - if self.xref_ctx { - return xref_groups() - .iter() - .map(|g| self.panel(g, ts, style_ctx, &|_| Vec::new())) - .collect(); - } - let Some(ctx) = self.underlay_ctx.as_ref() else { - return Vec::new(); - }; - groups() - .iter() - .map(|g| { - let lead = |_: bool| -> Vec> { - if g.title == ADJUST { - vec![adjust_rows(ctx)] - } else { - Vec::new() - } - }; - self.panel(g, ts, style_ctx, &lead) - }) - .collect() - } -} diff --git a/src/ui/ribbon/widgets.rs b/src/ui/ribbon/widgets.rs index 4c2ebf623..83c90bd97 100644 --- a/src/ui/ribbon/widgets.rs +++ b/src/ui/ribbon/widgets.rs @@ -40,9 +40,6 @@ pub(super) struct ToggleState { pub show_block_palette: bool, pub show_file_tabs: bool, pub show_layout_tabs: bool, - pub underlay_mono: bool, - pub underlay_shown: bool, - pub underlay_snap: bool, } // ── Layout constants (single source of truth: ROW_H from ui::mod) ───────── @@ -378,9 +375,6 @@ pub(super) fn is_active_tool( "UCSICON" => state.show_ucs_icon, "PROPERTIES" => state.show_properties, "BLOCKPALETTE" => state.show_block_palette, - "_PDFULMONO" => state.underlay_mono, - "_PDFULSHOW" => state.underlay_shown, - "_PDFULSNAP" => state.underlay_snap, "FILETAB" => state.show_file_tabs, "LAYOUTTAB" => state.show_layout_tabs, id => active_tool.as_deref() == Some(id), diff --git a/src/ui/side_toolbar.rs b/src/ui/side_toolbar.rs index d3adb3f62..b435bc32f 100644 --- a/src/ui/side_toolbar.rs +++ b/src/ui/side_toolbar.rs @@ -26,6 +26,8 @@ fn icon_el(icon: IconKind) -> Element<'static, Message> { } fn tip_panel(label: &'static str) -> Element<'static, Message> { + // Ribbon labels break over two lines; a tooltip reads them on one. + let label = crate::t!(label).replace('\n', " "); container(text(label).size(11)) .padding([2, 6]) .style(|theme: &Theme| { @@ -48,12 +50,21 @@ fn tip_panel(label: &'static str) -> Element<'static, Message> { /// centred over the canvas. Returns `None` when `tools` is empty so the caller /// can skip pushing an overlay. pub fn view(tools: &[ToolDef]) -> Option> { + view_with_active(tools, &|_| false) +} + +/// As [`view`], with the tools `active` names drawn as switched on. +pub fn view_with_active( + tools: &[ToolDef], + active: &dyn Fn(&str) -> bool, +) -> Option> { if tools.is_empty() { return None; } let mut col = column![].spacing(4).align_x(iced::Center); for t in tools { + let on = active(t.id); let btn = button(icon_el(t.icon)) .on_press(Message::RibbonToolClick { tool_id: t.id.to_string(), @@ -61,18 +72,22 @@ pub fn view(tools: &[ToolDef]) -> Option> { }) .width(Length::Fixed(BTN_SIZE)) .height(Length::Fixed(BTN_SIZE)) - .style(|theme: &Theme, status| { + .style(move |theme: &Theme, status| { let palette = theme.palette(); let hovered = matches!( status, button::Status::Hovered | button::Status::Pressed ); button::Style { - background: hovered - .then_some(Background::Color(palette.background.strong.color)), + background: if on { + Some(Background::Color(palette.primary.weak.color)) + } else { + hovered.then_some(Background::Color(palette.background.strong.color)) + }, border: Border { radius: 3.0.into(), - ..Default::default() + color: palette.primary.base.color, + width: if on { 1.0 } else { 0.0 }, }, text_color: palette.background.base.text, ..Default::default()