cosmic/dialog/file_chooser/
open.rs1#[cfg(feature = "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(feature = "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(feature = "xdg-portal")]
43 #[setters(skip)]
44 modal: bool,
45
46 #[cfg(feature = "xdg-portal")]
48 #[setters(skip)]
49 choices: Vec<super::Choice>,
50
51 #[cfg(feature = "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(feature = "xdg-portal")]
66 accept_label: None,
67 directory: None,
68 file_name: None,
69 #[cfg(feature = "xdg-portal")]
70 modal: true,
71 #[cfg(feature = "xdg-portal")]
72 current_filter: None,
73 #[cfg(feature = "xdg-portal")]
74 choices: Vec::new(),
75 filters: Vec::new(),
76 }
77 }
78
79 #[cfg(feature = "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(feature = "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(feature = "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(feature = "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(feature = "xdg-portal")]
135mod portal {
136 use super::Dialog;
137 use crate::dialog::file_chooser::Error;
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(feature = "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 request
175 .response()
176 .map(FileResponse)
177 .map_err(error_or_cancel)
178 }
179
180 fn multi_file_response(
181 request: ashpd::desktop::Request<SelectedFiles>,
182 ) -> Result<MultiFileResponse, Error> {
183 request
184 .response()
185 .map(MultiFileResponse)
186 .map_err(error_or_cancel)
187 }
188
189 pub async fn file(dialog: Dialog) -> Result<FileResponse, Error> {
190 file_response(create(dialog, false, false).await?)
191 }
192
193 pub async fn files(dialog: Dialog) -> Result<MultiFileResponse, Error> {
194 multi_file_response(create(dialog, false, true).await?)
195 }
196
197 pub async fn folder(dialog: Dialog) -> Result<FileResponse, Error> {
198 file_response(create(dialog, true, false).await?)
199 }
200
201 pub async fn folders(dialog: Dialog) -> Result<MultiFileResponse, Error> {
202 multi_file_response(create(dialog, true, true).await?)
203 }
204
205 pub struct FileResponse(pub SelectedFiles);
207
208 impl FileResponse {
209 pub fn choices(&self) -> &[(String, String)] {
210 self.0.choices()
211 }
212
213 pub fn url(&self) -> &Url {
214 self.0.uris().first().expect("no files selected")
215 }
216 }
217
218 pub struct MultiFileResponse(pub SelectedFiles);
220
221 impl MultiFileResponse {
222 pub fn choices(&self) -> &[(String, String)] {
223 self.0.choices()
224 }
225
226 pub fn urls(&self) -> &[Url] {
227 self.0.uris()
228 }
229 }
230}
231
232#[cfg(feature = "rfd")]
233mod rust_fd {
234 use super::Dialog;
235 use crate::dialog::file_chooser::Error;
236 use url::Url;
237
238 pub fn create(dialog: Dialog) -> rfd::AsyncFileDialog {
239 let mut builder = rfd::AsyncFileDialog::new().set_title(dialog.title);
240
241 if let Some(directory) = dialog.directory {
242 builder = builder.set_directory(directory);
243 }
244
245 if let Some(file_name) = dialog.file_name {
246 builder = builder.set_file_name(file_name);
247 }
248
249 for filter in dialog.filters {
250 builder = builder.add_filter(filter.description, &filter.extensions);
251 }
252
253 builder
254 }
255
256 fn file_response(request: Option<rfd::FileHandle>) -> Result<FileResponse, Error> {
257 if let Some(handle) = request {
258 let url = Url::from_file_path(handle.path()).map_err(|_| Error::UrlAbsolute)?;
259
260 return Ok(FileResponse(url));
261 }
262
263 Err(Error::Cancelled)
264 }
265
266 fn multi_file_response(
267 request: Option<Vec<rfd::FileHandle>>,
268 ) -> Result<MultiFileResponse, Error> {
269 if let Some(handles) = request {
270 let mut urls = Vec::with_capacity(handles.len());
271
272 for handle in &handles {
273 urls.push(Url::from_file_path(handle.path()).map_err(|()| Error::UrlAbsolute)?);
274 }
275
276 return Ok(MultiFileResponse(urls));
277 }
278
279 Err(Error::Cancelled)
280 }
281
282 pub async fn file(dialog: Dialog) -> Result<FileResponse, Error> {
283 file_response(create(dialog).pick_file().await)
284 }
285
286 pub async fn files(dialog: Dialog) -> Result<MultiFileResponse, Error> {
287 multi_file_response(create(dialog).pick_files().await)
288 }
289
290 pub async fn folder(dialog: Dialog) -> Result<FileResponse, Error> {
291 file_response(create(dialog).pick_folder().await)
292 }
293
294 pub async fn folders(dialog: Dialog) -> Result<MultiFileResponse, Error> {
295 multi_file_response(create(dialog).pick_folders().await)
296 }
297
298 pub struct FileResponse(Url);
300
301 impl FileResponse {
302 pub fn choices(&self) -> &[(String, String)] {
303 &[]
304 }
305
306 pub fn url(&self) -> &Url {
307 &self.0
308 }
309 }
310
311 pub struct MultiFileResponse(Vec<Url>);
313
314 impl MultiFileResponse {
315 pub fn choices(&self) -> &[(String, String)] {
316 &[]
317 }
318
319 pub fn urls(&self) -> &[Url] {
320 &self.0
321 }
322 }
323}