1use std::path::PathBuf;
5
6use native_dialog::FileDialog;
7use thiserror::Error;
8
9#[derive(Error, Debug)]
11pub enum ImNativeDialogError {
12 #[error("The dialog is already open.")]
13 AlreadyOpen,
14}
15
16pub struct ImNativeFileDialog<T> {
22 callback: Option<Box<dyn FnOnce(&Result<T, native_dialog::Error>) + Send>>,
23 receiver: Option<crossbeam_channel::Receiver<Result<T, native_dialog::Error>>>,
24}
25
26impl<T> Default for ImNativeFileDialog<T> {
27 fn default() -> Self {
28 Self { callback: None, receiver: None }
29 }
30}
31
32impl ImNativeFileDialog<Vec<PathBuf>> {
33 pub fn show_open_multiple_file(
35 &mut self,
36 location: Option<PathBuf>,
37 ) -> Result<(), ImNativeDialogError> {
38 self.show(|sender, dialog, callback| {
39 let dialog = match &location {
40 Some(location) => dialog.set_location(location),
41 None => dialog,
42 };
43 let result = dialog.show_open_multiple_file();
44 callback(&result);
45 sender
46 .send(result)
47 .expect("error sending show_open_multiple_file result to ui");
48 drop(location)
49 })
50 }
51}
52
53impl ImNativeFileDialog<Option<PathBuf>> {
54 pub fn open_single_dir(
56 &mut self,
57 location: Option<PathBuf>,
58 ) -> Result<(), ImNativeDialogError> {
59 self.show(|sender, dialog, callback| {
60 let dialog = match &location {
61 Some(location) => dialog.set_location(location),
62 None => dialog,
63 };
64 let result = dialog.show_open_single_dir();
65 callback(&result);
66 sender
67 .send(result)
68 .expect("error sending open_single_dir result to ui");
69 drop(location)
70 })
71 }
72
73 pub fn open_single_file(
75 &mut self,
76 location: Option<PathBuf>,
77 ) -> Result<(), ImNativeDialogError> {
78 self.show(|sender, dialog, callback| {
79 let dialog = match &location {
80 Some(location) => dialog.set_location(location),
81 None => dialog,
82 };
83 let result = dialog.show_open_single_file();
84 callback(&result);
85 sender
86 .send(result)
87 .expect("error sending open_single_file result to ui");
88 drop(location)
89 })
90 }
91
92 pub fn show_save_single_file(
94 &mut self,
95 location: Option<PathBuf>,
96 ) -> Result<(), ImNativeDialogError> {
97 self.show(|sender, dialog, callback| {
98 let dialog = match &location {
99 Some(location) => dialog.set_location(location),
100 None => dialog,
101 };
102 let result = dialog.show_save_single_file();
103 callback(&result);
104 sender
105 .send(result)
106 .expect("error sending show_save_single_file result to ui");
107 drop(location)
108 })
109 }
110}
111
112impl<T: Send + 'static + Default> ImNativeFileDialog<T> {
113 pub fn with_callback<C>(&mut self, callback: C) -> &mut Self
116 where
117 C: FnOnce(&Result<T, native_dialog::Error>) + Send + 'static
118 {
119 self.callback = Some(Box::new(callback));
120 self
121 }
122
123 pub fn show<
127 F: FnOnce(crossbeam_channel::Sender<Result<T, native_dialog::Error>>, FileDialog, Box<dyn FnOnce(&Result<T, native_dialog::Error>)>)
128 + Send
129 + 'static,
130 >(
131 &mut self,
132 run: F,
133 ) -> Result<(), ImNativeDialogError> {
134 if self.receiver.is_some() {
135 return Err(ImNativeDialogError::AlreadyOpen);
136 }
137
138 let (sender, receiver) = crossbeam_channel::bounded(1);
139
140 let callback = self.callback.take().unwrap_or_else(|| Box::new(|_| {}));
141 std::thread::spawn(move || {
142 let dialog = FileDialog::new();
143 run(sender, dialog, callback)
144 });
145
146 self.receiver = Some(receiver);
147
148 Ok(())
149 }
150
151 pub fn check(&mut self) -> Option<Result<T, native_dialog::Error>> {
156 match self.receiver.take() {
157 Some(receiver) => match receiver.try_recv() {
158 Ok(result) => Some(result),
159 Err(crossbeam_channel::TryRecvError::Disconnected) => {
160 log::warn!("OpenDialog channel disconnected");
161 Some(Ok(T::default()))
162 }
163 Err(crossbeam_channel::TryRecvError::Empty) => {
164 self.receiver = Some(receiver);
165 None
166 }
167 },
168 None => None,
169 }
170 }
171
172 pub fn is_open(&self) -> bool {
176 self.receiver.is_some()
177 }
178}