cosmic/dialog/file_chooser/
save.rs1#[cfg(xdg_portal)]
10pub use portal::{Response, file};
11
12#[cfg(feature = "rfd")]
13pub use rust_fd::{Response, file};
14
15use super::Error;
16use std::path::PathBuf;
17
18#[derive(derive_setters::Setters)]
20#[must_use]
21pub struct Dialog {
22 title: String,
24
25 #[cfg(xdg_portal)]
27 #[setters(skip)]
28 accept_label: Option<String>,
29
30 #[cfg(xdg_portal)]
32 #[setters(skip)]
33 modal: bool,
34
35 #[setters(strip_option)]
37 file_name: Option<String>,
38
39 #[setters(strip_option)]
41 directory: Option<PathBuf>,
42
43 #[cfg(xdg_portal)]
45 #[setters(skip)]
46 current_file: Option<PathBuf>,
47
48 #[cfg(xdg_portal)]
50 #[setters(skip)]
51 choices: Vec<super::Choice>,
52
53 #[cfg(xdg_portal)]
55 #[setters(skip)]
56 current_filter: Option<super::FileFilter>,
57
58 #[setters(skip)]
60 filters: Vec<super::FileFilter>,
61}
62
63impl Dialog {
64 pub const fn new() -> Self {
65 Self {
66 title: String::new(),
67 #[cfg(xdg_portal)]
68 accept_label: None,
69 #[cfg(xdg_portal)]
70 modal: true,
71 file_name: None,
72 directory: None,
73 #[cfg(xdg_portal)]
74 current_file: None,
75 #[cfg(xdg_portal)]
76 current_filter: None,
77 #[cfg(xdg_portal)]
78 choices: Vec::new(),
79 filters: Vec::new(),
80 }
81 }
82
83 #[cfg(xdg_portal)]
85 pub fn accept_label(mut self, label: impl Into<String>) -> Self {
86 self.accept_label = Some(label.into());
87 self
88 }
89
90 #[cfg(xdg_portal)]
92 pub fn choice(mut self, choice: impl Into<super::Choice>) -> Self {
93 self.choices.push(choice.into());
94 self
95 }
96
97 #[cfg(xdg_portal)]
99 pub fn current_filter(mut self, filter: impl Into<super::FileFilter>) -> Self {
100 self.current_filter = Some(filter.into());
101 self
102 }
103
104 pub fn filter(mut self, filter: impl Into<super::FileFilter>) -> Self {
106 self.filters.push(filter.into());
107 self
108 }
109
110 #[cfg(xdg_portal)]
112 pub fn modal(mut self, modal: bool) -> Self {
113 self.modal = modal;
114 self
115 }
116
117 pub async fn save_file(self) -> Result<Response, Error> {
119 file(self).await
120 }
121}
122
123impl Default for Dialog {
124 fn default() -> Self {
125 Self::new()
126 }
127}
128
129#[cfg(xdg_portal)]
130mod portal {
131 use super::Dialog;
132 use crate::dialog::file_chooser::{Error, parse_uri};
133 use ashpd::desktop::file_chooser::SelectedFiles;
134 use std::path::Path;
135 use url::Url;
136
137 pub async fn file(dialog: Dialog) -> Result<Response, Error> {
139 let files = ashpd::desktop::file_chooser::SaveFileRequest::default()
140 .title(Some(dialog.title.as_str()))
141 .accept_label(dialog.accept_label.as_deref())
142 .modal(dialog.modal)
143 .choices(dialog.choices)
144 .filters(dialog.filters)
145 .current_filter(dialog.current_filter)
146 .current_name(dialog.file_name.as_deref())
147 .current_folder::<&Path>(dialog.directory.as_deref())
148 .map_err(Error::SetDirectory)?
149 .current_file::<&Path>(dialog.current_file.as_deref())
150 .map_err(Error::SetAbsolutePath)?
151 .send()
152 .await
153 .map_err(Error::Save)?
154 .response()
155 .map_err(Error::Save)?;
156
157 let url = files.uris().first().map(parse_uri).transpose()?;
158
159 Ok(Response { files, url })
160 }
161
162 pub struct Response {
164 pub files: SelectedFiles,
165 url: Option<Url>,
166 }
167
168 impl Response {
169 pub fn choices(&self) -> &[(String, String)] {
170 self.files.choices()
171 }
172
173 pub fn url(&self) -> Option<&Url> {
174 self.url.as_ref()
175 }
176 }
177}
178
179#[cfg(feature = "rfd")]
180mod rust_fd {
181 use super::Dialog;
182 use crate::dialog::file_chooser::Error;
183 use url::Url;
184
185 pub async fn file(dialog: Dialog) -> Result<Response, Error> {
187 let mut request = rfd::AsyncFileDialog::new().set_title(dialog.title);
188
189 if let Some(directory) = dialog.directory {
190 request = request.set_directory(directory);
191 }
192
193 if let Some(file_name) = dialog.file_name {
194 request = request.set_file_name(file_name);
195 }
196
197 for filter in dialog.filters {
198 request = request.add_filter(filter.description, &filter.extensions);
199 }
200
201 if let Some(handle) = request.save_file().await {
202 let url = Url::from_file_path(handle.path()).map_err(|_| Error::UrlAbsolute)?;
203
204 return Ok(Response(Some(url)));
205 }
206
207 Ok(Response(None))
208 }
209
210 pub struct Response(Option<Url>);
212
213 impl Response {
214 pub fn url(&self) -> Option<&Url> {
215 self.0.as_ref()
216 }
217 }
218}