use crate::*;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct FileFilter {
pub name: String,
pub extensions: Vec<String>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
pub enum FileDialogMode {
#[default]
OpenFile,
OpenDirectory,
SaveFile,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct FileDialog {
pub title: String,
pub default_path: Option<String>,
pub filters: Vec<FileFilter>,
pub mode: FileDialogMode,
pub allow_multiple: bool,
}
#[derive(Debug, thiserror::Error)]
pub enum FileDialogError {
#[error("File dialog cancelled")]
Cancelled,
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Platform error: {0}")]
Platform(String),
}
impl FileDialog {
pub fn new(mode: FileDialogMode) -> Self {
Self {
mode,
..Default::default()
}
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = title.into();
self
}
pub fn add_filter(mut self, name: &str, extensions: &[&str]) -> Self {
self.filters.push(FileFilter {
name: name.to_string(),
extensions: extensions.iter().map(|s| s.to_string()).collect(),
});
self
}
pub fn default_path(mut self, path: impl Into<String>) -> Self {
self.default_path = Some(path.into());
self
}
pub fn allow_multiple(mut self, allow: bool) -> Self {
self.allow_multiple = allow;
self
}
}
#[cfg(not(target_arch = "wasm32"))]
impl FileDialog {
pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
let mut dialog = rfd::FileDialog::new();
dialog = dialog.set_title(&self.title);
if let Some(path) = &self.default_path {
dialog = dialog.set_directory(path);
}
for filter in &self.filters {
let refs: Vec<&str> = filter.extensions.iter().map(|s| s.as_str()).collect();
dialog = dialog.add_filter(&filter.name, &refs);
}
match self.mode {
FileDialogMode::OpenFile => {
if self.allow_multiple {
dialog.pick_files().ok_or(FileDialogError::Cancelled)
} else {
Ok(dialog.pick_file().into_iter().collect())
}
}
FileDialogMode::OpenDirectory => Ok(dialog.pick_folder().into_iter().collect()),
FileDialogMode::SaveFile => Ok(dialog.save_file().into_iter().collect()),
}
}
pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
let results = self.pick()?;
Ok(results.into_iter().next())
}
}
#[cfg(target_arch = "wasm32")]
impl FileDialog {
pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
Err(FileDialogError::Platform(
"FileDialog is not supported synchronously on WebAssembly".to_string(),
))
}
pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
Err(FileDialogError::Platform(
"FileDialog is not supported synchronously on WebAssembly".to_string(),
))
}
}