Skip to main content

libsurfer/
toolbar.rs

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