Skip to main content

libsurfer/
toolbar.rs

1//! Toolbar handling.
2use egui::{Button, Layout, Panel, RichText, Ui};
3use egui_remixicon::icons;
4use emath::{Align, Vec2};
5
6use crate::message::MessageTarget;
7use crate::mousegestures::AnnotationKind;
8use crate::time::time_input_widget;
9use crate::wave_container::SimulationStatus;
10use crate::wave_source::LoadOptions;
11use crate::{
12    SystemState,
13    file_dialog::OpenMode,
14    message::Message,
15    wave_data::{PER_SCROLL_EVENT, SCROLL_EVENTS_PER_PAGE},
16};
17
18/// Helper function to add a new toolbar button, setting up icon, hover text etc.
19fn add_toolbar_button(
20    ui: &mut Ui,
21    msgs: &mut Vec<Message>,
22    icon_string: &str,
23    hover_text: &str,
24    message: Message,
25    enabled: bool,
26) {
27    let button = Button::new(RichText::new(icon_string).heading()).frame(false);
28    if ui
29        .add_enabled(enabled, button)
30        .on_hover_text(hover_text)
31        .clicked()
32    {
33        msgs.push(message);
34    }
35}
36
37/// Helper function to help the annotation buttons know what icon to display and message to send.
38fn annotation_helper<'a>(
39    state: &SystemState,
40    hover_text: &'a str,
41    icon_unselected: &'a str,
42    icon_selected: &'a str,
43    annotation_kind: AnnotationKind,
44) -> (&'a str, Option<AnnotationKind>, &'a str) {
45    if state.annotation_kind == Some(annotation_kind) {
46        (icon_selected, None, "Cancel Action")
47    } else {
48        (icon_unselected, Some(annotation_kind), hover_text)
49    }
50}
51
52impl SystemState {
53    /// Add panel and draw toolbar.
54    pub(crate) fn add_toolbar_panel(&self, ui: &mut Ui, msgs: &mut Vec<Message>) {
55        Panel::top("toolbar").show_inside(ui, |ui| {
56            self.draw_toolbar(ui, msgs);
57        });
58    }
59
60    fn simulation_status_toolbar(&self, ui: &mut Ui, msgs: &mut Vec<Message>) {
61        let Some(waves) = &self.user.waves else {
62            return;
63        };
64        let Some(status) = waves.inner.simulation_status() else {
65            return;
66        };
67
68        ui.separator();
69
70        ui.label("Simulation ");
71        match status {
72            SimulationStatus::Paused => add_toolbar_button(
73                ui,
74                msgs,
75                icons::PLAY_CIRCLE_FILL,
76                "Run simulation",
77                Message::UnpauseSimulation,
78                true,
79            ),
80            SimulationStatus::Running => add_toolbar_button(
81                ui,
82                msgs,
83                icons::PAUSE_CIRCLE_FILL,
84                "Pause simulation",
85                Message::PauseSimulation,
86                true,
87            ),
88            SimulationStatus::Finished => {
89                ui.label("Finished");
90            }
91        }
92    }
93
94    fn draw_toolbar(&self, ui: &mut Ui, msgs: &mut Vec<Message>) {
95        let wave_loaded = self.user.waves.is_some();
96        let undo_available = !self.undo_stack.is_empty();
97        let redo_available = !self.redo_stack.is_empty();
98
99        let (item_selected, cursor_set, multiple_viewports) = if let Some(waves) = &self.user.waves
100        {
101            (
102                waves.focused_item.is_some(),
103                waves.cursor.is_some(),
104                waves.viewports.len() > 1,
105            )
106        } else {
107            (false, false, false)
108        };
109
110        ui.with_layout(Layout::left_to_right(Align::LEFT), |ui| {
111            if !self.show_menu() {
112                // Menu
113                ui.menu_button(RichText::new(icons::MENU_FILL).heading(), |ui| {
114                    self.menu_contents(ui, msgs);
115                });
116                ui.separator();
117            }
118            // Files
119            add_toolbar_button(
120                ui,
121                msgs,
122                icons::FOLDER_OPEN_FILL,
123                "Open file...",
124                Message::OpenFileDialog(OpenMode::Open),
125                true,
126            );
127            add_toolbar_button(
128                ui,
129                msgs,
130                icons::DOWNLOAD_CLOUD_FILL,
131                "Open URL...",
132                Message::SetUrlEntryVisible(
133                    true,
134                    Some(Box::new(|url: String| {
135                        Message::LoadWaveformFileFromUrl(url.clone(), LoadOptions::Clear)
136                    })),
137                ),
138                true,
139            );
140            add_toolbar_button(
141                ui,
142                msgs,
143                icons::REFRESH_LINE,
144                "Reload",
145                Message::ReloadWaveform(self.user.config.behavior.keep_during_reload),
146                wave_loaded,
147            );
148            add_toolbar_button(
149                ui,
150                msgs,
151                icons::RUN_LINE,
152                "Run command file...",
153                Message::OpenCommandFileDialog,
154                true,
155            );
156            if self.user.surver_url.is_some() {
157                ui.separator();
158                add_toolbar_button(
159                    ui,
160                    msgs,
161                    icons::FILE_LIST_FILL,
162                    "Select Surver file",
163                    Message::SetSurverFileWindowVisible(true),
164                    true,
165                );
166            }
167            ui.separator();
168            add_toolbar_button(
169                ui,
170                msgs,
171                icons::FILE_COPY_FILL,
172                "Copy variable value",
173                Message::VariableValueToClipbord(MessageTarget::CurrentSelection),
174                item_selected && cursor_set,
175            );
176
177            ui.separator();
178            // Zoom
179            add_toolbar_button(
180                ui,
181                msgs,
182                icons::ZOOM_IN_FILL,
183                "Zoom in",
184                Message::CanvasZoom {
185                    mouse_ptr: None,
186                    delta: 0.5,
187                    viewport_idx: 0,
188                },
189                wave_loaded,
190            );
191            add_toolbar_button(
192                ui,
193                msgs,
194                icons::ZOOM_OUT_FILL,
195                "Zoom out",
196                Message::CanvasZoom {
197                    mouse_ptr: None,
198                    delta: 2.0,
199                    viewport_idx: 0,
200                },
201                wave_loaded,
202            );
203            add_toolbar_button(
204                ui,
205                msgs,
206                icons::ASPECT_RATIO_FILL,
207                "Zoom to fit",
208                Message::ZoomToFit { viewport_idx: 0 },
209                wave_loaded,
210            );
211            ui.separator();
212
213            // Navigation
214            add_toolbar_button(
215                ui,
216                msgs,
217                icons::REWIND_START_FILL,
218                "Go to start",
219                Message::GoToStart { viewport_idx: 0 },
220                wave_loaded,
221            );
222            add_toolbar_button(
223                ui,
224                msgs,
225                icons::REWIND_FILL,
226                "Go one page left",
227                Message::CanvasScroll {
228                    delta: Vec2 {
229                        y: PER_SCROLL_EVENT * SCROLL_EVENTS_PER_PAGE,
230                        x: 0.,
231                    },
232                    viewport_idx: 0,
233                },
234                wave_loaded,
235            );
236            add_toolbar_button(
237                ui,
238                msgs,
239                icons::PLAY_REVERSE_FILL,
240                "Go left",
241                Message::CanvasScroll {
242                    delta: Vec2 {
243                        y: PER_SCROLL_EVENT,
244                        x: 0.,
245                    },
246                    viewport_idx: 0,
247                },
248                wave_loaded,
249            );
250            add_toolbar_button(
251                ui,
252                msgs,
253                icons::PLAY_FILL,
254                "Go right",
255                Message::CanvasScroll {
256                    delta: Vec2 {
257                        y: -PER_SCROLL_EVENT,
258                        x: 0.,
259                    },
260                    viewport_idx: 0,
261                },
262                wave_loaded,
263            );
264            add_toolbar_button(
265                ui,
266                msgs,
267                icons::SPEED_FILL,
268                "Go one page right",
269                Message::CanvasScroll {
270                    delta: Vec2 {
271                        y: -PER_SCROLL_EVENT * SCROLL_EVENTS_PER_PAGE,
272                        x: 0.,
273                    },
274                    viewport_idx: 0,
275                },
276                wave_loaded,
277            );
278            add_toolbar_button(
279                ui,
280                msgs,
281                icons::FORWARD_END_FILL,
282                "Go to end",
283                Message::GoToEnd { viewport_idx: 0 },
284                wave_loaded,
285            );
286            ui.separator();
287
288            // Next transition
289            add_toolbar_button(
290                ui,
291                msgs,
292                icons::CONTRACT_LEFT_FILL,
293                "Set cursor on previous transition of focused variable",
294                Message::MoveCursorToTransition {
295                    next: false,
296                    variable: None,
297                    skip_zero: false,
298                },
299                item_selected && cursor_set,
300            );
301            add_toolbar_button(
302                ui,
303                msgs,
304                icons::CONTRACT_RIGHT_FILL,
305                "Set cursor on next transition of focused variable",
306                Message::MoveCursorToTransition {
307                    next: true,
308                    variable: None,
309                    skip_zero: false,
310                },
311                item_selected && cursor_set,
312            );
313            ui.separator();
314
315            // Add items
316            add_toolbar_button(
317                ui,
318                msgs,
319                icons::SPACE,
320                "Add divider",
321                Message::AddDivider(None, None),
322                wave_loaded,
323            );
324            add_toolbar_button(
325                ui,
326                msgs,
327                icons::TIME_FILL,
328                "Add timeline",
329                Message::AddTimeLine(None),
330                wave_loaded,
331            );
332            ui.separator();
333
334            // Add/remove viewport
335            add_toolbar_button(
336                ui,
337                msgs,
338                icons::ADD_BOX_FILL,
339                "Add viewport",
340                Message::AddViewport,
341                wave_loaded,
342            );
343            add_toolbar_button(
344                ui,
345                msgs,
346                icons::CHECKBOX_INDETERMINATE_FILL,
347                "Remove viewport",
348                Message::RemoveViewport,
349                wave_loaded && multiple_viewports,
350            );
351
352            let undo_tooltip = if let Some(undo_op) = self.undo_stack.last() {
353                format!("Undo: {}", undo_op.message)
354            } else {
355                "Undo".into()
356            };
357            let redo_tooltip = if let Some(redo_op) = self.redo_stack.last() {
358                format!("Redo: {}", redo_op.message)
359            } else {
360                "Redo".into()
361            };
362            add_toolbar_button(
363                ui,
364                msgs,
365                icons::ARROW_GO_BACK_FILL,
366                &undo_tooltip,
367                Message::Undo(1),
368                undo_available,
369            );
370            add_toolbar_button(
371                ui,
372                msgs,
373                icons::ARROW_GO_FORWARD_FILL,
374                &redo_tooltip,
375                Message::Redo(1),
376                redo_available,
377            );
378
379            ui.separator();
380
381            let (rect_icon, rect_kind, rect_text) = annotation_helper(
382                self,
383                "Add Rectangle",
384                icons::EDIT_BOX_LINE,
385                icons::EDIT_BOX_FILL,
386                AnnotationKind::Rectangle,
387            );
388            add_toolbar_button(
389                ui,
390                msgs,
391                rect_icon,
392                rect_text,
393                Message::SetMouseGestureAnnotation(rect_kind),
394                wave_loaded,
395            );
396
397            let (arrow_icon, arrow_kind, arrow_text) = annotation_helper(
398                self,
399                "Add Arrow",
400                icons::ARROW_RIGHT_UP_BOX_LINE,
401                icons::ARROW_RIGHT_UP_BOX_FILL,
402                AnnotationKind::ArrowSingleHead,
403            );
404            add_toolbar_button(
405                ui,
406                msgs,
407                arrow_icon,
408                arrow_text,
409                Message::SetMouseGestureAnnotation(arrow_kind),
410                wave_loaded,
411            );
412
413            let (double_arrow_icon, double_arrow_kind, double_arrow_text) = annotation_helper(
414                self,
415                "Add Double Headed Arrow",
416                icons::ARROW_LEFT_RIGHT_FILL,
417                icons::CLOSE_LARGE_LINE,
418                AnnotationKind::ArrowDoubleHead,
419            );
420            add_toolbar_button(
421                ui,
422                msgs,
423                double_arrow_icon,
424                double_arrow_text,
425                Message::SetMouseGestureAnnotation(double_arrow_kind),
426                wave_loaded,
427            );
428
429            add_toolbar_button(
430                ui,
431                msgs,
432                icons::LIST_CHECK,
433                "Annotations list",
434                Message::ToggleAnnotationlistVisibility(),
435                wave_loaded,
436            );
437
438            self.simulation_status_toolbar(ui, msgs);
439            if let Some(waves) = &self.user.waves {
440                ui.separator();
441
442                time_input_widget(
443                    ui,
444                    waves,
445                    msgs,
446                    &mut self.time_widget.borrow_mut(),
447                    self.request_time_edit_focus,
448                );
449            }
450        });
451    }
452}