dear_file_browser/
native.rs

1//! Native (rfd) backend.
2//!
3//! This module implements the OS-native file dialogs via the `rfd` crate.
4//! On desktop platforms it opens the system dialog; on `wasm32` targets it
5//! uses the Web File Picker. Both blocking and async flows are exposed via the
6//! `FileDialog` builder:
7//!
8//! - `open_blocking()` opens a modal, OS-native dialog and returns on close.
9//! - `open_async()` awaits the selection (desktop and wasm32 supported).
10//!
11//! Notes
12//! - Filters map to `rfd::FileDialog::add_filter` and accept lowercase
13//!   extensions without dots (e.g. "png").
14//! - When `start_dir` is provided it is forwarded to `rfd`.
15//! - On the Web (wasm32), the ImGui in-UI browser cannot enumerate the local
16//!   filesystem – prefer the native backend to access user files.
17use 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: Vec<&str> = f.extensions.iter().map(|s| s.as_str()).collect();
33            d = d.add_filter(&f.name, &exts);
34        }
35        d
36    }
37
38    /// Open a dialog synchronously (blocking).
39    pub fn open_blocking(self) -> Result<Selection, FileDialogError> {
40        match self.effective_backend() {
41            Backend::Native => self.open_blocking_native(),
42            Backend::ImGui => Err(FileDialogError::Unsupported),
43            Backend::Auto => unreachable!("resolved in effective_backend"),
44        }
45    }
46
47    fn open_blocking_native(self) -> Result<Selection, FileDialogError> {
48        #[cfg(feature = "tracing")]
49        trace!(?self.mode, "rfd blocking open");
50        let mut sel = Selection::default();
51        match self.mode {
52            DialogMode::OpenFile => {
53                if let Some(p) = self.to_rfd().pick_file() {
54                    sel.paths.push(p);
55                }
56            }
57            DialogMode::OpenFiles => {
58                if let Some(v) = self.to_rfd().pick_files() {
59                    sel.paths.extend(v);
60                }
61            }
62            DialogMode::PickFolder => {
63                if let Some(p) = self.to_rfd().pick_folder() {
64                    sel.paths.push(p);
65                }
66            }
67            DialogMode::SaveFile => {
68                if let Some(p) = self.to_rfd().save_file() {
69                    sel.paths.push(p);
70                }
71            }
72        }
73        if sel.paths.is_empty() {
74            Err(FileDialogError::Cancelled)
75        } else {
76            Ok(sel)
77        }
78    }
79
80    /// Open a dialog asynchronously via `rfd::AsyncFileDialog`.
81    pub async fn open_async(self) -> Result<Selection, FileDialogError> {
82        use rfd::AsyncFileDialog as A;
83        #[cfg(feature = "tracing")]
84        trace!(?self.mode, "rfd async open");
85        let mut sel = Selection::default();
86        match self.mode {
87            DialogMode::OpenFile => {
88                let mut a = A::new();
89                if let Some(dir) = self.start_dir.as_deref() {
90                    a = a.set_directory(dir);
91                }
92                if let Some(name) = self.default_name.as_deref() {
93                    a = a.set_file_name(name);
94                }
95                let f = a.pick_file().await;
96                if let Some(h) = f {
97                    sel.paths.push(h.path().to_path_buf());
98                }
99            }
100            DialogMode::OpenFiles => {
101                let mut a = A::new();
102                if let Some(dir) = self.start_dir.as_deref() {
103                    a = a.set_directory(dir);
104                }
105                if let Some(name) = self.default_name.as_deref() {
106                    a = a.set_file_name(name);
107                }
108                let v = a.pick_files().await;
109                if let Some(v) = v {
110                    sel.paths
111                        .extend(v.into_iter().map(|h| h.path().to_path_buf()));
112                }
113            }
114            DialogMode::PickFolder => {
115                let mut a = A::new();
116                if let Some(dir) = self.start_dir.as_deref() {
117                    a = a.set_directory(dir);
118                }
119                let f = a.pick_folder().await;
120                if let Some(h) = f {
121                    sel.paths.push(h.path().to_path_buf());
122                }
123            }
124            DialogMode::SaveFile => {
125                let mut a = A::new();
126                if let Some(dir) = self.start_dir.as_deref() {
127                    a = a.set_directory(dir);
128                }
129                if let Some(name) = self.default_name.as_deref() {
130                    a = a.set_file_name(name);
131                }
132                let f = a.save_file().await;
133                if let Some(h) = f {
134                    sel.paths.push(h.path().to_path_buf());
135                }
136            }
137        }
138        if sel.paths.is_empty() {
139            Err(FileDialogError::Cancelled)
140        } else {
141            Ok(sel)
142        }
143    }
144}