1use 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
18fn 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
37fn 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 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 ui.menu_button(RichText::new(icons::MENU_FILL).heading(), |ui| {
114 self.menu_contents(ui, msgs);
115 });
116 ui.separator();
117 }
118 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 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 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 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_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_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}