1#![deny(unused_crate_dependencies)]
2
3pub mod analog_renderer;
4pub mod analog_signal_cache;
5pub mod annotation;
6pub mod annotation_list;
7pub mod arrow;
8pub mod async_util;
9pub mod batch_commands;
10#[cfg(feature = "performance_plot")]
11pub mod benchmark;
12mod channels;
13pub mod clock_highlighting;
14pub mod command_parser;
15pub mod command_prompt;
16pub mod comment;
17pub mod config;
18pub mod cxxrtl;
19pub mod cxxrtl_container;
20pub mod data_container;
21pub mod dialog;
22pub mod displayed_item;
23pub mod displayed_item_tree;
24pub mod drawing_canvas;
25pub mod file_dialog;
26pub mod file_history;
27pub mod file_watcher;
28pub mod frame_buffer;
29#[cfg(not(target_arch = "wasm32"))]
30pub mod fst_export;
31pub mod fzcmd;
32pub mod graphics;
33pub mod help;
34pub mod hierarchy;
35pub mod item_drawing_info;
36pub mod keyboard_shortcuts;
37pub mod keys;
38pub mod logs;
39pub mod marker;
40pub mod memory_viewer;
41pub mod menus;
42pub mod message;
43pub mod mousegestures;
44pub mod overview;
45pub mod rectangle;
46pub mod remote;
47pub mod server_file_window;
48pub mod state;
49pub mod state_file_io;
50pub mod state_util;
51pub mod statusbar;
52pub mod system_state;
53#[cfg(test)]
54pub mod tests;
55pub mod time;
56pub mod toolbar;
57pub mod tooltips;
58pub mod trace_style;
59pub mod transaction_container;
60pub mod transactions;
61pub mod translation;
62pub mod util;
63pub mod variable_direction;
64pub mod variable_filter;
65mod variable_index;
66pub mod variable_meta;
67pub mod variable_name_type;
68pub mod view;
69pub mod viewport;
70#[cfg(target_arch = "wasm32")]
71pub mod wasm_api;
72#[cfg(target_arch = "wasm32")]
73pub mod wasm_panic;
74pub mod wave_container;
75pub mod wave_data;
76pub mod wave_source;
77pub mod wcp;
78pub mod wellen;
79
80use crate::annotation::Annotatable;
81use crate::annotation::Annotation;
82use crate::annotation_list::AnnotationGroup;
83use crate::annotation_list::DEFAULT_GROUP_NAME;
84use crate::arrow::ArrowAnnotation;
85use crate::channels::checked_send;
86use crate::comment::CommentMessage;
87use crate::config::AutoLoad;
88use crate::displayed_item_tree::ItemIndex;
89use crate::displayed_item_tree::TargetPosition;
90use crate::rectangle::RectAnnotation;
91use crate::remote::get_time_table_from_server;
92use crate::variable_name_type::VariableNameType;
93
94use std::collections::HashMap;
95use std::sync::atomic::{AtomicUsize, Ordering};
96use std::sync::mpsc::{self, Receiver, Sender};
97use std::sync::{Arc, LazyLock, RwLock};
98
99use batch_commands::read_command_bytes;
100use batch_commands::read_command_file;
101#[cfg(target_arch = "wasm32")]
102use channels::{GlobalChannelTx, IngressHandler, IngressReceiver};
103use derive_more::Display;
104use displayed_item::DisplayedVariable;
105use displayed_item_tree::DisplayedItemTree;
106use eframe::{App, CreationContext};
107use egui::Id;
108use egui::{FontData, FontDefinitions, FontFamily};
109use eyre::{Result, WrapErr as _};
110use ftr_parser::types::Transaction;
111use futures::executor::block_on;
112use itertools::Itertools;
113use message::MessageTarget;
114use num::BigInt;
115use serde::Deserialize;
116use surfer_translation_types::Translator;
117use surfer_wcp::{WcpCSMessage, WcpEvent, WcpSCMessage};
118pub use system_state::SystemState;
119#[cfg(target_arch = "wasm32")]
120use tokio_stream as _;
121use tracing::{error, info, warn};
122#[cfg(all(not(target_arch = "wasm32"), feature = "wasm_plugins"))]
123use translation::wasm_translator::PluginTranslator;
124use wave_container::ScopeRef;
125
126#[cfg(all(not(target_arch = "wasm32"), feature = "wasm_plugins"))]
127use crate::async_util::perform_work;
128use crate::config::{SurferConfig, SurferTheme};
129use crate::dialog::{OpenSiblingStateFileDialog, ReloadWaveformDialog};
130use crate::displayed_item::{
131 AnalogVarState, DisplayedFieldRef, DisplayedItem, DisplayedItemRef, FieldFormat,
132};
133use crate::displayed_item_tree::VisibleItemIndex;
134use crate::drawing_canvas::TxDrawingCommands;
135use crate::frame_buffer::{FrameBufferColorMode, FrameBufferContent, build_frame_buffer_content};
136use crate::message::Message;
137use crate::transaction_container::{TransactionRef, TransactionStreamRef};
138use crate::translation::{AnyTranslator, all_translators};
139use crate::variable_filter::{VariableIOFilterType, VariableNameFilterType};
140use crate::viewport::Viewport;
141use crate::wave_container::{ScopeRefExt, VariableRefExt, WaveContainer};
142use crate::wave_data::WaveData;
143use crate::wave_source::{LoadOptions, WaveFormat, WaveSource};
144use crate::wellen::{HeaderResult, convert_format};
145
146pub(crate) static OUTSTANDING_TRANSACTIONS: AtomicUsize = AtomicUsize::new(0);
154
155pub static EGUI_CONTEXT: LazyLock<RwLock<Option<Arc<egui::Context>>>> =
156 LazyLock::new(|| RwLock::new(None));
157
158#[cfg(target_arch = "wasm32")]
159pub(crate) static WCP_CS_HANDLER: LazyLock<IngressHandler<WcpCSMessage>> =
160 LazyLock::new(IngressHandler::new);
161
162#[cfg(target_arch = "wasm32")]
163pub(crate) static WCP_SC_HANDLER: LazyLock<GlobalChannelTx<WcpSCMessage>> =
164 LazyLock::new(GlobalChannelTx::new);
165
166#[derive(Default)]
167pub struct StartupParams {
168 pub waves: Option<WaveSource>,
169 pub wcp_initiate: Option<u16>,
170 pub startup_commands: Vec<String>,
171}
172
173fn setup_custom_font(ctx: &egui::Context) {
174 let mut fonts = FontDefinitions::default();
175
176 fonts.font_data.insert(
177 "remix_icons".to_owned(),
178 FontData::from_static(egui_remixicon::FONT).into(),
179 );
180
181 fonts
182 .families
183 .get_mut(&FontFamily::Proportional)
184 .unwrap()
185 .push("remix_icons".to_owned());
186
187 fonts
188 .families
189 .get_mut(&FontFamily::Monospace)
190 .unwrap()
191 .push("remix_icons".to_owned());
192
193 ctx.set_fonts(fonts);
194}
195
196pub fn run_egui(cc: &CreationContext, mut state: SystemState) -> Result<Box<dyn App>> {
197 let ctx_arc = Arc::new(cc.egui_ctx.clone());
198 *EGUI_CONTEXT.write().unwrap() = Some(ctx_arc.clone());
199 state.context = Some(ctx_arc.clone());
200 cc.egui_ctx
201 .set_visuals_of(egui::Theme::Dark, state.get_visuals());
202 cc.egui_ctx
203 .set_visuals_of(egui::Theme::Light, state.get_visuals());
204 cc.egui_ctx.all_styles_mut(|style| {
205 if state.user.config.animation_time == 0.0 {
206 info!("With animation_time set to 0.0, animations cannot be enabled.");
207 }
208 style.animation_time = if state.user.config.animation_enabled() {
209 state.user.config.animation_time
210 } else {
211 0.0
212 };
213 });
214 #[cfg(not(target_arch = "wasm32"))]
215 if state.user.config.wcp.autostart {
216 state.start_wcp_server(Some(state.user.config.wcp.address.clone()), false);
217 }
218 setup_custom_font(&cc.egui_ctx);
219 Ok(Box::new(state))
220}
221
222#[derive(Debug, Clone, Copy, Deserialize, Display, PartialEq, Eq)]
223pub enum MoveDir {
224 #[display("up")]
225 Up,
226
227 #[display("down")]
228 Down,
229}
230
231pub enum ColorSpecifier {
232 Index(usize),
233 Name(String),
234}
235
236enum CachedDrawData {
237 WaveDrawData(CachedWaveDrawData),
238 TransactionDrawData(CachedTransactionDrawData),
239}
240
241struct CachedWaveDrawData {
242 pub draw_commands: HashMap<DisplayedFieldRef, drawing_canvas::DrawingCommands>,
243 pub clock_edges: crate::clock_highlighting::ClockHighlightData,
244 pub ticks: Vec<(String, f32, i64)>,
245}
246
247struct CachedTransactionDrawData {
248 pub draw_commands: HashMap<TransactionRef, TxDrawingCommands>,
249 pub stream_to_displayed_txs: HashMap<TransactionStreamRef, Vec<TransactionRef>>,
250 pub inc_relation_tx_ids: Vec<TransactionRef>,
251 pub out_relation_tx_ids: Vec<TransactionRef>,
252}
253
254pub struct Channels {
255 pub msg_sender: Sender<Message>,
256 pub msg_receiver: Receiver<Message>,
257 #[cfg(target_arch = "wasm32")]
258 wcp_c2s_receiver: Option<IngressReceiver<WcpCSMessage>>,
259 #[cfg(not(target_arch = "wasm32"))]
260 wcp_c2s_receiver: Option<tokio::sync::mpsc::Receiver<WcpCSMessage>>,
261 wcp_s2c_sender: Option<tokio::sync::mpsc::Sender<WcpSCMessage>>,
262}
263impl Channels {
264 fn new() -> Self {
265 let (msg_sender, msg_receiver) = mpsc::channel();
266 Self {
267 msg_sender,
268 msg_receiver,
269 wcp_c2s_receiver: None,
270 wcp_s2c_sender: None,
271 }
272 }
273}
274
275pub struct WcpClientCapabilities {
276 pub waveforms_loaded: bool,
277 pub goto_declaration: bool,
278 pub add_drivers: bool,
279 pub add_loads: bool,
280}
281impl WcpClientCapabilities {
282 fn new() -> Self {
283 Self {
284 waveforms_loaded: false,
285 goto_declaration: false,
286 add_drivers: false,
287 add_loads: false,
288 }
289 }
290}
291
292struct CanvasState {
294 message: String,
295 focused_item: Option<VisibleItemIndex>,
296 focused_transaction: (Option<TransactionRef>, Option<Transaction>),
297 items_tree: DisplayedItemTree,
298 displayed_items: HashMap<DisplayedItemRef, DisplayedItem>,
299 markers: HashMap<u8, BigInt>,
300 annotations: Vec<Annotation>,
301 annotation_group: Vec<AnnotationGroup>,
302 annotation_list: bool,
303 selected_annotation: Option<Id>,
304 annotation_counter: i32,
305}
306
307impl SystemState {
308 pub fn update(&mut self, message: Message) -> Option<()> {
309 if tracing::enabled!(tracing::Level::TRACE)
310 && !matches!(message, Message::CommandPromptUpdate { .. })
311 {
312 tracing::trace!("{message:?}");
313 }
314 match message {
315 Message::SetActiveScope(scope) => {
316 let waves = self.user.waves.as_mut()?;
317 waves.set_active_scope(scope)?;
318 }
319
320 Message::ExpandScope(scope_ref) => {
321 *self.scope_ref_to_expand.borrow_mut() = Some(scope_ref);
322 }
323 Message::AddVariables(vars) => {
324 if !vars.is_empty() {
325 let undo_msg = if vars.len() == 1 {
326 format!("Add variable {}", vars[0].name)
327 } else {
328 format!("Add {} variables", vars.len())
329 };
330 self.save_current_canvas(undo_msg);
331 if let Some(waves) = self.user.waves.as_mut() {
332 if let (Some(cmd), _) =
333 waves.add_variables(&self.translators, vars, None, true, false, None)
334 {
335 self.load_variables(cmd);
336 }
337 self.invalidate_draw_commands();
338 } else {
339 error!("Could not load signals, no waveform loaded");
340 }
341 }
342 }
343 Message::DownloadDefaultConfig => {
344 #[cfg(not(target_arch = "wasm32"))]
345 {
346 if let Err(e) = crate::config::write_default_config() {
347 tracing::error!("Failed to write default config: {}", e);
348 }
349 }
350
351 #[cfg(target_arch = "wasm32")]
352 {
353 tracing::warn!("Download default config is not supported on WASM");
354 }
355 }
356
357 Message::AddDivider(name, vidx) => {
358 self.save_current_canvas("Add divider".into());
359 let waves = self.user.waves.as_mut()?;
360 waves.add_divider(name, vidx);
361 }
362 Message::AddTimeLine(vidx) => {
363 self.save_current_canvas("Add timeline".into());
364 let waves = self.user.waves.as_mut()?;
365 waves.add_timeline(vidx);
366 }
367 Message::AddScope(scope, recursive) => {
368 self.save_current_canvas(format!("Add scope {}", scope.name()));
369
370 let vars = self.get_scope(&scope, recursive);
371 let waves = self.user.waves.as_mut()?;
372
373 if let (Some(cmd), _) =
375 waves.add_variables(&self.translators, vars, None, true, false, None)
376 {
377 self.load_variables(cmd);
378 }
379
380 self.invalidate_draw_commands();
381 }
382 Message::AddScopeAsGroup(scope, recursive) => {
383 self.save_current_canvas(format!("Add scope {} as group", scope.name()));
384 let waves = self.user.waves.as_mut()?;
385 let passed_or_focused = waves.insert_position(waves.focused_item);
386 let target = passed_or_focused.unwrap_or_else(|| waves.end_insert_position());
387
388 self.add_scope_as_group(&scope, target, recursive, None);
389 self.invalidate_draw_commands();
390
391 self.user.waves.as_mut()?.compute_variable_display_names();
392 }
393 Message::AddCount(digit) => {
394 if let Some(count) = &mut self.user.count {
395 count.push(digit);
396 } else {
397 self.user.count = Some(digit.to_string());
398 }
399 }
400 Message::AddStreamOrGenerator(s) => {
401 let undo_msg = if let Some(gen_id) = s.gen_id {
402 format!("Add generator(id: {gen_id})")
403 } else {
404 format!("Add stream(id: {})", s.stream_id)
405 };
406 self.save_current_canvas(undo_msg);
407
408 let waves = self.user.waves.as_mut()?;
409 if s.gen_id.is_some() {
410 waves.add_generator(s);
411 } else {
412 waves.add_stream(s);
413 }
414 self.invalidate_draw_commands();
415 }
416 Message::AddStreamOrGeneratorFromName(scope, name) => {
417 self.save_current_canvas(format!("Add Stream/Generator from name: {name}"));
418 let waves = self.user.waves.as_mut()?;
419 waves.add_stream_or_generator_from_name(scope, name)?;
420 self.invalidate_draw_commands();
421 }
422 Message::AddAllFromStreamScope(scope_name) => {
423 self.save_current_canvas(format!("Add all from scope {}", scope_name.clone()));
424 let waves = self.user.waves.as_mut()?;
425 waves.add_all_from_stream_scope(scope_name)?;
426 self.invalidate_draw_commands();
427 }
428 Message::InvalidateCount => self.user.count = None,
429 Message::SetNameAlignRight(align_right) => {
430 self.user.align_names_right = Some(align_right);
431 }
432 Message::FocusItem(idx) => {
433 let waves = self.user.waves.as_mut()?;
434
435 let visible_items_len = waves.displayed_items.len();
436 if idx.0 < visible_items_len {
437 waves.focused_item = Some(idx);
438 } else {
439 error!(
440 "Can not focus variable {} because only {visible_items_len} variables are visible.",
441 idx.0
442 );
443 }
444 }
445 Message::ItemSelectRange(select_to) => {
446 let waves = self.user.waves.as_mut()?;
447 let select_from = waves.focused_item?;
448 waves
449 .items_tree
450 .xselect_visible_range(select_from, select_to, true);
451 }
452 Message::ItemSelectAll => {
453 let waves = self.user.waves.as_mut()?;
454 waves.items_tree.xselect_all_visible(true);
455 }
456 Message::SetItemSelected(vidx, selected) => {
457 let waves = self.user.waves.as_mut()?;
458 waves.items_tree.xselect(vidx, selected);
459 }
460 Message::ToggleItemSelected(vidx) => {
461 let waves = self.user.waves.as_mut()?;
462 let node = vidx
463 .or(waves.focused_item)
464 .and_then(|vidx| waves.items_tree.to_displayed(vidx))
465 .and_then(|item| waves.items_tree.get_mut(item))?;
466 node.selected = !node.selected;
467 }
468 Message::SetDefaultTimeline(v) => {
469 self.user.show_default_timeline = Some(v);
470 }
471 Message::UnfocusItem => {
472 let waves = self.user.waves.as_mut()?;
473 waves.focused_item = None;
474 }
475 Message::MoveFocus(direction, count, select) => {
476 let waves = self.user.waves.as_mut()?;
477 let visible_item_cnt = waves.items_tree.iter_visible().count();
478 if visible_item_cnt == 0 {
479 return None;
480 }
481
482 let new_focus_vidx = VisibleItemIndex(match direction {
483 MoveDir::Up => waves
484 .focused_item
485 .map_or(visible_item_cnt, |vidx| vidx.0)
486 .saturating_sub(count),
487 MoveDir::Down => waves
488 .focused_item
489 .map_or(usize::MAX, |vidx| vidx.0)
490 .wrapping_add(count)
491 .clamp(0, visible_item_cnt - 1),
492 });
493
494 if select {
495 if let Some(vidx) = waves.focused_item {
496 waves.items_tree.xselect(vidx, true);
497 }
498 waves.items_tree.xselect(new_focus_vidx, true);
499 }
500 waves.focused_item = Some(new_focus_vidx);
501 }
502 Message::FocusTransaction(tx_ref, tx) => {
503 if let Some(tx_ref) = tx_ref.as_ref()
504 && tx.is_none()
505 {
506 self.save_current_canvas(format!("Focus Transaction id: {}", tx_ref.id));
507 }
508 let waves = self.user.waves.as_mut()?;
509 let invalidate = tx.is_none();
510 waves.focused_transaction =
511 (tx_ref, tx.or_else(|| waves.focused_transaction.1.clone()));
512 if invalidate {
513 self.invalidate_draw_commands();
514 }
515 }
516 Message::ScrollToItem(position) => {
517 let waves = self.user.waves.as_mut()?;
518 waves.scroll_to_item(position);
519 }
520 Message::SetScrollOffset(offset) => {
521 let waves = self.user.waves.as_mut()?;
522 waves.scroll_offset = offset;
523 }
524 Message::SetLogsVisible(visibility) => self.user.show_logs = visibility,
525 Message::SetFrameBufferVariable(variable_ref) => {
526 let waves = self.user.waves.as_mut()?;
527 if let Some(cmd) = waves
528 .inner
529 .as_waves_mut()?
530 .load_variables(std::iter::once(&variable_ref))
531 .map_err(|e| error!("{e:#?}"))
532 .ok()
533 .flatten()
534 {
535 self.load_variables(cmd);
536 }
537 self.frame_buffer_content = Some(FrameBufferContent::Variable(variable_ref));
538 }
539 Message::SetFrameBufferVisibleVariable(None) => {
540 self.frame_buffer_content = None;
541 }
542 Message::SetFrameBufferVisibleVariable(Some(vidx)) => {
543 let waves = self.user.waves.as_ref()?;
544 self.frame_buffer_content = waves
545 .items_tree
546 .get_visible(vidx)
547 .and_then(|node| waves.displayed_items.get(&node.item_ref))
548 .and_then(|item| match item {
549 DisplayedItem::Variable(variable) => {
550 Some(FrameBufferContent::Variable(variable.variable_ref.clone()))
551 }
552 _ => None,
553 });
554 }
555 Message::SetFrameBufferArray(scope_ref) => {
556 let waves = self.user.waves.as_mut()?;
557 let (levels, all_leaf_vars) = {
558 let wave_container = waves.inner.as_waves()?;
559 build_frame_buffer_content(wave_container, &scope_ref)?
560 };
561 if let Some(cmd) = waves
562 .inner
563 .as_waves_mut()?
564 .load_variables(all_leaf_vars.iter())
565 .map_err(|e| error!("{e:#?}"))
566 .ok()
567 .flatten()
568 {
569 self.load_variables(cmd);
570 }
571 self.frame_buffer_content = Some(FrameBufferContent::Array { scope_ref, levels });
572 }
573
574 Message::SetFrameBufferMode(mode, bits1, bits2, bits3) => {
575 let settings = &mut self.user.frame_buffer.color_settings;
576 settings.color_mode = mode;
577 match mode {
578 FrameBufferColorMode::Grayscale => {
579 settings.grayscale_bits = bits1.clamp(1, 8);
580 }
581 FrameBufferColorMode::Rgb => {
582 settings.r_bits = bits1.min(8);
583 settings.g_bits = bits2.min(8);
584 settings.b_bits = bits3.min(8);
585 }
586 FrameBufferColorMode::YCbCr => {
587 settings.y_bits = bits1.min(8);
588 settings.cb_bits = bits2.min(8);
589 settings.cr_bits = bits3.min(8);
590 }
591 }
592 self.frame_buffer_pixel_cache = None;
593 }
594 Message::SetFrameBufferWidth(width) => {
595 self.user.frame_buffer.pixels_per_row = width.max(1);
596 }
597 Message::SetFrameBufferRange(ranges) => {
598 let Some(FrameBufferContent::Array { levels, .. }) =
599 self.frame_buffer_content.as_mut()
600 else {
601 return None;
602 };
603
604 for (level, (first, last)) in levels.iter_mut().zip(ranges) {
605 let mut clamped_first = first.clamp(level.min_index, level.max_index);
606 let mut clamped_last = last.clamp(level.min_index, level.max_index);
607 if clamped_first > clamped_last {
608 std::mem::swap(&mut clamped_first, &mut clamped_last);
609 }
610 level.first_index = clamped_first;
611 level.last_index = clamped_last;
612 }
613 }
614 Message::OpenMemoryViewer { scope, name } => {
615 let waves = self.user.waves.as_mut()?;
616 let wave_container = waves.inner.as_waves()?;
617
618 let sibling_variables = wave_container.variables_in_scope(&scope);
619
620 if let Some(cmd) = waves
621 .inner
622 .as_waves_mut()?
623 .load_variables(sibling_variables.iter())
624 .map_err(|e| error!("{e:#?}"))
625 .ok()
626 .flatten()
627 {
628 self.load_variables(cmd);
629 }
630
631 self.memory_viewer.open = true;
632 self.memory_viewer.scope = Some(scope);
633 self.memory_viewer.name = name;
634 }
635 Message::SetCursorWindowVisible(visibility) => {
636 self.user.show_cursor_window = visibility;
637 }
638 Message::VerticalScroll(direction, count) => {
639 let waves = self.user.waves.as_mut()?;
640 let current_item = waves.get_top_item();
641 match direction {
642 MoveDir::Down => {
643 waves.scroll_to_item(current_item + count);
644 }
645 MoveDir::Up => {
646 if current_item > count {
647 waves.scroll_to_item(current_item - count);
648 } else {
649 waves.scroll_to_item(0);
650 }
651 }
652 }
653 }
654 Message::SetSurverFileWindowVisible(visibility) => {
655 self.user.show_server_file_window = visibility;
656 }
657 Message::LoadSurverFileByIndex(file_index, load_options) => {
658 self.user.show_server_file_window = false;
660 let force_switch = self.user.selected_server_file_index != file_index;
661 if let Some(url) = self.user.surver_url.as_ref() {
662 self.load_wave_from_url(url.clone(), load_options, force_switch, file_index);
663 }
664 }
665 Message::LoadSurverFileByName(file_name, load_options) => {
666 self.user.show_server_file_window = false;
668 let file_index = self
669 .user
670 .surver_file_infos
671 .as_ref()?
672 .iter()
673 .position(|fi| fi.filename == file_name);
674 let force_switch = self.user.selected_server_file_index != file_index;
675
676 if let Some(url) = self.user.surver_url.as_ref() {
677 self.load_wave_from_url(url.clone(), load_options, force_switch, file_index);
678 }
679 }
680 Message::RemoveVisibleItems(target) => match target {
681 MessageTarget::Explicit(vidx) => {
682 let waves = self.user.waves.as_ref();
683 let item_ref = waves
684 .and_then(|waves| waves.items_tree.get_visible(vidx))
685 .map(|node| node.item_ref);
686 let undo_msg = item_ref
687 .and_then(|item_ref| {
688 waves.and_then(|waves| waves.displayed_items.get(&item_ref))
689 })
690 .map(displayed_item::DisplayedItem::name)
691 .map_or("Remove one item".to_string(), |name| {
692 format!("Remove item {name}")
693 });
694 self.save_current_canvas(undo_msg);
695
696 if let Some(waves) = self.user.waves.as_mut()
697 && let Some(item_ref) = item_ref
698 {
699 waves.remove_displayed_item(item_ref);
700 waves.compute_variable_display_names();
701 }
702 }
703 MessageTarget::CurrentSelection => {
704 self.save_current_canvas("Remove selected items".to_owned());
705 let waves = self.user.waves.as_mut()?;
706
707 let mut remove_ids: Vec<_> = waves
708 .items_tree
709 .iter_visible_selected()
710 .map(|node| node.item_ref)
711 .collect();
712 if let Some(node) = waves
713 .focused_item
714 .and_then(|focus| waves.items_tree.get_visible(focus))
715 {
716 remove_ids.push(node.item_ref);
717 }
718 for &item_ref in &remove_ids {
719 waves.remove_displayed_item(item_ref);
720 }
721 waves.compute_variable_display_names();
722 }
723 },
724 Message::RemoveItems(items) => {
725 let undo_msg = self
726 .user
727 .waves
728 .as_ref()
729 .and_then(|waves| {
730 if items.len() == 1 {
731 items.first().and_then(|item_ref| {
732 waves
733 .displayed_items
734 .get(item_ref)
735 .map(|item| format!("Remove item {}", item.name()))
736 })
737 } else {
738 Some(format!("Remove {} items", items.len()))
739 }
740 })
741 .unwrap_or_default();
742 self.save_current_canvas(undo_msg);
743
744 let waves = self.user.waves.as_mut()?;
745 for id in items.iter().sorted_unstable_by(|a, b| Ord::cmp(b, a)) {
746 waves.remove_displayed_item(*id);
747 }
748 }
749 Message::MoveFocusedItem(direction, count) => {
750 self.save_current_canvas(format!("Move item {direction}, {count}"));
751 self.invalidate_draw_commands();
752 let waves = self.user.waves.as_mut()?;
753 let mut vidx = waves.focused_item?;
754 for _ in 0..count {
755 vidx = waves
756 .items_tree
757 .move_item(vidx, direction, |node| {
758 matches!(
759 waves.displayed_items.get(&node.item_ref),
760 Some(DisplayedItem::Group(..))
761 )
762 })
763 .expect("move failed for unknown reason");
764 }
765 waves.focused_item = waves.focused_item.and(Some(vidx));
766 }
767 Message::CanvasScroll {
768 delta,
769 viewport_idx,
770 } => {
771 let waves = self.user.waves.as_mut()?;
772 waves.viewports[viewport_idx]
773 .handle_canvas_scroll(f64::from(delta.y) + f64::from(delta.x));
774 self.invalidate_draw_commands();
775 }
776 Message::CanvasZoom {
777 delta,
778 mouse_ptr,
779 viewport_idx,
780 } => {
781 let waves = self.user.waves.as_mut()?;
782 if waves.max_timestamp().is_some() {
783 let range = waves.time_range().clone();
784 waves.viewports[viewport_idx].handle_canvas_zoom(
785 mouse_ptr,
786 f64::from(delta),
787 &range,
788 );
789 self.invalidate_draw_commands();
790 } else {
791 warn!(
792 "Canvas zoom: No timestamps count, even though waveforms should be loaded"
793 );
794 }
795 }
796 Message::ZoomToCursor {
797 delta,
798 viewport_idx,
799 } => {
800 let waves = self.user.waves.as_mut()?;
801 if waves.max_timestamp().is_some() {
802 let cursor = waves.cursor.clone()?;
803 let range = waves.time_range().clone();
804 waves.viewports[viewport_idx].zoom_to_time(&cursor, f64::from(delta), &range);
805 self.invalidate_draw_commands();
806 } else {
807 warn!(
808 "Zoom to cursor: No timestamps count, even though waveforms should be loaded"
809 );
810 }
811 }
812 Message::ZoomToFit { viewport_idx } => {
813 let waves = self.user.waves.as_mut()?;
814 waves.viewports[viewport_idx].zoom_to_fit();
815 self.invalidate_draw_commands();
816 }
817 Message::GoToEnd { viewport_idx } => {
818 let waves = self.user.waves.as_mut()?;
819 waves.viewports[viewport_idx].go_to_end();
820 self.invalidate_draw_commands();
821 }
822 Message::GoToStart { viewport_idx } => {
823 let waves = self.user.waves.as_mut()?;
824 waves.viewports[viewport_idx].go_to_start();
825 self.invalidate_draw_commands();
826 }
827 Message::GoToTime(time, viewport_idx) => {
828 let waves = self.user.waves.as_mut()?;
829 if waves.max_timestamp().is_some() {
831 let time = time?;
832 let range = waves.time_range().clone();
833 waves.viewports[viewport_idx].go_to_time(&time.clone(), &range);
834 self.invalidate_draw_commands();
835 } else {
836 warn!(
837 "Go to time: No timestamps count, even though waveforms should be loaded"
838 );
839 }
840 }
841 Message::SetTimeUnit(timeunit) => {
842 self.user.wanted_timeunit = timeunit;
843 self.invalidate_draw_commands();
844 }
845 Message::SetTimeStringFormatting(format) => {
846 self.user.time_string_format = format;
847 self.invalidate_draw_commands();
848 }
849 Message::ZoomToRange {
850 start,
851 end,
852 viewport_idx,
853 } => {
854 let waves = self.user.waves.as_mut()?;
855 if waves.max_timestamp().is_some() {
857 let range = waves.time_range().clone();
858 waves.viewports[viewport_idx].zoom_to_range(&start, &end, &range);
859 self.invalidate_draw_commands();
860 } else {
861 warn!(
862 "Zoom to range: No timestamps count, even though waveforms should be loaded"
863 );
864 }
865 }
866 Message::VariableFormatChange(displayed_field_ref, format) => {
867 let waves = self.user.waves.as_mut()?;
868 if !self
869 .translators
870 .all_translator_names()
871 .contains(&format.as_str())
872 {
873 warn!("No translator {format}");
874 return None;
875 }
876
877 let update_format =
878 |variable: &mut DisplayedVariable, field_ref: DisplayedFieldRef| {
879 if field_ref.field.is_empty() {
880 let Ok(meta) = waves
881 .inner
882 .as_waves()
883 .unwrap()
884 .variable_meta(&variable.variable_ref)
885 .map_err(|e| {
886 warn!("Error trying to get variable metadata: {e:#?}");
887 })
888 else {
889 return;
890 };
891 let translator = self.translators.get_translator(&format);
892 let new_info = translator.variable_info(&meta).unwrap();
893
894 variable.format = Some(format.clone());
895 variable.info = new_info;
896
897 variable.downgrade_type_limits_if_unsupported(translator, &meta);
898 } else {
899 variable
900 .field_formats
901 .retain(|ff| ff.field != field_ref.field);
902 variable.field_formats.push(FieldFormat {
903 field: field_ref.field,
904 format: format.clone(),
905 });
906 }
907 };
908
909 let focused = waves
911 .focused_item
912 .and_then(|vidx| waves.items_tree.get_visible(vidx))
913 .map(|node| node.item_ref);
914
915 let mut redraw = false;
916
917 match displayed_field_ref {
918 MessageTarget::Explicit(field_ref) => {
919 if let Some(DisplayedItem::Variable(displayed_variable)) =
920 waves.displayed_items.get_mut(&field_ref.item)
921 {
922 update_format(displayed_variable, field_ref);
923 redraw = true;
924 }
925 }
926 MessageTarget::CurrentSelection => {
927 if let Some(focused) = focused
929 && let Some(DisplayedItem::Variable(displayed_variable)) =
930 waves.displayed_items.get_mut(&focused)
931 {
932 update_format(displayed_variable, DisplayedFieldRef::from(focused));
933 redraw = true;
934 }
935 for item in waves
936 .items_tree
937 .iter_visible_selected()
938 .map(|node| node.item_ref)
939 {
940 let field_ref = DisplayedFieldRef::from(item);
942 if let Some(DisplayedItem::Variable(variable)) =
943 waves.displayed_items.get_mut(&item)
944 {
945 update_format(variable, field_ref);
946 }
947 redraw = true;
948 }
949 }
950 }
951
952 if redraw {
953 self.invalidate_draw_commands();
954 }
955 }
956 Message::ItemSelectionClear => {
957 let waves = self.user.waves.as_mut()?;
958 waves.items_tree.xselect_all_visible(false);
959 }
960 Message::ItemColorChange(vidx, color_name) => {
961 self.save_current_canvas(format!(
962 "Change item color to {}",
963 color_name.clone().unwrap_or("default".into())
964 ));
965 self.invalidate_draw_commands();
966 let waves = self.user.waves.as_mut()?;
967
968 match vidx {
969 MessageTarget::Explicit(vidx) => {
970 let node = waves.items_tree.get_visible(vidx)?;
971 waves
972 .displayed_items
973 .entry(node.item_ref)
974 .and_modify(|item| item.set_color(&color_name));
975 }
976 MessageTarget::CurrentSelection => {
977 if let Some(focused) = waves.focused_item {
978 let node = waves.items_tree.get_visible(focused)?;
979 waves
980 .displayed_items
981 .entry(node.item_ref)
982 .and_modify(|item| item.set_color(&color_name));
983 }
984
985 for node in waves.items_tree.iter_visible_selected() {
986 waves
987 .displayed_items
988 .entry(node.item_ref)
989 .and_modify(|item| item.set_color(&color_name));
990 }
991 }
992 }
993 }
994 Message::ItemNameChange(vidx, name) => {
995 self.save_current_canvas(format!(
996 "Change item name to {}",
997 name.clone().unwrap_or("default".into())
998 ));
999 let waves = self.user.waves.as_mut()?;
1000 let vidx = vidx.or(waves.focused_item)?;
1001 let node = waves.items_tree.get_visible(vidx)?;
1002 waves
1003 .displayed_items
1004 .entry(node.item_ref)
1005 .and_modify(|item| item.set_name(name));
1006 }
1007 Message::ItemNameReset(target) => {
1008 self.save_current_canvas("Resetting item name(s)".to_owned());
1009 let waves = self.user.waves.as_mut()?;
1010 match target {
1011 MessageTarget::Explicit(vidx) => {
1012 let node = waves.items_tree.get_visible(vidx)?;
1013 waves
1014 .displayed_items
1015 .entry(node.item_ref)
1016 .and_modify(|item| item.set_name(None));
1017 }
1018 MessageTarget::CurrentSelection => {
1019 for node in waves.items_tree.iter_visible_selected() {
1020 waves
1021 .displayed_items
1022 .entry(node.item_ref)
1023 .and_modify(|item| item.set_name(None));
1024 }
1025 }
1026 }
1027 }
1028 Message::ItemBackgroundColorChange(vidx, color_name) => {
1029 self.save_current_canvas(format!(
1030 "Change item background color to {}",
1031 color_name.clone().unwrap_or("default".into())
1032 ));
1033 let waves = self.user.waves.as_mut()?;
1034
1035 match vidx {
1036 MessageTarget::Explicit(vidx) => {
1037 let node = waves.items_tree.get_visible(vidx)?;
1038 waves
1039 .displayed_items
1040 .entry(node.item_ref)
1041 .and_modify(|item| item.set_background_color(&color_name));
1042 }
1043 MessageTarget::CurrentSelection => {
1044 if let Some(focused) = waves.focused_item {
1045 let node = waves.items_tree.get_visible(focused)?;
1046 waves
1047 .displayed_items
1048 .entry(node.item_ref)
1049 .and_modify(|item| item.set_background_color(&color_name));
1050 }
1051
1052 for node in waves.items_tree.iter_visible_selected() {
1053 waves
1054 .displayed_items
1055 .entry(node.item_ref)
1056 .and_modify(|item| item.set_background_color(&color_name));
1057 }
1058 }
1059 }
1060 }
1061 Message::ItemHeightScalingFactorChange(vidx, scale) => {
1062 self.save_current_canvas(format!("Change item height scaling factor to {scale}"));
1063 let waves = self.user.waves.as_mut()?;
1064
1065 match vidx {
1066 MessageTarget::Explicit(vidx) => {
1067 let node = waves.items_tree.get_visible(vidx)?;
1068 waves
1069 .displayed_items
1070 .entry(node.item_ref)
1071 .and_modify(|item| item.set_height_scaling_factor(scale));
1072 }
1073 MessageTarget::CurrentSelection => {
1074 if let Some(focused) = waves.focused_item {
1075 let node = waves.items_tree.get_visible(focused)?;
1076 waves
1077 .displayed_items
1078 .entry(node.item_ref)
1079 .and_modify(|item| item.set_height_scaling_factor(scale));
1080 }
1081
1082 for node in waves.items_tree.iter_visible_selected() {
1083 waves
1084 .displayed_items
1085 .entry(node.item_ref)
1086 .and_modify(|item| item.set_height_scaling_factor(scale));
1087 }
1088 }
1089 }
1090 }
1091 Message::SetAnalogSettings(vidx, new_settings) => {
1092 self.save_current_canvas("Set analog state".into());
1093 self.invalidate_draw_commands();
1094 let analog_waveform_multiplier = self.user.config.layout.analog_waveform_multiplier;
1095 let waves = self.user.waves.as_mut()?;
1096
1097 let update = |item: &mut DisplayedItem| {
1099 if let DisplayedItem::Variable(var) = item {
1100 match (&mut var.analog, new_settings) {
1101 (Some(s), Some(new)) => s.settings = new,
1102 (None, Some(new)) => {
1103 var.analog = Some(AnalogVarState::new(new));
1104 var.height_scaling_factor = Some(analog_waveform_multiplier);
1105 }
1106 (_, None) => var.analog = None,
1107 }
1108 }
1109 };
1110
1111 match vidx {
1112 MessageTarget::Explicit(vidx) => {
1113 let node = waves.items_tree.get_visible(vidx)?;
1114 waves
1115 .displayed_items
1116 .entry(node.item_ref)
1117 .and_modify(update);
1118 }
1119 MessageTarget::CurrentSelection => {
1120 if let Some(focused) = waves.focused_item {
1121 let node = waves.items_tree.get_visible(focused)?;
1122 waves
1123 .displayed_items
1124 .entry(node.item_ref)
1125 .and_modify(update);
1126 }
1127 for node in waves.items_tree.iter_visible_selected() {
1128 waves
1129 .displayed_items
1130 .entry(node.item_ref)
1131 .and_modify(update);
1132 }
1133 }
1134 }
1135 }
1136 Message::MoveCursorToTransition {
1137 next,
1138 variable,
1139 skip_zero,
1140 } => {
1141 let waves = self.user.waves.as_mut()?;
1142 if waves.max_timestamp().is_some() {
1144 if waves.cursor.is_none()
1148 && waves.focused_item.is_some()
1149 && let Some(vp) = waves.viewports.first()
1150 {
1151 waves.cursor = if next {
1152 Some(vp.left_edge_time(waves.time_range()))
1153 } else {
1154 Some(vp.right_edge_time(waves.time_range()))
1155 };
1156 }
1157 waves.set_cursor_at_transition(next, variable, skip_zero);
1158 let moved = waves.go_to_cursor_if_not_in_view();
1159 if moved {
1160 self.invalidate_draw_commands();
1161 }
1162 } else {
1163 warn!(
1164 "Move cursor to transition: No timestamps count, even though waveforms should be loaded"
1165 );
1166 }
1167 }
1168 Message::MoveTransaction { next } => {
1169 let undo_msg = if next {
1170 "Move to next transaction"
1171 } else {
1172 "Move to previous transaction"
1173 };
1174 self.save_current_canvas(undo_msg.to_string());
1175 let waves = self.user.waves.as_mut()?;
1176 waves.move_to_transaction(next)?;
1177 self.invalidate_draw_commands();
1178 }
1179 Message::ResetVariableFormat(displayed_field_ref) => {
1180 let waves = self.user.waves.as_mut()?;
1181 if let Some(DisplayedItem::Variable(displayed_variable)) =
1182 waves.displayed_items.get_mut(&displayed_field_ref.item)
1183 {
1184 if displayed_field_ref.field.is_empty() {
1185 displayed_variable.format = None;
1186 } else {
1187 displayed_variable
1188 .field_formats
1189 .retain(|ff| ff.field != displayed_field_ref.field);
1190 }
1191 self.invalidate_draw_commands();
1192 }
1193 }
1194 Message::CursorSet(time) => {
1195 let waves = self.user.waves.as_mut()?;
1196 waves.cursor = Some(time);
1197 }
1198 Message::ExpandParameterSection => {
1199 self.expand_parameter_section = true;
1200 }
1201 Message::LoadFile(filename, load_options) => {
1202 self.user.selected_server_file_index = None;
1203 *self.surver_selected_file.borrow_mut() = None;
1204 #[cfg(not(target_arch = "wasm32"))]
1205 self.load_from_file(filename, load_options).ok();
1206 #[cfg(target_arch = "wasm32")]
1207 error!("Cannot load file from path in WASM");
1208 }
1209 Message::LoadWaveformFileFromUrl(url, load_options) => {
1210 self.user.selected_server_file_index = None;
1211 *self.surver_selected_file.borrow_mut() = None;
1212 self.load_wave_from_url(url, load_options, true, None);
1214 }
1215 Message::LoadFromData(data, load_options) => {
1216 self.user.selected_server_file_index = None;
1217 *self.surver_selected_file.borrow_mut() = None;
1218 self.load_from_data(data, load_options).ok();
1219 }
1220 #[cfg(feature = "python")]
1221 Message::LoadPythonTranslator(filename) => {
1222 try_log_error!(
1223 self.translators.load_python_translator(filename),
1224 "Error loading Python translator",
1225 )
1226 }
1227 #[cfg(all(not(target_arch = "wasm32"), feature = "wasm_plugins"))]
1228 Message::LoadWasmTranslator(path) => {
1229 let sender = self.channels.msg_sender.clone();
1230 let max_memory_mib = self.user.config.plugin.max_memory_mib;
1231 perform_work(move || {
1232 match PluginTranslator::new(path.into_std_path_buf(), max_memory_mib) {
1233 Ok(t) => {
1234 checked_send(&sender, Message::TranslatorLoaded(Arc::new(t)));
1235 }
1236 Err(e) => {
1237 error!("Failed to load wasm translator {e:#}");
1238 }
1239 }
1240 });
1241 }
1242 Message::LoadCommandFile(path) => {
1243 self.add_batch_commands(read_command_file(&path));
1244 }
1245 Message::LoadCommandFileFromUrl(url) => {
1246 self.load_commands_from_url(url);
1247 }
1248 Message::LoadCommandFromData(bytes) => {
1249 self.add_batch_commands(read_command_bytes(bytes));
1250 }
1251 Message::ExecuteBatchCommand { line, command } => {
1252 match crate::fzcmd::parse_command(&command, command_parser::get_parser(self)) {
1253 Ok(message) => {
1254 self.update(message);
1255 }
1256 Err(error) => {
1257 error!("Error on batch commands line {line}: {error:#?}");
1258 }
1259 }
1260 }
1261 Message::SetupCxxrtl(kind) => self.connect_to_cxxrtl(kind, false),
1262 Message::SetSurverStatus(_start, server, status) => {
1263 self.user.surver_file_infos = Some(status.file_infos.clone());
1264 info!(
1265 "Received surfer server status from {server}. {} files available.",
1266 status.file_infos.len()
1267 );
1268 self.user.surver_url = Some(server.clone());
1269 if status.file_infos.is_empty() {
1270 warn!("Received surfer server status with no file infos");
1271 return None;
1272 }
1273 if self.user.selected_server_file_index.is_none() {
1274 if status.file_infos.len() == 1 {
1275 info!(
1277 "Only one file available on server {}, loading it automatically",
1278 server
1279 );
1280 self.load_wave_from_url(server.clone(), LoadOptions::Clear, false, Some(0));
1281 } else {
1282 self.user.show_server_file_window = true;
1284 self.progress_tracker = None;
1285 }
1286 }
1287
1288 if let Some(file_index) = self.user.selected_server_file_index {
1289 if file_index >= status.file_infos.len() {
1290 warn!(
1291 "Selected server file index {file_index} is out of bounds ({} files available)",
1292 status.file_infos.len()
1293 );
1294 return None;
1295 }
1296 self.server_status_to_progress(&server, &status.file_infos[file_index]);
1297 }
1298 }
1299 Message::FileDropped(dropped_file) => {
1300 self.load_from_dropped(dropped_file)
1301 .map_err(|e| error!("{e:#?}"))
1302 .ok();
1303 }
1304 Message::DroppedFileBytesLoaded(path, bytes) => {
1305 self.load_from_dropped_bytes(path, bytes)
1306 .map_err(|e| error!("{e:#?}"))
1307 .ok();
1308 }
1309 Message::StopProgressTracker => {
1310 self.progress_tracker = None;
1311 }
1312 Message::WaveHeaderLoaded(start, source, load_options, header) => {
1313 info!(
1315 "Loaded the hierarchy and meta-data of {source} in {:?}",
1316 start.elapsed()
1317 );
1318 match header {
1319 HeaderResult::LocalFile(header) => {
1320 let shared_hierarchy = Arc::new(header.hierarchy);
1322 let new_waves =
1323 Box::new(WaveContainer::new_waveform(shared_hierarchy.clone()));
1324 self.on_waves_loaded(
1325 source.clone(),
1326 convert_format(header.file_format),
1327 new_waves,
1328 load_options,
1329 );
1330 self.load_wave_body(source, header.body, header.body_len, shared_hierarchy);
1332 }
1333 HeaderResult::LocalBytes(header) => {
1334 let shared_hierarchy = Arc::new(header.hierarchy);
1336 let new_waves =
1337 Box::new(WaveContainer::new_waveform(shared_hierarchy.clone()));
1338 self.on_waves_loaded(
1339 source.clone(),
1340 convert_format(header.file_format),
1341 new_waves,
1342 load_options,
1343 );
1344 self.load_wave_body(source, header.body, header.body_len, shared_hierarchy);
1346 }
1347 HeaderResult::Remote(hierarchy, file_format, server, file_index) => {
1348 let new_waves = Box::new(WaveContainer::new_remote_waveform(
1350 &server,
1351 hierarchy.clone(),
1352 file_index,
1353 ));
1354 self.on_waves_loaded(
1355 source.clone(),
1356 convert_format(file_format),
1357 new_waves,
1358 load_options,
1359 );
1360 get_time_table_from_server(
1362 self.channels.msg_sender.clone(),
1363 server,
1364 file_index,
1365 );
1366 }
1367 }
1368 }
1369 Message::WaveBodyLoaded(start, source, body) => {
1370 info!("Loaded the body of {source} in {:?}", start.elapsed());
1372 self.progress_tracker = None;
1373 let enable_time_offset = self.enable_time_offset();
1374 let waves = self
1375 .user
1376 .waves
1377 .as_mut()
1378 .expect("Waves should be loaded at this point!");
1379 let maybe_cmd = waves .inner
1382 .as_waves_mut()?
1383 .wellen_add_body(body)
1384 .map_err(|err| {
1385 error!("While getting commands to lazy-load signals: {err:?}");
1386 })
1387 .ok()
1388 .flatten();
1389 let param_cmd = waves
1391 .inner
1392 .as_waves_mut()?
1393 .load_parameters()
1394 .map_err(|err| {
1395 error!("While getting commands to lazy-load parameters: {err:?}");
1396 })
1397 .ok()
1398 .flatten();
1399
1400 if self.wcp_greeted_signal.load(Ordering::Relaxed)
1401 && self.wcp_client_capabilities.waveforms_loaded
1402 {
1403 let source = match source {
1404 WaveSource::File(path) => path.to_string(),
1405 WaveSource::Url(url) => url,
1406 _ => String::new(),
1407 };
1408 self.channels.wcp_s2c_sender.as_ref().map(|ch| {
1409 block_on(
1410 ch.send(WcpSCMessage::event(WcpEvent::waveforms_loaded { source })),
1411 )
1412 });
1413 }
1414
1415 waves.refresh_time_range(enable_time_offset);
1417 waves.update_viewports();
1419 self.invalidate_draw_commands();
1421 if let Some(cmd) = param_cmd {
1423 self.load_variables(cmd);
1424 }
1425 if let Some(cmd) = maybe_cmd {
1427 self.load_variables(cmd);
1428 }
1429 }
1430 Message::SignalsLoaded(start, res) => {
1431 info!("Loaded {} variables in {:?}", res.len(), start.elapsed());
1432 self.progress_tracker = None;
1433 let waves = self
1434 .user
1435 .waves
1436 .as_mut()
1437 .expect("Waves should be loaded at this point!");
1438 match waves.inner.as_waves_mut()?.on_signals_loaded(res) {
1439 Err(err) => error!("{err:?}"),
1440 Ok(Some(cmd)) => self.load_variables(cmd),
1441 _ => {}
1442 }
1443 self.invalidate_draw_commands();
1445 }
1446 Message::WavesLoaded(filename, format, new_waves, load_options) => {
1447 self.on_waves_loaded(filename, format, new_waves, load_options);
1448 let enable_time_offset = self.enable_time_offset();
1450 let waves = self
1451 .user
1452 .waves
1453 .as_mut()
1454 .expect("Waves should be loaded at this point!");
1455 waves.refresh_time_range(enable_time_offset);
1456 waves.update_viewports();
1457 self.progress_tracker = None;
1458 }
1459 Message::TransactionStreamsLoaded(filename, format, new_ftr, loaded_options) => {
1460 self.on_transaction_streams_loaded(filename, format, new_ftr, loaded_options);
1461 let enable_time_offset = self.enable_time_offset();
1462 let waves = self
1463 .user
1464 .waves
1465 .as_mut()
1466 .expect("Waves should be loaded at this point!");
1467 waves.refresh_time_range(enable_time_offset);
1468 waves.update_viewports();
1469 }
1470 Message::BlacklistTranslator(idx, translator) => {
1471 self.user.blacklisted_translators.insert((idx, translator));
1472 }
1473 Message::TranslatorLoaded(t) => {
1474 info!("Translator {} loaded", t.name());
1475 t.set_wave_source(
1476 self.user
1477 .waves
1478 .as_ref()
1479 .map(|waves| waves.source.into_translation_type()),
1480 );
1481
1482 self.translators.add_or_replace(AnyTranslator::Full(t));
1483 }
1484 Message::SetSidePanelVisible(v) => self.user.show_hierarchy = Some(v),
1485 Message::SetMenuVisible(v) => self.user.show_menu = Some(v),
1486 Message::ToggleMenu => {
1487 self.user.show_menu = Some(!self.show_menu());
1488 }
1489 Message::SetToolbarVisible(v) => self.user.show_toolbar = Some(v),
1490 Message::SetToolbarGroupEnabled(group_id, enabled) => {
1491 self.user
1492 .toolbar_group_enabled
1493 .insert(group_id, Some(enabled));
1494 }
1495 Message::SetToolbarGroupRow(group_id, row) => {
1496 self.set_toolbar_group_row(&group_id, row);
1497 }
1498 Message::SetShowEmptyScopes(v) => self.user.show_empty_scopes = Some(v),
1499 Message::SetShowHierarchyIcons(v) => self.user.show_hierarchy_icons = Some(v),
1500 Message::SetParameterDisplayLocation(location) => {
1501 self.user.parameter_display_location = Some(location);
1502 }
1503 Message::SetStatusbarVisible(v) => self.user.show_statusbar = Some(v),
1504 Message::SetTickLines(v) => self.user.show_ticks = Some(v),
1505 Message::SetVariableTooltip(v) => self.user.show_tooltip = Some(v),
1506 Message::SetScopeTooltip(v) => self.user.show_scope_tooltip = Some(v),
1507 Message::SetOverviewVisible(v) => self.user.show_overview = Some(v),
1508 Message::SetShowVariableDirection(v) => self.user.show_variable_direction = Some(v),
1509 Message::SetTransitionValue(v) => self.user.transition_value = Some(v),
1510 Message::SetDrawVectorUnknownsAsLine(v) => {
1511 self.user.draw_vector_unknowns_as_line = Some(v);
1512 }
1513 Message::SetFocusHighlight(focus_highlight) => {
1514 self.user.focus_highlight = Some(focus_highlight);
1515 }
1516 Message::SetShowIndices(v) => {
1517 let new = v;
1518 self.user.show_variable_indices = Some(new);
1519 let waves = self.user.waves.as_mut()?;
1520 waves.display_variable_indices = new;
1521 waves.compute_variable_display_names();
1522 }
1523 Message::HideCommandPrompt => {
1524 *self.command_prompt_text.borrow_mut() = String::new();
1525 self.command_prompt.suggestions = vec![];
1526 self.command_prompt.selected = self.command_prompt.previous_commands.len();
1527 self.command_prompt.visible = false;
1528 }
1529 Message::ShowCommandPrompt(text, selected) => {
1530 self.command_prompt.new_text = Some((text, selected.unwrap_or(String::new())));
1531 self.command_prompt.visible = true;
1532 }
1533 Message::FileDownloaded(url, bytes, load_options) => {
1534 self.load_from_bytes(WaveSource::Url(url), bytes.to_vec(), load_options);
1535 }
1536 Message::CommandFileDownloaded(_url, bytes) => {
1537 self.add_batch_commands(read_command_bytes(bytes.to_vec()));
1538 self.progress_tracker = None;
1539 }
1540 Message::SetConfigFromString(s) => {
1541 let config = SurferConfig::new_from_toml(&s)
1543 .with_context(|| "Failed to load config file")
1544 .ok()?;
1545
1546 self.user.config = config;
1547
1548 let enable_time_offset = self.enable_time_offset();
1550 if let Some(waves) = &mut self.user.waves {
1551 waves.refresh_time_range(enable_time_offset);
1552 }
1553
1554 let ctx = &self.context.as_ref()?;
1555 ctx.set_visuals(self.get_visuals());
1556 }
1557 Message::ReloadConfig => {
1558 let config = SurferConfig::new(false)
1560 .with_context(|| "Failed to load config file")
1561 .ok()?;
1562 self.translators = all_translators();
1563 self.user.config = config;
1564
1565 let enable_time_offset = self.enable_time_offset();
1567 if let Some(waves) = &mut self.user.waves {
1568 waves.refresh_time_range(enable_time_offset);
1569 }
1570
1571 let ctx = &self.context.as_ref()?;
1572 ctx.set_visuals(self.get_visuals());
1573 }
1574 Message::ReloadWaveform(keep_unavailable) => {
1575 let waves = self.user.waves.as_ref()?;
1576 let options = if keep_unavailable {
1577 LoadOptions::KeepAll
1578 } else {
1579 LoadOptions::KeepAvailable
1580 };
1581 match &waves.source {
1582 WaveSource::File(filename) => {
1583 self.load_from_file(filename.clone(), options).ok();
1584 }
1585 WaveSource::Data => {} WaveSource::Cxxrtl(..) => {} WaveSource::DragAndDrop(filename) => {
1588 filename
1589 .clone()
1590 .and_then(|filename| self.load_from_file(filename, options).ok());
1591 }
1592 WaveSource::Url(url) => {
1593 self.load_wave_from_url(
1594 url.clone(),
1595 options,
1596 false,
1597 self.user.selected_server_file_index,
1598 );
1599 }
1600 }
1601
1602 for translator in self.translators.all_translators() {
1603 translator.reload(self.channels.msg_sender.clone());
1604 }
1605 self.variable_name_info_cache.borrow_mut().clear();
1606 self.translator_generation += 1;
1607
1608 if let Some(waves) = self.user.waves.as_mut() {
1609 waves.compute_variable_display_names();
1610 }
1611 }
1612 Message::SuggestReloadWaveform => match self.autoreload_files() {
1613 AutoLoad::Always => self.update(Message::ReloadWaveform(true))?,
1614 AutoLoad::Never => (),
1615 AutoLoad::Ask => {
1616 self.user.show_reload_suggestion = Some(ReloadWaveformDialog::default());
1617 }
1618 },
1619 Message::CloseReloadWaveformDialog {
1620 reload_file,
1621 do_not_show_again,
1622 } => {
1623 if do_not_show_again {
1624 self.user.autoreload_files = Some(AutoLoad::from_bool(reload_file));
1627 }
1628 self.user.show_reload_suggestion = None;
1629 if reload_file {
1630 self.update(Message::ReloadWaveform(true));
1631 }
1632 }
1633 Message::UpdateReloadWaveformDialog(dialog) => {
1634 self.user.show_reload_suggestion = Some(dialog);
1635 }
1636 Message::OpenSiblingStateFile(open) => {
1637 if !open {
1638 return None;
1639 }
1640 let waves = self.user.waves.as_ref()?;
1641 let state_file_path = waves.source.sibling_state_file()?;
1642 self.load_state_file(Some(state_file_path.clone()));
1643 }
1644 Message::SuggestOpenSiblingStateFile => match self.autoload_sibling_state_files() {
1645 AutoLoad::Always => {
1646 self.update(Message::OpenSiblingStateFile(true));
1647 }
1648 AutoLoad::Never => {}
1649 AutoLoad::Ask => {
1650 self.user.show_open_sibling_state_file_suggestion =
1651 Some(OpenSiblingStateFileDialog::default());
1652 }
1653 },
1654 Message::CloseOpenSiblingStateFileDialog {
1655 load_state,
1656 do_not_show_again,
1657 } => {
1658 if do_not_show_again {
1659 self.user.autoload_sibling_state_files = Some(AutoLoad::from_bool(load_state));
1660 }
1661 self.user.show_open_sibling_state_file_suggestion = None;
1662 if load_state {
1663 self.update(Message::OpenSiblingStateFile(true));
1664 }
1665 }
1666 Message::UpdateOpenSiblingStateFileDialog(dialog) => {
1667 self.user.show_open_sibling_state_file_suggestion = Some(dialog);
1668 }
1669 Message::RemovePlaceholders => {
1670 let waves = self.user.waves.as_mut()?;
1671 waves.remove_placeholders();
1672 }
1673 Message::SetClockHighlightType(new_type) => {
1674 self.user.clock_highlight_type = Some(new_type);
1675 self.invalidate_draw_commands();
1676 }
1677 Message::SetFillHighValues(fill) => self.user.fill_high_values = Some(fill),
1678 Message::SetTraceStyle(trace_style) => {
1679 self.user.trace_style = Some(trace_style);
1680 self.invalidate_draw_commands();
1681 }
1682 Message::ResolveMarkerSet { name, time } => {
1683 let marker_id = self.user.waves.as_ref()?.resolve_marker_name(&name);
1684 let msg = match marker_id {
1685 Some(id) => Message::SetMarker { id, time },
1686 None => Message::AddMarker {
1687 time,
1688 name: Some(name),
1689 move_focus: true,
1690 },
1691 };
1692 self.update(msg);
1693 }
1694 Message::AddMarker {
1695 time,
1696 name,
1697 move_focus,
1698 } => {
1699 if let Some(name) = &name {
1700 self.save_current_canvas(format!("Add marker {name} at {time}"));
1701 } else {
1702 self.save_current_canvas(format!("Add marker at {time}"));
1703 }
1704 let waves = self.user.waves.as_mut()?;
1705 waves.add_marker(&time, name, move_focus);
1706 }
1707 Message::SetMarker { id, time } => {
1708 self.save_current_canvas(format!("Set marker {id} to {time}"));
1709 let waves = self.user.waves.as_mut()?;
1710 waves.set_marker_position(id, &time);
1711 }
1712 Message::ResolveMarkerRemove(name) => {
1713 if let Some(id) = self.user.waves.as_ref()?.resolve_marker_name(&name) {
1714 self.update(Message::RemoveMarker(id));
1715 }
1716 }
1717 Message::RemoveMarker(id) => {
1718 let waves = self.user.waves.as_mut()?;
1719 waves.remove_marker(id);
1720 }
1721 Message::MoveMarkerToCursor(idx) => {
1722 self.save_current_canvas("Move marker".into());
1723 let waves = self.user.waves.as_mut()?;
1724 waves.move_marker_to_cursor(idx);
1725 }
1726 Message::GoToCursorIfNotInView => {
1727 let waves = self.user.waves.as_mut()?;
1728 if waves.go_to_cursor_if_not_in_view() {
1729 self.invalidate_draw_commands();
1730 }
1731 }
1732 Message::GoToMarkerPosition(idx, viewport_idx) => {
1733 let waves = self.user.waves.as_mut()?;
1734 if waves.max_timestamp().is_some() {
1736 let cursor = waves.markers.get(&idx)?.clone();
1737 let range = waves.time_range().clone();
1738 waves.viewports[viewport_idx].go_to_time(&cursor, &range);
1739 self.invalidate_draw_commands();
1740 } else {
1741 warn!(
1742 "Go to marker position: No timestamps count, even though waveforms should be loaded"
1743 );
1744 }
1745 }
1746 Message::ChangeVariableNameType(target, name_type) => {
1747 let waves = self.user.waves.as_mut()?;
1748 let recompute_names = waves.change_variable_name_type(target, name_type);
1749
1750 if recompute_names {
1751 waves.compute_variable_display_names();
1752 }
1753 }
1754 Message::ForceVariableNameTypes(name_type) => {
1755 let waves = self.user.waves.as_mut()?;
1756 waves.force_variable_name_type(name_type);
1757 }
1758 Message::CommandPromptClear => {
1759 *self.command_prompt_text.borrow_mut() = String::new();
1760 self.command_prompt.suggestions = vec![];
1761 self.command_prompt.selected = if self.command_prompt_text.borrow().is_empty() {
1763 self.command_prompt.previous_commands.len().clamp(0, 3)
1764 } else {
1765 0
1766 };
1767 }
1768 Message::CommandPromptUpdate { suggestions } => {
1769 self.command_prompt.suggestions = suggestions;
1770 self.command_prompt.selected = if self.command_prompt_text.borrow().is_empty() {
1771 self.command_prompt.previous_commands.len().clamp(0, 3)
1772 } else {
1773 0
1774 };
1775 self.command_prompt.new_selection =
1776 Some(if self.command_prompt_text.borrow().is_empty() {
1777 self.command_prompt.previous_commands.len().clamp(0, 3)
1778 } else {
1779 0
1780 });
1781 }
1782 Message::CommandPromptPushPrevious(cmd) => {
1783 let len = cmd.len();
1784 self.command_prompt
1785 .previous_commands
1786 .insert(0, (cmd, vec![false; len]));
1787 }
1788 Message::OpenFileDialog(mode) => {
1789 self.open_file_dialog(mode);
1790 }
1791 Message::OpenCommandFileDialog => {
1792 self.open_command_file_dialog();
1793 }
1794 #[cfg(feature = "python")]
1795 Message::OpenPythonPluginDialog => {
1796 self.open_python_file_dialog();
1797 }
1798 #[cfg(feature = "python")]
1799 Message::ReloadPythonPlugin => {
1800 try_log_error!(
1801 self.translators.reload_python_translator(),
1802 "Error reloading Python translator"
1803 );
1804 self.translator_generation += 1;
1805 self.invalidate_draw_commands();
1806 }
1807 Message::SaveStateFile(path) => self.save_state_file(path),
1808 #[cfg(not(target_arch = "wasm32"))]
1809 Message::ExportSignalsToFst(path) => self.export_signals_to_fst(path),
1810 Message::LoadStateFromData(bytes) => self.load_state_from_bytes(&bytes),
1811 Message::LoadStateFile(path) => self.load_state_file(path),
1812 Message::LoadState(state, path) => self.load_state(state, path),
1813 Message::SetStateFile(path) => {
1814 #[cfg(not(target_arch = "wasm32"))]
1816 {
1817 self.user.state_file = Some(path);
1818 }
1819 #[cfg(target_arch = "wasm32")]
1820 {
1821 error!("Failed to load {path:?}. Loading state files is unsupported on wasm")
1822 }
1823 }
1824 Message::SetAboutVisible(s) => self.user.show_about = s,
1825 Message::SetKeyHelpVisible(s) => self.user.show_keys = s,
1826 Message::SetGestureHelpVisible(s) => self.user.show_gestures = s,
1827 Message::SetUrlEntryVisible(s, f) => {
1828 self.user.show_url_entry = s;
1829 self.url_callback = f;
1830 }
1831 Message::SetLicenseVisible(s) => self.user.show_license = s,
1832 Message::SetQuickStartVisible(s) => self.user.show_quick_start = s,
1833 Message::SetPerformanceVisible(s) => {
1834 if !s {
1835 self.continuous_redraw = false;
1836 }
1837 self.user.show_performance = s;
1838 }
1839 Message::SetContinuousRedraw(s) => self.continuous_redraw = s,
1840 Message::SetMouseGestureDragStart(pos, time) => {
1841 self.gesture_start_location = pos;
1842 self.gesture_start_time = time;
1843 }
1844 Message::SetMeasureDragStart(pos) => self.measure_start_location = pos,
1845 Message::SetTextEditFocused(id, s) => {
1846 self.text_edit_focused.insert(id, s);
1847 }
1848 Message::SetRequestTextEditFocus(id, s) => {
1849 self.text_edit_request_focus.insert(id, s);
1850 }
1851 Message::ClearAllTextEditFocuses => {
1852 self.text_edit_focused.clear();
1853 self.text_edit_request_focus.clear();
1854 }
1855 Message::SetVariableNameFilterType(variable_name_filter_type) => {
1856 self.user.variable_filter.name_filter_type = variable_name_filter_type;
1857 }
1858 Message::SetVariableNameFilterCaseInsensitive(s) => {
1859 self.user.variable_filter.name_filter_case_insensitive = s;
1860 }
1861 Message::SetVariableIOFilter(t, b) => match t {
1862 VariableIOFilterType::Output => self.user.variable_filter.include_outputs = b,
1863 VariableIOFilterType::Input => self.user.variable_filter.include_inputs = b,
1864 VariableIOFilterType::InOut => self.user.variable_filter.include_inouts = b,
1865 VariableIOFilterType::Other => self.user.variable_filter.include_others = b,
1866 },
1867 Message::SetVariableGroupByDirection(b) => {
1868 self.user.variable_filter.group_by_direction = b;
1869 }
1870 Message::SetUIZoomFactor(scale) => {
1871 if let Some(ctx) = &mut self.context.as_ref() {
1872 ctx.set_zoom_factor(scale);
1873 }
1874 self.user.ui_zoom_factor = Some(scale);
1875 }
1876 Message::SelectPrevCommand => {
1877 self.command_prompt.new_selection = Some(
1878 self.command_prompt
1879 .new_selection
1880 .unwrap_or(self.command_prompt.selected)
1881 .saturating_sub(1),
1882 );
1883 }
1884 Message::SelectNextCommand => {
1885 self.command_prompt.new_selection = Some(
1886 self.command_prompt
1887 .new_selection
1888 .unwrap_or(self.command_prompt.selected)
1889 .saturating_add(1)
1890 .min(self.command_prompt.suggestions.len().saturating_sub(1)),
1891 );
1892 }
1893 Message::SetHierarchyStyle(style) => self.user.hierarchy_style = Some(style),
1894 Message::SetArrowKeyBindings(bindings) => {
1895 self.user.arrow_key_bindings = Some(bindings);
1896 }
1897 Message::SetPrimaryMouseDragBehavior(behavior) => {
1898 self.user.primary_button_drag_behavior = Some(behavior);
1899 }
1900 Message::SetTimeOffsetEnabled(enabled) => {
1901 self.user.enable_time_offset = Some(enabled);
1902 let enable_time_offset = self.enable_time_offset();
1903 if let Some(waves) = &mut self.user.waves {
1904 waves.refresh_time_range(enable_time_offset);
1905 waves.update_viewports();
1906 }
1907 self.invalidate_draw_commands();
1908 }
1909 Message::InvalidateDrawCommands => self.invalidate_draw_commands(),
1910 Message::UnpauseSimulation => {
1911 let waves = self.user.waves.as_ref()?;
1912 waves.inner.as_waves()?.unpause_simulation();
1913 }
1914 Message::PauseSimulation => {
1915 let waves = self.user.waves.as_ref()?;
1916 waves.inner.as_waves()?.pause_simulation();
1917 }
1918 Message::Batch(messages) => {
1919 for message in messages {
1920 self.update(message);
1921 }
1922 }
1923 Message::AddDraggedVariables(variables) => {
1924 let waves = self.user.waves.as_mut()?;
1925
1926 waves.focused_item = None;
1927 self.user.drag_source_idx = None;
1928 let target = self.user.drag_target_idx.take();
1929
1930 if let (Some(cmd), _) =
1931 waves.add_variables(&self.translators, variables, target, true, false, None)
1932 {
1933 self.load_variables(cmd);
1934 }
1935 self.invalidate_draw_commands();
1936 }
1937 Message::VariableDragStarted(vidx) => {
1938 self.user.drag_started = true;
1939 self.user.drag_source_idx = Some(vidx);
1940 self.user.drag_target_idx = None;
1941 }
1942 Message::VariableDragTargetChanged(position) => {
1943 self.user.drag_target_idx = Some(position);
1944 }
1945 Message::VariableDragFinished => {
1946 self.user.drag_started = false;
1947
1948 let source_vidx = self.user.drag_source_idx.take()?;
1949 let target_position = self.user.drag_target_idx.take()?;
1950
1951 self.save_current_canvas("Drag item".to_string());
1953 self.invalidate_draw_commands();
1954 let waves = self.user.waves.as_mut()?;
1955
1956 let focused_index = waves
1957 .focused_item
1958 .and_then(|vidx| waves.items_tree.to_displayed(vidx));
1959 let focused_item_ref = focused_index
1960 .and_then(|idx| waves.items_tree.get(idx))
1961 .map(|node| node.item_ref);
1962
1963 let mut to_move = waves
1964 .items_tree
1965 .iter_visible_extra()
1966 .filter_map(|info| info.node.selected.then_some(info.idx))
1967 .collect::<Vec<_>>();
1968 if let Some(idx) = focused_index {
1969 to_move.push(idx);
1970 }
1971 if let Some(vidx) = waves.items_tree.to_displayed(source_vidx) {
1972 to_move.push(vidx);
1973 }
1974
1975 let _ = waves.items_tree.move_items(to_move, target_position);
1976
1977 waves.focused_item = focused_item_ref
1978 .and_then(|item_ref| {
1979 waves
1980 .items_tree
1981 .iter_visible()
1982 .position(|node| node.item_ref == item_ref)
1983 })
1984 .map(VisibleItemIndex);
1985 }
1986 Message::VariableValueToClipbord(vidx) => {
1987 self.handle_variable_clipboard_operation(
1988 vidx,
1989 |waves, item_ref: DisplayedItemRef| {
1990 if let Some(DisplayedItem::Variable(_)) =
1991 waves.displayed_items.get(&item_ref)
1992 {
1993 let field_ref = item_ref.into();
1994 self.get_variable_value(
1995 waves,
1996 &field_ref,
1997 waves
1998 .cursor
1999 .as_ref()
2000 .and_then(num::BigInt::to_biguint)
2001 .as_ref(),
2002 )
2003 } else {
2004 None
2005 }
2006 },
2007 );
2008 }
2009 Message::VariableNameToClipboard(vidx) => {
2010 self.handle_variable_clipboard_operation(
2011 vidx,
2012 |waves, item_ref: DisplayedItemRef| {
2013 if let Some(DisplayedItem::Variable(variable)) =
2014 waves.displayed_items.get(&item_ref)
2015 {
2016 Some(variable.variable_ref.name.clone())
2017 } else {
2018 None
2019 }
2020 },
2021 );
2022 }
2023 Message::VariableFullNameToClipboard(vidx) => {
2024 self.handle_variable_clipboard_operation(
2025 vidx,
2026 |waves, item_ref: DisplayedItemRef| {
2027 if let Some(DisplayedItem::Variable(variable)) =
2028 waves.displayed_items.get(&item_ref)
2029 {
2030 Some(variable.variable_ref.full_path_string())
2031 } else {
2032 None
2033 }
2034 },
2035 );
2036 }
2037 Message::SetViewportStrategy(s) => {
2038 if let Some(waves) = &mut self.user.waves {
2039 for vp in &mut waves.viewports {
2040 vp.move_strategy = s;
2041 }
2042 }
2043 }
2044 Message::Undo(count) => {
2045 let waves = self.user.waves.as_mut()?;
2046 for _ in 0..count {
2047 if let Some(prev_state) = self.undo_stack.pop() {
2048 self.redo_stack
2049 .push(SystemState::current_canvas_state(waves, prev_state.message));
2050 waves.focused_item = prev_state.focused_item;
2051 waves.focused_transaction = prev_state.focused_transaction;
2052 waves.items_tree = prev_state.items_tree;
2053 waves.displayed_items = prev_state.displayed_items;
2054 waves.markers = prev_state.markers;
2055 waves.annotations = prev_state.annotations;
2056 waves.annotation_groups = prev_state.annotation_group;
2057 waves.annotation_list_visible = prev_state.annotation_list;
2058 waves.selected_annotation = prev_state.selected_annotation;
2059 waves.annotation_counter = prev_state.annotation_counter;
2060 } else {
2061 break;
2062 }
2063 }
2064 self.invalidate_draw_commands();
2065 }
2066 Message::Redo(count) => {
2067 let waves = self.user.waves.as_mut()?;
2068 for _ in 0..count {
2069 if let Some(prev_state) = self.redo_stack.pop() {
2070 self.undo_stack
2071 .push(SystemState::current_canvas_state(waves, prev_state.message));
2072 waves.focused_item = prev_state.focused_item;
2073 waves.focused_transaction = prev_state.focused_transaction;
2074 waves.items_tree = prev_state.items_tree;
2075 waves.displayed_items = prev_state.displayed_items;
2076 waves.markers = prev_state.markers;
2077 waves.annotations = prev_state.annotations;
2078 waves.annotation_groups = prev_state.annotation_group;
2079 waves.annotation_list_visible = prev_state.annotation_list;
2080 waves.selected_annotation = prev_state.selected_annotation;
2081 waves.annotation_counter = prev_state.annotation_counter;
2082 } else {
2083 break;
2084 }
2085 }
2086 self.invalidate_draw_commands();
2087 }
2088 Message::DumpTree => {
2089 let waves = self.user.waves.as_ref()?;
2090 dump_tree(waves);
2091 }
2092 Message::GroupNew {
2093 name,
2094 before,
2095 items,
2096 } => {
2097 self.save_current_canvas(format!(
2098 "Create group {}",
2099 name.clone().unwrap_or(String::new())
2100 ));
2101 self.invalidate_draw_commands();
2102 let waves = self.user.waves.as_mut()?;
2103
2104 let passed_or_focused = before
2105 .and_then(|before| {
2106 waves
2107 .items_tree
2108 .get(before)
2109 .map(|node| node.level)
2110 .map(|level| TargetPosition { before, level })
2111 })
2112 .or_else(|| waves.insert_position(waves.focused_item));
2113 let final_target = passed_or_focused.unwrap_or_else(|| waves.end_insert_position());
2114
2115 let mut item_refs = items.unwrap_or_else(|| {
2116 waves
2117 .items_tree
2118 .iter_visible_selected()
2119 .map(|node| node.item_ref)
2120 .collect::<Vec<_>>()
2121 });
2122
2123 let item_refs = if before.is_none() & passed_or_focused.is_some() {
2125 let focus_index = waves
2126 .items_tree
2127 .to_displayed(waves.focused_item.expect("Inconsistent state"))
2128 .expect("Inconsistent state");
2129 item_refs.push(
2130 waves
2131 .items_tree
2132 .get(focus_index)
2133 .expect("Inconsistent state")
2134 .item_ref,
2135 );
2136 item_refs
2137 } else {
2138 item_refs
2139 };
2140
2141 if item_refs.is_empty() {
2142 return None;
2143 }
2144
2145 let group_ref =
2146 waves.add_group(name.unwrap_or("Group".to_owned()), Some(final_target));
2147
2148 let item_idxs = waves
2149 .items_tree
2150 .iter()
2151 .enumerate()
2152 .filter_map(|(idx, node)| {
2153 item_refs
2154 .contains(&node.item_ref)
2155 .then_some(crate::displayed_item_tree::ItemIndex(idx))
2156 })
2157 .collect::<Vec<_>>();
2158
2159 if let Err(e) = waves.items_tree.move_items(
2160 item_idxs,
2161 crate::displayed_item_tree::TargetPosition {
2162 before: ItemIndex(final_target.before.0 + 1),
2163 level: final_target.level.saturating_add(1),
2164 },
2165 ) {
2166 dump_tree(waves);
2167 error!("failed to move items into group: {e:?}");
2168 }
2169 waves.items_tree.xselect_all_visible(false);
2170 waves.focused_item = waves
2171 .items_tree
2172 .iter_visible_extra()
2173 .find_map(|info| (info.node.item_ref == group_ref).then_some(info.vidx));
2174 }
2175 Message::GroupDissolve(item_ref) => {
2176 self.save_current_canvas("Dissolve group".to_owned());
2177 self.invalidate_draw_commands();
2178 let waves = self.user.waves.as_mut()?;
2179 let item_index = waves.index_for_ref_or_focus(item_ref)?;
2180
2181 let removed = waves.items_tree.remove_dissolve(item_index);
2182 waves.displayed_items.remove(&removed);
2183 }
2184 Message::GroupFold(item_ref)
2185 | Message::GroupUnfold(item_ref)
2186 | Message::GroupFoldRecursive(item_ref)
2187 | Message::GroupUnfoldRecursive(item_ref) => {
2188 let unfold = matches!(
2189 message,
2190 Message::GroupUnfold(..) | Message::GroupUnfoldRecursive(..)
2191 );
2192 let recursive = matches!(
2193 message,
2194 Message::GroupFoldRecursive(..) | Message::GroupUnfoldRecursive(..)
2195 );
2196
2197 let undo_msg = if unfold {
2198 "Unfold group".to_owned()
2199 } else {
2200 "Fold group".to_owned()
2201 } + &(if recursive {
2202 " recursive".to_owned()
2203 } else {
2204 String::new()
2205 });
2206 self.save_current_canvas(undo_msg);
2208 self.invalidate_draw_commands();
2209
2210 let waves = self.user.waves.as_mut()?;
2211 let item = waves.index_for_ref_or_focus(item_ref)?;
2212
2213 if let Some(focused_item) = waves.focused_item {
2214 let info = waves
2215 .items_tree
2216 .get_visible_extra(focused_item)
2217 .expect("Inconsistent state");
2218 if waves.items_tree.subtree_contains(item, info.idx) {
2219 waves.focused_item = None;
2220 }
2221 }
2222 if recursive {
2223 waves.items_tree.xfold_recursive(item, unfold);
2224 } else {
2225 waves.items_tree.xfold(item, unfold);
2226 }
2227 }
2228 Message::GroupFoldAll | Message::GroupUnfoldAll => {
2229 let unfold = matches!(message, Message::GroupUnfoldAll);
2230 let undo_msg = if unfold {
2231 "Fold all groups".to_owned()
2232 } else {
2233 "Unfold all groups".to_owned()
2234 };
2235 self.save_current_canvas(undo_msg);
2236 self.invalidate_draw_commands();
2237
2238 let waves = self.user.waves.as_mut()?;
2239
2240 if let Some(focused_item) = waves.focused_item {
2243 let focused_level = waves
2244 .items_tree
2245 .get_visible(focused_item)
2246 .expect("Inconsistent state")
2247 .level;
2248 if !unfold && (focused_level > 0) {
2249 waves.focused_item = None;
2250 }
2251 }
2252 waves.items_tree.xfold_all(unfold);
2253 }
2254 #[cfg(target_arch = "wasm32")]
2255 Message::StartWcpServer { .. } => {
2256 error!("Wcp is not supported on wasm")
2257 }
2258 #[cfg(target_arch = "wasm32")]
2259 Message::StopWcpServer => {
2260 error!("Wcp is not supported on wasm")
2261 }
2262 #[cfg(not(target_arch = "wasm32"))]
2263 Message::StartWcpServer { address, initiate } => {
2264 self.start_wcp_server(address, initiate);
2265 }
2266 #[cfg(not(target_arch = "wasm32"))]
2267 Message::StopWcpServer => {
2268 self.stop_wcp_server();
2269 }
2270 Message::SetupChannelWCP => {
2271 #[cfg(target_arch = "wasm32")]
2272 {
2273 use futures::executor::block_on;
2274 self.channels.wcp_c2s_receiver = block_on(WCP_CS_HANDLER.rx.write()).take();
2275 if self.channels.wcp_c2s_receiver.is_none() {
2276 error!("Failed to claim wasm tx, was SetupWasmWCP executed twice?");
2277 }
2278 self.channels.wcp_s2c_sender = Some(WCP_SC_HANDLER.tx.clone());
2279 }
2280 }
2281 Message::BuildAnalogCache {
2282 display_id,
2283 cache_key,
2284 } => {
2285 let waves = self.user.waves.as_mut()?;
2286 let generation = waves.cache_generation;
2287
2288 let item = waves.displayed_items.get(&display_id)?;
2290 let DisplayedItem::Variable(var) = item else {
2291 return None;
2292 };
2293 if var
2294 .analog
2295 .as_ref()?
2296 .cache
2297 .as_ref()
2298 .is_some_and(|e| e.generation == generation && e.cache_key == cache_key)
2299 {
2300 return None;
2301 }
2302
2303 if let Some(entry) = waves.inflight_caches.get(&cache_key)
2305 && entry.generation == generation
2306 {
2307 if let DisplayedItem::Variable(var) =
2308 waves.displayed_items.get_mut(&display_id)?
2309 {
2310 var.analog.as_mut()?.cache = Some(entry.clone());
2311 }
2312 return None; }
2314
2315 let existing = waves
2317 .displayed_items
2318 .values()
2319 .filter_map(|item| match item {
2320 DisplayedItem::Variable(v) => v.analog.as_ref()?.cache.as_ref(),
2321 _ => None,
2322 })
2323 .find(|e| e.cache_key == cache_key && e.generation == generation)
2324 .cloned();
2325
2326 if let Some(entry) = existing {
2327 if let DisplayedItem::Variable(var) =
2328 waves.displayed_items.get_mut(&display_id)?
2329 {
2330 var.analog.as_mut()?.cache = Some(entry);
2331 }
2332 return None; }
2334
2335 let variable_ref = match waves.displayed_items.get(&display_id)? {
2337 DisplayedItem::Variable(v) => v.variable_ref.clone(),
2338 _ => return None,
2339 };
2340
2341 let entry = std::sync::Arc::new(crate::analog_signal_cache::AnalogCacheEntry::new(
2343 cache_key.clone(),
2344 generation,
2345 ));
2346
2347 if let DisplayedItem::Variable(var) = waves.displayed_items.get_mut(&display_id)? {
2348 var.analog.as_mut()?.cache = Some(entry.clone());
2349 }
2350
2351 let translator = self.translators.clone_translator(&cache_key.1);
2352
2353 waves
2355 .inflight_caches
2356 .insert(cache_key.clone(), entry.clone());
2357
2358 waves.build_analog_cache_async(
2359 entry,
2360 &variable_ref,
2361 translator,
2362 &self.channels.msg_sender,
2363 );
2364 }
2365 Message::AnalogCacheBuilt { entry, result } => {
2366 OUTSTANDING_TRANSACTIONS.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
2367 if let Some(waves) = self.user.waves.as_mut() {
2369 waves.inflight_caches.remove(&entry.cache_key);
2370 }
2371 match result {
2372 Ok(cache) => {
2373 entry.set(cache);
2374 }
2375 Err(err) => {
2376 warn!("Failed to build analog cache: {err}");
2377 }
2378 }
2379 self.invalidate_draw_commands();
2380 }
2381 Message::Exit | Message::ToggleFullscreen => {} Message::AddViewport => {
2383 let waves = self.user.waves.as_mut()?;
2384 let viewport = Viewport::new();
2385 waves.viewports.push(viewport);
2386 self.draw_data.borrow_mut().push(None);
2387 }
2388 Message::RemoveViewport => {
2389 let waves = self.user.waves.as_mut()?;
2390 if waves.viewports.len() > 1 {
2391 waves.viewports.pop();
2392 self.draw_data.borrow_mut().pop();
2393 waves.last_active_viewport_idx = waves
2394 .last_active_viewport_idx
2395 .min(waves.viewports.len() - 1);
2396 }
2397 }
2398 Message::SelectTheme(theme_name) => {
2399 let theme = SurferTheme::new(theme_name)
2400 .with_context(|| "Failed to set theme")
2401 .ok()?;
2402 self.user.config.theme = theme;
2403 let ctx = self.context.as_ref()?;
2404 ctx.set_visuals(self.get_visuals());
2405 }
2406 Message::EnableAnimations(enable) => {
2407 let ctx = self.context.as_ref()?;
2408 self.user.animation_enabled = Some(enable);
2409 ctx.all_styles_mut(|style| {
2410 style.animation_time = if self.animation_enabled() {
2411 self.user.config.animation_time
2412 } else {
2413 0.0
2414 };
2415 });
2416 }
2417 Message::ShowDividerText(show) => {
2418 self.user.config.show_divider_text = show;
2419 }
2420 Message::AsyncDone(_) => (),
2421 Message::AddGraphic(id, g) => {
2422 let waves = self.user.waves.as_mut()?;
2423 waves.graphics.insert(id, g);
2424 }
2425 Message::RemoveGraphic(id) => {
2426 let waves = self.user.waves.as_mut()?;
2427 waves.graphics.retain(|k, _| k != &id);
2428 }
2429 Message::ExpandDrawnItem { item, levels } => {
2430 self.invalidate_draw_commands();
2431 let waves = self.user.waves.as_mut()?;
2432 if let Some(DisplayedItem::Variable(var)) = waves.displayed_items.get_mut(&item) {
2433 var.unfolded_fields = Self::expand_levels_to_unfolded_fields(&var.info, levels);
2434 }
2435 }
2436 Message::ToggleVariableFieldFold(item, field) => {
2437 self.invalidate_draw_commands();
2438 let waves = self.user.waves.as_mut()?;
2439 if let Some(DisplayedItem::Variable(var)) = waves.displayed_items.get_mut(&item) {
2440 var.toggle_field_fold(field);
2441 }
2442 }
2443 Message::SetMouseGestureAnnotation(annotation_kind) => {
2444 self.annotation_kind = annotation_kind;
2445 }
2446 Message::RectangleAdded {
2447 time_at_start,
2448 time_at_end,
2449 wave_from,
2450 wave_to,
2451 rect,
2452 } => {
2453 let id = self.annotation_id();
2454 self.save_current_canvas(format!("Add rectangle {id:?}"));
2455 let waves = self.user.waves.as_mut()?;
2456 waves.annotation_counter += 1;
2457
2458 let new_rect = Annotation::Rect(RectAnnotation::new(
2459 id,
2460 time_at_start,
2461 time_at_end,
2462 wave_from,
2463 wave_to,
2464 rect,
2465 waves.annotation_counter,
2466 ));
2467 let new_id = new_rect.get_id();
2468 waves.annotations.push(new_rect);
2469
2470 waves.add_annotation_to_group(DEFAULT_GROUP_NAME, new_id);
2471 }
2472 Message::ArrowAdded {
2473 wave_point_from,
2474 wave_point_to,
2475 head_mode,
2476 } => {
2477 let id = self.annotation_id();
2478 self.save_current_canvas(format!("Add arrow {id:?}"));
2479 let waves = self.user.waves.as_mut()?;
2480 waves.annotation_counter += 1;
2481 let new_arrow = Annotation::Arrow(ArrowAnnotation::new(
2482 id,
2483 wave_point_from,
2484 wave_point_to,
2485 head_mode,
2486 waves.annotation_counter,
2487 ));
2488 let new_id = new_arrow.get_id();
2489 waves.annotations.push(new_arrow);
2490
2491 waves.add_annotation_to_group(DEFAULT_GROUP_NAME, new_id);
2492 }
2493
2494 Message::RemoveAnnotation(anno_id) => {
2495 self.save_current_canvas(format!("Removed annotation {anno_id:?}"));
2496 let waves = self.user.waves.as_mut()?;
2497 waves.delete_annotation(anno_id);
2498 waves.remove_annotation_from_group(anno_id);
2499 }
2500
2501 Message::ToggleAnnotationVisiblility(anno_id) => {
2502 self.save_current_canvas(format!("Changed visibility on {anno_id:?}"));
2503 let waves = self.user.waves.as_mut()?;
2504 if let Some(target) = waves.annotations.iter_mut().find(|a| a.get_id() == anno_id) {
2505 target.set_visibility(!target.is_visible());
2506 }
2507 }
2508
2509 Message::ToggleAnnotationListShowComments(anno_id) => {
2510 let waves = self.user.waves.as_mut()?;
2511 if let Some(target) = waves.annotations.iter_mut().find(|a| a.get_id() == anno_id) {
2512 target.set_show_comments(!target.show_comments());
2513 }
2514 }
2515
2516 Message::GoToAnnotationPosition(anno_id, viewport_idx) => {
2517 self.go_to_annotation_position(anno_id, viewport_idx);
2518 }
2519
2520 Message::ToggleAnnotationlistVisibility() => {
2521 let waves = self.user.waves.as_mut()?;
2522 waves.annotation_list_visible = !waves.annotation_list_visible;
2523 self.user.show_annotation_list = waves.annotation_list_visible;
2524 }
2525
2526 Message::CreateAnnotationGroup(name) => {
2527 self.save_current_canvas(format!("Added annotation group {name}"));
2528 let waves = self.user.waves.as_mut()?;
2529
2530 let new_group = AnnotationGroup {
2531 name: name.clone(),
2532 cycle_counter: 0,
2533 annotations: Vec::new(),
2534 };
2535
2536 waves.annotation_groups.push(new_group);
2537 }
2538
2539 Message::DeleteAnnotationGroup(name) => {
2540 self.save_current_canvas(format!("Removed annotation group {name}"));
2541 let waves = self.user.waves.as_mut()?;
2542
2543 waves.delete_group(&name);
2544 }
2545
2546 Message::DeleteAllAnnotationInGroup(name) => {
2547 self.save_current_canvas(format!(
2548 "Removed annotation group {name} and all it's annotations"
2549 ));
2550 let waves = self.user.waves.as_mut()?;
2551
2552 waves.remove_all_annotations_from_group(&name);
2553 }
2554
2555 Message::AddCharToPrompt(c) => *self.char_to_add_to_prompt.borrow_mut() = Some(c),
2556
2557 Message::UpdateAnnotationGroup(anno_id, name) => {
2558 self.save_current_canvas(format!("Added {anno_id:?} to {name:?}"));
2559 let waves = self.user.waves.as_mut()?;
2560
2561 let target = waves.remove_annotation_from_group(anno_id);
2562
2563 match target {
2564 Some(id) => {
2565 waves.add_annotation_to_group(name.as_ref()?, id);
2566 }
2567 None => {
2568 warn!("Error: Just removed non existent id!");
2569 }
2570 }
2571 }
2572
2573 Message::UpdateAnnotationName(anno_id, name) => {
2574 self.save_current_canvas(format!("Changed {anno_id:?} name to {name}"));
2575 let waves = self.user.waves.as_mut()?;
2576 if let Some(target) = waves.annotations.iter_mut().find(|a| a.get_id() == anno_id) {
2577 target.set_name(&name);
2578 }
2579 }
2580
2581 Message::SetGroupVisibility(group, visible) => {
2582 self.save_current_canvas(format!("Changed group {:?} visibility", group.name));
2583 if let Some(waves) = self.user.waves.as_mut() {
2584 for annotation_id in group.annotations {
2585 for annotation in &mut waves.annotations {
2586 if annotation_id == annotation.get_id() {
2587 annotation.set_visibility(visible);
2588 }
2589 }
2590 }
2591 }
2592 }
2593
2594 Message::AnnotationClicked(id, menu_pos, viewport_idx, to_screen, frame_width) => {
2595 if let Some(waves) = self.user.waves.as_mut() {
2596 waves.select_annotation(id);
2597
2598 let range = waves.time_range();
2599
2600 let menu_pos_local = to_screen?.inverse().transform_pos(menu_pos?);
2601
2602 let menu_pos_time: BigInt = waves.viewports[viewport_idx?].as_time_bigint(
2603 menu_pos_local.x,
2604 frame_width?,
2605 range,
2606 );
2607
2608 waves.annotation_menu_time = Some(menu_pos_time);
2609
2610 waves.annotation_menu_pos = if id.is_some() { menu_pos } else { None };
2611 }
2612 }
2613
2614 Message::SetActiveViewport(idx) => {
2615 if let Some(waves) = self.user.waves.as_mut() {
2616 let last_idx = waves.viewports.len().checked_sub(1)?;
2617 waves.last_active_viewport_idx = idx.min(last_idx);
2618 }
2619 }
2620
2621 Message::RemoveCommentMessage(annotation_id, message_id) => {
2622 let waves = self.user.waves.as_mut()?;
2624 if let Some(target) = waves
2625 .annotations
2626 .iter_mut()
2627 .find(|a| a.get_id() == annotation_id)
2628 {
2629 target
2630 .get_comment_box_mut()
2631 .message_chain
2632 .retain(|comment_message| comment_message.id != message_id);
2633 }
2634 }
2635
2636 Message::ClickHandled() => {
2637 self.click_handled = true;
2638 }
2639 Message::UpdateCommentBox(changes) => {
2640 let waves = self.user.waves.as_mut()?;
2641 for (annotation_id, comment) in changes {
2642 if let Some(target) = waves
2643 .annotations
2644 .iter_mut()
2645 .find(|a| a.get_id() == annotation_id)
2646 {
2647 target.update_comment_box(comment);
2648 }
2649 }
2650 }
2651 Message::AddCommentMessage(annotation_id, message, user) => {
2652 let waves = self.user.waves.as_mut()?;
2654 if let Some(target) = waves
2655 .annotations
2656 .iter_mut()
2657 .find(|a| a.get_id() == annotation_id)
2658 {
2659 let comment = target.get_comment_box_mut();
2660 comment.message_chain.push(CommentMessage {
2661 id: egui::Id::new(("comment", comment.message_id_source)),
2662 user,
2663 text: message,
2664 });
2665 comment.message_id_source += 1;
2666 }
2667 }
2668 Message::ToggleCommentVisibility(annotation_id) => {
2669 let waves = self.user.waves.as_mut()?;
2670 if let Some(target) = waves
2671 .annotations
2672 .iter_mut()
2673 .find(|a| a.get_id() == annotation_id)
2674 {
2675 target.get_comment_box_mut().visible = !target.get_comment_box().visible;
2676 }
2677 }
2678 }
2679
2680 Some(())
2681 }
2682
2683 pub fn add_scope_as_group(
2684 &mut self,
2685 scope: &ScopeRef,
2686 pos: TargetPosition,
2687 recursive: bool,
2688 variable_name_type: Option<VariableNameType>,
2689 ) -> TargetPosition {
2690 let Some(waves) = self.user.waves.as_mut() else {
2691 return pos;
2692 };
2693 let Some(container) = waves.inner.as_waves() else {
2694 return pos;
2695 };
2696
2697 let variables = container
2698 .variables_in_scope(scope)
2699 .iter()
2700 .sorted_by(|a, b| numeric_sort::cmp(&a.name, &b.name))
2701 .cloned()
2702 .collect_vec();
2703 let child_scopes = container.child_scopes(scope);
2704 let variable_name_type = variable_name_type.or_else(|| {
2705 container
2706 .scope_is_variable(scope)
2707 .then_some(VariableNameType::Local)
2708 });
2709
2710 waves.add_group(scope.name(), Some(pos));
2711 let into_group_pos = TargetPosition {
2712 before: ItemIndex(pos.before.0 + 1),
2713 level: pos.level + 1,
2714 };
2715
2716 let (cmd, variable_refs) = waves.add_variables(
2717 &self.translators,
2718 variables,
2719 Some(into_group_pos),
2720 false,
2721 false,
2722 variable_name_type,
2723 );
2724 let mut into_group_pos = TargetPosition {
2725 before: ItemIndex(into_group_pos.before.0 + variable_refs.len()),
2726 level: pos.level + 1,
2727 };
2728
2729 if let Some(cmd) = cmd {
2730 self.load_variables(cmd);
2731 }
2732
2733 if recursive {
2734 for child in child_scopes.unwrap_or(vec![]) {
2735 into_group_pos =
2736 self.add_scope_as_group(&child, into_group_pos, recursive, variable_name_type);
2737 into_group_pos.level = pos.level + 1;
2738 }
2739 }
2740 into_group_pos
2741 }
2742
2743 fn handle_variable_clipboard_operation<F>(
2744 &self,
2745 vidx: MessageTarget<VisibleItemIndex>,
2746 get_text: F,
2747 ) where
2748 F: FnOnce(&WaveData, DisplayedItemRef) -> Option<String>,
2749 {
2750 let Some(waves) = &self.user.waves else {
2751 return;
2752 };
2753 let vidx = if let MessageTarget::Explicit(vidx) = vidx {
2754 vidx
2755 } else if let Some(focused) = waves.focused_item {
2756 focused
2757 } else {
2758 return;
2759 };
2760 let Some(item_ref) = waves.items_tree.get_visible(vidx).map(|node| node.item_ref) else {
2761 return;
2762 };
2763
2764 if let Some(text) = get_text(waves, item_ref)
2765 && let Some(ctx) = &self.context
2766 {
2767 ctx.copy_text(text);
2768 }
2769 }
2770}
2771
2772fn dump_tree(waves: &WaveData) {
2773 let mut result = String::new();
2774 for (idx, node) in waves.items_tree.iter().enumerate() {
2775 for _ in 0..node.level.saturating_sub(1) {
2776 result.push(' ');
2777 }
2778
2779 if node.level > 0 {
2780 match waves.items_tree.get(ItemIndex(idx + 1)) {
2781 Some(next) if next.level < node.level => result.push_str("╰╴"),
2782 _ => result.push_str("├╴"),
2783 }
2784 }
2785
2786 result.push_str(
2787 &waves
2788 .displayed_items
2789 .get(&node.item_ref)
2790 .map_or("?".to_owned(), displayed_item::DisplayedItem::name),
2791 );
2792 result.push_str(&format!(" ({:?})", node.item_ref));
2793 if node.selected {
2794 result.push_str(" !SEL! ");
2795 }
2796 result.push('\n');
2797 }
2798 info!("tree: \n{}", &result);
2799}
2800
2801pub struct StateWrapper(Arc<RwLock<SystemState>>);
2802impl App for StateWrapper {
2803 fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
2804 App::ui(&mut *self.0.write().unwrap(), ui, frame);
2805 }
2806}