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            #[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
218/// Long list of key binding for the dialog.
219fn 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        ("", &divider_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
329/// Shorter list displayed at startup screen.
330fn 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
371// Display information about licenses for Surfer and used crates.
372pub 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
402// Replace Ctrl with Cmd in case of macos, unless we are running tests
403fn 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}