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