1use egui::{Context, Grid, OpenUrl, RichText, ScrollArea, Ui, Window};
3use egui_remixicon::icons;
4use emath::{Align2, Pos2};
5
6use crate::keyboard_shortcuts::{ShortcutAction, SurferShortcuts};
7use crate::wave_source::LoadOptions;
8use crate::{SystemState, message::Message};
9
10impl SystemState {
11 pub fn help_message(&self, ui: &mut Ui) {
12 if self.user.waves.is_none() {
13 let show_command_prompt = self
14 .user
15 .config
16 .shortcuts
17 .format_shortcut(ShortcutAction::ShowCommandPrompt);
18
19 ui.label(RichText::new(
20 "Drag and drop a VCD, FST, or GHW file here to open it",
21 ));
22
23 #[cfg(target_arch = "wasm32")]
24 ui.label(RichText::new(format!(
25 "Or press {show_command_prompt} and type load_url"
26 )));
27 #[cfg(not(target_arch = "wasm32"))]
28 ui.label(RichText::new(format!(
29 "Or press {show_command_prompt} and type load_file or load_url"
30 )));
31 #[cfg(target_arch = "wasm32")]
32 ui.label(RichText::new(
33 "Or use the file menu or toolbar to open a URL",
34 ));
35 #[cfg(not(target_arch = "wasm32"))]
36 ui.label(RichText::new(
37 "Or use the file menu or toolbar to open a file or a URL",
38 ));
39 ui.horizontal(|ui| {
40 ui.label(RichText::new("Or click"));
41 if ui.link("here").clicked() {
42 self.channels
43 .msg_sender
44 .send(Message::LoadWaveformFileFromUrl(
45 "https://app.surfer-project.org/picorv32.vcd".to_string(),
46 LoadOptions::Clear,
47 ))
48 .ok();
49 }
50 ui.label("to open an example waveform");
51 });
52
53 #[cfg(not(test))]
54 if !self.file_history.files().is_empty() {
55 ui.add_space(10.0);
56 ui.label(RichText::new("Recent files"));
57
58 let labels = self.file_history.display_labels();
59 for (path, label) in self.file_history.files().iter().zip(labels.iter()) {
60 if ui.link(label).on_hover_text(path.as_str()).clicked() {
61 self.channels
62 .msg_sender
63 .send(Message::LoadFile(path.clone(), LoadOptions::Clear))
64 .ok();
65 }
66 }
67 }
68
69 ui.add_space(20.0);
70 ui.separator();
71 ui.add_space(20.0);
72 }
73
74 controls_listing(ui, &self.user.config.shortcuts);
75
76 ui.add_space(20.0);
77 ui.separator();
78 ui.add_space(20.0);
79
80 #[cfg(target_arch = "wasm32")]
81 {
82 ui.label(RichText::new(
83 "Note that this web based version is a bit slower than a natively installed version. There may also be a long delay with unresponsiveness when loading large waveforms because the web assembly version does not currently support multi threading.",
84 ));
85
86 ui.hyperlink_to(
87 "See https://gitlab.com/surfer-project/surfer for install instructions",
88 "https://gitlab.com/surfer-project/surfer",
89 );
90 }
91 }
92}
93
94pub fn draw_about_window(ctx: &Context, msgs: &mut Vec<Message>) {
95 let mut open = true;
96 Window::new("About Surfer")
97 .open(&mut open)
98 .collapsible(false)
99 .resizable(true)
100 .show(ctx, |ui| {
101 ui.vertical_centered(|ui| {
102 ui.label(RichText::new("🏄 Surfer").monospace().size(24.));
103 ui.add_space(20.);
104 ui.label(format!(
105 "Cargo version: {ver}",
106 ver = env!("CARGO_PKG_VERSION")
107 ));
108 if ui
109 .small_button(format!(
110 "Git version: {ver}",
111 ver = env!("VERGEN_GIT_DESCRIBE")
112 ))
113 .on_hover_text("Click to copy git version")
114 .clicked()
115 {
116 ctx.copy_text(env!("VERGEN_GIT_DESCRIBE").to_string());
117 }
118 ui.label(format!(
119 "Build date: {date}",
120 date = env!("VERGEN_BUILD_DATE")
121 ));
122 ui.hyperlink_to(
123 (icons::GITLAB_FILL).to_string() + " repository",
124 "https://gitlab.com/surfer-project/surfer",
125 );
126 ui.hyperlink_to("Homepage", "https://surfer-project.org/");
127 ui.add_space(10.);
128 if ui.button("Close").clicked() {
129 msgs.push(Message::SetAboutVisible(false));
130 }
131 })
132 });
133 if !open {
134 msgs.push(Message::SetAboutVisible(false));
135 }
136}
137
138pub fn draw_quickstart_help_window(
139 ctx: &Context,
140 msgs: &mut Vec<Message>,
141 shortcuts: &SurferShortcuts,
142) {
143 let mut open = true;
144 let show_command_prompt = shortcuts.format_shortcut(ShortcutAction::ShowCommandPrompt);
145 Window::new("🏄 Surfer quick start")
146 .collapsible(true)
147 .resizable(true)
148 .pivot(Align2::CENTER_CENTER)
149 .open(&mut open)
150 .default_pos(Pos2::new(
151 ctx.content_rect().size().x * 0.5,
152 ctx.content_rect().size().y * 0.5,
153 ))
154 .show(ctx, |ui| {
155 ui.vertical(|ui| {
156 ui.add_space(5.);
157
158 ui.label(RichText::new("Controls").size(20.));
159 ui.add_space(5.);
160 ui.label("↔ Use scroll and ctrl+scroll to navigate the waveform");
161 ui.label(format!(
162 "🚀 Press {show_command_prompt} to open the command palette"
163 ));
164 ui.label("✋ Click the middle mouse button for gestures");
165 ui.label("❓ See the help menu for more controls");
166 ui.add_space(10.);
167 ui.label(RichText::new("Adding traces").size(20.));
168 ui.add_space(5.);
169 ui.label("Add more traces using the command palette or using the sidebar");
170 ui.add_space(10.);
171 ui.label(RichText::new("Opening files").size(20.));
172 ui.add_space(5.);
173 ui.label("Open a new file by");
174 ui.label("- dragging a VCD, FST, or GHW file");
175 #[cfg(target_arch = "wasm32")]
176 ui.label("- typing load_url in the command palette");
177 #[cfg(not(target_arch = "wasm32"))]
178 ui.label("- typing load_url or load_file in the command palette");
179 ui.label("- using the file menu");
180 ui.label("- using the toolbar");
181 ui.add_space(10.);
182 });
183 ui.vertical_centered(|ui| {
184 if ui.button("Close").clicked() {
185 msgs.push(Message::SetQuickStartVisible(false));
186 }
187 })
188 });
189 if !open {
190 msgs.push(Message::SetQuickStartVisible(false));
191 }
192}
193
194pub fn draw_control_help_window(
195 ctx: &Context,
196 msgs: &mut Vec<Message>,
197 shortcuts: &SurferShortcuts,
198) {
199 let mut open = true;
200 Window::new("🖮 Surfer controls")
201 .collapsible(true)
202 .resizable(true)
203 .open(&mut open)
204 .show(ctx, |ui| {
205 ui.vertical_centered(|ui| {
206 key_listing(ui, shortcuts);
207 ui.add_space(10.);
208 if ui.button("Close").clicked() {
209 msgs.push(Message::SetKeyHelpVisible(false));
210 }
211 });
212 });
213 if !open {
214 msgs.push(Message::SetKeyHelpVisible(false));
215 }
216}
217
218fn key_listing(ui: &mut Ui, shortcuts: &SurferShortcuts) {
220 let save_state_file = shortcuts.format_shortcut(ShortcutAction::SaveStateFile);
221 let toggle_hierarchy = shortcuts.format_shortcut(ShortcutAction::ToggleSidePanel);
222 let toggle_toolbar = shortcuts.format_shortcut(ShortcutAction::ToggleToolbar);
223 let reload_waveform = shortcuts.format_shortcut(ShortcutAction::ReloadWaveform);
224 let focus_item = shortcuts.format_shortcut(ShortcutAction::ItemFocus);
225 let goto_end = shortcuts.format_shortcut(ShortcutAction::GoToEnd);
226 let goto_start = shortcuts.format_shortcut(ShortcutAction::GoToStart);
227 let zoom_in = shortcuts.format_shortcut(ShortcutAction::ZoomIn);
228 let zoom_out = shortcuts.format_shortcut(ShortcutAction::ZoomOut);
229 let show_command_prompt = shortcuts.format_shortcut(ShortcutAction::ShowCommandPrompt);
230 let selected_item_toggle = shortcuts.format_shortcut(ShortcutAction::SelectToggle);
231 let undo = shortcuts.format_shortcut(ShortcutAction::Undo);
232 let redo = shortcuts.format_shortcut(ShortcutAction::Redo);
233 let add_marker = shortcuts.format_shortcut(ShortcutAction::MarkerAdd);
234 let scroll_up = shortcuts.format_shortcut(ShortcutAction::ScrollUp);
235 let scroll_down = shortcuts.format_shortcut(ShortcutAction::ScrollDown);
236 let delete_selected = shortcuts.format_shortcut(ShortcutAction::DeleteSelected);
237 let toggle_menu = shortcuts.format_shortcut(ShortcutAction::ToggleMenu);
238 let divider_add = shortcuts.format_shortcut(ShortcutAction::DividerAdd);
239 let zoom_to_cursor = shortcuts.format_shortcut(ShortcutAction::ZoomToCursor);
240 #[cfg(not(target_arch = "wasm32"))]
241 let ui_zoom_in = shortcuts.format_shortcut(ShortcutAction::UiZoomIn);
242 #[cfg(not(target_arch = "wasm32"))]
243 let ui_zoom_out = shortcuts.format_shortcut(ShortcutAction::UiZoomOut);
244 let keys = vec![
245 ("🚀", show_command_prompt.as_str(), "Show command prompt"),
246 ("↔", "Scroll", "Pan"),
247 ("🔎", "Ctrl+Scroll", "Zoom"),
248 (icons::SAVE_FILL, &save_state_file, "Save the state"),
249 (
250 icons::LAYOUT_LEFT_FILL,
251 &toggle_hierarchy,
252 "Show or hide the design hierarchy",
253 ),
254 (icons::MENU_FILL, &toggle_menu, "Show or hide menu"),
255 (icons::TOOLS_FILL, &toggle_toolbar, "Show or hide toolbar"),
256 (icons::ZOOM_IN_FILL, &zoom_in, "Zoom in"),
257 (icons::ZOOM_OUT_FILL, &zoom_out, "Zoom out"),
258 (icons::TARGET_FILL, &zoom_to_cursor, "Zoom in on cursor"),
259 #[cfg(not(target_arch = "wasm32"))]
260 ("", &ui_zoom_in, "UI Zoom in"),
261 #[cfg(not(target_arch = "wasm32"))]
262 ("", &ui_zoom_out, "UI Zoom out"),
263 ("", "k/⬆", "Scroll up"),
264 ("", "j/⬇", "Scroll down"),
265 ("", "Ctrl+k/⬆", "Move focused item up"),
266 ("", "Ctrl+j/⬇", "Move focused item down"),
267 ("", "Alt+k/⬆", "Move focus up"),
268 ("", "Alt+j/⬇", "Move focus down"),
269 ("", &selected_item_toggle, "Add focused item to selection"),
270 ("", "Ctrl+Alt+k/⬆", "Extend selection up"),
271 ("", "Ctrl+Alt+j/⬇", "Extend selection down"),
272 ("", &undo, "Undo last change"),
273 ("", &redo, "Redo last change"),
274 ("", &focus_item, "Fast focus a variable"),
275 ("", &add_marker, "Add marker at current cursor"),
276 ("", "Ctrl+0-9", "Add numbered marker"),
277 ("", "0-9", "Center view at numbered marker"),
278 ("", ÷r_add, "Add divider"),
279 (icons::REWIND_START_FILL, &goto_start, "Go to start"),
280 (icons::FORWARD_END_FILL, &goto_end, "Go to end"),
281 (icons::REFRESH_LINE, &reload_waveform, "Reload waveform"),
282 (icons::SPEED_FILL, &scroll_up, "Go one page/screen right"),
283 (icons::REWIND_FILL, &scroll_down, "Go one page/screen left"),
284 (
285 icons::PLAY_FILL,
286 "➡/l",
287 "Go to next transition of focused variable (changeable in config)",
288 ),
289 (
290 icons::PLAY_REVERSE_FILL,
291 "⬅/h",
292 "Go to previous transition of focused variable (changeable in config)",
293 ),
294 (
295 "",
296 "Ctrl+➡/l",
297 "Go to next non-zero transition of focused variable",
298 ),
299 (
300 "",
301 "Ctrl+⬅/h",
302 "Go to previous non-zero transition of focused variable",
303 ),
304 (
305 icons::DELETE_BIN_2_FILL,
306 &delete_selected,
307 "Delete focused item",
308 ),
309 #[cfg(not(target_arch = "wasm32"))]
310 (icons::FULLSCREEN_LINE, "F11", "Toggle full screen"),
311 ];
312
313 Grid::new("keys")
314 .num_columns(3)
315 .spacing([5., 5.])
316 .show(ui, |ui| {
317 for (symbol, control, description) in keys {
318 let control = ctrl_to_cmd(control);
319 ui.label(symbol);
320 ui.label(control);
321 ui.label(description);
322 ui.end_row();
323 }
324 });
325
326 add_hint_text(ui);
327}
328
329fn controls_listing(ui: &mut Ui, shortcuts: &SurferShortcuts) {
331 let show_command_prompt = shortcuts.format_shortcut(ShortcutAction::ShowCommandPrompt);
332 let toggle_hierarchy = shortcuts.format_shortcut(ShortcutAction::ToggleSidePanel);
333 let toggle_toolbar = shortcuts.format_shortcut(ShortcutAction::ToggleToolbar);
334 let toggle_menu = shortcuts.format_shortcut(ShortcutAction::ToggleMenu);
335
336 let controls = vec![
337 ("🚀", show_command_prompt.as_str(), "Show command prompt"),
338 ("↔", "Horizontal Scroll", "Pan"),
339 ("↕", "j, k, Up, Down", "Scroll down/up"),
340 ("⌖", "Ctrl+j, k, Up, Down", "Move focus down/up"),
341 ("🔃", "Alt+j, k, Up, Down", "Move focused item down/up"),
342 ("🔎", "Ctrl+Scroll", "Zoom"),
343 (
344 icons::LAYOUT_LEFT_2_FILL,
345 &toggle_hierarchy,
346 "Show or hide the design hierarchy",
347 ),
348 (icons::MENU_FILL, &toggle_menu, "Show or hide menu"),
349 (icons::TOOLS_FILL, &toggle_toolbar, "Show or hide toolbar"),
350 ];
351
352 Grid::new("controls")
353 .num_columns(2)
354 .spacing([20., 5.])
355 .show(ui, |ui| {
356 for (symbol, control, description) in controls {
357 let control = ctrl_to_cmd(control);
358 ui.label(format!("{symbol} {control}"));
359 ui.label(description);
360 ui.end_row();
361 }
362 });
363 add_hint_text(ui);
364}
365
366fn add_hint_text(ui: &mut Ui) {
367 ui.add_space(20.);
368 ui.label(RichText::new("Hint: You can repeat keybinds by typing Alt+0-9 before them. For example, Alt+1 Alt+0 k scrolls 10 steps up."));
369}
370
371pub fn draw_license_window(ctx: &Context, msgs: &mut Vec<Message>) {
373 let mut open = true;
374 let text = include_str!("../../LICENSE-EUPL-1.2.txt");
375 Window::new("Surfer License")
376 .open(&mut open)
377 .collapsible(false)
378 .max_height(600.)
379 .default_size((600., 600.))
380 .show(ctx, |ui| {
381 ScrollArea::vertical().show(ui, |ui| {
382 ui.label(text);
383 });
384 ui.add_space(10.);
385 ui.horizontal(|ui| {
386 if ui.button("Dependency licenses").clicked() {
387 ctx.open_url(OpenUrl {
388 url: "https://docs.surfer-project.org/licenses.html".to_string(),
389 new_tab: true,
390 });
391 }
392 if ui.button("Close").clicked() {
393 msgs.push(Message::SetLicenseVisible(false));
394 }
395 });
396 });
397 if !open {
398 msgs.push(Message::SetLicenseVisible(false));
399 }
400}
401
402fn ctrl_to_cmd(instr: &str) -> String {
404 #[cfg(all(target_os = "macos", not(test)))]
405 let instring = instr.to_string().replace("Ctrl", "Cmd");
406 #[cfg(any(not(target_os = "macos"), test))]
407 let instring = instr.to_string();
408 instring
409}