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