use std::path::PathBuf;
use native_dialog::FileDialog;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ImNativeDialogError {
#[error("The dialog is already open.")]
AlreadyOpen,
}
pub struct ImNativeFileDialog<T> {
callback: Option<Box<dyn FnOnce(&Result<T, native_dialog::Error>) + Send>>,
receiver: Option<crossbeam_channel::Receiver<Result<T, native_dialog::Error>>>,
}
impl<T> Default for ImNativeFileDialog<T> {
fn default() -> Self {
Self { callback: None, receiver: None }
}
}
impl ImNativeFileDialog<Vec<PathBuf>> {
pub fn show_open_multiple_file(
&mut self,
location: Option<PathBuf>,
) -> Result<(), ImNativeDialogError> {
self.show(|sender, dialog, callback| {
let dialog = match &location {
Some(location) => dialog.set_location(location),
None => dialog,
};
let result = dialog.show_open_multiple_file();
callback(&result);
sender
.send(result)
.expect("error sending show_open_multiple_file result to ui");
drop(location)
})
}
}
impl ImNativeFileDialog<Option<PathBuf>> {
pub fn open_single_dir(
&mut self,
location: Option<PathBuf>,
) -> Result<(), ImNativeDialogError> {
self.show(|sender, dialog, callback| {
let dialog = match &location {
Some(location) => dialog.set_location(location),
None => dialog,
};
let result = dialog.show_open_single_dir();
callback(&result);
sender
.send(result)
.expect("error sending open_single_dir result to ui");
drop(location)
})
}
pub fn open_single_file(
&mut self,
location: Option<PathBuf>,
) -> Result<(), ImNativeDialogError> {
self.show(|sender, dialog, callback| {
let dialog = match &location {
Some(location) => dialog.set_location(location),
None => dialog,
};
let result = dialog.show_open_single_file();
callback(&result);
sender
.send(result)
.expect("error sending open_single_file result to ui");
drop(location)
})
}
pub fn show_save_single_file(
&mut self,
location: Option<PathBuf>,
) -> Result<(), ImNativeDialogError> {
self.show(|sender, dialog, callback| {
let dialog = match &location {
Some(location) => dialog.set_location(location),
None => dialog,
};
let result = dialog.show_save_single_file();
callback(&result);
sender
.send(result)
.expect("error sending show_save_single_file result to ui");
drop(location)
})
}
}
impl<T: Send + 'static + Default> ImNativeFileDialog<T> {
pub fn with_callback<C>(&mut self, callback: C) -> &mut Self
where
C: FnOnce(&Result<T, native_dialog::Error>) + Send + 'static
{
self.callback = Some(Box::new(callback));
self
}
pub fn show<
F: FnOnce(crossbeam_channel::Sender<Result<T, native_dialog::Error>>, FileDialog, Box<dyn FnOnce(&Result<T, native_dialog::Error>)>)
+ Send
+ 'static,
>(
&mut self,
run: F,
) -> Result<(), ImNativeDialogError> {
if self.receiver.is_some() {
return Err(ImNativeDialogError::AlreadyOpen);
}
let (sender, receiver) = crossbeam_channel::bounded(1);
let callback = self.callback.take().unwrap_or_else(|| Box::new(|_| {}));
std::thread::spawn(move || {
let dialog = FileDialog::new();
run(sender, dialog, callback)
});
self.receiver = Some(receiver);
Ok(())
}
pub fn check(&mut self) -> Option<Result<T, native_dialog::Error>> {
match self.receiver.take() {
Some(receiver) => match receiver.try_recv() {
Ok(result) => Some(result),
Err(crossbeam_channel::TryRecvError::Disconnected) => {
log::warn!("OpenDialog channel disconnected");
Some(Ok(T::default()))
}
Err(crossbeam_channel::TryRecvError::Empty) => {
self.receiver = Some(receiver);
None
}
},
None => None,
}
}
pub fn is_open(&self) -> bool {
self.receiver.is_some()
}
}