dear_file_browser/
native.rs1use crate::core::{Backend, DialogMode, FileDialog, FileDialogError, Selection};
18
19#[cfg(feature = "tracing")]
20use tracing::trace;
21
22impl FileDialog {
23 fn to_rfd(&self) -> rfd::FileDialog {
24 let mut d = rfd::FileDialog::new();
25 if let Some(dir) = &self.start_dir {
26 d = d.set_directory(dir);
27 }
28 if let Some(name) = &self.default_name {
29 d = d.set_file_name(name);
30 }
31 for f in &self.filters {
32 let exts_owned: Vec<String> = f
33 .extensions
34 .iter()
35 .filter_map(|s| plain_extension_for_native(s))
36 .collect();
37 let exts: Vec<&str> = exts_owned.iter().map(|s| s.as_str()).collect();
38 if !exts.is_empty() {
39 d = d.add_filter(&f.name, &exts);
40 }
41 }
42 d
43 }
44
45 fn to_rfd_async(&self) -> rfd::AsyncFileDialog {
46 let mut a = rfd::AsyncFileDialog::new();
47 if let Some(dir) = self.start_dir.as_deref() {
48 a = a.set_directory(dir);
49 }
50 if let Some(name) = self.default_name.as_deref() {
51 a = a.set_file_name(name);
52 }
53 for f in &self.filters {
54 let exts_owned: Vec<String> = f
55 .extensions
56 .iter()
57 .filter_map(|s| plain_extension_for_native(s))
58 .collect();
59 if !exts_owned.is_empty() {
60 a = a.add_filter(&f.name, &exts_owned);
61 }
62 }
63 a
64 }
65
66 pub fn open_blocking(self) -> Result<Selection, FileDialogError> {
68 match self.effective_backend() {
69 Backend::Native => self.open_blocking_native(),
70 Backend::ImGui => Err(FileDialogError::Unsupported),
71 Backend::Auto => unreachable!("resolved in effective_backend"),
72 }
73 }
74
75 fn open_blocking_native(self) -> Result<Selection, FileDialogError> {
76 #[cfg(feature = "tracing")]
77 trace!(?self.mode, "rfd blocking open");
78 let mut sel = Selection::default();
79 match self.mode {
80 DialogMode::OpenFile => {
81 if let Some(p) = self.to_rfd().pick_file() {
82 sel.paths.push(p);
83 }
84 }
85 DialogMode::OpenFiles => {
86 if !self.allow_multi {
87 if let Some(p) = self.to_rfd().pick_file() {
88 sel.paths.push(p);
89 }
90 } else if let Some(v) = self.to_rfd().pick_files() {
91 sel.paths.extend(v);
92 }
93 }
94 DialogMode::PickFolder => {
95 if let Some(p) = self.to_rfd().pick_folder() {
96 sel.paths.push(p);
97 }
98 }
99 DialogMode::SaveFile => {
100 if let Some(p) = self.to_rfd().save_file() {
101 sel.paths.push(p);
102 }
103 }
104 }
105 if let Some(max) = self.max_selection.filter(|&m| m > 0) {
106 sel.paths.truncate(max);
107 }
108 if sel.paths.is_empty() {
109 Err(FileDialogError::Cancelled)
110 } else {
111 Ok(sel)
112 }
113 }
114
115 pub async fn open_async(self) -> Result<Selection, FileDialogError> {
117 use rfd::AsyncFileDialog as A;
118 #[cfg(feature = "tracing")]
119 trace!(?self.mode, "rfd async open");
120 let mut sel = Selection::default();
121 match self.mode {
122 DialogMode::OpenFile => {
123 let a = self.to_rfd_async();
124 let f = a.pick_file().await;
125 if let Some(h) = f {
126 sel.paths.push(h.path().to_path_buf());
127 }
128 }
129 DialogMode::OpenFiles => {
130 let a = self.to_rfd_async();
131 if !self.allow_multi {
132 let f = a.pick_file().await;
133 if let Some(h) = f {
134 sel.paths.push(h.path().to_path_buf());
135 }
136 } else {
137 let v = a.pick_files().await;
138 if let Some(v) = v {
139 sel.paths
140 .extend(v.into_iter().map(|h| h.path().to_path_buf()));
141 }
142 }
143 }
144 DialogMode::PickFolder => {
145 let mut a = A::new();
146 if let Some(dir) = self.start_dir.as_deref() {
147 a = a.set_directory(dir);
148 }
149 let f = a.pick_folder().await;
150 if let Some(h) = f {
151 sel.paths.push(h.path().to_path_buf());
152 }
153 }
154 DialogMode::SaveFile => {
155 let a = self.to_rfd_async();
156 let f = a.save_file().await;
157 if let Some(h) = f {
158 sel.paths.push(h.path().to_path_buf());
159 }
160 }
161 }
162 if let Some(max) = self.max_selection.filter(|&m| m > 0) {
163 sel.paths.truncate(max);
164 }
165 if sel.paths.is_empty() {
166 Err(FileDialogError::Cancelled)
167 } else {
168 Ok(sel)
169 }
170 }
171}
172
173fn is_plain_extension_token(token: &str) -> bool {
174 let t = token.trim();
175 if t.is_empty() {
176 return false;
177 }
178 if t.starts_with("((") && t.ends_with("))") {
179 return false;
180 }
181 !(t.contains('*') || t.contains('?'))
182}
183
184fn plain_extension_for_native(token: &str) -> Option<String> {
185 if !is_plain_extension_token(token) {
186 return None;
187 }
188 let t = token.trim().trim_start_matches('.');
189 if t.is_empty() {
190 return None;
191 }
192 Some(t.to_lowercase())
193}