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 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
202fn 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 ("", ÷r_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
311fn 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
353pub 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
384fn 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}