cosmic/dialog/file_chooser/
save.rs1#[cfg(feature = "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(feature = "xdg-portal")]
27 #[setters(skip)]
28 accept_label: Option<String>,
29
30 #[cfg(feature = "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(feature = "xdg-portal")]
45 #[setters(skip)]
46 current_file: Option<PathBuf>,
47
48 #[cfg(feature = "xdg-portal")]
50 #[setters(skip)]
51 choices: Vec<super::Choice>,
52
53 #[cfg(feature = "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(feature = "xdg-portal")]
68 accept_label: None,
69 #[cfg(feature = "xdg-portal")]
70 modal: true,
71 file_name: None,
72 directory: None,
73 #[cfg(feature = "xdg-portal")]
74 current_file: None,
75 #[cfg(feature = "xdg-portal")]
76 current_filter: None,
77 #[cfg(feature = "xdg-portal")]
78 choices: Vec::new(),
79 filters: Vec::new(),
80 }
81 }
82
83 #[cfg(feature = "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(feature = "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(feature = "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(feature = "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(feature = "xdg-portal")]
130mod portal {
131 use super::Dialog;
132 use crate::dialog::file_chooser::Error;
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 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 .map(Response)
157 }
158
159 pub struct Response(pub SelectedFiles);
161
162 impl Response {
163 pub fn choices(&self) -> &[(String, String)] {
164 self.0.choices()
165 }
166
167 pub fn url(&self) -> Option<&Url> {
168 self.0.uris().first()
169 }
170 }
171}
172
173#[cfg(feature = "rfd")]
174mod rust_fd {
175 use super::Dialog;
176 use crate::dialog::file_chooser::Error;
177 use url::Url;
178
179 pub async fn file(dialog: Dialog) -> Result<Response, Error> {
181 let mut request = rfd::AsyncFileDialog::new().set_title(dialog.title);
182
183 if let Some(directory) = dialog.directory {
184 request = request.set_directory(directory);
185 }
186
187 if let Some(file_name) = dialog.file_name {
188 request = request.set_file_name(file_name);
189 }
190
191 for filter in dialog.filters {
192 request = request.add_filter(filter.description, &filter.extensions);
193 }
194
195 if let Some(handle) = request.save_file().await {
196 let url = Url::from_file_path(handle.path()).map_err(|_| Error::UrlAbsolute)?;
197
198 return Ok(Response(Some(url)));
199 }
200
201 Ok(Response(None))
202 }
203
204 pub struct Response(Option<Url>);
206
207 impl Response {
208 pub fn url(&self) -> Option<&Url> {
209 self.0.as_ref()
210 }
211 }
212}