Skip to main content

libsurfer/
help.rs

1//! Help texts and dialogs.
2use 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            ui.add_space(20.0);
54            ui.separator();
55            ui.add_space(20.0);
56        }
57
58        controls_listing(ui, &self.user.config.shortcuts);
59
60        ui.add_space(20.0);
61        ui.separator();
62        ui.add_space(20.0);
63
64        #[cfg(target_arch = "wasm32")]
65        {
66            ui.label(RichText::new(
67            "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.",
68        ));
69
70            ui.hyperlink_to(
71                "See https://gitlab.com/surfer-project/surfer for install instructions",
72                "https://gitlab.com/surfer-project/surfer",
73            );
74        }
75    }
76}
77
78pub fn draw_about_window(ctx: &Context, msgs: &mut Vec<Message>) {
79    let mut open = true;
80    Window::new("About Surfer")
81        .open(&mut open)
82        .collapsible(false)
83        .resizable(true)
84        .show(ctx, |ui| {
85            ui.vertical_centered(|ui| {
86                ui.label(RichText::new("🏄 Surfer").monospace().size(24.));
87                ui.add_space(20.);
88                ui.label(format!(
89                    "Cargo version: {ver}",
90                    ver = env!("CARGO_PKG_VERSION")
91                ));
92                if ui
93                    .small_button(format!(
94                        "Git version: {ver}",
95                        ver = env!("VERGEN_GIT_DESCRIBE")
96                    ))
97                    .on_hover_text("Click to copy git version")
98                    .clicked()
99                {
100                    ctx.copy_text(env!("VERGEN_GIT_DESCRIBE").to_string());
101                }
102                ui.label(format!(
103                    "Build date: {date}",
104                    date = env!("VERGEN_BUILD_DATE")
105                ));
106                ui.hyperlink_to(
107                    (icons::GITLAB_FILL).to_string() + " repository",
108                    "https://gitlab.com/surfer-project/surfer",
109                );
110                ui.hyperlink_to("Homepage", "https://surfer-project.org/");
111                ui.add_space(10.);
112                if ui.button("Close").clicked() {
113                    msgs.push(Message::SetAboutVisible(false));
114                }
115            })
116        });
117    if !open {
118        msgs.push(Message::SetAboutVisible(false));
119    }
120}
121
122pub fn draw_quickstart_help_window(
123    ctx: &Context,
124    msgs: &mut Vec<Message>,
125    shortcuts: &SurferShortcuts,
126) {
127    let mut open = true;
128    let show_command_prompt = shortcuts.format_shortcut(ShortcutAction::ShowCommandPrompt);
129    Window::new("🏄 Surfer quick start")
130        .collapsible(true)
131        .resizable(true)
132        .pivot(Align2::CENTER_CENTER)
133        .open(&mut open)
134        .default_pos(Pos2::new(
135            ctx.available_rect().size().x / 2.,
136            ctx.available_rect().size().y / 2.,
137        ))
138        .show(ctx, |ui| {
139            ui.vertical(|ui| {
140                ui.add_space(5.);
141
142                ui.label(RichText::new("Controls").size(20.));
143                ui.add_space(5.);
144                ui.label("↔ Use scroll and ctrl+scroll to navigate the waveform");
145                ui.label(format!(
146                    "🚀 Press {show_command_prompt} to open the command palette"
147                ));
148                ui.label("✋ Click the middle mouse button for gestures");
149                ui.label("❓ See the help menu for more controls");
150                ui.add_space(10.);
151                ui.label(RichText::new("Adding traces").size(20.));
152                ui.add_space(5.);
153                ui.label("Add more traces using the command palette or using the sidebar");
154                ui.add_space(10.);
155                ui.label(RichText::new("Opening files").size(20.));
156                ui.add_space(5.);
157                ui.label("Open a new file by");
158                ui.label("- dragging a VCD, FST, or GHW file");
159                #[cfg(target_arch = "wasm32")]
160                ui.label("- typing load_url in the command palette");
161                #[cfg(not(target_arch = "wasm32"))]
162                ui.label("- typing load_url or load_file in the command palette");
163                ui.label("- using the file menu");
164                ui.label("- using the toolbar");
165                ui.add_space(10.);
166            });
167            ui.vertical_centered(|ui| {
168                if ui.button("Close").clicked() {
169                    msgs.push(Message::SetQuickStartVisible(false));
170                }
171            })
172        });
173    if !open {
174        msgs.push(Message::SetQuickStartVisible(false));
175    }
176}
177
178pub fn draw_control_help_window(
179    ctx: &Context,
180    msgs: &mut Vec<Message>,
181    shortcuts: &SurferShortcuts,
182) {
183    let mut open = true;
184    Window::new("🖮 Surfer controls")
185        .collapsible(true)
186        .resizable(true)
187        .open(&mut open)
188        .show(ctx, |ui| {
189            ui.vertical_centered(|ui| {
190                key_listing(ui, shortcuts);
191                ui.add_space(10.);
192                if ui.button("Close").clicked() {
193                    msgs.push(Message::SetKeyHelpVisible(false));
194                }
195            });
196        });
197    if !open {
198        msgs.push(Message::SetKeyHelpVisible(false));
199    }
200}
201
202/// Long list of key binding for the dialog.
203fn key_listing(ui: &mut Ui, shortcuts: &SurferShortcuts) {
204    let save_state_file = shortcuts.format_shortcut(ShortcutAction::SaveStateFile);
205    let toggle_hierarchy = shortcuts.format_shortcut(ShortcutAction::ToggleSidePanel);
206    let toggle_toolbar = shortcuts.format_shortcut(ShortcutAction::ToggleToolbar);
207    let reload_waveform = shortcuts.format_shortcut(ShortcutAction::ReloadWaveform);
208    let focus_item = shortcuts.format_shortcut(ShortcutAction::ItemFocus);
209    let goto_end = shortcuts.format_shortcut(ShortcutAction::GoToEnd);
210    let goto_start = shortcuts.format_shortcut(ShortcutAction::GoToStart);
211    let zoom_in = shortcuts.format_shortcut(ShortcutAction::ZoomIn);
212    let zoom_out = shortcuts.format_shortcut(ShortcutAction::ZoomOut);
213    let show_command_prompt = shortcuts.format_shortcut(ShortcutAction::ShowCommandPrompt);
214    let selected_item_toggle = shortcuts.format_shortcut(ShortcutAction::SelectToggle);
215    let undo = shortcuts.format_shortcut(ShortcutAction::Undo);
216    let redo = shortcuts.format_shortcut(ShortcutAction::Redo);
217    let add_marker = shortcuts.format_shortcut(ShortcutAction::MarkerAdd);
218    let scroll_up = shortcuts.format_shortcut(ShortcutAction::ScrollUp);
219    let scroll_down = shortcuts.format_shortcut(ShortcutAction::ScrollDown);
220    let delete_selected = shortcuts.format_shortcut(ShortcutAction::DeleteSelected);
221    let toggle_menu = shortcuts.format_shortcut(ShortcutAction::ToggleMenu);
222    let divider_add = shortcuts.format_shortcut(ShortcutAction::DividerAdd);
223    #[cfg(not(target_arch = "wasm32"))]
224    let ui_zoom_in = shortcuts.format_shortcut(ShortcutAction::UiZoomIn);
225    #[cfg(not(target_arch = "wasm32"))]
226    let ui_zoom_out = shortcuts.format_shortcut(ShortcutAction::UiZoomOut);
227    let keys = vec![
228        ("🚀", show_command_prompt.as_str(), "Show command prompt"),
229        ("↔", "Scroll", "Pan"),
230        ("🔎", "Ctrl+Scroll", "Zoom"),
231        (icons::SAVE_FILL, &save_state_file, "Save the state"),
232        (
233            icons::LAYOUT_LEFT_FILL,
234            &toggle_hierarchy,
235            "Show or hide the design hierarchy",
236        ),
237        (icons::MENU_FILL, &toggle_menu, "Show or hide menu"),
238        (icons::TOOLS_FILL, &toggle_toolbar, "Show or hide toolbar"),
239        (icons::ZOOM_IN_FILL, &zoom_in, "Zoom in"),
240        (icons::ZOOM_OUT_FILL, &zoom_out, "Zoom out"),
241        #[cfg(not(target_arch = "wasm32"))]
242        ("", &ui_zoom_in, "UI Zoom in"),
243        #[cfg(not(target_arch = "wasm32"))]
244        ("", &ui_zoom_out, "UI Zoom out"),
245        ("", "k/⬆", "Scroll up"),
246        ("", "j/⬇", "Scroll down"),
247        ("", "Ctrl+k/⬆", "Move focused item up"),
248        ("", "Ctrl+j/⬇", "Move focused item down"),
249        ("", "Alt+k/⬆", "Move focus up"),
250        ("", "Alt+j/⬇", "Move focus down"),
251        ("", &selected_item_toggle, "Add focused item to selection"),
252        ("", "Ctrl+Alt+k/⬆", "Extend selection up"),
253        ("", "Ctrl+Alt+j/⬇", "Extend selection down"),
254        ("", &undo, "Undo last change"),
255        ("", &redo, "Redo last change"),
256        ("", &focus_item, "Fast focus a variable"),
257        ("", &add_marker, "Add marker at current cursor"),
258        ("", "Ctrl+0-9", "Add numbered marker"),
259        ("", "0-9", "Center view at numbered marker"),
260        ("", &divider_add, "Add divider"),
261        (icons::REWIND_START_FILL, &goto_start, "Go to start"),
262        (icons::FORWARD_END_FILL, &goto_end, "Go to end"),
263        (icons::REFRESH_LINE, &reload_waveform, "Reload waveform"),
264        (icons::SPEED_FILL, &scroll_up, "Go one page/screen right"),
265        (icons::REWIND_FILL, &scroll_down, "Go one page/screen left"),
266        (
267            icons::PLAY_FILL,
268            "➡/l",
269            "Go to next transition of focused variable (changeable in config)",
270        ),
271        (
272            icons::PLAY_REVERSE_FILL,
273            "⬅/h",
274            "Go to previous transition of focused variable (changeable in config)",
275        ),
276        (
277            "",
278            "Ctrl+➡/l",
279            "Go to next non-zero transition of focused variable",
280        ),
281        (
282            "",
283            "Ctrl+⬅/h",
284            "Go to previous non-zero transition of focused variable",
285        ),
286        (
287            icons::DELETE_BIN_2_FILL,
288            &delete_selected,
289            "Delete focused item",
290        ),
291        #[cfg(not(target_arch = "wasm32"))]
292        (icons::FULLSCREEN_LINE, "F11", "Toggle full screen"),
293    ];
294
295    Grid::new("keys")
296        .num_columns(3)
297        .spacing([5., 5.])
298        .show(ui, |ui| {
299            for (symbol, control, description) in keys {
300                let control = ctrl_to_cmd(control);
301                ui.label(symbol);
302                ui.label(control);
303                ui.label(description);
304                ui.end_row();
305            }
306        });
307
308    add_hint_text(ui);
309}
310
311/// Shorter list displayed at startup screen.
312fn controls_listing(ui: &mut Ui, shortcuts: &SurferShortcuts) {
313    let show_command_prompt = shortcuts.format_shortcut(ShortcutAction::ShowCommandPrompt);
314    let toggle_hierarchy = shortcuts.format_shortcut(ShortcutAction::ToggleSidePanel);
315    let toggle_toolbar = shortcuts.format_shortcut(ShortcutAction::ToggleToolbar);
316    let toggle_menu = shortcuts.format_shortcut(ShortcutAction::ToggleMenu);
317
318    let controls = vec![
319        ("🚀", show_command_prompt.as_str(), "Show command prompt"),
320        ("↔", "Horizontal Scroll", "Pan"),
321        ("↕", "j, k, Up, Down", "Scroll down/up"),
322        ("⌖", "Ctrl+j, k, Up, Down", "Move focus down/up"),
323        ("🔃", "Alt+j, k, Up, Down", "Move focused item down/up"),
324        ("🔎", "Ctrl+Scroll", "Zoom"),
325        (
326            icons::LAYOUT_LEFT_2_FILL,
327            &toggle_hierarchy,
328            "Show or hide the design hierarchy",
329        ),
330        (icons::MENU_FILL, &toggle_menu, "Show or hide menu"),
331        (icons::TOOLS_FILL, &toggle_toolbar, "Show or hide toolbar"),
332    ];
333
334    Grid::new("controls")
335        .num_columns(2)
336        .spacing([20., 5.])
337        .show(ui, |ui| {
338            for (symbol, control, description) in controls {
339                let control = ctrl_to_cmd(control);
340                ui.label(format!("{symbol}  {control}"));
341                ui.label(description);
342                ui.end_row();
343            }
344        });
345    add_hint_text(ui);
346}
347
348fn add_hint_text(ui: &mut Ui) {
349    ui.add_space(20.);
350    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."));
351}
352
353// Display information about licenses for Surfer and used crates.
354pub fn draw_license_window(ctx: &Context, msgs: &mut Vec<Message>) {
355    let mut open = true;
356    let text = include_str!("../../LICENSE-EUPL-1.2.txt");
357    Window::new("Surfer License")
358        .open(&mut open)
359        .collapsible(false)
360        .max_height(600.)
361        .default_width(600.)
362        .show(ctx, |ui| {
363            ScrollArea::vertical().show(ui, |ui| {
364                ui.label(text);
365            });
366            ui.add_space(10.);
367            ui.horizontal(|ui| {
368                if ui.button("Dependency licenses").clicked() {
369                    ctx.open_url(OpenUrl {
370                        url: "https://docs.surfer-project.org/licenses.html".to_string(),
371                        new_tab: true,
372                    });
373                }
374                if ui.button("Close").clicked() {
375                    msgs.push(Message::SetLicenseVisible(false));
376                }
377            });
378        });
379    if !open {
380        msgs.push(Message::SetLicenseVisible(false));
381    }
382}
383
384// Replace Ctrl with Cmd in case of macos, unless we are running tests
385fn ctrl_to_cmd(instr: &str) -> String {
386    #[cfg(all(target_os = "macos", not(test)))]
387    let instring = instr.to_string().replace("Ctrl", "Cmd");
388    #[cfg(any(not(target_os = "macos"), test))]
389    let instring = instr.to_string();
390    instring
391}