pub mod dialog;
mod types;
#[cfg(feature = "gui-win32")]
mod win32;
#[cfg(all(feature = "gui-gtk", not(feature = "gui-win32")))]
mod gtk;
pub use dialog::{choose_language, confirm, error, info, warn};
#[doc(hidden)]
pub fn __set_window_icon_png(bytes: &'static [u8]) {
#[cfg(all(feature = "gui-gtk", not(feature = "gui-win32")))]
{
gtk::set_icon_bytes(bytes);
}
#[cfg(not(all(feature = "gui-gtk", not(feature = "gui-win32"))))]
{
let _ = bytes;
}
}
pub use types::{
ButtonLabels, ConfiguredPage, CustomPageBuilder, CustomWidget, GuiMessage, InstallCallback,
OnBeforeLeaveCallback, OnEnterCallback, WizardConfig, WizardPage,
};
use anyhow::Result;
use crate::Installer;
pub struct InstallerGui {
config: WizardConfig,
}
impl InstallerGui {
pub fn wizard(title: impl AsRef<str>) -> Self {
Self {
config: WizardConfig {
title: title.as_ref().to_string(),
pages: Vec::new(),
buttons: ButtonLabels::default(),
},
}
}
pub fn buttons(&mut self, labels: ButtonLabels) {
self.config.buttons = labels;
}
pub fn welcome(&mut self, title: impl AsRef<str>, message: impl AsRef<str>) -> PageHandle<'_> {
self.push_page(WizardPage::Welcome {
title: title.as_ref().to_string(),
message: message.as_ref().to_string(),
widgets: Vec::new(),
})
}
pub fn license(
&mut self,
heading: impl AsRef<str>,
text: impl AsRef<str>,
accept_label: impl AsRef<str>,
) -> PageHandle<'_> {
self.push_page(WizardPage::License {
heading: heading.as_ref().to_string(),
text: text.as_ref().to_string(),
accept_label: accept_label.as_ref().to_string(),
})
}
pub fn components_page(
&mut self,
heading: impl AsRef<str>,
label: impl AsRef<str>,
) -> PageHandle<'_> {
self.push_page(WizardPage::Components {
heading: heading.as_ref().to_string(),
label: label.as_ref().to_string(),
})
}
pub fn directory_picker(
&mut self,
heading: impl AsRef<str>,
label: impl AsRef<str>,
key: impl AsRef<str>,
) -> PageHandle<'_> {
self.push_page(WizardPage::DirectoryPicker {
heading: heading.as_ref().to_string(),
label: label.as_ref().to_string(),
key: key.as_ref().to_string(),
})
}
pub fn install_page(
&mut self,
callback: impl FnOnce(&mut Installer) -> Result<()> + Send + 'static,
) -> PageHandle<'_> {
self.push_page(WizardPage::Install {
callback: Box::new(callback),
is_uninstall: false,
show_log: true,
})
}
pub fn uninstall_page(
&mut self,
callback: impl FnOnce(&mut Installer) -> Result<()> + Send + 'static,
) -> PageHandle<'_> {
self.push_page(WizardPage::Install {
callback: Box::new(callback),
is_uninstall: true,
show_log: true,
})
}
pub fn finish_page(
&mut self,
title: impl AsRef<str>,
message: impl AsRef<str>,
) -> PageHandle<'_> {
self.push_page(WizardPage::Finish {
title: title.as_ref().to_string(),
message: message.as_ref().to_string(),
widgets: Vec::new(),
})
}
pub fn custom_page(
&mut self,
heading: impl AsRef<str>,
label: impl AsRef<str>,
build: impl FnOnce(&mut CustomPageBuilder),
) -> PageHandle<'_> {
let mut b = CustomPageBuilder::new();
build(&mut b);
self.push_page(WizardPage::Custom {
heading: heading.as_ref().to_string(),
label: label.as_ref().to_string(),
widgets: b.widgets,
})
}
pub fn error_page(
&mut self,
title: impl AsRef<str>,
message: impl AsRef<str>,
) -> PageHandle<'_> {
self.push_page(WizardPage::Error {
title: title.as_ref().to_string(),
message: message.as_ref().to_string(),
})
}
fn push_page(&mut self, page: WizardPage) -> PageHandle<'_> {
self.config.pages.push(ConfiguredPage::new(page));
PageHandle {
page: self.config.pages.last_mut().unwrap(),
}
}
pub fn run(self, installer: &mut Installer) -> Result<()> {
for configured in &self.config.pages {
if let WizardPage::DirectoryPicker { key, .. } = &configured.page {
if !installer.is_option_registered(key) {
installer.option(key.clone(), crate::OptionKind::String, "");
}
}
}
if installer.headless {
self.run_headless(installer)
} else {
self.run_platform(installer)
}
}
fn run_headless(self, installer: &mut Installer) -> Result<()> {
let mut install_callback: Option<InstallCallback> = None;
for configured in self.config.pages {
if let WizardPage::Install { callback, .. } = configured.page {
install_callback = Some(callback);
}
}
if !installer.has_progress_sink() {
installer.set_progress_sink(Box::new(crate::StderrProgressSink::new()));
}
installer.reset_progress();
let result = if let Some(cb) = install_callback {
cb(installer)
} else {
Ok(())
};
if let Err(ref e) = result {
installer.log_error(e);
}
installer.clear_progress_sink();
result
}
#[cfg(feature = "gui-win32")]
fn run_platform(self, installer: &mut Installer) -> Result<()> {
win32::run_wizard(self.config, installer)
}
#[cfg(all(feature = "gui-gtk", not(feature = "gui-win32")))]
fn run_platform(self, installer: &mut Installer) -> Result<()> {
gtk::run_wizard(self.config, installer)
}
#[cfg(all(feature = "gui", not(any(feature = "gui-win32", feature = "gui-gtk"))))]
fn run_platform(self, _installer: &mut Installer) -> Result<()> {
Err(anyhow::anyhow!(
"No GUI backend available for this platform. Enable `gui-win32` on Windows or `gui-gtk` on Linux."
))
}
}
pub struct PageHandle<'a> {
page: &'a mut ConfiguredPage,
}
impl<'a> PageHandle<'a> {
pub fn on_enter<F>(self, f: F) -> Self
where
F: Fn(&mut Installer) -> Result<()> + 'static,
{
self.page.on_enter = Some(Box::new(f));
self
}
pub fn on_before_leave<F>(self, f: F) -> Self
where
F: Fn(&mut Installer) -> Result<bool> + 'static,
{
self.page.on_before_leave = Some(Box::new(f));
self
}
pub fn skip_if<F>(self, f: F) -> Self
where
F: Fn(&Installer) -> bool + 'static,
{
self.page.skip_if = Some(Box::new(f));
self
}
pub fn hide_log(self) -> Self {
match &mut self.page.page {
WizardPage::Install { show_log, .. } => {
*show_log = false;
}
_ => panic!("hide_log is only supported on install / uninstall pages"),
}
self
}
pub fn with_widgets(self, build: impl FnOnce(&mut CustomPageBuilder)) -> Self {
let mut b = CustomPageBuilder::new();
build(&mut b);
match &mut self.page.page {
WizardPage::Welcome { widgets, .. } | WizardPage::Finish { widgets, .. } => {
widgets.extend(b.widgets);
}
_ => panic!(
"with_widgets is only supported on welcome and finish pages; \
use custom_page for other widget layouts"
),
}
self
}
}