libsurfer/
file_dialog.rs

1use std::future::Future;
2#[cfg(not(target_arch = "wasm32"))]
3use std::path::PathBuf;
4
5#[cfg(not(target_arch = "wasm32"))]
6use camino::Utf8PathBuf;
7use rfd::{AsyncFileDialog, FileHandle};
8use serde::Deserialize;
9
10use crate::async_util::perform_async_work;
11use crate::message::Message;
12use crate::wave_source::LoadOptions;
13use crate::SystemState;
14
15#[derive(Debug, Deserialize)]
16pub enum OpenMode {
17    Open,
18    Switch,
19}
20
21impl SystemState {
22    #[cfg(not(target_arch = "wasm32"))]
23    pub fn file_dialog_open<F>(
24        &mut self,
25        title: &'static str,
26        filter: (String, Vec<String>),
27        messages: F,
28    ) where
29        F: FnOnce(PathBuf) -> Vec<Message> + Send + 'static,
30    {
31        let sender = self.channels.msg_sender.clone();
32
33        perform_async_work(async move {
34            if let Some(file) = create_file_dialog(filter, title).pick_file().await {
35                for message in messages(file.path().to_path_buf()) {
36                    sender.send(message).unwrap();
37                }
38            }
39        });
40    }
41
42    #[cfg(target_arch = "wasm32")]
43    pub fn file_dialog_open<F>(
44        &mut self,
45        title: &'static str,
46        filter: (String, Vec<String>),
47        messages: F,
48    ) where
49        F: FnOnce(Vec<u8>) -> Vec<Message> + 'static,
50    {
51        let sender = self.channels.msg_sender.clone();
52
53        perform_async_work(async move {
54            if let Some(file) = create_file_dialog(filter, title).pick_file().await {
55                for message in messages(file.read().await) {
56                    sender.send(message).unwrap();
57                }
58            }
59        });
60    }
61
62    #[cfg(not(target_arch = "wasm32"))]
63    pub fn file_dialog_save<F, Fut>(
64        &mut self,
65        title: &'static str,
66        filter: (String, Vec<String>),
67        messages: F,
68    ) where
69        F: FnOnce(FileHandle) -> Fut + Send + 'static,
70        Fut: Future<Output = Vec<Message>> + Send + 'static,
71    {
72        let sender = self.channels.msg_sender.clone();
73
74        perform_async_work(async move {
75            if let Some(file) = create_file_dialog(filter, title).save_file().await {
76                let msgs = messages(file).await;
77                for message in msgs {
78                    sender.send(message).unwrap();
79                }
80            }
81        });
82    }
83
84    #[cfg(target_arch = "wasm32")]
85    pub fn file_dialog_save<F, Fut>(
86        &mut self,
87        title: &'static str,
88        filter: (String, Vec<String>),
89        messages: F,
90    ) where
91        F: FnOnce(FileHandle) -> Fut + 'static,
92        Fut: Future<Output = Vec<Message>> + 'static,
93    {
94        let sender = self.channels.msg_sender.clone();
95
96        perform_async_work(async move {
97            if let Some(file) = create_file_dialog(filter, title).save_file().await {
98                let msgs = messages(file).await;
99                for message in msgs {
100                    sender.send(message).unwrap();
101                }
102            }
103        });
104    }
105
106    pub fn open_file_dialog(&mut self, mode: OpenMode) {
107        let keep_unavailable = self.user.config.behavior.keep_during_reload;
108        let keep_variables = match mode {
109            OpenMode::Open => false,
110            OpenMode::Switch => true,
111        };
112
113        #[cfg(not(target_arch = "wasm32"))]
114        let message = move |file: PathBuf| {
115            vec![Message::LoadFile(
116                Utf8PathBuf::from_path_buf(file).unwrap(),
117                LoadOptions {
118                    keep_variables,
119                    keep_unavailable,
120                },
121            )]
122        };
123
124        #[cfg(target_arch = "wasm32")]
125        let message = move |file: Vec<u8>| {
126            vec![Message::LoadFromData(
127                file,
128                LoadOptions {
129                    keep_variables,
130                    keep_unavailable,
131                },
132            )]
133        };
134
135        self.file_dialog_open(
136            "Open waveform file",
137            (
138                "Waveform/Transaction-files (*.vcd, *.fst, *.ghw, *.ftr)".to_string(),
139                vec![
140                    "vcd".to_string(),
141                    "fst".to_string(),
142                    "ghw".to_string(),
143                    "ftr".to_string(),
144                ],
145            ),
146            message,
147        );
148    }
149
150    pub fn open_command_file_dialog(&mut self) {
151        #[cfg(not(target_arch = "wasm32"))]
152        let message = move |file: PathBuf| {
153            vec![Message::LoadCommandFile(
154                Utf8PathBuf::from_path_buf(file).unwrap(),
155            )]
156        };
157
158        #[cfg(target_arch = "wasm32")]
159        let message = move |file: Vec<u8>| vec![Message::LoadCommandFromData(file)];
160
161        self.file_dialog_open(
162            "Open command file",
163            (
164                "Command-file (*.sucl)".to_string(),
165                vec!["sucl".to_string()],
166            ),
167            message,
168        );
169    }
170
171    #[cfg(feature = "python")]
172    pub fn open_python_file_dialog(&mut self) {
173        self.file_dialog_open(
174            "Open Python translator file",
175            ("Python files (*.py)".to_string(), vec!["py".to_string()]),
176            |file| {
177                vec![Message::LoadPythonTranslator(
178                    Utf8PathBuf::from_path_buf(file).unwrap(),
179                )]
180            },
181        );
182    }
183}
184
185fn create_file_dialog(filter: (String, Vec<String>), title: &'static str) -> AsyncFileDialog {
186    AsyncFileDialog::new()
187        .set_title(title)
188        .add_filter(filter.0, &filter.1)
189        .add_filter("All files", &["*"])
190}