feat(plot): add Print All workflow

This commit is contained in:
Hakan Seven committed 2026-08-04 11:29:11 +03:00
1 parent 734afd5611
commit f6b9d6514e
11 files changed
+655 -41

No files matched your search

+3
View File
@@ -448,6 +448,9 @@ impl OpenCADStudio {
"PLOT" | "PRINT" => {
return Some(Task::done(Message::PlotDialogOpen));
}
"PRINTALL" => {
return Some(Task::done(Message::PrintAllOpen));
}
"EXPORT" | "EXPORTPDF" => {
return Some(Task::done(Message::PlotExport));
}
+1
View File
@@ -282,6 +282,7 @@ inventory::submit!(crate::command::CommandRegistration {
// Standard aliases for existing commands.
"BMAKE",
"EXPORTPDF",
"PRINTALL",
"DDIM",
// Inquiry: list the whole drawing database.
"DBLIST",
+33
View File
@@ -669,6 +669,16 @@ pub(super) struct OpenCADStudio {
/// Snapshot of the dialog's settings taken when it opened, restored by the
/// `<previous>` list entry.
plot_prev: Option<crate::ui::window::plot::PlotDialogState>,
/// Paper layouts shown by Print All, in tab order with their selection.
print_all_layouts: Vec<(String, bool)>,
/// True while the Plot dialog is editing settings for Print All.
print_all_options: bool,
/// Settings restored when the Print All options dialog is cancelled.
print_all_options_prev: Option<crate::ui::window::plot::PlotDialogState>,
/// Plot style restored together with cancelled Print All options.
print_all_plot_style_prev: Option<Option<crate::io::plot_style::PlotStyleTable>>,
/// Plot window restored together with cancelled Print All options.
print_all_plot_window_prev: Option<Option<(f64, f64, f64, f64)>>,
// ── Plot Style Table ──────────────────────────────────────────────────
/// Currently loaded CTB/STB table (None = no override).
@@ -1409,6 +1419,7 @@ pub enum ModalKind {
LayerStateManager,
LayerStateEditor,
Plot,
PrintAll,
LayoutManager,
Plotstyle,
TextStyle,
@@ -2455,6 +2466,23 @@ pub enum Message {
PlotDialogOpen,
/// An edit inside the Plot / Print dialog.
PlotDlg(crate::ui::window::plot::PlotDlgMsg),
/// Open the paper-layout batch output dialog.
PrintAllOpen,
/// Toggle one paper layout in the batch.
PrintAllToggle(String),
/// Select or clear every paper layout in the batch.
PrintAllSelectAll,
PrintAllSelectNone,
/// Edit the shared batch output settings in the Plot dialog.
PrintAllOptions,
/// Save the selected layouts as one multi-page PDF.
PrintAllPdf,
/// Callback after the multi-page PDF path is picked or cancelled.
PrintAllPdfPath(Option<std::path::PathBuf>),
/// Send the selected layouts to the configured printer as one job.
PrintAllPrint,
/// Completion of a Print All PDF or printer job.
PrintAllFinished(Result<String, String>),
// ── Plot Style Table ─────────────────────────────────────────────────
/// Open file dialog to load a CTB/STB plot style table.
PlotStyleLoad,
@@ -2896,6 +2924,11 @@ impl OpenCADStudio {
plot_orientation: crate::io::paper_sizes::Orientation::Landscape,
plot_dialog: crate::ui::window::plot::PlotDialogState::default(),
plot_prev: None,
print_all_layouts: Vec::new(),
print_all_options: false,
print_all_options_prev: None,
print_all_plot_style_prev: None,
print_all_plot_window_prev: None,
opening: None,
open_job_serial: 0,
recovery_report: None,
+234
View File
@@ -2408,6 +2408,192 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
}
}
pub(super) fn on_print_all_open(&mut self) -> Task<Message> {
self.print_all_layouts = self.tabs[self.active_tab]
.scene
.layout_names()
.into_iter()
.filter(|name| name != "Model")
.map(|name| (name, true))
.collect();
self.active_modal = Some(crate::app::ModalKind::PrintAll);
self.reset_modal_geometry();
Task::none()
}
pub(super) fn on_print_all_options(&mut self) -> Task<Message> {
let previous = self.plot_dialog.clone();
let previous_style = self.active_plot_style.clone();
let previous_window = self.plot_window;
let task = self.on_plot_dialog_open();
self.print_all_options_prev = Some(previous);
self.print_all_plot_style_prev = Some(previous_style);
self.print_all_plot_window_prev = Some(previous_window);
self.print_all_options = true;
self.plot_dialog.paper_space = true;
self.plot_dialog.area = "Layout".into();
task
}
fn print_all_pages(&mut self) -> Result<Vec<crate::io::pdf_export::PdfPageInput>, String> {
let available = self.tabs[self.active_tab].scene.layout_names();
let selected: Vec<String> = self
.print_all_layouts
.iter()
.filter(|(name, checked)| *checked && available.contains(name))
.map(|(name, _)| name.clone())
.collect();
if selected.is_empty() {
return Err(crate::t!("Select at least one layout.").into_owned());
}
let i = self.active_tab;
let original_layout = self.tabs[i].scene.current_layout.clone();
let original_viewport = self.tabs[i].scene.active_viewport;
let dialog = self.plot_dialog.clone();
let mut pages = Vec::with_capacity(selected.len());
for name in selected {
// Plot helpers read the active layout. Swap only this transient
// selector so the drawing's saved active-space metadata is not
// touched while the owned page snapshot is collected.
{
let scene = &mut self.tabs[i].scene;
scene.current_layout = name;
scene.active_viewport = None;
}
let (
wires,
hatches,
wipeouts,
group_splits,
paper_w,
paper_h,
offset_x,
offset_y,
rotation_deg,
scale,
clip,
) = self.layout_plot_params_for("Layout");
pages.push(crate::io::pdf_export::PdfPageInput {
wires,
hatches,
wipeouts,
paper_w,
paper_h,
offset_x,
offset_y,
rotation_deg,
scale,
clip,
options: Self::pdf_plot_options(&dialog, group_splits),
});
}
self.tabs[i].scene.current_layout = original_layout;
self.tabs[i].scene.active_viewport = original_viewport;
Ok(pages)
}
pub(super) fn on_print_all_pdf_path_some(
&mut self,
path: std::path::PathBuf,
) -> Task<Message> {
let dialog = self.plot_dialog.clone();
if dialog.style_missing {
self.command_line.push_error(crate::tf!(
"Plot style table '{}' is not loaded.",
dialog.style_name
).as_ref());
return Task::none();
}
let pages = match self.print_all_pages() {
Ok(pages) => pages,
Err(error) => {
self.command_line.push_error(&error);
return Task::none();
}
};
let plot_style = self.dialog_plot_style(&dialog);
let worker_path = path.clone();
self.save_config();
self.close_active_modal();
let work = move || {
crate::io::pdf_export::export_pdf_pages(
&pages,
&worker_path,
plot_style.as_ref(),
)
.map(|_| format!("Exported {} layouts to {}", pages.len(), worker_path.display()))
.map_err(|error| format!("Export failed: {error}"))
};
self.run_print_all_work(dialog.background, work)
}
pub(super) fn on_print_all_print(&mut self) -> Task<Message> {
#[cfg(target_arch = "wasm32")]
{
self.command_line.push_error(
crate::t!("Printing is not available in the web version.").as_ref(),
);
Task::none()
}
#[cfg(not(target_arch = "wasm32"))]
{
let dialog = self.plot_dialog.clone();
if dialog.style_missing {
self.command_line.push_error(crate::tf!(
"Plot style table '{}' is not loaded.",
dialog.style_name
).as_ref());
return Task::none();
}
let pages = match self.print_all_pages() {
Ok(pages) => pages,
Err(error) => {
self.command_line.push_error(&error);
return Task::none();
}
};
let plot_style = self.dialog_plot_style(&dialog);
let options = self.plot_print_options(&dialog, Default::default());
let stamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|duration| duration.as_nanos())
.unwrap_or(0);
let temp_path = std::env::temp_dir().join(format!(
"open_cad_studio_print_all_{stamp}.pdf"
));
self.save_config();
self.close_active_modal();
self.command_line.push_info(
crate::t!("Sending selected layouts to the system printer…").as_ref(),
);
let work = move || {
crate::io::pdf_export::export_pdf_pages(
&pages,
&temp_path,
plot_style.as_ref(),
)
.and_then(|_| {
crate::io::print_to_printer::print_existing_pdf(&temp_path, &options)
})
.map(|printer| format!("Sent {} layouts to printer: {printer}", pages.len()))
.map_err(|error| format!("Print failed: {error}"))
};
self.run_print_all_work(dialog.background, work)
}
}
fn run_print_all_work<F>(&mut self, background: bool, work: F) -> Task<Message>
where
F: FnOnce() -> Result<String, String> + Send + 'static,
{
if background {
background_task(work, Message::PrintAllFinished)
} else {
Task::done(Message::PrintAllFinished(work()))
}
}
fn run_plot_work<F>(
&mut self,
background: bool,
@@ -2940,6 +3126,9 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
Task::none()
}
M::Area(s) => {
if self.print_all_options && s != "Layout" {
return Task::none();
}
// A paper Layout page setup commonly carries physical-sheet
// origin and 1:1 scale. Those values are correct only for
// Layout; carrying them into Window/Extents/Display moves a
@@ -3040,6 +3229,9 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
Task::none()
}
M::PickWindow => {
if self.print_all_options {
return Task::none();
}
let i = self.active_tab;
if self.tabs[i].scene.current_layout != "Model"
&& self.tabs[i].scene.active_viewport.is_some()
@@ -3060,14 +3252,24 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
}
M::SelectSetup(name) => {
self.select_page_setup(&name);
if self.print_all_options {
self.plot_dialog.paper_space = true;
self.plot_dialog.area = "Layout".into();
}
Task::none()
}
M::SetCurrent => {
if self.print_all_options {
return Task::none();
}
self.apply_dialog_to_layout();
self.command_line.push_info(crate::t!("Page setup applied to the layout.").as_ref());
Task::none()
}
M::NewSetup => {
if self.print_all_options {
return Task::none();
}
// Create a setup from the current editor values, then start an
// inline rename so the user can name it.
let name = self.next_page_setup_name("Setup");
@@ -3081,6 +3283,9 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
Task::none()
}
M::CopySetup => {
if self.print_all_options {
return Task::none();
}
// Duplicate the selected entry — a layout OR a named setup —
// into a new standalone named page setup.
let sel = self.plot_dialog.selected_setup.clone();
@@ -3103,6 +3308,9 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
Task::none()
}
M::RenameStart(name) => {
if self.print_all_options {
return Task::none();
}
// Only standalone named setups can be renamed.
if is_layout_entry(&name) || is_special_entry(&name) {
return Task::none();
@@ -3116,6 +3324,9 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
Task::none()
}
M::DeleteSetup => {
if self.print_all_options {
return Task::none();
}
let sel = self.plot_dialog.selected_setup.clone();
if !sel.is_empty() && !is_layout_entry(&sel) {
self.tabs[self.active_tab].scene.page_setup_delete(&sel);
@@ -3153,7 +3364,30 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
self.plot_dialog.name_rename = false;
Task::none()
}
M::Preview if self.print_all_options => Task::none(),
M::Preview => self.on_plot_dlg_commit(true),
M::Commit if self.print_all_options => {
if self.plot_dialog.style_missing {
self.command_line.push_error(crate::tf!(
"Plot style table '{}' is not loaded.",
self.plot_dialog.style_name
).as_ref());
return Task::none();
}
self.plot_dialog.paper_space = true;
self.plot_dialog.area = "Layout".into();
self.sync_dialog_plot_runtime();
self.save_config();
self.print_all_options = false;
self.print_all_options_prev = None;
self.print_all_plot_style_prev = None;
if let Some(previous) = self.print_all_plot_window_prev.take() {
self.plot_window = previous;
}
self.active_modal = Some(crate::app::ModalKind::PrintAll);
self.reset_modal_geometry();
Task::none()
}
M::Commit => self.on_plot_dlg_commit(false),
}
}
+82
View File
@@ -121,6 +121,21 @@ impl OpenCADStudio {
/// changes, and the ribbon tool that launched the dialog is de-highlighted.
fn close_active_modal(&mut self) {
use super::ModalKind::*;
if self.active_modal == Some(Plot) && self.print_all_options {
if let Some(previous) = self.print_all_options_prev.take() {
self.plot_dialog = previous;
}
if let Some(previous) = self.print_all_plot_style_prev.take() {
self.active_plot_style = previous;
}
if let Some(previous) = self.print_all_plot_window_prev.take() {
self.plot_window = previous;
}
self.print_all_options = false;
self.active_modal = Some(PrintAll);
self.reset_modal_geometry();
return;
}
if matches!(
self.active_modal,
Some(TextStyle | DimStyle | TableStyle | MLeaderStyle | MlStyle)
@@ -5461,6 +5476,73 @@ impl OpenCADStudio {
}
Message::PlotDialogOpen => self.on_plot_dialog_open(),
Message::PlotDlg(m) => self.on_plot_dlg(m),
Message::PrintAllOpen => self.on_print_all_open(),
Message::PrintAllToggle(name) => {
if let Some((_, selected)) = self
.print_all_layouts
.iter_mut()
.find(|(layout, _)| layout == &name)
{
*selected = !*selected;
}
Task::none()
}
Message::PrintAllSelectAll => {
for (_, selected) in &mut self.print_all_layouts {
*selected = true;
}
Task::none()
}
Message::PrintAllSelectNone => {
for (_, selected) in &mut self.print_all_layouts {
*selected = false;
}
Task::none()
}
Message::PrintAllOptions => self.on_print_all_options(),
Message::PrintAllPdf => {
let i = self.active_tab;
let stem = self.tabs[i]
.current_path
.as_deref()
.and_then(|path| path.file_stem())
.map(|name| format!("{}_layouts", name.to_string_lossy()))
.unwrap_or_else(|| "drawing_layouts".into());
#[cfg(not(target_arch = "wasm32"))]
{
let Some(window_id) = self.main_window else {
return Task::done(Message::PrintAllPdfPath(None));
};
iced::window::run(window_id, move |parent| {
crate::io::pdf_export::pick_pdf_path_owned(stem, parent)
})
.map(Message::PrintAllPdfPath)
}
#[cfg(target_arch = "wasm32")]
{
let _ = stem;
self.command_line.push_error(
crate::t!("PDF export is not available in the web version.").as_ref(),
);
Task::none()
}
}
Message::PrintAllPdfPath(None) => Task::none(),
Message::PrintAllPdfPath(Some(path)) => self.on_print_all_pdf_path_some(path),
Message::PrintAllPrint => self.on_print_all_print(),
Message::PrintAllFinished(result) => {
match result {
Ok(message) => self.command_line.push_info(&message),
Err(error) => {
self.command_line.push_error(&error);
if self.active_modal.is_none() {
self.active_modal = Some(super::ModalKind::PrintAll);
self.reset_modal_geometry();
}
}
}
Task::none()
}
// ── Plot / Export ─────────────────────────────────────────────
Message::PlotExport => {
+20 -1
View File
@@ -21,6 +21,7 @@ impl OpenCADStudio {
Some(K::LayerStateManager) => crate::tr!("modal-layer-state-manager"),
Some(K::LayerStateEditor) => crate::tr!("modal-edit-layer-state"),
Some(K::Plot) => crate::tr!("modal-plot"),
Some(K::PrintAll) => t!("Print All").into_owned(),
Some(K::LayoutManager) => crate::tr!("modal-layout-manager"),
Some(K::ScaleManager) => crate::tr!("modal-scale-manager"),
Some(K::AnnoObjectScale) => crate::tr!("modal-annotation-object-scale"),
@@ -238,9 +239,27 @@ impl OpenCADStudio {
ex,
760,
540,
|flow| crate::ui::window::plot::view_window(&self.plot_dialog, flow),
|flow| {
crate::ui::window::plot::view_window(
&self.plot_dialog,
self.print_all_options,
flow,
)
},
)
}
super::super::ModalKind::PrintAll => sized_flow(
ex,
520,
420,
|flow| {
crate::ui::window::print_all::view_window(
&self.print_all_layouts,
self.plot_dialog.printer.as_deref(),
flow,
)
},
),
super::super::ModalKind::LayoutManager => {
let i = self.active_tab;
let layouts = self.tabs[i].scene.layout_names();
+99 -3
View File
@@ -46,6 +46,15 @@ pub fn export_pdf(
Err("PDF export is not available in the web version.".into())
}
#[cfg(target_arch = "wasm32")]
pub fn export_pdf_pages(
_pages: &[PdfPageInput],
_path: &Path,
_plot_style: Option<&PlotStyleTable>,
) -> Result<(), String> {
Err("PDF export is not available in the web version.".into())
}
#[cfg(target_arch = "wasm32")]
pub async fn pick_pdf_path_owned(_stem: String) -> Option<std::path::PathBuf> {
None
@@ -83,6 +92,22 @@ pub struct PlotGroupSplits {
pub wipeouts: usize,
}
/// Owned render data for one page in a multi-page PDF.
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub struct PdfPageInput {
pub wires: std::sync::Arc<Vec<WireModel>>,
pub hatches: Vec<HatchModel>,
pub wipeouts: Vec<HatchModel>,
pub paper_w: f64,
pub paper_h: f64,
pub offset_x: f64,
pub offset_y: f64,
pub rotation_deg: i32,
pub scale: f32,
pub clip: Option<(f32, f32, f32, f32)>,
pub options: PdfPlotOptions,
}
impl Default for PdfPlotOptions {
fn default() -> Self {
Self {
@@ -138,6 +163,21 @@ pub fn export_pdf(
file.write_all(&bytes).map_err(|e| e.to_string())
}
/// Export several independently sized pages into one PDF file.
#[cfg(not(target_arch = "wasm32"))]
pub fn export_pdf_pages(
pages: &[PdfPageInput],
path: &Path,
plot_style: Option<&PlotStyleTable>,
) -> Result<(), String> {
if pages.is_empty() {
return Err("No pages were selected.".into());
}
let bytes = build_pdf_pages(pages, plot_style);
let mut file = std::fs::File::create(path).map_err(|e| e.to_string())?;
file.write_all(&bytes).map_err(|e| e.to_string())
}
/// Show a parented PDF save-file dialog and return the chosen path.
///
/// The parent comes from `iced::window::run`, keeping the portal request tied
@@ -181,6 +221,65 @@ fn build_pdf(
options: PdfPlotOptions,
) -> Vec<u8> {
let mut doc = PdfDocument::new("Open CAD Studio Export");
append_pdf_page(
&mut doc,
wires,
hatches,
wipeouts,
paper_w,
paper_h,
ox,
oy,
rotation_deg,
scale,
clip,
plot_style,
options,
);
let mut warnings = Vec::new();
doc.save(&PdfSaveOptions::default(), &mut warnings)
}
#[cfg(not(target_arch = "wasm32"))]
fn build_pdf_pages(pages: &[PdfPageInput], plot_style: Option<&PlotStyleTable>) -> Vec<u8> {
let mut doc = PdfDocument::new("Open CAD Studio Export");
for page in pages {
append_pdf_page(
&mut doc,
&page.wires,
&page.hatches,
&page.wipeouts,
page.paper_w as f32,
page.paper_h as f32,
page.offset_x,
page.offset_y,
page.rotation_deg,
page.scale,
page.clip,
plot_style,
page.options,
);
}
let mut warnings = Vec::new();
doc.save(&PdfSaveOptions::default(), &mut warnings)
}
#[cfg(not(target_arch = "wasm32"))]
fn append_pdf_page(
doc: &mut PdfDocument,
wires: &[WireModel],
hatches: &[HatchModel],
wipeouts: &[HatchModel],
paper_w: f32,
paper_h: f32,
ox: f64,
oy: f64,
rotation_deg: i32,
scale: f32,
clip: Option<(f32, f32, f32, f32)>,
plot_style: Option<&PlotStyleTable>,
options: PdfPlotOptions,
) {
let mut ops: Vec<Op> = Vec::new();
// White page background.
@@ -474,9 +573,6 @@ fn build_pdf(
let page = PdfPage::new(Mm(paper_w), Mm(paper_h), ops);
doc.pages.push(page);
let mut warnings = Vec::new();
doc.save(&PdfSaveOptions::default(), &mut warnings)
}
/// Build a PDF dash array (in points) from a WireModel linetype pattern.
+6 -6
View File
@@ -18,10 +18,10 @@ pub fn paper_space_tools() -> Vec<ToolDef> {
event: ModuleEvent::Command("PAGESETUP".to_string()),
},
ToolDef {
id: "PLOT",
label: "Export PDF",
id: "PRINTALL",
label: "Print All",
icon: IconKind::Svg(include_bytes!("../../../assets/icons/plot.svg")),
event: ModuleEvent::Command("PLOT".to_string()),
event: ModuleEvent::Command("PRINTALL".to_string()),
},
]
}
@@ -57,10 +57,10 @@ impl CadModule for LayoutModule {
}
.into(),
ToolDef {
id: "PLOT",
label: "Export PDF",
id: "PRINTALL",
label: "Print All",
icon: IconKind::Svg(include_bytes!("../../../assets/icons/plot.svg")),
event: ModuleEvent::Command("PLOT".to_string()),
event: ModuleEvent::Command("PRINTALL".to_string()),
}
.into(),
],
+1
View File
@@ -2,6 +2,7 @@ pub mod about;
pub mod layout_manager;
pub mod layer_state_manager;
pub mod plot;
pub mod print_all;
pub mod plugin_manager;
pub mod shortcuts;
pub mod layers;
+49 -31
View File
@@ -490,11 +490,18 @@ fn choices(items: &[&str]) -> Vec<PlotChoice> {
pub fn view_window(
s: &PlotDialogState,
print_all_options: bool,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'_, Message> {
let width = sizing.width;
let height = sizing.height;
let action = if s.to_file { t!("Export PDF") } else { t!("Print") };
let action = if print_all_options {
t!("Apply")
} else if s.to_file {
t!("Export PDF")
} else {
t!("Print")
};
let is_special = s.selected_setup == SETUP_NONE || s.selected_setup == SETUP_PREV;
let sel_is_layout = s.selected_setup.len() >= 2
&& s.selected_setup.starts_with('*')
@@ -578,23 +585,26 @@ pub fn view_window(
left: 12.0,
});
let mut new_button = button(text(t!("New")).size(11))
.style(btn(false))
.padding([4, 12]);
if !print_all_options {
new_button = new_button.on_press(Message::PlotDlg(PlotDlgMsg::NewSetup));
}
let mut copy_button = button(text(t!("Copy")).size(11))
.style(btn(false))
.padding([4, 12]);
if can_copy {
if can_copy && !print_all_options {
copy_button = copy_button.on_press(Message::PlotDlg(PlotDlgMsg::CopySetup));
}
let mut delete_button = button(text(t!("Delete")).size(11))
.style(btn(false))
.padding([4, 12]);
if is_named {
if is_named && !print_all_options {
delete_button = delete_button.on_press(Message::PlotDlg(PlotDlgMsg::DeleteSetup));
}
let left_bar = row![
button(text(t!("New")).size(11))
.on_press(Message::PlotDlg(PlotDlgMsg::NewSetup))
.style(btn(false))
.padding([4, 12]),
new_button,
copy_button,
delete_button,
]
@@ -672,8 +682,12 @@ pub fn view_window(
paper_note,
].spacing(7));
let mut area_options = choices(&["Extents", "Display", "Window"]);
if s.paper_space {
let mut area_options = if print_all_options {
choices(&["Layout"])
} else {
choices(&["Extents", "Display", "Window"])
};
if s.paper_space && !print_all_options {
area_options.insert(0, PlotChoice::localized("Layout"));
}
let mut area_row = row![
@@ -690,7 +704,7 @@ pub fn view_window(
]
.spacing(8)
.align_y(iced::Center);
if s.area == "Window" {
if s.area == "Window" && !print_all_options {
area_row = area_row.push(
button(text(t!("Pick…")).size(11))
.on_press(Message::PlotDlg(PlotDlgMsg::PickWindow))
@@ -877,27 +891,31 @@ pub fn view_window(
let body = row![list_panel, vsep(height), detail]
.width(width)
.height(height);
let toolbar = container(
row![
left_bar,
Space::new().width(width),
button(text(t!("Set current")).size(11))
.on_press(Message::PlotDlg(PlotDlgMsg::SetCurrent))
.style(btn(false))
.padding([4, 12]),
Space::new().width(6),
button(text(t!("Preview")).size(11))
.on_press(Message::PlotDlg(PlotDlgMsg::Preview))
.style(btn(false))
.padding([4, 12]),
Space::new().width(6),
button(text(action).size(11))
.on_press(Message::PlotDlg(PlotDlgMsg::Commit))
.style(btn(true))
.padding([4, 18]),
]
.align_y(iced::Center),
)
let mut toolbar_row = row![left_bar, Space::new().width(width)].align_y(iced::Center);
if !print_all_options {
toolbar_row = toolbar_row
.push(
button(text(t!("Set current")).size(11))
.on_press(Message::PlotDlg(PlotDlgMsg::SetCurrent))
.style(btn(false))
.padding([4, 12]),
)
.push(Space::new().width(6))
.push(
button(text(t!("Preview")).size(11))
.on_press(Message::PlotDlg(PlotDlgMsg::Preview))
.style(btn(false))
.padding([4, 12]),
)
.push(Space::new().width(6));
}
toolbar_row = toolbar_row.push(
button(text(action).size(11))
.on_press(Message::PlotDlg(PlotDlgMsg::Commit))
.style(btn(true))
.padding([4, 18]),
);
let toolbar = container(toolbar_row)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(theme.palette().background.weak.color)),
..Default::default()
+127
View File
@@ -0,0 +1,127 @@
use crate::app::Message;
use crate::t;
use iced::widget::{button, checkbox, column, container, row, scrollable, text, Space};
use iced::{Background, Border, Element, Length, Theme};
fn button_style(primary: bool) -> impl Fn(&Theme, button::Status) -> button::Style {
move |theme, status| {
if primary {
button::primary(theme, status)
} else {
button::secondary(theme, status)
}
}
}
fn muted(theme: &Theme) -> iced::widget::text::Style {
iced::widget::text::Style {
color: Some(theme.palette().background.base.text.scale_alpha(0.68)),
}
}
pub fn view_window<'a>(
layouts: &'a [(String, bool)],
printer: Option<&'a str>,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'a, Message> {
let selected = layouts.iter().filter(|(_, checked)| *checked).count();
let rows: Vec<Element<'a, Message>> = layouts
.iter()
.map(|(name, checked)| {
let layout = name.clone();
container(
checkbox(*checked)
.label(name.clone())
.on_toggle(move |_| Message::PrintAllToggle(layout.clone()))
.size(16)
.text_size(12),
)
.padding([6, 8])
.width(Length::Fill)
.into()
})
.collect();
let list: Element<'a, Message> = if rows.is_empty() {
container(text(t!("No paper layouts are available.")).size(12).style(muted))
.center_x(Length::Fill)
.center_y(Length::Fill)
.into()
} else {
scrollable(column(rows).spacing(2)).height(sizing.height).into()
};
let printer = printer
.filter(|name| !name.is_empty())
.map(str::to_owned)
.unwrap_or_else(|| t!("Default printer").into_owned());
let summary = if selected == 1 {
t!("1 layout selected").into_owned()
} else {
format!("{selected} {}", t!("layouts selected"))
};
let mut pdf = button(text(t!("PDF")).size(11))
.style(button_style(false))
.padding([5, 16]);
let mut print = button(text(t!("Print")).size(11))
.style(button_style(true))
.padding([5, 18]);
if selected > 0 {
pdf = pdf.on_press(Message::PrintAllPdf);
print = print.on_press(Message::PrintAllPrint);
}
let list_panel = container(list)
.width(sizing.width)
.height(sizing.height)
.padding(4)
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(theme.palette().background.weak.color)),
border: Border {
color: theme.palette().background.neutral.color,
width: 1.0,
radius: 4.0.into(),
},
..Default::default()
});
container(
column![
text(t!("Select the layouts to output. Each page is printed in Layout mode."))
.size(12),
row![
button(text(t!("Select all")).size(11))
.on_press(Message::PrintAllSelectAll)
.style(button_style(false))
.padding([4, 10]),
button(text(t!("Select none")).size(11))
.on_press(Message::PrintAllSelectNone)
.style(button_style(false))
.padding([4, 10]),
Space::new().width(Length::Fill),
text(summary).size(11).style(muted),
]
.spacing(6)
.align_y(iced::Center),
list_panel,
row![
text(format!("{}: {printer}", t!("Printer")))
.size(11)
.style(muted),
Space::new().width(Length::Fill),
button(text(t!("Options…")).size(11))
.on_press(Message::PrintAllOptions)
.style(button_style(false))
.padding([5, 12]),
pdf,
print,
]
.spacing(7)
.align_y(iced::Center),
]
.spacing(10),
)
.padding(14)
.width(sizing.width)
.height(sizing.height)
.into()
}