use std::sync::mpsc::{Receiver, TryRecvError};
use std::time::Duration;
use crate::wake::{self, Reason};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Printer {
pub name: String,
pub default: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Duplex {
Off,
LongEdge,
ShortEdge,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Settings {
pub printer: String,
pub copies: u32,
pub duplex: Option<Duplex>,
pub grayscale: bool,
}
impl Default for Settings {
fn default() -> Self {
Self { printer: String::new(), copies: 1, duplex: None, grayscale: false }
}
}
pub type Discover = Box<dyn Fn() -> Receiver<Vec<Printer>>>;
pub type Submission = Receiver<Result<String, String>>;
pub type Submit = Box<dyn Fn(Vec<u8>, Settings) -> Submission>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Feedback {
FindingPrinters,
NoPrinters,
DiscoveryFailed,
Submitting,
Submitted(String),
Failed(String),
WorkerStopped,
}
pub struct Labels<'a> {
pub printer: &'a str,
pub select_printer: &'a str,
pub refresh: &'a str,
pub copies: &'a str,
pub printer_default: &'a str,
pub duplex: &'a str,
pub one_sided: &'a str,
pub long_edge: &'a str,
pub short_edge: &'a str,
pub grayscale: &'a str,
}
#[derive(Default)]
pub struct State {
pub settings: Settings,
pub printers: Vec<Printer>,
pub message: Option<Feedback>,
discovery: Option<Receiver<Vec<Printer>>>,
submission: Option<Submission>,
loaded: bool,
}
const POLL_INTERVAL: Duration = Duration::from_millis(100);
impl State {
pub fn busy(&self) -> bool {
self.submission.is_some()
}
pub fn discovering(&self) -> bool {
self.discovery.is_some()
}
pub fn ready(&self) -> bool {
!self.busy() && !self.discovering() && self.printers.iter().any(|p| p.name == self.settings.printer)
}
pub fn begin(&mut self, receiver: Submission) -> Result<(), Submission> {
if self.busy() {
return Err(receiver);
}
self.submission = Some(receiver);
self.message = Some(Feedback::Submitting);
Ok(())
}
pub fn refresh(&mut self, discover: &Discover) -> bool {
if self.discovering() || self.busy() {
return false;
}
self.discovery = Some(discover());
self.loaded = true;
self.message = Some(Feedback::FindingPrinters);
true
}
pub fn poll(&mut self, ctx: &egui::Context) {
if let Some(receiver) = &self.discovery {
match receiver.try_recv() {
Ok(list) => {
self.discovery = None;
self.take_printers(list);
}
Err(TryRecvError::Empty) => {}
Err(TryRecvError::Disconnected) => {
self.discovery = None;
self.printers.clear();
self.message = Some(Feedback::DiscoveryFailed);
}
}
}
if let Some(receiver) = &self.submission {
let outcome = match receiver.try_recv() {
Ok(Ok(receipt)) => Some(Feedback::Submitted(receipt)),
Ok(Err(error)) => Some(Feedback::Failed(error)),
Err(TryRecvError::Empty) => None,
Err(TryRecvError::Disconnected) => Some(Feedback::WorkerStopped),
};
if let Some(outcome) = outcome {
self.submission = None;
self.message = Some(outcome);
}
}
if self.discovering() || self.busy() {
wake::wake_after(ctx, Reason::JOB, POLL_INTERVAL);
}
}
fn take_printers(&mut self, list: Vec<Printer>) {
self.printers = list;
if !self.printers.iter().any(|p| p.name == self.settings.printer) {
let pick = self.printers.iter().find(|p| p.default).or_else(|| self.printers.first());
self.settings.printer = pick.map(|p| p.name.clone()).unwrap_or_default();
}
self.message = self.printers.is_empty().then_some(Feedback::NoPrinters);
}
pub fn show(&mut self, ui: &mut egui::Ui, id: egui::Id, discover: &Discover, labels: &Labels<'_>) {
if !self.loaded {
self.refresh(discover);
}
self.poll(ui.ctx());
let busy = self.busy();
ui.push_id(id, |ui| {
ui.add_enabled_ui(!busy, |ui| {
let width = ui.available_width().clamp(32.0, 230.0);
let printer_label = ui.label(labels.printer);
let current = if self.settings.printer.is_empty() { labels.select_printer.to_string() } else { self.settings.printer.clone() };
let printers = &self.printers;
let chosen = &mut self.settings.printer;
let combo = egui::ComboBox::from_id_salt("printer").width(width).truncate().selected_text(current).show_ui(ui, |ui| {
for p in printers {
ui.selectable_value(chosen, p.name.clone(), p.name.as_str());
}
});
combo.response.labelled_by(printer_label.id);
let discovering = self.discovering();
if ui.add_enabled(!discovering, egui::Button::new(labels.refresh)).clicked() {
self.refresh(discover);
}
ui.horizontal(|ui| {
let copies_label = ui.label(labels.copies);
ui.add(egui::DragValue::new(&mut self.settings.copies).range(1..=999)).labelled_by(copies_label.id);
});
let duplex_label = ui.label(labels.duplex);
let choices = [
(None, labels.printer_default),
(Some(Duplex::Off), labels.one_sided),
(Some(Duplex::LongEdge), labels.long_edge),
(Some(Duplex::ShortEdge), labels.short_edge),
];
let shown = choices.iter().find(|(value, _)| *value == self.settings.duplex).map_or(labels.printer_default, |(_, text)| text);
let duplex = &mut self.settings.duplex;
let combo = egui::ComboBox::from_id_salt("duplex").width(width).truncate().selected_text(shown).show_ui(ui, |ui| {
for (value, text) in choices {
ui.selectable_value(duplex, value, text);
}
});
combo.response.labelled_by(duplex_label.id);
ui.checkbox(&mut self.settings.grayscale, labels.grayscale);
});
});
}
}
#[cfg(test)]
mod tests;