cosmic/dialog/file_chooser/
open.rs1#[cfg(xdg_portal)]
10pub use portal::{FileResponse, MultiFileResponse, file, files, folder, folders};
11
12#[cfg(feature = "rfd")]
13pub use rust_fd::{FileResponse, MultiFileResponse, file, files, folder, folders};
14
15use super::Error;
16use std::path::PathBuf;
17
18#[derive(derive_setters::Setters)]
20#[must_use]
21pub struct Dialog {
22 #[setters(into)]
24 title: String,
25
26 #[cfg(xdg_portal)]
28 #[setters(skip)]
29 accept_label: Option<String>,
30
31 #[setters(into, strip_option)]
33 #[allow(dead_code)] directory: Option<PathBuf>,
35
36 #[setters(into, strip_option)]
38 #[allow(dead_code)] file_name: Option<String>,
40
41 #[cfg(xdg_portal)]
43 #[setters(skip)]
44 modal: bool,
45
46 #[cfg(xdg_portal)]
48 #[setters(skip)]
49 choices: Vec<super::Choice>,
50
51 #[cfg(xdg_portal)]
53 #[setters(skip)]
54 current_filter: Option<super::FileFilter>,
55
56 #[setters(skip)]
58 pub(self) filters: Vec<super::FileFilter>,
59}
60
61impl Dialog {
62 pub const fn new() -> Self {
63 Self {
64 title: String::new(),
65 #[cfg(xdg_portal)]
66 accept_label: None,
67 directory: None,
68 file_name: None,
69 #[cfg(xdg_portal)]
70 modal: true,
71 #[cfg(xdg_portal)]
72 current_filter: None,
73 #[cfg(xdg_portal)]
74 choices: Vec::new(),
75 filters: Vec::new(),
76 }
77 }
78
79 #[cfg(xdg_portal)]
81 pub fn accept_label(mut self, label: impl Into<String>) -> Self {
82 self.accept_label = Some(label.into());
83 self
84 }
85
86 #[cfg(xdg_portal)]
88 pub fn choice(mut self, choice: impl Into<super::Choice>) -> Self {
89 self.choices.push(choice.into());
90 self
91 }
92
93 #[cfg(xdg_portal)]
95 pub fn current_filter(mut self, filter: impl Into<super::FileFilter>) -> Self {
96 self.current_filter = Some(filter.into());
97 self
98 }
99
100 pub fn filter(mut self, filter: impl Into<super::FileFilter>) -> Self {
102 self.filters.push(filter.into());
103 self
104 }
105
106 #[cfg(xdg_portal)]
108 pub fn modal(mut self, modal: bool) -> Self {
109 self.modal = modal;
110 self
111 }
112
113 pub async fn open_file(self) -> Result<FileResponse, Error> {
115 file(self).await
116 }
117
118 pub async fn open_files(self) -> Result<MultiFileResponse, Error> {
120 files(self).await
121 }
122
123 pub async fn open_folder(self) -> Result<FileResponse, Error> {
125 folder(self).await
126 }
127
128 pub async fn open_folders(self) -> Result<MultiFileResponse, Error> {
130 folders(self).await
131 }
132}
133
134#[cfg(xdg_portal)]
135mod portal {
136 use super::Dialog;
137 use crate::dialog::file_chooser::{Error, parse_uri};
138 use ashpd::desktop::file_chooser::SelectedFiles;
139 use url::Url;
140
141 fn error_or_cancel(error: ashpd::Error) -> Error {
142 if let ashpd::Error::Response(ashpd::desktop::ResponseError::Cancelled) = error {
143 Error::Cancelled
144 } else {
145 Error::Open(error)
146 }
147 }
148
149 #[cfg(xdg_portal)]
151 pub async fn create(
152 dialog: super::Dialog,
153 folders: bool,
154 multiple: bool,
155 ) -> Result<ashpd::desktop::Request<SelectedFiles>, Error> {
156 ashpd::desktop::file_chooser::OpenFileRequest::default()
158 .title(Some(dialog.title.as_str()))
159 .accept_label(dialog.accept_label.as_deref())
160 .directory(folders)
161 .modal(dialog.modal)
162 .multiple(multiple)
163 .choices(dialog.choices)
164 .filters(dialog.filters)
165 .current_filter(dialog.current_filter)
166 .send()
167 .await
168 .map_err(error_or_cancel)
169 }
170
171 fn file_response(
172 request: ashpd::desktop::Request<SelectedFiles>,
173 ) -> Result<FileResponse, Error> {
174 let files = request.response().map_err(error_or_cancel)?;
175 let url = parse_uri(files.uris().first().ok_or(Error::Cancelled)?)?;
176 Ok(FileResponse { files, url })
177 }
178
179 fn multi_file_response(
180 request: ashpd::desktop::Request<SelectedFiles>,
181 ) -> Result<MultiFileResponse, Error> {
182 let files = request.response().map_err(error_or_cancel)?;
183 let urls = files
184 .uris()
185 .iter()
186 .map(parse_uri)
187 .collect::<Result<Vec<Url>, _>>()?;
188 Ok(MultiFileResponse { files, urls })
189 }
190
191 pub async fn file(dialog: Dialog) -> Result<FileResponse, Error> {
192 file_response(create(dialog, false, false).await?)
193 }
194
195 pub async fn files(dialog: Dialog) -> Result<MultiFileResponse, Error> {
196 multi_file_response(create(dialog, false, true).await?)
197 }
198
199 pub async fn folder(dialog: Dialog) -> Result<FileResponse, Error> {
200 file_response(create(dialog, true, false).await?)
201 }
202
203 pub async fn folders(dialog: Dialog) -> Result<MultiFileResponse, Error> {
204 multi_file_response(create(dialog, true, true).await?)
205 }
206
207 pub struct FileResponse {
209 pub files: SelectedFiles,
210 url: Url,
211 }
212
213 impl FileResponse {
214 pub fn choices(&self) -> &[(String, String)] {
215 self.files.choices()
216 }
217
218 pub fn url(&self) -> &Url {
219 &self.url
220 }
221 }
222
223 pub struct MultiFileResponse {
225 pub files: SelectedFiles,
226 urls: Vec<Url>,
227 }
228
229 impl MultiFileResponse {
230 pub fn choices(&self) -> &[(String, String)] {
231 self.files.choices()
232 }
233
234 pub fn urls(&self) -> &[Url] {
235 &self.urls
236 }
237 }
238}
239
240#[cfg(feature = "rfd")]
241mod rust_fd {
242 use super::Dialog;
243 use crate::dialog::file_chooser::Error;
244 use url::Url;
245
246 pub fn create(dialog: Dialog) -> rfd::AsyncFileDialog {
247 let mut builder = rfd::AsyncFileDialog::new().set_title(dialog.title);
248
249 if let Some(directory) = dialog.directory {
250 builder = builder.set_directory(directory);
251 }
252
253 if let Some(file_name) = dialog.file_name {
254 builder = builder.set_file_name(file_name);
255 }
256
257 for filter in dialog.filters {
258 builder = builder.add_filter(filter.description, &filter.extensions);
259 }
260
261 builder
262 }
263
264 fn file_response(request: Option<rfd::FileHandle>) -> Result<FileResponse, Error> {
265 if let Some(handle) = request {
266 let url = Url::from_file_path(handle.path()).map_err(|_| Error::UrlAbsolute)?;
267
268 return Ok(FileResponse(url));
269 }
270
271 Err(Error::Cancelled)
272 }
273
274 fn multi_file_response(
275 request: Option<Vec<rfd::FileHandle>>,
276 ) -> Result<MultiFileResponse, Error> {
277 if let Some(handles) = request {
278 let mut urls = Vec::with_capacity(handles.len());
279
280 for handle in &handles {
281 urls.push(Url::from_file_path(handle.path()).map_err(|()| Error::UrlAbsolute)?);
282 }
283
284 return Ok(MultiFileResponse(urls));
285 }
286
287 Err(Error::Cancelled)
288 }
289
290 pub async fn file(dialog: Dialog) -> Result<FileResponse, Error> {
291 file_response(create(dialog).pick_file().await)
292 }
293
294 pub async fn files(dialog: Dialog) -> Result<MultiFileResponse, Error> {
295 multi_file_response(create(dialog).pick_files().await)
296 }
297
298 pub async fn folder(dialog: Dialog) -> Result<FileResponse, Error> {
299 file_response(create(dialog).pick_folder().await)
300 }
301
302 pub async fn folders(dialog: Dialog) -> Result<MultiFileResponse, Error> {
303 multi_file_response(create(dialog).pick_folders().await)
304 }
305
306 pub struct FileResponse(Url);
308
309 impl FileResponse {
310 pub fn choices(&self) -> &[(String, String)] {
311 &[]
312 }
313
314 pub fn url(&self) -> &Url {
315 &self.0
316 }
317 }
318
319 pub struct MultiFileResponse(Vec<Url>);
321
322 impl MultiFileResponse {
323 pub fn choices(&self) -> &[(String, String)] {
324 &[]
325 }
326
327 pub fn urls(&self) -> &[Url] {
328 &self.0
329 }
330 }
331}