Merge pull request #1486 from ramox81/codex/xref-context-tab

Selection tools on the right-edge toolbar for PDF underlays and xrefs
This commit is contained in:
Hakan Seven committed 2026-09-25 15:18:33 +03:00
commit 82fe204a70
34 files changed
+387 -353

No files matched your search

+9
View File
@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64" fill="none" stroke-linecap="round" stroke-linejoin="round">
<rect x="4" y="4" width="16" height="16" rx="1" stroke="#B4B6B9" stroke-width="1.5" stroke-dasharray="3 2"/>
<polygon points="4,4 14,4 14,14 4,14" fill="#6DB7ED" fill-opacity="0.15" stroke="#6DB7ED" stroke-width="1.5"/>
<line x1="14" y1="4" x2="14" y2="20" stroke="#6DB7ED" stroke-width="1.5" stroke-dasharray="3 2"/>
<line x1="4" y1="14" x2="20" y2="14" stroke="#6DB7ED" stroke-width="1.5" stroke-dasharray="3 2"/>
<circle cx="19" cy="19" r="4.3" fill="#1a1a1a"/>
<line x1="16.9" y1="16.9" x2="21.1" y2="21.1" stroke="#E53935" stroke-width="1.9"/>
<line x1="21.1" y1="16.9" x2="16.9" y2="21.1" stroke="#E53935" stroke-width="1.9"/>
</svg>

After

Width:  |  Height:  |  Size: 797 B

+6
View File
@@ -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
+6
View File
@@ -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
+6
View File
@@ -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
+6
View File
@@ -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
+6
View File
@@ -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
+6
View File
@@ -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
+6
View File
@@ -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
+6
View File
@@ -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
+6
View File
@@ -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
+6
View File
@@ -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
+6
View File
@@ -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
+6
View File
@@ -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
+6
View File
@@ -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
+6
View File
@@ -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
+6
View File
@@ -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
+6
View File
@@ -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
+6
View File
@@ -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
+6
View File
@@ -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
+7 -1
View File
@@ -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ı
+6
View File
@@ -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
+6
View File
@@ -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
+69 -49
View File
@@ -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.
@@ -20,23 +20,44 @@ 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<codec::Handle> {
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
}
/// 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();
let context = if self.tabs[i].is_start {
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);
self.ribbon.set_underlay_context(context, xref);
}
/// One undo step that edits every selected PDF underlay.
@@ -68,43 +89,7 @@ impl OpenCADStudio {
self.refresh_properties();
}
pub(in crate::app) fn update_underlay_tab(&mut self, message: UnderlayTabMsg) -> Task<Message> {
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::<u8>().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<Task<Message>> {
match cmd {
"_PDFULMONO" => {
@@ -127,6 +112,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();
+1 -2
View File
@@ -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) {
-3
View File
@@ -3838,11 +3838,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<Vec<u8>>), String>),
/// Result of the PDFATTACH file picker.
PdfAttachPickResult(Result<(std::path::PathBuf, std::sync::Arc<Vec<u8>>), String>),
+12 -5
View File
@@ -318,6 +318,18 @@ impl OpenCADStudio {
}
pub fn update(&mut self, msg: Message) -> Task<Message> {
// 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<Message> {
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),
@@ -1772,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);
+26 -3
View File
@@ -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())
{
+2
View File
@@ -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")),
+9
View File
@@ -109,6 +109,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<Handle>, 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(),
+98
View File
@@ -0,0 +1,98 @@
//! 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");
/// 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<ToolDef> {
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", UNCLIP),
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<ToolDef> {
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", UNCLIP),
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<UnderlayContext>, 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,
}
}
}
+11 -50
View File
@@ -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,10 +105,10 @@ 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<AtomicBool>,
/// 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<UnderlayContext>,
/// The contextual underlay tab is the one shown.
underlay_tab_active: bool,
/// Only xrefs are selected.
xref_ctx: bool,
/// XDWGFADECTL as the Reference slide-out shows it (negative = off).
pub xref_fade: i32,
}
@@ -205,7 +205,7 @@ 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,
}
}
@@ -284,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;
}
}
@@ -348,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),
}
}
@@ -518,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))
@@ -588,10 +584,6 @@ impl Ribbon {
},
);
let mut tab_items = tab_items;
if self.underlay_ctx.is_some() {
tab_items.push(underlay_tab_button(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
@@ -673,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<Panel<'_>> = 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<Panel<'_>> = 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())
@@ -1627,30 +1615,3 @@ 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))
.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()
}
-230
View File
@@ -1,230 +0,0 @@
//! The contextual "PDF Underlay" tab: shown and brought forward while only
//! PDF underlays are selected, gone when the selection 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<Vec<RibbonGroup>> = 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"),
))],
},
]
})
}
/// 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, 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<UnderlayContext>) {
match (&self.underlay_ctx, &context) {
(None, Some(_)) => self.underlay_tab_active = true,
(Some(_), None) => self.underlay_tab_active = false,
_ => {}
}
// 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" is the contextual tab.
pub fn select_by_id(&mut self, id: &str) -> bool {
if id == "pdf_underlay" {
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.underlay_ctx.is_some() {
self.underlay_tab_active = true;
}
}
pub(super) fn underlay_tab_shown(&self) -> bool {
self.underlay_tab_active && self.underlay_ctx.is_some()
}
pub(super) fn underlay_panels<'a>(
&'a self,
ts: widgets::ToggleState,
style_ctx: &StyleContext<'_>,
) -> Vec<Panel<'a>> {
let Some(ctx) = self.underlay_ctx.as_ref() else {
return Vec::new();
};
groups()
.iter()
.map(|g| {
let lead = |_: bool| -> Vec<Element<'a, Message>> {
if g.title == ADJUST {
vec![adjust_rows(ctx)]
} else {
Vec::new()
}
};
self.panel(g, ts, style_ctx, &lead)
})
.collect()
}
}
-6
View File
@@ -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),
+23 -4
View File
@@ -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 {
@@ -26,6 +29,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 +53,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<Element<'static, Message>> {
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<Element<'static, Message>> {
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 +75,22 @@ pub fn view(tools: &[ToolDef]) -> Option<Element<'static, Message>> {
})
.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()
@@ -107,6 +125,7 @@ pub fn view(tools: &[ToolDef]) -> Option<Element<'static, Message>> {
.align_y(iced::Center)
.padding(iced::Padding {
right: EDGE_MARGIN,
top: TOP_RESERVE,
..iced::Padding::ZERO
})
.into(),