use anyhow::Result;
use std::sync::{Arc, Mutex};
use crate::Installer;
pub type InstallCallback = Box<dyn FnOnce(&mut GuiContext) -> Result<()> + Send + 'static>;
pub type OnEnterCallback = Box<dyn Fn(&mut PageContext) -> Result<()> + 'static>;
pub type OnBeforeLeaveCallback = Box<dyn Fn(&mut PageContext) -> Result<bool> + 'static>;
pub type SkipIfCallback = Box<dyn Fn(&PageContext) -> bool + 'static>;
pub type StartCallback = Box<dyn FnOnce(&mut crate::Installer) -> Result<()> + 'static>;
pub type ExitCallback = Box<dyn FnOnce(&mut crate::Installer) -> Result<()> + 'static>;
pub struct WizardConfig {
pub title: String,
pub pages: Vec<ConfiguredPage>,
pub buttons: ButtonLabels,
pub on_start: Option<StartCallback>,
pub on_exit: Option<ExitCallback>,
}
pub struct ConfiguredPage {
pub page: WizardPage,
pub on_enter: Option<OnEnterCallback>,
pub on_before_leave: Option<OnBeforeLeaveCallback>,
pub skip_if: Option<SkipIfCallback>,
}
impl ConfiguredPage {
pub fn new(page: WizardPage) -> Self {
Self {
page,
on_enter: None,
on_before_leave: None,
skip_if: None,
}
}
}
pub struct ButtonLabels {
pub back: String,
pub next: String,
pub install: String,
pub uninstall: String,
pub finish: String,
pub cancel: String,
}
impl Default for ButtonLabels {
fn default() -> Self {
Self {
back: "< Back".into(),
next: "Next >".into(),
install: "Install".into(),
uninstall: "Uninstall".into(),
finish: "Finish".into(),
cancel: "Cancel".into(),
}
}
}
pub enum WizardPage {
Welcome {
title: String,
message: String,
},
License {
heading: String,
text: String,
accept_label: String,
},
Components {
heading: String,
label: String,
},
DirectoryPicker {
heading: String,
label: String,
default: String,
},
Install {
callback: InstallCallback,
is_uninstall: bool,
},
Finish {
title: String,
message: String,
},
Error {
title: String,
message: String,
},
Custom {
heading: String,
label: String,
widgets: Vec<CustomWidget>,
},
}
#[derive(Clone, Debug)]
pub enum CustomWidget {
Text {
key: String,
label: String,
default: String,
password: bool,
},
Multiline {
key: String,
label: String,
default: String,
rows: u32,
},
Number {
key: String,
label: String,
default: i64,
},
Checkbox {
key: String,
label: String,
default: bool,
},
Dropdown {
key: String,
label: String,
choices: Vec<(String, String)>,
default: String,
},
Radio {
key: String,
label: String,
choices: Vec<(String, String)>,
default: String,
},
FilePicker {
key: String,
label: String,
default: String,
filters: Vec<(String, String)>,
},
DirPicker {
key: String,
label: String,
default: String,
},
}
pub struct CustomPageBuilder {
pub(crate) widgets: Vec<CustomWidget>,
}
impl CustomPageBuilder {
pub(crate) fn new() -> Self {
Self {
widgets: Vec::new(),
}
}
pub fn text(&mut self, key: &str, label: &str, default: &str) -> &mut Self {
self.widgets.push(CustomWidget::Text {
key: key.into(),
label: label.into(),
default: default.into(),
password: false,
});
self
}
pub fn password(&mut self, key: &str, label: &str) -> &mut Self {
self.widgets.push(CustomWidget::Text {
key: key.into(),
label: label.into(),
default: String::new(),
password: true,
});
self
}
pub fn checkbox(&mut self, key: &str, label: &str, default: bool) -> &mut Self {
self.widgets.push(CustomWidget::Checkbox {
key: key.into(),
label: label.into(),
default,
});
self
}
pub fn dropdown(
&mut self,
key: &str,
label: &str,
choices: &[(&str, &str)],
default: &str,
) -> &mut Self {
self.widgets.push(CustomWidget::Dropdown {
key: key.into(),
label: label.into(),
choices: choices
.iter()
.map(|(v, d)| ((*v).into(), (*d).into()))
.collect(),
default: default.into(),
});
self
}
pub fn radio(
&mut self,
key: &str,
label: &str,
choices: &[(&str, &str)],
default: &str,
) -> &mut Self {
self.widgets.push(CustomWidget::Radio {
key: key.into(),
label: label.into(),
choices: choices
.iter()
.map(|(v, d)| ((*v).into(), (*d).into()))
.collect(),
default: default.into(),
});
self
}
pub fn number(&mut self, key: &str, label: &str, default: i64) -> &mut Self {
self.widgets.push(CustomWidget::Number {
key: key.into(),
label: label.into(),
default,
});
self
}
pub fn multiline(&mut self, key: &str, label: &str, default: &str, rows: u32) -> &mut Self {
self.widgets.push(CustomWidget::Multiline {
key: key.into(),
label: label.into(),
default: default.into(),
rows,
});
self
}
pub fn file_picker(
&mut self,
key: &str,
label: &str,
default: &str,
filters: &[(&str, &str)],
) -> &mut Self {
self.widgets.push(CustomWidget::FilePicker {
key: key.into(),
label: label.into(),
default: default.into(),
filters: filters
.iter()
.map(|(d, p)| ((*d).into(), (*p).into()))
.collect(),
});
self
}
pub fn dir_picker(&mut self, key: &str, label: &str, default: &str) -> &mut Self {
self.widgets.push(CustomWidget::DirPicker {
key: key.into(),
label: label.into(),
default: default.into(),
});
self
}
}
pub enum GuiMessage {
SetStatus(String),
SetProgress(f64),
Log(String),
Finished(Result<()>),
}
struct ChannelSink {
tx: std::sync::mpsc::Sender<GuiMessage>,
}
impl crate::ProgressSink for ChannelSink {
fn set_status(&self, status: &str) {
let _ = self.tx.send(GuiMessage::SetStatus(status.to_string()));
}
fn set_progress(&self, fraction: f64) {
let _ = self.tx.send(GuiMessage::SetProgress(fraction));
}
fn log(&self, message: &str) {
let _ = self.tx.send(GuiMessage::Log(message.to_string()));
}
}
pub struct GuiContext {
tx: std::sync::mpsc::Sender<GuiMessage>,
installer: Arc<Mutex<Installer>>,
install_dir: Arc<Mutex<String>>,
cancelled: Arc<std::sync::atomic::AtomicBool>,
}
impl GuiContext {
pub fn new(
tx: std::sync::mpsc::Sender<GuiMessage>,
installer: Arc<Mutex<Installer>>,
install_dir: Arc<Mutex<String>>,
cancelled: Arc<std::sync::atomic::AtomicBool>,
) -> Self {
Self {
tx,
installer,
install_dir,
cancelled,
}
}
pub fn set_status(&self, status: &str) {
let _ = self.tx.send(GuiMessage::SetStatus(status.to_string()));
}
pub fn set_progress(&self, progress: f64) {
let _ = self.tx.send(GuiMessage::SetProgress(progress));
}
pub fn log(&self, message: &str) {
let _ = self.tx.send(GuiMessage::Log(message.to_string()));
}
pub fn install_dir(&self) -> String {
self.install_dir.lock().unwrap().clone()
}
pub fn installer(&self) -> std::sync::MutexGuard<'_, Installer> {
self.installer.lock().unwrap()
}
pub fn progress_sink(&self) -> Box<dyn crate::ProgressSink> {
Box::new(ChannelSink {
tx: self.tx.clone(),
})
}
pub fn is_cancelled(&self) -> bool {
self.cancelled.load(std::sync::atomic::Ordering::Relaxed)
}
}
pub struct PageContext {
installer: Arc<Mutex<Installer>>,
install_dir: Arc<Mutex<String>>,
cancelled: Arc<std::sync::atomic::AtomicBool>,
}
impl PageContext {
pub fn new(
installer: Arc<Mutex<Installer>>,
install_dir: Arc<Mutex<String>>,
cancelled: Arc<std::sync::atomic::AtomicBool>,
) -> Self {
Self {
installer,
install_dir,
cancelled,
}
}
pub fn installer(&self) -> std::sync::MutexGuard<'_, Installer> {
self.installer.lock().unwrap()
}
pub fn install_dir(&self) -> String {
self.install_dir.lock().unwrap().clone()
}
pub fn set_install_dir(&self, dir: &str) {
*self.install_dir.lock().unwrap() = dir.to_string();
}
pub fn is_cancelled(&self) -> bool {
self.cancelled.load(std::sync::atomic::Ordering::Relaxed)
}
}