PDF underlays: attach dialog with pages, underlay tab, layers, import settings and file import, unload on the definition

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ramox81andClaude Opus 5.5 committed 2026-09-24 18:31:40 +03:00
1 parent 0c50013ca7
commit 3f6bdf8c59
57 files changed
+5049 -365

No files matched your search

+215
View File
@@ -0,0 +1,215 @@
//! 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()
}
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()
}
}