1use 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
16fn 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 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 ui.menu_button(RichText::new(icons::MENU_FILL).heading(), |ui| {
86 self.menu_contents(ui, msgs);
87 });
88 ui.separator();
89 }
90 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 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 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 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_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_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}