use egui::vec2;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PrintDialogAction {
Print {
printer: String,
},
SaveToFile,
}
pub fn preselected_printer_index(count: usize, default_index: Option<usize>) -> usize {
default_index
.filter(|&i| i < count)
.unwrap_or(0)
.min(count.saturating_sub(1))
}
pub struct PrintDialog {
open: bool,
printer_names: Vec<String>,
selected: usize,
win: crate::widget::detached::DetachedWindow,
}
impl Default for PrintDialog {
fn default() -> Self {
Self::new()
}
}
impl PrintDialog {
pub fn new() -> Self {
Self {
open: false,
printer_names: Vec::new(),
selected: 0,
win: crate::widget::detached::DetachedWindow::new(
egui::Id::new("siplot_print_dialog"),
vec2(300.0, 110.0),
),
}
}
pub fn is_open(&self) -> bool {
self.open
}
pub fn open_with(&mut self, printer_names: Vec<String>, default_index: Option<usize>) {
self.selected = preselected_printer_index(printer_names.len(), default_index);
self.printer_names = printer_names;
self.open = true;
}
pub fn open_with_system_printers(&mut self) {
let printers = printers::get_printers();
let names = printers.iter().map(|p| p.system_name.clone()).collect();
let default = printers.iter().position(|p| p.is_default);
self.open_with(names, default);
}
pub fn show(&mut self, ctx: &egui::Context) -> Option<PrintDialogAction> {
if !self.open {
return None;
}
let mut action = None;
let pos = self.win.position(ctx);
let id = self.win.id();
let size = self.win.size();
let signals = crate::widget::detached::show_detached(ctx, id, "Print", size, pos, |ui| {
ui.horizontal(|ui| {
ui.label("Printer:");
if self.printer_names.is_empty() {
ui.label("No printers found");
} else {
let current = self
.printer_names
.get(self.selected)
.cloned()
.unwrap_or_default();
egui::ComboBox::from_id_salt("print_dialog_printer")
.selected_text(current)
.show_ui(ui, |ui| {
for (i, name) in self.printer_names.iter().enumerate() {
ui.selectable_value(&mut self.selected, i, name);
}
});
}
});
ui.separator();
ui.horizontal(|ui| {
let can_print = !self.printer_names.is_empty();
if ui
.add_enabled(can_print, egui::Button::new("Print"))
.clicked()
&& let Some(name) = self.printer_names.get(self.selected)
{
action = Some(PrintDialogAction::Print {
printer: name.clone(),
});
}
if ui
.button("Save to file…")
.on_hover_text("Save the figure as PNG / JPEG / TIFF / PDF / … instead")
.clicked()
{
action = Some(PrintDialogAction::SaveToFile);
}
if ui.button("Cancel").clicked() {
action = None;
self.open = false;
}
});
});
if action.is_some() {
self.open = false;
}
let mut open = self.open;
self.win.apply_signals(&signals, &mut open);
self.open = open;
action
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn preselected_index_uses_default_when_valid() {
assert_eq!(preselected_printer_index(3, Some(2)), 2);
}
#[test]
fn preselected_index_falls_back_to_first_when_default_missing_or_invalid() {
assert_eq!(preselected_printer_index(3, None), 0);
assert_eq!(preselected_printer_index(3, Some(7)), 0);
}
#[test]
fn preselected_index_empty_list_is_zero() {
assert_eq!(preselected_printer_index(0, None), 0);
assert_eq!(preselected_printer_index(0, Some(1)), 0);
}
#[test]
fn open_with_sets_state_and_clamps_selection() {
let mut dlg = PrintDialog::new();
assert!(!dlg.is_open());
dlg.open_with(vec!["a".into(), "b".into()], Some(1));
assert!(dlg.is_open());
assert_eq!(dlg.selected, 1);
dlg.open_with(vec!["a".into()], Some(5));
assert_eq!(dlg.selected, 0);
}
#[test]
fn show_closed_returns_none_without_rendering() {
let ctx = egui::Context::default();
let mut dlg = PrintDialog::new();
let _ = ctx.run_ui(egui::RawInput::default(), |ui| {
assert!(dlg.show(ui.ctx()).is_none());
});
}
#[test]
fn show_open_renders_without_panic_and_stays_open() {
let ctx = egui::Context::default();
let mut dlg = PrintDialog::new();
dlg.open_with(Vec::new(), None);
let _ = ctx.run_ui(egui::RawInput::default(), |ui| {
assert!(dlg.show(ui.ctx()).is_none());
});
assert!(dlg.is_open());
let mut dlg = PrintDialog::new();
dlg.open_with(vec!["Office".into(), "Lab".into()], Some(1));
let _ = ctx.run_ui(egui::RawInput::default(), |ui| {
assert!(dlg.show(ui.ctx()).is_none());
});
assert!(dlg.is_open());
}
}