From 68721813f45713287015f5e03296cd27f4db8998 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Fri, 25 Sep 2026 10:33:50 +0300 Subject: [PATCH 1/6] Contextual External Reference ribbon tab Shown and brought forward while only xrefs are selected: Edit Reference In-Place, Open Reference, Create Clipping Boundary, Remove Clipping and External References. Co-Authored-By: Claude Opus 5.5 --- locales/ar-SA/opencadstudio.ftl | 6 +++ locales/bg-BG/opencadstudio.ftl | 6 +++ locales/cs-CZ/opencadstudio.ftl | 6 +++ locales/de-DE/opencadstudio.ftl | 6 +++ locales/el-GR/opencadstudio.ftl | 6 +++ locales/en-US/opencadstudio.ftl | 6 +++ locales/es-ES/opencadstudio.ftl | 6 +++ locales/fi-FI/opencadstudio.ftl | 6 +++ locales/fr-FR/opencadstudio.ftl | 6 +++ locales/hi-IN/opencadstudio.ftl | 6 +++ locales/hu-HU/opencadstudio.ftl | 6 +++ locales/it-IT/opencadstudio.ftl | 6 +++ locales/ja-JP/opencadstudio.ftl | 6 +++ locales/ko-KR/opencadstudio.ftl | 6 +++ locales/nl-NL/opencadstudio.ftl | 6 +++ locales/pl-PL/opencadstudio.ftl | 6 +++ locales/pt-BR/opencadstudio.ftl | 6 +++ locales/ru-RU/opencadstudio.ftl | 6 +++ locales/tr-TR/opencadstudio.ftl | 8 ++- locales/zh-CN/opencadstudio.ftl | 6 +++ locales/zh-TW/opencadstudio.ftl | 6 +++ src/app/commands/pdf_underlay.rs | 61 +++++++++++++++++++++- src/locale_catalog.rs | 2 + src/modules/insert/xclip.rs | 9 ++++ src/ui/ribbon/mod.rs | 13 +++-- src/ui/ribbon/underlay_tab.rs | 87 +++++++++++++++++++++++++++----- 26 files changed, 278 insertions(+), 22 deletions(-) diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index 41b5274fd..d3afe0afa 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -3586,6 +3586,12 @@ insert = .refui-requires-int-90 = Requires an integer between -90 and 90. .refui-xref-fading = Xref fading .refui-edit-reference = Edit Reference + .refui-edit-reference-in-place = + Edit Reference + In-Place + .refui-open-reference = + Open + Reference .refui-snap-to-underlays-off = Snap to Underlays OFF .refui-snap-to-underlays-on = Snap to Underlays ON .refui-snap-to-underlays = Snap to Underlays diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index afa5f27c6..7d3e0ce5d 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -3573,6 +3573,12 @@ insert = .refui-requires-int-90 = Requires an integer between -90 and 90. .refui-xref-fading = Xref fading .refui-edit-reference = Edit Reference + .refui-edit-reference-in-place = + Edit Reference + In-Place + .refui-open-reference = + Open + Reference .refui-snap-to-underlays-off = Snap to Underlays OFF .refui-snap-to-underlays-on = Snap to Underlays ON .refui-snap-to-underlays = Snap to Underlays diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 28a81eef9..3633cd515 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -3571,6 +3571,12 @@ insert = .refui-requires-int-90 = Requires an integer between -90 and 90. .refui-xref-fading = Xref fading .refui-edit-reference = Edit Reference + .refui-edit-reference-in-place = + Edit Reference + In-Place + .refui-open-reference = + Open + Reference .refui-snap-to-underlays-off = Snap to Underlays OFF .refui-snap-to-underlays-on = Snap to Underlays ON .refui-snap-to-underlays = Snap to Underlays diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index 49aa5435e..5f06c5e29 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -3574,6 +3574,12 @@ insert = .refui-requires-int-90 = Requires an integer between -90 and 90. .refui-xref-fading = Xref fading .refui-edit-reference = Edit Reference + .refui-edit-reference-in-place = + Edit Reference + In-Place + .refui-open-reference = + Open + Reference .refui-snap-to-underlays-off = Snap to Underlays OFF .refui-snap-to-underlays-on = Snap to Underlays ON .refui-snap-to-underlays = Snap to Underlays diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index 42807d01d..80ee77915 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -3574,6 +3574,12 @@ insert = .refui-requires-int-90 = Requires an integer between -90 and 90. .refui-xref-fading = Xref fading .refui-edit-reference = Edit Reference + .refui-edit-reference-in-place = + Edit Reference + In-Place + .refui-open-reference = + Open + Reference .refui-snap-to-underlays-off = Snap to Underlays OFF .refui-snap-to-underlays-on = Snap to Underlays ON .refui-snap-to-underlays = Snap to Underlays diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index 25797df8f..4706965fb 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -3575,6 +3575,12 @@ insert = .refui-requires-int-90 = Requires an integer between -90 and 90. .refui-xref-fading = Xref fading .refui-edit-reference = Edit Reference + .refui-edit-reference-in-place = + Edit Reference + In-Place + .refui-open-reference = + Open + Reference .refui-snap-to-underlays-off = Snap to Underlays OFF .refui-snap-to-underlays-on = Snap to Underlays ON .refui-snap-to-underlays = Snap to Underlays diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 88b5c13fd..1ce5f4834 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -3574,6 +3574,12 @@ insert = .refui-requires-int-90 = Requires an integer between -90 and 90. .refui-xref-fading = Xref fading .refui-edit-reference = Edit Reference + .refui-edit-reference-in-place = + Edit Reference + In-Place + .refui-open-reference = + Open + Reference .refui-snap-to-underlays-off = Snap to Underlays OFF .refui-snap-to-underlays-on = Snap to Underlays ON .refui-snap-to-underlays = Snap to Underlays diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index 3cd366010..97cee839e 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -3573,6 +3573,12 @@ insert = .refui-requires-int-90 = Requires an integer between -90 and 90. .refui-xref-fading = Xref fading .refui-edit-reference = Edit Reference + .refui-edit-reference-in-place = + Edit Reference + In-Place + .refui-open-reference = + Open + Reference .refui-snap-to-underlays-off = Snap to Underlays OFF .refui-snap-to-underlays-on = Snap to Underlays ON .refui-snap-to-underlays = Snap to Underlays diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 7e0219344..c3ae35108 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -3574,6 +3574,12 @@ insert = .refui-requires-int-90 = Requires an integer between -90 and 90. .refui-xref-fading = Xref fading .refui-edit-reference = Edit Reference + .refui-edit-reference-in-place = + Edit Reference + In-Place + .refui-open-reference = + Open + Reference .refui-snap-to-underlays-off = Snap to Underlays OFF .refui-snap-to-underlays-on = Snap to Underlays ON .refui-snap-to-underlays = Snap to Underlays diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index cd2b00dbb..2844c23a7 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -3562,6 +3562,12 @@ insert = .refui-requires-int-90 = Requires an integer between -90 and 90. .refui-xref-fading = Xref fading .refui-edit-reference = Edit Reference + .refui-edit-reference-in-place = + Edit Reference + In-Place + .refui-open-reference = + Open + Reference .refui-snap-to-underlays-off = Snap to Underlays OFF .refui-snap-to-underlays-on = Snap to Underlays ON .refui-snap-to-underlays = Snap to Underlays diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index f39afb99b..3fc5da1ad 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -3573,6 +3573,12 @@ insert = .refui-requires-int-90 = Requires an integer between -90 and 90. .refui-xref-fading = Xref fading .refui-edit-reference = Edit Reference + .refui-edit-reference-in-place = + Edit Reference + In-Place + .refui-open-reference = + Open + Reference .refui-snap-to-underlays-off = Snap to Underlays OFF .refui-snap-to-underlays-on = Snap to Underlays ON .refui-snap-to-underlays = Snap to Underlays diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 332b99767..fa7f97aa2 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -3573,6 +3573,12 @@ insert = .refui-requires-int-90 = Requires an integer between -90 and 90. .refui-xref-fading = Xref fading .refui-edit-reference = Edit Reference + .refui-edit-reference-in-place = + Edit Reference + In-Place + .refui-open-reference = + Open + Reference .refui-snap-to-underlays-off = Snap to Underlays OFF .refui-snap-to-underlays-on = Snap to Underlays ON .refui-snap-to-underlays = Snap to Underlays diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 6ef6bebc1..571cf1255 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -3564,6 +3564,12 @@ insert = .refui-requires-int-90 = Requires an integer between -90 and 90. .refui-xref-fading = Xref fading .refui-edit-reference = Edit Reference + .refui-edit-reference-in-place = + Edit Reference + In-Place + .refui-open-reference = + Open + Reference .refui-snap-to-underlays-off = Snap to Underlays OFF .refui-snap-to-underlays-on = Snap to Underlays ON .refui-snap-to-underlays = Snap to Underlays diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index fa5d64017..0bea09964 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -3573,6 +3573,12 @@ insert = .refui-requires-int-90 = Requires an integer between -90 and 90. .refui-xref-fading = Xref fading .refui-edit-reference = Edit Reference + .refui-edit-reference-in-place = + Edit Reference + In-Place + .refui-open-reference = + Open + Reference .refui-snap-to-underlays-off = Snap to Underlays OFF .refui-snap-to-underlays-on = Snap to Underlays ON .refui-snap-to-underlays = Snap to Underlays diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 5ec3e222e..0f98e0ac8 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -3572,6 +3572,12 @@ insert = .refui-requires-int-90 = Requires an integer between -90 and 90. .refui-xref-fading = Xref fading .refui-edit-reference = Edit Reference + .refui-edit-reference-in-place = + Edit Reference + In-Place + .refui-open-reference = + Open + Reference .refui-snap-to-underlays-off = Snap to Underlays OFF .refui-snap-to-underlays-on = Snap to Underlays ON .refui-snap-to-underlays = Snap to Underlays diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index b518cae38..ded45105b 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -3573,6 +3573,12 @@ insert = .refui-requires-int-90 = Requires an integer between -90 and 90. .refui-xref-fading = Xref fading .refui-edit-reference = Edit Reference + .refui-edit-reference-in-place = + Edit Reference + In-Place + .refui-open-reference = + Open + Reference .refui-snap-to-underlays-off = Snap to Underlays OFF .refui-snap-to-underlays-on = Snap to Underlays ON .refui-snap-to-underlays = Snap to Underlays diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 4c3a95027..94641aebb 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -3574,6 +3574,12 @@ insert = .refui-requires-int-90 = Requires an integer between -90 and 90. .refui-xref-fading = Xref fading .refui-edit-reference = Edit Reference + .refui-edit-reference-in-place = + Edit Reference + In-Place + .refui-open-reference = + Open + Reference .refui-snap-to-underlays-off = Snap to Underlays OFF .refui-snap-to-underlays-on = Snap to Underlays ON .refui-snap-to-underlays = Snap to Underlays diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index a5fb82176..2fe909619 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -3575,6 +3575,12 @@ insert = .refui-requires-int-90 = Requires an integer between -90 and 90. .refui-xref-fading = Xref fading .refui-edit-reference = Edit Reference + .refui-edit-reference-in-place = + Edit Reference + In-Place + .refui-open-reference = + Open + Reference .refui-snap-to-underlays-off = Snap to Underlays OFF .refui-snap-to-underlays-on = Snap to Underlays ON .refui-snap-to-underlays = Snap to Underlays diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 86a7aee2f..50d6a6f5b 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -3548,6 +3548,12 @@ insert = .refui-requires-int-90 = -90 ile 90 arasında bir tam sayı gerekir. .refui-xref-fading = Xref soldurma .refui-edit-reference = Referansı Düzenle + .refui-edit-reference-in-place = + Yerinde Referansı + Düzenle + .refui-open-reference = + Referansı + Aç .refui-snap-to-underlays-off = Altlıklara Yakala Kapalı .refui-snap-to-underlays-on = Altlıklara Yakala Açık .refui-snap-to-underlays = Altlıklara Yakala @@ -4501,7 +4507,7 @@ properties = .extended-lighting = Gelişmiş Aydınlatma .extents-max = En Büyük Sınırlar .extents-min = En Küçük Sınırlar - .external-reference = Dış Referans + .external-reference = Harici Referans .face = Yüz .face-count = Yüz sayısı .falloff-angle = Düşüş Açısı diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 1f508ef20..0d784ed0b 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -3561,6 +3561,12 @@ insert = .refui-requires-int-90 = Requires an integer between -90 and 90. .refui-xref-fading = Xref fading .refui-edit-reference = Edit Reference + .refui-edit-reference-in-place = + Edit Reference + In-Place + .refui-open-reference = + Open + Reference .refui-snap-to-underlays-off = Snap to Underlays OFF .refui-snap-to-underlays-on = Snap to Underlays ON .refui-snap-to-underlays = Snap to Underlays diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index ff52f4561..17f2ea56c 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -3573,6 +3573,12 @@ insert = .refui-requires-int-90 = Requires an integer between -90 and 90. .refui-xref-fading = Xref fading .refui-edit-reference = Edit Reference + .refui-edit-reference-in-place = + Edit Reference + In-Place + .refui-open-reference = + Open + Reference .refui-snap-to-underlays-off = Snap to Underlays OFF .refui-snap-to-underlays-on = Snap to Underlays ON .refui-snap-to-underlays = Snap to Underlays diff --git a/src/app/commands/pdf_underlay.rs b/src/app/commands/pdf_underlay.rs index 976990c30..3a4606199 100644 --- a/src/app/commands/pdf_underlay.rs +++ b/src/app/commands/pdf_underlay.rs @@ -20,9 +20,31 @@ impl OpenCADStudio { out } - /// Show the tab while only PDF underlays are selected. + /// The selected xrefs, or nothing when anything else is selected. + pub(in crate::app) fn selected_xrefs(&self, i: usize) -> Vec { + let doc = &self.tabs[i].scene.document; + let mut out = Vec::new(); + for (handle, entity) in self.tabs[i].scene.selected_entities() { + let xref = match entity { + codec::EntityType::Insert(ins) => doc + .block_records + .get(&ins.block_name) + .is_some_and(|br| br.flags.is_xref || br.flags.is_xref_overlay), + _ => false, + }; + if !xref { + return Vec::new(); + } + out.push(handle); + } + out + } + + /// Show the PDF Underlay tab while only PDF underlays are selected, the + /// External Reference tab while 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(); let context = if self.tabs[i].is_start { None } else { @@ -36,7 +58,7 @@ impl OpenCADStudio { ) }) }; - self.ribbon.set_underlay_context(context); + self.ribbon.set_underlay_context(context, xref); } /// One undo step that edits every selected PDF underlay. @@ -127,6 +149,41 @@ impl OpenCADStudio { self.tabs[i].scene.reseed_underlays(); self.sync_underlay_tab(); } + // External Reference tab: the selected xrefs. + "_XREFEDIT" | "_XREFOPEN" => { + let Some(&handle) = self.selected_xrefs(i).first() else { + return Some(Task::none()); + }; + self.tabs[i].scene.deselect_all(); + self.tabs[i].scene.select_entity(handle, true); + let command = if cmd == "_XREFEDIT" { "REFEDIT" } else { "XOPEN" }; + return Some(self.dispatch_command(command)); + } + "_XREFCLIP" => { + use crate::command::CadCommand; + let inserts = self.selected_xrefs(i); + if inserts.is_empty() { + return Some(Task::none()); + } + let clipped = inserts.iter().any(|h| { + crate::scene::pick::xclip::filter_handle(&self.tabs[i].scene.document, *h).is_some() + }); + let (command, first) = + crate::modules::insert::xclip::XclipCommand::start_new_boundary(inserts, clipped); + if let crate::command::CmdResult::ReportMeasurement(text) = first { + for line in text.lines() { + self.command_line.push_output(line); + } + } + self.command_line.push_info(&command.prompt()); + self.tabs[i].active_cmd = Some(Box::new(command)); + } + "_XREFUNCLIP" => { + let inserts = self.selected_xrefs(i); + if !inserts.is_empty() { + self.apply_xclip(i, inserts, crate::modules::insert::xclip::XclipAction::Delete); + } + } "_PDFULUNCLIP" => { self.edit_selected_underlays(i, "PDFCLIP", |u| { u.clip_boundary_vertices.clear(); diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index 87b1740ff..0ba970977 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -2950,6 +2950,8 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Snap to Underlays ON" => Some(("insert", "refui-snap-to-underlays-on")), "Snap to Underlays OFF" => Some(("insert", "refui-snap-to-underlays-off")), "Edit Reference" => Some(("insert", "refui-edit-reference")), + "Edit Reference\nIn-Place" => Some(("insert", "refui-edit-reference-in-place")), + "Open\nReference" => Some(("insert", "refui-open-reference")), "Xref fading" => Some(("insert", "refui-xref-fading")), "Requires an integer between -90 and 90." => Some(("insert", "refui-requires-int-90")), "Underlay" => Some(("properties", "underlay")), diff --git a/src/modules/insert/xclip.rs b/src/modules/insert/xclip.rs index f1ea06b28..8c14c01cb 100644 --- a/src/modules/insert/xclip.rs +++ b/src/modules/insert/xclip.rs @@ -103,6 +103,15 @@ impl XclipCommand { } } + /// A new boundary for block references already chosen (the External + /// Reference tab's Create Clipping Boundary): the New boundary branch at + /// once. Returns the command and the result of its first step. + pub fn start_new_boundary(inserts: Vec, clipped: bool) -> (Self, CmdResult) { + let mut command = Self::for_inserts(inserts, clipped); + let first = command.option("N"); + (command, first) + } + fn act(&self, action: XclipAction) -> CmdResult { CmdResult::XClip { inserts: self.inserts.clone(), diff --git a/src/ui/ribbon/mod.rs b/src/ui/ribbon/mod.rs index 0fd851d09..d1bc827c4 100644 --- a/src/ui/ribbon/mod.rs +++ b/src/ui/ribbon/mod.rs @@ -109,6 +109,8 @@ pub struct Ribbon { 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. + xref_ctx: bool, /// XDWGFADECTL as the Reference slide-out shows it (negative = off). pub xref_fade: i32, } @@ -206,6 +208,7 @@ impl Ribbon { collapse_tight: Arc::new(AtomicBool::new(false)), underlay_ctx: None, underlay_tab_active: false, + xref_ctx: false, xref_fade: 50, } } @@ -589,8 +592,8 @@ impl Ribbon { ); let mut tab_items = tab_items; - if self.underlay_ctx.is_some() { - tab_items.push(underlay_tab_button(self.underlay_tab_shown())); + 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 @@ -1627,9 +1630,9 @@ mod tests { } } -/// The contextual underlay tab's button: framed in the accent colour. -fn underlay_tab_button<'a>(active: bool) -> Element<'a, Message> { - let btn = button(text(t!("PDF Underlay")).size(12)) +/// 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(); diff --git a/src/ui/ribbon/underlay_tab.rs b/src/ui/ribbon/underlay_tab.rs index ec36772c3..485e21342 100644 --- a/src/ui/ribbon/underlay_tab.rs +++ b/src/ui/ribbon/underlay_tab.rs @@ -1,5 +1,6 @@ -//! The contextual "PDF Underlay" tab: shown and brought forward while only -//! PDF underlays are selected, gone when the selection changes. +//! 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}; @@ -112,6 +113,47 @@ fn groups() -> &'static [RibbonGroup] { }) } +/// 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, @@ -156,15 +198,16 @@ fn adjust_rows<'a>(ctx: &'a UnderlayContext) -> Element<'a, Message> { } impl Ribbon { - /// Show the tab for the selected underlay, or drop it (`None`). 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) { - match (&self.underlay_ctx, &context) { - (None, Some(_)) => self.underlay_tab_active = true, - (Some(_), None) => self.underlay_tab_active = false, - _ => {} + /// 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) { @@ -180,9 +223,10 @@ impl Ribbon { self.underlay_ctx.as_mut() } - /// Select a tab by its module id; "pdf_underlay" is the contextual tab. + /// 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" { + if (id == "pdf_underlay" && self.underlay_ctx.is_some()) || (id == "xref" && self.xref_ctx) { self.select_underlay_tab(); return self.underlay_tab_active; } @@ -196,13 +240,22 @@ impl Ribbon { } pub fn select_underlay_tab(&mut self) { - if self.underlay_ctx.is_some() { + if self.contextual_tab().is_some() { self.underlay_tab_active = true; } } pub(super) fn underlay_tab_shown(&self) -> bool { - self.underlay_tab_active && self.underlay_ctx.is_some() + 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>( @@ -210,6 +263,12 @@ impl Ribbon { 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(); }; From 5d3987791ef6ff0bfe920b89c286d6591948651e Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Fri, 25 Sep 2026 13:56:13 +0300 Subject: [PATCH 2/6] Contextual ribbon tab follows the active document Every message that makes another document active (switching, opening, creating or closing a tab) resyncs the contextual tab with that document's selection, so a tab offered for one drawing no longer stays on another. Co-Authored-By: Claude Opus 5.5 --- src/app/update/mod.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 632ace129..8da7408a8 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -318,6 +318,18 @@ impl OpenCADStudio { } pub fn update(&mut self, msg: Message) -> Task { + // Whatever makes another document active (switching, opening, + // creating or closing a tab), the contextual ribbon tab follows that + // document's selection. + let before = self.tabs.get(self.active_tab).map(|tab| tab.id); + let task = self.update_message(msg); + if self.tabs.get(self.active_tab).map(|tab| tab.id) != before { + self.sync_underlay_tab(); + } + task + } + + fn update_message(&mut self, msg: Message) -> Task { if let Some(tab) = self.tabs.get(self.active_tab) { crate::entities::common::set_unit_context( crate::entities::common::UnitContext::from_header(&tab.scene.document.header), From 5a31d2089a3e89791e7156431e200e38447c7659 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Fri, 25 Sep 2026 14:25:22 +0300 Subject: [PATCH 3/6] Selection tools on the right-edge toolbar instead of contextual tabs The PDF Underlay and External Reference ribbon tabs are gone. While only PDF underlays are selected their tools (Display in Monochrome, Create Clipping Boundary, Remove Clipping, Show Underlay, Enable Snap, External References, Edit Layers, Import As Objects) sit on the right-edge toolbar, the switches drawn as on while on; while only xrefs are selected the xref tools do. Contrast and fade stay in Properties. Toolbar tooltips are translated. Co-Authored-By: Claude Opus 5.5 --- src/app/commands/pdf_underlay.rs | 61 ++----- src/app/control/actions.rs | 3 +- src/app/mod.rs | 3 - src/app/update/mod.rs | 5 - src/app/view/mod.rs | 29 +++- src/ui/ribbon/context_tools.rs | 96 ++++++++++ src/ui/ribbon/mod.rs | 60 +------ src/ui/ribbon/underlay_tab.rs | 289 ------------------------------- src/ui/ribbon/widgets.rs | 6 - src/ui/side_toolbar.rs | 23 ++- 10 files changed, 163 insertions(+), 412 deletions(-) create mode 100644 src/ui/ribbon/context_tools.rs delete mode 100644 src/ui/ribbon/underlay_tab.rs 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() From 4d9379ad6eb80f5bbb3ea194d7d05edf7ecb61e3 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Fri, 25 Sep 2026 14:32:29 +0300 Subject: [PATCH 4/6] Right-edge toolbar centres below the view cube and UCS list Co-Authored-By: Claude Opus 5.5 --- src/ui/side_toolbar.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ui/side_toolbar.rs b/src/ui/side_toolbar.rs index b435bc32f..2cdce0013 100644 --- a/src/ui/side_toolbar.rs +++ b/src/ui/side_toolbar.rs @@ -17,6 +17,9 @@ const ICON_SIZE: f32 = 22.0; const BTN_SIZE: f32 = 38.0; /// Gap between the toolbar and the right edge of the canvas. const EDGE_MARGIN: f32 = 8.0; +/// Room kept free at the top right for the view cube and the UCS list; the +/// toolbar is centred in the height below it. +const TOP_RESERVE: f32 = 200.0; fn icon_el(icon: IconKind) -> Element<'static, Message> { match icon { @@ -122,6 +125,7 @@ pub fn view_with_active( .align_y(iced::Center) .padding(iced::Padding { right: EDGE_MARGIN, + top: TOP_RESERVE, ..iced::Padding::ZERO }) .into(), From cc7cae7d8e9e8e518cbb4173d23dee76cd303f0d Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Fri, 25 Sep 2026 14:47:44 +0300 Subject: [PATCH 5/6] Remove Clipping tools show a small red cross on the clip icon Co-Authored-By: Claude Opus 5.5 --- assets/icons/xclip_remove.svg | 9 +++++++++ src/ui/ribbon/context_tools.rs | 2 ++ 2 files changed, 11 insertions(+) create mode 100644 assets/icons/xclip_remove.svg diff --git a/assets/icons/xclip_remove.svg b/assets/icons/xclip_remove.svg new file mode 100644 index 000000000..2c808ee42 --- /dev/null +++ b/assets/icons/xclip_remove.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/ui/ribbon/context_tools.rs b/src/ui/ribbon/context_tools.rs index c5f69819f..c642a0bd0 100644 --- a/src/ui/ribbon/context_tools.rs +++ b/src/ui/ribbon/context_tools.rs @@ -24,6 +24,8 @@ fn tool(id: &'static str, label: &'static str, icon: &'static [u8]) -> ToolDef { } const CLIP: &[u8] = include_bytes!("../../../assets/icons/xclip.svg"); +/// The clip icon with a small red cross at its lower right. +const UNCLIP: &[u8] = include_bytes!("../../../assets/icons/xclip_remove.svg"); /// PDF underlay tools: display, clipping, options, layers and import. pub fn pdf_underlay_tools() -> Vec { From 3bc3a0adfe5e340316e961c2e2cb164ae3438e4a Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Fri, 25 Sep 2026 14:54:16 +0300 Subject: [PATCH 6/6] Remove Clipping tools use the crossed clip icon Co-Authored-By: Claude Opus 5.5 --- src/ui/ribbon/context_tools.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/ribbon/context_tools.rs b/src/ui/ribbon/context_tools.rs index c642a0bd0..8428f39bf 100644 --- a/src/ui/ribbon/context_tools.rs +++ b/src/ui/ribbon/context_tools.rs @@ -33,7 +33,7 @@ pub fn pdf_underlay_tools() -> Vec { vec![ tool("_PDFULMONO", "Display in\nMonochrome", FRAMES), tool("_PDFULCLIP", "Create Clipping\nBoundary", CLIP), - tool("_PDFULUNCLIP", "Remove\nClipping", CLIP), + tool("_PDFULUNCLIP", "Remove\nClipping", UNCLIP), tool("_PDFULSHOW", "Show\nUnderlay", FRAMES), tool( "_PDFULSNAP", @@ -64,7 +64,7 @@ pub fn xref_tools() -> Vec { ), tool("_XREFOPEN", "Open\nReference", crate::ui::icons::FOLDER_OPEN), tool("_XREFCLIP", "Create Clipping\nBoundary", CLIP), - tool("_XREFUNCLIP", "Remove\nClipping", CLIP), + tool("_XREFUNCLIP", "Remove\nClipping", UNCLIP), tool("EXTERNALREFERENCES", "External\nReferences", crate::ui::icons::FOLDER_OPEN), ] }