use std::marker::PhantomData;
use std::path::PathBuf;
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_tasks::prelude::*;
use bevy_winit::{EventLoopProxy, EventLoopProxyWrapper};
use crossbeam_channel::bounded;
use rfd::AsyncFileDialog;
use crate::{
handle_dialog_result, DialogResult, FileDialog, FileDialogPlugin, StreamReceiver, StreamSender,
WakeUpOnDrop,
};
#[derive(Message)]
pub struct DialogDirectoryPicked<T: PickDirectoryPath> {
pub path: PathBuf,
marker: PhantomData<T>,
}
#[derive(Message)]
pub struct DialogDirectoryPickCanceled<T: PickDirectoryPath>(PhantomData<T>);
impl<T: PickDirectoryPath> Default for DialogDirectoryPickCanceled<T> {
fn default() -> Self {
Self(Default::default())
}
}
pub trait PickDirectoryPath: Send + Sync + 'static {}
impl<T> PickDirectoryPath for T where T: Send + Sync + 'static {}
#[derive(Message)]
pub struct DialogFilePicked<T: PickFilePath> {
pub path: PathBuf,
marker: PhantomData<T>,
}
#[derive(Message)]
pub struct DialogFilePickCanceled<T: PickFilePath>(PhantomData<T>);
impl<T: PickFilePath> Default for DialogFilePickCanceled<T> {
fn default() -> Self {
Self(Default::default())
}
}
pub trait PickFilePath: Send + Sync + 'static {}
impl<T> PickFilePath for T where T: Send + Sync + 'static {}
impl FileDialogPlugin {
pub fn with_pick_directory<T: PickDirectoryPath>(mut self) -> Self {
self.0.push(Box::new(|app| {
let (tx, rx) = bounded::<DialogResult<DialogDirectoryPicked<T>>>(1);
app.insert_resource(StreamSender(tx));
app.insert_resource(StreamReceiver(rx));
app.add_message::<DialogDirectoryPicked<T>>();
app.add_message::<DialogDirectoryPickCanceled<T>>();
app.add_systems(
First,
handle_dialog_result::<DialogDirectoryPicked<T>, DialogDirectoryPickCanceled<T>>,
);
}));
self
}
pub fn with_pick_file<T: PickFilePath>(mut self) -> Self {
self.0.push(Box::new(|app| {
let (tx, rx) = bounded::<DialogResult<DialogFilePicked<T>>>(1);
app.insert_resource(StreamSender(tx));
app.insert_resource(StreamReceiver(rx));
app.add_message::<DialogFilePicked<T>>();
app.add_message::<DialogFilePickCanceled<T>>();
app.add_systems(
First,
handle_dialog_result::<DialogFilePicked<T>, DialogFilePickCanceled<T>>,
);
}));
self
}
}
impl FileDialog<'_, '_, '_> {
pub fn pick_directory_path<T: PickDirectoryPath>(self) {
self.commands.queue(|world: &mut World| {
let sender = world
.get_resource::<StreamSender<DialogResult<DialogDirectoryPicked<T>>>>()
.expect("FileDialogPlugin not initialized with 'with_pick_directory::<T>()'")
.0
.clone();
let event_loop_proxy = world
.get_resource::<EventLoopProxyWrapper>()
.map(|proxy| EventLoopProxy::clone(&**proxy));
AsyncComputeTaskPool::get()
.spawn(async move {
let file = self.dialog.pick_folder().await;
let _wake_up = event_loop_proxy.as_ref().map(WakeUpOnDrop);
let Some(file) = file else {
sender.send(DialogResult::Canceled).unwrap();
return;
};
let event = DialogDirectoryPicked {
path: file.path().to_path_buf(),
marker: PhantomData,
};
sender.send(DialogResult::Single(event)).unwrap();
})
.detach();
});
}
pub fn pick_multiple_directory_paths<T: PickDirectoryPath>(self) {
self.commands.queue(|world: &mut World| {
let sender = world
.get_resource::<StreamSender<DialogResult<DialogDirectoryPicked<T>>>>()
.expect("FileDialogPlugin not initialized with 'with_pick_directory::<T>()'")
.0
.clone();
let event_loop_proxy = world
.get_resource::<EventLoopProxyWrapper>()
.map(|proxy| EventLoopProxy::clone(&**proxy));
AsyncComputeTaskPool::get()
.spawn(async move {
let files = AsyncFileDialog::new().pick_folders().await;
let _wake_up = event_loop_proxy.as_ref().map(WakeUpOnDrop);
let Some(files) = files else {
sender.send(DialogResult::Canceled).unwrap();
return;
};
let events = files
.into_iter()
.map(|file| DialogDirectoryPicked {
path: file.path().to_path_buf(),
marker: PhantomData,
})
.collect();
sender.send(DialogResult::Batch(events)).unwrap();
})
.detach();
});
}
pub fn pick_file_path<T: PickFilePath>(self) {
self.commands.queue(|world: &mut World| {
let sender = world
.get_resource::<StreamSender<DialogResult<DialogFilePicked<T>>>>()
.expect("FileDialogPlugin not initialized with 'with_pick_file::<T>()'")
.0
.clone();
let event_loop_proxy = world
.get_resource::<EventLoopProxyWrapper>()
.map(|proxy| EventLoopProxy::clone(&**proxy));
AsyncComputeTaskPool::get()
.spawn(async move {
let file = self.dialog.pick_file().await;
let _wake_up = event_loop_proxy.as_ref().map(WakeUpOnDrop);
let Some(file) = file else {
sender.send(DialogResult::Canceled).unwrap();
return;
};
let event = DialogFilePicked {
path: file.path().to_path_buf(),
marker: PhantomData,
};
sender.send(DialogResult::Single(event)).unwrap();
})
.detach();
});
}
pub fn pick_multiple_file_paths<T: PickDirectoryPath>(self) {
self.commands.queue(|world: &mut World| {
let sender = world
.get_resource::<StreamSender<DialogResult<DialogFilePicked<T>>>>()
.expect("FileDialogPlugin not initialized with 'with_pick_file::<T>()'")
.0
.clone();
let event_loop_proxy = world
.get_resource::<EventLoopProxyWrapper>()
.map(|proxy| EventLoopProxy::clone(&**proxy));
AsyncComputeTaskPool::get()
.spawn(async move {
let files = AsyncFileDialog::new().pick_files().await;
let _wake_up = event_loop_proxy.as_ref().map(WakeUpOnDrop);
let Some(files) = files else {
sender.send(DialogResult::Canceled).unwrap();
return;
};
let events = files
.into_iter()
.map(|file| DialogFilePicked {
path: file.path().to_path_buf(),
marker: PhantomData,
})
.collect();
sender.send(DialogResult::Batch(events)).unwrap();
})
.detach();
});
}
}