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