Skip to main content

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