Skip to main content

libsurfer/
lib.rs

1#![deny(unused_crate_dependencies)]
2
3pub mod analog_renderer;
4pub mod analog_signal_cache;
5pub mod async_util;
6pub mod batch_commands;
7#[cfg(feature = "performance_plot")]
8pub mod benchmark;
9mod channels;
10pub mod clock_highlighting;
11pub mod command_parser;
12pub mod command_prompt;
13pub mod config;
14pub mod cxxrtl;
15pub mod cxxrtl_container;
16pub mod data_container;
17pub mod dialog;
18pub mod displayed_item;
19pub mod displayed_item_tree;
20pub mod drawing_canvas;
21pub mod file_dialog;
22pub mod file_watcher;
23pub mod frame_buffer;
24pub mod fzcmd;
25pub mod graphics;
26pub mod help;
27pub mod hierarchy;
28pub mod keyboard_shortcuts;
29pub mod keys;
30pub mod logs;
31pub mod marker;
32pub mod menus;
33pub mod message;
34pub mod mousegestures;
35pub mod overview;
36pub mod remote;
37pub mod server_file_window;
38pub mod state;
39pub mod state_file_io;
40pub mod state_util;
41pub mod statusbar;
42pub mod system_state;
43#[cfg(test)]
44pub mod tests;
45pub mod time;
46pub mod toolbar;
47pub mod tooltips;
48pub mod transaction_container;
49pub mod transactions;
50pub mod translation;
51pub mod util;
52pub mod variable_direction;
53pub mod variable_filter;
54mod variable_index;
55pub mod variable_meta;
56pub mod variable_name_type;
57pub mod view;
58pub mod viewport;
59#[cfg(target_arch = "wasm32")]
60pub mod wasm_api;
61#[cfg(target_arch = "wasm32")]
62pub mod wasm_panic;
63pub mod wave_container;
64pub mod wave_data;
65pub mod wave_source;
66pub mod wcp;
67pub mod wellen;
68
69use crate::channels::checked_send;
70use crate::config::AutoLoad;
71use crate::displayed_item_tree::ItemIndex;
72use crate::displayed_item_tree::TargetPosition;
73use crate::remote::get_time_table_from_server;
74use crate::variable_name_type::VariableNameType;
75
76use std::collections::HashMap;
77use std::sync::atomic::{AtomicUsize, Ordering};
78use std::sync::mpsc::{self, Receiver, Sender};
79use std::sync::{Arc, LazyLock, RwLock};
80
81use batch_commands::read_command_bytes;
82use batch_commands::read_command_file;
83#[cfg(target_arch = "wasm32")]
84use channels::{GlobalChannelTx, IngressHandler, IngressReceiver};
85use derive_more::Display;
86use displayed_item::DisplayedVariable;
87use displayed_item_tree::DisplayedItemTree;
88use eframe::{App, CreationContext};
89use egui::{FontData, FontDefinitions, FontFamily};
90use eyre::Context;
91use eyre::Result;
92use ftr_parser::types::Transaction;
93use futures::executor::block_on;
94use itertools::Itertools;
95use message::MessageTarget;
96use num::BigInt;
97use serde::Deserialize;
98use surfer_translation_types::Translator;
99use surfer_wcp::{WcpCSMessage, WcpEvent, WcpSCMessage};
100pub use system_state::SystemState;
101#[cfg(target_arch = "wasm32")]
102use tokio_stream as _;
103use tracing::{error, info, warn};
104#[cfg(all(not(target_arch = "wasm32"), feature = "wasm_plugins"))]
105use translation::wasm_translator::PluginTranslator;
106use wave_container::ScopeRef;
107
108#[cfg(all(not(target_arch = "wasm32"), feature = "wasm_plugins"))]
109use crate::async_util::perform_work;
110use crate::config::{SurferConfig, SurferTheme};
111use crate::dialog::{OpenSiblingStateFileDialog, ReloadWaveformDialog};
112use crate::displayed_item::{
113    AnalogVarState, DisplayedFieldRef, DisplayedItem, DisplayedItemRef, FieldFormat,
114};
115use crate::displayed_item_tree::VisibleItemIndex;
116use crate::drawing_canvas::TxDrawingCommands;
117use crate::frame_buffer::{FrameBufferContent, build_frame_buffer_content};
118use crate::message::Message;
119use crate::transaction_container::{TransactionRef, TransactionStreamRef};
120use crate::translation::{AnyTranslator, all_translators};
121use crate::variable_filter::{VariableIOFilterType, VariableNameFilterType};
122use crate::viewport::Viewport;
123use crate::wave_container::{ScopeRefExt, VariableRefExt, WaveContainer};
124use crate::wave_data::WaveData;
125use crate::wave_source::{LoadOptions, WaveFormat, WaveSource};
126use crate::wellen::{HeaderResult, convert_format};
127
128/// A number that is non-zero if there are asynchronously triggered operations that
129/// have been triggered but not successfully completed yet. In practice, if this is
130/// non-zero, we will re-run the egui update function in order to ensure that we deal
131/// with the outstanding transactions eventually.
132/// When incrementing this, it is important to make sure that it gets decremented
133/// whenever the asynchronous transaction is completed, otherwise we will re-render
134/// things until program exit
135pub(crate) static OUTSTANDING_TRANSACTIONS: AtomicUsize = AtomicUsize::new(0);
136
137pub static EGUI_CONTEXT: LazyLock<RwLock<Option<Arc<egui::Context>>>> =
138    LazyLock::new(|| RwLock::new(None));
139
140#[cfg(target_arch = "wasm32")]
141pub(crate) static WCP_CS_HANDLER: LazyLock<IngressHandler<WcpCSMessage>> =
142    LazyLock::new(IngressHandler::new);
143
144#[cfg(target_arch = "wasm32")]
145pub(crate) static WCP_SC_HANDLER: LazyLock<GlobalChannelTx<WcpSCMessage>> =
146    LazyLock::new(GlobalChannelTx::new);
147
148#[derive(Default)]
149pub struct StartupParams {
150    pub waves: Option<WaveSource>,
151    pub wcp_initiate: Option<u16>,
152    pub startup_commands: Vec<String>,
153}
154
155fn setup_custom_font(ctx: &egui::Context) {
156    let mut fonts = FontDefinitions::default();
157
158    fonts.font_data.insert(
159        "remix_icons".to_owned(),
160        FontData::from_static(egui_remixicon::FONT).into(),
161    );
162
163    fonts
164        .families
165        .get_mut(&FontFamily::Proportional)
166        .unwrap()
167        .push("remix_icons".to_owned());
168
169    fonts
170        .families
171        .get_mut(&FontFamily::Monospace)
172        .unwrap()
173        .push("remix_icons".to_owned());
174
175    ctx.set_fonts(fonts);
176}
177
178pub fn run_egui(cc: &CreationContext, mut state: SystemState) -> Result<Box<dyn App>> {
179    let ctx_arc = Arc::new(cc.egui_ctx.clone());
180    *EGUI_CONTEXT.write().unwrap() = Some(ctx_arc.clone());
181    state.context = Some(ctx_arc.clone());
182    cc.egui_ctx
183        .set_visuals_of(egui::Theme::Dark, state.get_visuals());
184    cc.egui_ctx
185        .set_visuals_of(egui::Theme::Light, state.get_visuals());
186    cc.egui_ctx.all_styles_mut(|style| {
187        if state.user.config.animation_time == 0.0 {
188            info!("With animation_time set to 0.0, animations cannot be enabled.");
189        }
190        style.animation_time = if state.user.config.animation_enabled() {
191            state.user.config.animation_time
192        } else {
193            0.0
194        };
195    });
196    #[cfg(not(target_arch = "wasm32"))]
197    if state.user.config.wcp.autostart {
198        state.start_wcp_server(Some(state.user.config.wcp.address.clone()), false);
199    }
200    setup_custom_font(&cc.egui_ctx);
201    Ok(Box::new(state))
202}
203
204#[derive(Debug, Clone, Copy, Deserialize, Display, PartialEq, Eq)]
205pub enum MoveDir {
206    #[display("up")]
207    Up,
208
209    #[display("down")]
210    Down,
211}
212
213pub enum ColorSpecifier {
214    Index(usize),
215    Name(String),
216}
217
218enum CachedDrawData {
219    WaveDrawData(CachedWaveDrawData),
220    TransactionDrawData(CachedTransactionDrawData),
221}
222
223struct CachedWaveDrawData {
224    pub draw_commands: HashMap<DisplayedFieldRef, drawing_canvas::DrawingCommands>,
225    pub clock_edges: Vec<f32>,
226    pub ticks: Vec<(String, f32)>,
227}
228
229struct CachedTransactionDrawData {
230    pub draw_commands: HashMap<TransactionRef, TxDrawingCommands>,
231    pub stream_to_displayed_txs: HashMap<TransactionStreamRef, Vec<TransactionRef>>,
232    pub inc_relation_tx_ids: Vec<TransactionRef>,
233    pub out_relation_tx_ids: Vec<TransactionRef>,
234}
235
236pub struct Channels {
237    pub msg_sender: Sender<Message>,
238    pub msg_receiver: Receiver<Message>,
239    #[cfg(target_arch = "wasm32")]
240    wcp_c2s_receiver: Option<IngressReceiver<WcpCSMessage>>,
241    #[cfg(not(target_arch = "wasm32"))]
242    wcp_c2s_receiver: Option<tokio::sync::mpsc::Receiver<WcpCSMessage>>,
243    wcp_s2c_sender: Option<tokio::sync::mpsc::Sender<WcpSCMessage>>,
244}
245impl Channels {
246    fn new() -> Self {
247        let (msg_sender, msg_receiver) = mpsc::channel();
248        Self {
249            msg_sender,
250            msg_receiver,
251            wcp_c2s_receiver: None,
252            wcp_s2c_sender: None,
253        }
254    }
255}
256
257pub struct WcpClientCapabilities {
258    pub waveforms_loaded: bool,
259    pub goto_declaration: bool,
260    pub add_drivers: bool,
261    pub add_loads: bool,
262}
263impl WcpClientCapabilities {
264    fn new() -> Self {
265        Self {
266            waveforms_loaded: false,
267            goto_declaration: false,
268            add_drivers: false,
269            add_loads: false,
270        }
271    }
272}
273
274/// Stores the current canvas state to enable undo/redo operations
275struct CanvasState {
276    message: String,
277    focused_item: Option<VisibleItemIndex>,
278    focused_transaction: (Option<TransactionRef>, Option<Transaction>),
279    items_tree: DisplayedItemTree,
280    displayed_items: HashMap<DisplayedItemRef, DisplayedItem>,
281    markers: HashMap<u8, BigInt>,
282}
283
284impl SystemState {
285    pub fn update(&mut self, message: Message) -> Option<()> {
286        if tracing::enabled!(tracing::Level::TRACE)
287            && !matches!(message, Message::CommandPromptUpdate { .. })
288        {
289            tracing::trace!("{message:?}");
290        }
291        match message {
292            Message::SetActiveScope(scope) => {
293                let waves = self.user.waves.as_mut()?;
294                waves.set_active_scope(scope)?;
295            }
296            Message::ExpandScope(scope_ref) => {
297                *self.scope_ref_to_expand.borrow_mut() = Some(scope_ref);
298            }
299            Message::AddVariables(vars) => {
300                if !vars.is_empty() {
301                    let undo_msg = if vars.len() == 1 {
302                        format!("Add variable {}", vars[0].name)
303                    } else {
304                        format!("Add {} variables", vars.len())
305                    };
306                    self.save_current_canvas(undo_msg);
307                    if let Some(waves) = self.user.waves.as_mut() {
308                        if let (Some(cmd), _) =
309                            waves.add_variables(&self.translators, vars, None, true, false, None)
310                        {
311                            self.load_variables(cmd);
312                        }
313                        self.invalidate_draw_commands();
314                    } else {
315                        error!("Could not load signals, no waveform loaded");
316                    }
317                }
318            }
319            Message::AddDivider(name, vidx) => {
320                self.save_current_canvas("Add divider".into());
321                let waves = self.user.waves.as_mut()?;
322                waves.add_divider(name, vidx);
323            }
324            Message::AddTimeLine(vidx) => {
325                self.save_current_canvas("Add timeline".into());
326                let waves = self.user.waves.as_mut()?;
327                waves.add_timeline(vidx);
328            }
329            Message::AddScope(scope, recursive) => {
330                self.save_current_canvas(format!("Add scope {}", scope.name()));
331
332                let vars = self.get_scope(scope, recursive);
333                let waves = self.user.waves.as_mut()?;
334
335                // TODO add parameter to add_variables, insert to (self.drag_target_idx, self.drag_source_idx)
336                if let (Some(cmd), _) =
337                    waves.add_variables(&self.translators, vars, None, true, false, None)
338                {
339                    self.load_variables(cmd);
340                }
341
342                self.invalidate_draw_commands();
343            }
344            Message::AddScopeAsGroup(scope, recursive) => {
345                self.save_current_canvas(format!("Add scope {} as group", scope.name()));
346                let waves = self.user.waves.as_mut()?;
347                let passed_or_focused = waves.insert_position(waves.focused_item);
348                let target = passed_or_focused.unwrap_or_else(|| waves.end_insert_position());
349
350                self.add_scope_as_group(&scope, target, recursive, None);
351                self.invalidate_draw_commands();
352
353                self.user.waves.as_mut()?.compute_variable_display_names();
354            }
355            Message::AddCount(digit) => {
356                if let Some(count) = &mut self.user.count {
357                    count.push(digit);
358                } else {
359                    self.user.count = Some(digit.to_string());
360                }
361            }
362            Message::AddStreamOrGenerator(s) => {
363                let undo_msg = if let Some(gen_id) = s.gen_id {
364                    format!("Add generator(id: {gen_id})")
365                } else {
366                    format!("Add stream(id: {})", s.stream_id)
367                };
368                self.save_current_canvas(undo_msg);
369
370                let waves = self.user.waves.as_mut()?;
371                if s.gen_id.is_some() {
372                    waves.add_generator(s);
373                } else {
374                    waves.add_stream(s);
375                }
376                self.invalidate_draw_commands();
377            }
378            Message::AddStreamOrGeneratorFromName(scope, name) => {
379                self.save_current_canvas(format!("Add Stream/Generator from name: {}", &name));
380                let waves = self.user.waves.as_mut()?;
381                waves.add_stream_or_generator_from_name(scope, name)?;
382                self.invalidate_draw_commands();
383            }
384            Message::AddAllFromStreamScope(scope_name) => {
385                self.save_current_canvas(format!("Add all from scope {}", scope_name.clone()));
386                let waves = self.user.waves.as_mut()?;
387                waves.add_all_from_stream_scope(scope_name)?;
388                self.invalidate_draw_commands();
389            }
390            Message::InvalidateCount => self.user.count = None,
391            Message::SetNameAlignRight(align_right) => {
392                self.user.align_names_right = Some(align_right);
393            }
394            Message::FocusItem(idx) => {
395                let waves = self.user.waves.as_mut()?;
396
397                let visible_items_len = waves.displayed_items.len();
398                if idx.0 < visible_items_len {
399                    waves.focused_item = Some(idx);
400                } else {
401                    error!(
402                        "Can not focus variable {} because only {visible_items_len} variables are visible.",
403                        idx.0
404                    );
405                }
406            }
407            Message::ItemSelectRange(select_to) => {
408                let waves = self.user.waves.as_mut()?;
409                let select_from = waves.focused_item?;
410                waves
411                    .items_tree
412                    .xselect_visible_range(select_from, select_to, true);
413            }
414            Message::ItemSelectAll => {
415                let waves = self.user.waves.as_mut()?;
416                waves.items_tree.xselect_all_visible(true);
417            }
418            Message::SetItemSelected(vidx, selected) => {
419                let waves = self.user.waves.as_mut()?;
420                waves.items_tree.xselect(vidx, selected);
421            }
422            Message::ToggleItemSelected(vidx) => {
423                let waves = self.user.waves.as_mut()?;
424                let node = vidx
425                    .or(waves.focused_item)
426                    .and_then(|vidx| waves.items_tree.to_displayed(vidx))
427                    .and_then(|item| waves.items_tree.get_mut(item))?;
428                node.selected = !node.selected;
429            }
430            Message::SetDefaultTimeline(v) => {
431                self.user.show_default_timeline = Some(v);
432            }
433            Message::UnfocusItem => {
434                let waves = self.user.waves.as_mut()?;
435                waves.focused_item = None;
436            }
437            Message::MoveFocus(direction, count, select) => {
438                let waves = self.user.waves.as_mut()?;
439                let visible_item_cnt = waves.items_tree.iter_visible().count();
440                if visible_item_cnt == 0 {
441                    return None;
442                }
443
444                let new_focus_vidx = VisibleItemIndex(match direction {
445                    MoveDir::Up => waves
446                        .focused_item
447                        .map_or(visible_item_cnt, |vidx| vidx.0)
448                        .saturating_sub(count),
449                    MoveDir::Down => waves
450                        .focused_item
451                        .map_or(usize::MAX, |vidx| vidx.0)
452                        .wrapping_add(count)
453                        .clamp(0, visible_item_cnt - 1),
454                });
455
456                if select {
457                    if let Some(vidx) = waves.focused_item {
458                        waves.items_tree.xselect(vidx, true);
459                    }
460                    waves.items_tree.xselect(new_focus_vidx, true);
461                }
462                waves.focused_item = Some(new_focus_vidx);
463            }
464            Message::FocusTransaction(tx_ref, tx) => {
465                if let Some(tx_ref) = tx_ref.as_ref()
466                    && tx.is_none()
467                {
468                    self.save_current_canvas(format!("Focus Transaction id: {}", tx_ref.id));
469                }
470                let waves = self.user.waves.as_mut()?;
471                let invalidate = tx.is_none();
472                waves.focused_transaction =
473                    (tx_ref, tx.or_else(|| waves.focused_transaction.1.clone()));
474                if invalidate {
475                    self.invalidate_draw_commands();
476                }
477            }
478            Message::ScrollToItem(position) => {
479                let waves = self.user.waves.as_mut()?;
480                waves.scroll_to_item(position);
481            }
482            Message::SetScrollOffset(offset) => {
483                let waves = self.user.waves.as_mut()?;
484                waves.scroll_offset = offset;
485            }
486            Message::SetLogsVisible(visibility) => self.user.show_logs = visibility,
487            Message::SetFrameBufferVariable(variable_ref) => {
488                let waves = self.user.waves.as_mut()?;
489                if let Some(cmd) = waves
490                    .inner
491                    .as_waves_mut()?
492                    .load_variables(std::iter::once(&variable_ref))
493                    .map_err(|e| error!("{e:#?}"))
494                    .ok()
495                    .flatten()
496                {
497                    self.load_variables(cmd);
498                }
499                self.frame_buffer_content = Some(FrameBufferContent::Variable(variable_ref));
500            }
501            Message::SetFrameBufferVisibleVariable(None) => {
502                self.frame_buffer_content = None;
503            }
504            Message::SetFrameBufferVisibleVariable(Some(vidx)) => {
505                let waves = self.user.waves.as_ref()?;
506                self.frame_buffer_content = waves
507                    .items_tree
508                    .get_visible(vidx)
509                    .and_then(|node| waves.displayed_items.get(&node.item_ref))
510                    .and_then(|item| match item {
511                        DisplayedItem::Variable(variable) => {
512                            Some(FrameBufferContent::Variable(variable.variable_ref.clone()))
513                        }
514                        _ => None,
515                    });
516            }
517            Message::SetFrameBufferArray(scope_ref) => {
518                let waves = self.user.waves.as_mut()?;
519                let (levels, all_leaf_vars) = {
520                    let wave_container = waves.inner.as_waves()?;
521                    build_frame_buffer_content(wave_container, &scope_ref)?
522                };
523                if let Some(cmd) = waves
524                    .inner
525                    .as_waves_mut()?
526                    .load_variables(all_leaf_vars.iter())
527                    .map_err(|e| error!("{e:#?}"))
528                    .ok()
529                    .flatten()
530                {
531                    self.load_variables(cmd);
532                }
533                self.frame_buffer_content = Some(FrameBufferContent::Array { scope_ref, levels });
534            }
535            Message::SetCursorWindowVisible(visibility) => {
536                self.user.show_cursor_window = visibility;
537            }
538            Message::VerticalScroll(direction, count) => {
539                let waves = self.user.waves.as_mut()?;
540                let current_item = waves.get_top_item();
541                match direction {
542                    MoveDir::Down => {
543                        waves.scroll_to_item(current_item + count);
544                    }
545                    MoveDir::Up => {
546                        if current_item > count {
547                            waves.scroll_to_item(current_item - count);
548                        } else {
549                            waves.scroll_to_item(0);
550                        }
551                    }
552                }
553            }
554            Message::SetSurverFileWindowVisible(visibility) => {
555                self.user.show_server_file_window = visibility;
556            }
557            Message::LoadSurverFileByIndex(file_index, load_options) => {
558                // Disable file window in case executing from command/test
559                self.user.show_server_file_window = false;
560                let force_switch = self.user.selected_server_file_index != file_index;
561                self.user.selected_server_file_index = file_index;
562                *self.surver_selected_file.borrow_mut() = file_index;
563                if let Some(url) = self.user.surver_url.as_ref() {
564                    self.load_wave_from_url(url.to_string(), load_options, force_switch);
565                }
566            }
567            Message::LoadSurverFileByName(file_name, load_options) => {
568                // Disable file window in case executing from command/test
569                self.user.show_server_file_window = false;
570                let file_index = self
571                    .user
572                    .surver_file_infos
573                    .as_ref()?
574                    .iter()
575                    .position(|fi| fi.filename == file_name);
576                let force_switch = self.user.selected_server_file_index != file_index;
577
578                self.user.selected_server_file_index = file_index;
579                *self.surver_selected_file.borrow_mut() = file_index;
580                if let Some(url) = self.user.surver_url.as_ref() {
581                    self.load_wave_from_url(url.to_string(), load_options, force_switch);
582                }
583            }
584            Message::RemoveVisibleItems(target) => match target {
585                MessageTarget::Explicit(vidx) => {
586                    let waves = self.user.waves.as_ref();
587                    let item_ref = waves
588                        .and_then(|waves| waves.items_tree.get_visible(vidx))
589                        .map(|node| node.item_ref);
590                    let undo_msg = item_ref
591                        .and_then(|item_ref| {
592                            waves.and_then(|waves| waves.displayed_items.get(&item_ref))
593                        })
594                        .map(displayed_item::DisplayedItem::name)
595                        .map_or("Remove one item".to_string(), |name| {
596                            format!("Remove item {name}")
597                        });
598                    self.save_current_canvas(undo_msg);
599
600                    if let Some(waves) = self.user.waves.as_mut()
601                        && let Some(item_ref) = item_ref
602                    {
603                        waves.remove_displayed_item(item_ref);
604                    }
605                }
606                MessageTarget::CurrentSelection => {
607                    self.save_current_canvas("Remove selected items".to_owned());
608                    let waves = self.user.waves.as_mut()?;
609
610                    let mut remove_ids: Vec<_> = waves
611                        .items_tree
612                        .iter_visible_selected()
613                        .map(|node| node.item_ref)
614                        .collect();
615                    if let Some(node) = waves
616                        .focused_item
617                        .and_then(|focus| waves.items_tree.get_visible(focus))
618                    {
619                        remove_ids.push(node.item_ref);
620                    }
621                    for &item_ref in remove_ids.iter() {
622                        waves.remove_displayed_item(item_ref);
623                    }
624                }
625            },
626            Message::RemoveItems(items) => {
627                let undo_msg = self
628                    .user
629                    .waves
630                    .as_ref()
631                    .and_then(|waves| {
632                        if items.len() == 1 {
633                            items.first().and_then(|item_ref| {
634                                waves
635                                    .displayed_items
636                                    .get(item_ref)
637                                    .map(|item| format!("Remove item {}", item.name()))
638                            })
639                        } else {
640                            Some(format!("Remove {} items", items.len()))
641                        }
642                    })
643                    .unwrap_or_default();
644                self.save_current_canvas(undo_msg);
645
646                let waves = self.user.waves.as_mut()?;
647                for id in items.iter().sorted_unstable_by(|a, b| Ord::cmp(b, a)) {
648                    waves.remove_displayed_item(*id);
649                }
650            }
651            Message::MoveFocusedItem(direction, count) => {
652                self.save_current_canvas(format!("Move item {direction}, {count}"));
653                self.invalidate_draw_commands();
654                let waves = self.user.waves.as_mut()?;
655                let mut vidx = waves.focused_item?;
656                for _ in 0..count {
657                    vidx = waves
658                        .items_tree
659                        .move_item(vidx, direction, |node| {
660                            matches!(
661                                waves.displayed_items.get(&node.item_ref),
662                                Some(DisplayedItem::Group(..))
663                            )
664                        })
665                        .expect("move failed for unknown reason");
666                }
667                waves.focused_item = waves.focused_item.and(Some(vidx));
668            }
669            Message::CanvasScroll {
670                delta,
671                viewport_idx,
672            } => {
673                let waves = self.user.waves.as_mut()?;
674                waves.viewports[viewport_idx]
675                    .handle_canvas_scroll(f64::from(delta.y) + f64::from(delta.x));
676                self.invalidate_draw_commands();
677            }
678            Message::CanvasZoom {
679                delta,
680                mouse_ptr,
681                viewport_idx,
682            } => {
683                let waves = self.user.waves.as_mut()?;
684                if let Some(num_timestamps) = waves.num_timestamps() {
685                    waves.viewports[viewport_idx].handle_canvas_zoom(
686                        mouse_ptr,
687                        f64::from(delta),
688                        &num_timestamps,
689                    );
690                    self.invalidate_draw_commands();
691                } else {
692                    warn!(
693                        "Canvas zoom: No timestamps count, even though waveforms should be loaded"
694                    );
695                }
696            }
697            Message::ZoomToFit { viewport_idx } => {
698                let waves = self.user.waves.as_mut()?;
699                waves.viewports[viewport_idx].zoom_to_fit();
700                self.invalidate_draw_commands();
701            }
702            Message::GoToEnd { viewport_idx } => {
703                let waves = self.user.waves.as_mut()?;
704                waves.viewports[viewport_idx].go_to_end();
705                self.invalidate_draw_commands();
706            }
707            Message::GoToStart { viewport_idx } => {
708                let waves = self.user.waves.as_mut()?;
709                waves.viewports[viewport_idx].go_to_start();
710                self.invalidate_draw_commands();
711            }
712            Message::GoToTime(time, viewport_idx) => {
713                let waves = self.user.waves.as_mut()?;
714                // If there are no timestamps, the file is not fully loaded
715                if let Some(num_timestamps) = waves.num_timestamps() {
716                    let time = time?;
717                    waves.viewports[viewport_idx].go_to_time(&time.clone(), &num_timestamps);
718                    self.invalidate_draw_commands();
719                } else {
720                    warn!(
721                        "Go to time: No timestamps count, even though waveforms should be loaded"
722                    );
723                }
724            }
725            Message::SetTimeUnit(timeunit) => {
726                self.user.wanted_timeunit = timeunit;
727                self.invalidate_draw_commands();
728            }
729            Message::SetTimeStringFormatting(format) => {
730                self.user.time_string_format = format;
731                self.invalidate_draw_commands();
732            }
733            Message::ZoomToRange {
734                start,
735                end,
736                viewport_idx,
737            } => {
738                let waves = self.user.waves.as_mut()?;
739                // If there are no timestamps, the file is not fully loaded
740                if let Some(num_timestamps) = waves.num_timestamps() {
741                    waves.viewports[viewport_idx].zoom_to_range(&start, &end, &num_timestamps);
742                    self.invalidate_draw_commands();
743                } else {
744                    warn!(
745                        "Zoom to range: No timestamps count, even though waveforms should be loaded"
746                    );
747                }
748            }
749            Message::VariableFormatChange(displayed_field_ref, format) => {
750                let waves = self.user.waves.as_mut()?;
751                if !self
752                    .translators
753                    .all_translator_names()
754                    .contains(&format.as_str())
755                {
756                    warn!("No translator {format}");
757                    return None;
758                }
759
760                let update_format =
761                    |variable: &mut DisplayedVariable, field_ref: DisplayedFieldRef| {
762                        if field_ref.field.is_empty() {
763                            let Ok(meta) = waves
764                                .inner
765                                .as_waves()
766                                .unwrap()
767                                .variable_meta(&variable.variable_ref)
768                                .map_err(|e| {
769                                    warn!("Error trying to get variable metadata: {e:#?}");
770                                })
771                            else {
772                                return;
773                            };
774                            let translator = self.translators.get_translator(&format);
775                            let new_info = translator.variable_info(&meta).unwrap();
776
777                            variable.format = Some(format.clone());
778                            variable.info = new_info;
779
780                            variable.downgrade_type_limits_if_unsupported(translator, &meta);
781                        } else {
782                            variable
783                                .field_formats
784                                .retain(|ff| ff.field != field_ref.field);
785                            variable.field_formats.push(FieldFormat {
786                                field: field_ref.field,
787                                format: format.clone(),
788                            });
789                        }
790                    };
791
792                // convert focused item index to item ref
793                let focused = waves
794                    .focused_item
795                    .and_then(|vidx| waves.items_tree.get_visible(vidx))
796                    .map(|node| node.item_ref);
797
798                let mut redraw = false;
799
800                match displayed_field_ref {
801                    MessageTarget::Explicit(field_ref) => {
802                        if let Some(DisplayedItem::Variable(displayed_variable)) =
803                            waves.displayed_items.get_mut(&field_ref.item)
804                        {
805                            update_format(displayed_variable, field_ref);
806                            redraw = true;
807                        }
808                    }
809                    MessageTarget::CurrentSelection => {
810                        //If an item is focused, update its format too
811                        if let Some(focused) = focused
812                            && let Some(DisplayedItem::Variable(displayed_variable)) =
813                                waves.displayed_items.get_mut(&focused)
814                        {
815                            update_format(displayed_variable, DisplayedFieldRef::from(focused));
816                            redraw = true;
817                        }
818                        for item in waves
819                            .items_tree
820                            .iter_visible_selected()
821                            .map(|node| node.item_ref)
822                        {
823                            //Update format for all selected
824                            let field_ref = DisplayedFieldRef::from(item);
825                            if let Some(DisplayedItem::Variable(variable)) =
826                                waves.displayed_items.get_mut(&item)
827                            {
828                                update_format(variable, field_ref);
829                            }
830                            redraw = true;
831                        }
832                    }
833                }
834
835                if redraw {
836                    self.invalidate_draw_commands();
837                }
838            }
839            Message::ItemSelectionClear => {
840                let waves = self.user.waves.as_mut()?;
841                waves.items_tree.xselect_all_visible(false);
842            }
843            Message::ItemColorChange(vidx, color_name) => {
844                self.save_current_canvas(format!(
845                    "Change item color to {}",
846                    color_name.clone().unwrap_or("default".into())
847                ));
848                self.invalidate_draw_commands();
849                let waves = self.user.waves.as_mut()?;
850
851                match vidx {
852                    MessageTarget::Explicit(vidx) => {
853                        let node = waves.items_tree.get_visible(vidx)?;
854                        waves
855                            .displayed_items
856                            .entry(node.item_ref)
857                            .and_modify(|item| item.set_color(&color_name));
858                    }
859                    MessageTarget::CurrentSelection => {
860                        if let Some(focused) = waves.focused_item {
861                            let node = waves.items_tree.get_visible(focused)?;
862                            waves
863                                .displayed_items
864                                .entry(node.item_ref)
865                                .and_modify(|item| item.set_color(&color_name));
866                        }
867
868                        for node in waves.items_tree.iter_visible_selected() {
869                            waves
870                                .displayed_items
871                                .entry(node.item_ref)
872                                .and_modify(|item| item.set_color(&color_name));
873                        }
874                    }
875                }
876            }
877            Message::ItemNameChange(vidx, name) => {
878                self.save_current_canvas(format!(
879                    "Change item name to {}",
880                    name.clone().unwrap_or("default".into())
881                ));
882                let waves = self.user.waves.as_mut()?;
883                let vidx = vidx.or(waves.focused_item)?;
884                let node = waves.items_tree.get_visible(vidx)?;
885                waves
886                    .displayed_items
887                    .entry(node.item_ref)
888                    .and_modify(|item| item.set_name(name));
889            }
890            Message::ItemNameReset(target) => {
891                self.save_current_canvas("Resetting item name(s)".to_owned());
892                let waves = self.user.waves.as_mut()?;
893                match target {
894                    MessageTarget::Explicit(vidx) => {
895                        let node = waves.items_tree.get_visible(vidx)?;
896                        waves
897                            .displayed_items
898                            .entry(node.item_ref)
899                            .and_modify(|item| item.set_name(None));
900                    }
901                    MessageTarget::CurrentSelection => {
902                        for node in waves.items_tree.iter_visible_selected() {
903                            waves
904                                .displayed_items
905                                .entry(node.item_ref)
906                                .and_modify(|item| item.set_name(None));
907                        }
908                    }
909                }
910            }
911            Message::ItemBackgroundColorChange(vidx, color_name) => {
912                self.save_current_canvas(format!(
913                    "Change item background color to {}",
914                    color_name.clone().unwrap_or("default".into())
915                ));
916                let waves = self.user.waves.as_mut()?;
917
918                match vidx {
919                    MessageTarget::Explicit(vidx) => {
920                        let node = waves.items_tree.get_visible(vidx)?;
921                        waves
922                            .displayed_items
923                            .entry(node.item_ref)
924                            .and_modify(|item| item.set_background_color(&color_name));
925                    }
926                    MessageTarget::CurrentSelection => {
927                        if let Some(focused) = waves.focused_item {
928                            let node = waves.items_tree.get_visible(focused)?;
929                            waves
930                                .displayed_items
931                                .entry(node.item_ref)
932                                .and_modify(|item| item.set_background_color(&color_name));
933                        }
934
935                        for node in waves.items_tree.iter_visible_selected() {
936                            waves
937                                .displayed_items
938                                .entry(node.item_ref)
939                                .and_modify(|item| item.set_background_color(&color_name));
940                        }
941                    }
942                }
943            }
944            Message::ItemHeightScalingFactorChange(vidx, scale) => {
945                self.save_current_canvas(format!("Change item height scaling factor to {scale}"));
946                let waves = self.user.waves.as_mut()?;
947
948                match vidx {
949                    MessageTarget::Explicit(vidx) => {
950                        let node = waves.items_tree.get_visible(vidx)?;
951                        waves
952                            .displayed_items
953                            .entry(node.item_ref)
954                            .and_modify(|item| item.set_height_scaling_factor(scale));
955                    }
956                    MessageTarget::CurrentSelection => {
957                        if let Some(focused) = waves.focused_item {
958                            let node = waves.items_tree.get_visible(focused)?;
959                            waves
960                                .displayed_items
961                                .entry(node.item_ref)
962                                .and_modify(|item| item.set_height_scaling_factor(scale));
963                        }
964
965                        for node in waves.items_tree.iter_visible_selected() {
966                            waves
967                                .displayed_items
968                                .entry(node.item_ref)
969                                .and_modify(|item| item.set_height_scaling_factor(scale));
970                        }
971                    }
972                }
973            }
974            Message::SetAnalogSettings(vidx, new_settings) => {
975                self.save_current_canvas("Set analog state".into());
976                self.invalidate_draw_commands();
977                let waves = self.user.waves.as_mut()?;
978
979                // Update settings while preserving existing cache
980                let update = |item: &mut DisplayedItem| {
981                    if let DisplayedItem::Variable(var) = item {
982                        match (&mut var.analog, new_settings) {
983                            (Some(s), Some(new)) => s.settings = new,
984                            (None, Some(new)) => var.analog = Some(AnalogVarState::new(new)),
985                            (_, None) => var.analog = None,
986                        }
987                    }
988                };
989
990                match vidx {
991                    MessageTarget::Explicit(vidx) => {
992                        let node = waves.items_tree.get_visible(vidx)?;
993                        waves
994                            .displayed_items
995                            .entry(node.item_ref)
996                            .and_modify(update);
997                    }
998                    MessageTarget::CurrentSelection => {
999                        if let Some(focused) = waves.focused_item {
1000                            let node = waves.items_tree.get_visible(focused)?;
1001                            waves
1002                                .displayed_items
1003                                .entry(node.item_ref)
1004                                .and_modify(update);
1005                        }
1006                        for node in waves.items_tree.iter_visible_selected() {
1007                            waves
1008                                .displayed_items
1009                                .entry(node.item_ref)
1010                                .and_modify(update);
1011                        }
1012                    }
1013                }
1014            }
1015            Message::MoveCursorToTransition {
1016                next,
1017                variable,
1018                skip_zero,
1019            } => {
1020                let waves = self.user.waves.as_mut()?;
1021                // If there are no timestamps, the file is not fully loaded
1022                if let Some(num_timestamps) = waves.num_timestamps() {
1023                    // if no cursor is set, move it to
1024                    // start of visible area transition for next transition
1025                    // end of visible area for previous transition
1026                    if waves.cursor.is_none()
1027                        && waves.focused_item.is_some()
1028                        && let Some(vp) = waves.viewports.first()
1029                    {
1030                        waves.cursor = if next {
1031                            Some(vp.left_edge_time(&num_timestamps))
1032                        } else {
1033                            Some(vp.right_edge_time(&num_timestamps))
1034                        };
1035                    }
1036                    waves.set_cursor_at_transition(next, variable, skip_zero);
1037                    let moved = waves.go_to_cursor_if_not_in_view();
1038                    if moved {
1039                        self.invalidate_draw_commands();
1040                    }
1041                } else {
1042                    warn!(
1043                        "Move cursor to transition: No timestamps count, even though waveforms should be loaded"
1044                    );
1045                }
1046            }
1047            Message::MoveTransaction { next } => {
1048                let undo_msg = if next {
1049                    "Move to next transaction"
1050                } else {
1051                    "Move to previous transaction"
1052                };
1053                self.save_current_canvas(undo_msg.to_string());
1054                let waves = self.user.waves.as_mut()?;
1055                waves.move_to_transaction(next)?;
1056                self.invalidate_draw_commands();
1057            }
1058            Message::ResetVariableFormat(displayed_field_ref) => {
1059                let waves = self.user.waves.as_mut()?;
1060                if let Some(DisplayedItem::Variable(displayed_variable)) =
1061                    waves.displayed_items.get_mut(&displayed_field_ref.item)
1062                {
1063                    if displayed_field_ref.field.is_empty() {
1064                        displayed_variable.format = None;
1065                    } else {
1066                        displayed_variable
1067                            .field_formats
1068                            .retain(|ff| ff.field != displayed_field_ref.field);
1069                    }
1070                    self.invalidate_draw_commands();
1071                }
1072            }
1073            Message::CursorSet(time) => {
1074                let waves = self.user.waves.as_mut()?;
1075                waves.cursor = Some(time);
1076            }
1077            Message::ExpandParameterSection => {
1078                self.expand_parameter_section = true;
1079            }
1080            Message::LoadFile(filename, load_options) => {
1081                self.user.selected_server_file_index = None;
1082                *self.surver_selected_file.borrow_mut() = None;
1083                #[cfg(not(target_arch = "wasm32"))]
1084                self.load_from_file(filename, load_options).ok();
1085                #[cfg(target_arch = "wasm32")]
1086                error!("Cannot load file from path in WASM");
1087            }
1088            Message::LoadWaveformFileFromUrl(url, load_options) => {
1089                self.user.selected_server_file_index = None;
1090                *self.surver_selected_file.borrow_mut() = None;
1091                // If we provide URL at command line and it is a Surver URL, we want to force a switch
1092                self.load_wave_from_url(url, load_options, true);
1093            }
1094            Message::LoadFromData(data, load_options) => {
1095                self.user.selected_server_file_index = None;
1096                *self.surver_selected_file.borrow_mut() = None;
1097                self.load_from_data(data, load_options).ok();
1098            }
1099            #[cfg(feature = "python")]
1100            Message::LoadPythonTranslator(filename) => {
1101                try_log_error!(
1102                    self.translators.load_python_translator(filename),
1103                    "Error loading Python translator",
1104                )
1105            }
1106            #[cfg(all(not(target_arch = "wasm32"), feature = "wasm_plugins"))]
1107            Message::LoadWasmTranslator(path) => {
1108                let sender = self.channels.msg_sender.clone();
1109                perform_work(
1110                    move || match PluginTranslator::new(path.into_std_path_buf()) {
1111                        Ok(t) => {
1112                            checked_send(&sender, Message::TranslatorLoaded(Arc::new(t)));
1113                        }
1114                        Err(e) => {
1115                            error!("Failed to load wasm translator {e:#}");
1116                        }
1117                    },
1118                );
1119            }
1120            Message::LoadCommandFile(path) => {
1121                self.add_batch_commands(read_command_file(&path));
1122            }
1123            Message::LoadCommandFileFromUrl(url) => {
1124                self.load_commands_from_url(url);
1125            }
1126            Message::LoadCommandFromData(bytes) => {
1127                self.add_batch_commands(read_command_bytes(bytes));
1128            }
1129            Message::SetupCxxrtl(kind) => self.connect_to_cxxrtl(kind, false),
1130            Message::SetSurverStatus(_start, server, status) => {
1131                self.user.surver_file_infos = Some(status.file_infos.clone());
1132                info!(
1133                    "Received surfer server status from {server}. {} files available.",
1134                    status.file_infos.len()
1135                );
1136                self.user.surver_url = Some(server.clone());
1137                if status.file_infos.is_empty() {
1138                    warn!("Received surfer server status with no file infos");
1139                    return None;
1140                }
1141                if self.user.selected_server_file_index.is_none() {
1142                    if status.file_infos.len() == 1 {
1143                        // if only one file is available, select it automatically
1144                        self.user.selected_server_file_index = Some(0);
1145                        *self.surver_selected_file.borrow_mut() = Some(0);
1146                        info!(
1147                            "Only one file available on server {}, loading it automatically",
1148                            server
1149                        );
1150                        self.load_wave_from_url(server.clone(), LoadOptions::Clear, false);
1151                    } else {
1152                        // if no file is selected, show the server file selection window
1153                        self.user.show_server_file_window = true;
1154                        self.progress_tracker = None;
1155                    }
1156                }
1157
1158                if let Some(file_index) = self.user.selected_server_file_index {
1159                    if file_index >= status.file_infos.len() {
1160                        warn!(
1161                            "Selected server file index {file_index} is out of bounds ({} files available)",
1162                            status.file_infos.len()
1163                        );
1164                        return None;
1165                    }
1166                    self.server_status_to_progress(&server, &status.file_infos[file_index]);
1167                }
1168            }
1169            Message::FileDropped(dropped_file) => {
1170                self.load_from_dropped(dropped_file)
1171                    .map_err(|e| error!("{e:#?}"))
1172                    .ok();
1173            }
1174            Message::StopProgressTracker => {
1175                self.progress_tracker = None;
1176            }
1177            Message::WaveHeaderLoaded(start, source, load_options, header) => {
1178                // for files using the `wellen` backend, we load the header before parsing the body
1179                info!(
1180                    "Loaded the hierarchy and meta-data of {source} in {:?}",
1181                    start.elapsed()
1182                );
1183                match header {
1184                    HeaderResult::LocalFile(header) => {
1185                        // register waveform as loaded (but with no variable info yet!)
1186                        let shared_hierarchy = Arc::new(header.hierarchy);
1187                        let new_waves =
1188                            Box::new(WaveContainer::new_waveform(shared_hierarchy.clone()));
1189                        self.on_waves_loaded(
1190                            source.clone(),
1191                            convert_format(header.file_format),
1192                            new_waves,
1193                            load_options,
1194                        );
1195                        // start parsing of the body
1196                        self.load_wave_body(source, header.body, header.body_len, shared_hierarchy);
1197                    }
1198                    HeaderResult::LocalBytes(header) => {
1199                        // register waveform as loaded (but with no variable info yet!)
1200                        let shared_hierarchy = Arc::new(header.hierarchy);
1201                        let new_waves =
1202                            Box::new(WaveContainer::new_waveform(shared_hierarchy.clone()));
1203                        self.on_waves_loaded(
1204                            source.clone(),
1205                            convert_format(header.file_format),
1206                            new_waves,
1207                            load_options,
1208                        );
1209                        // start parsing of the body
1210                        self.load_wave_body(source, header.body, header.body_len, shared_hierarchy);
1211                    }
1212                    HeaderResult::Remote(hierarchy, file_format, server, file_index) => {
1213                        // register waveform as loaded (but with no variable info yet!)
1214                        let new_waves = Box::new(WaveContainer::new_remote_waveform(
1215                            &server,
1216                            hierarchy.clone(),
1217                        ));
1218                        self.on_waves_loaded(
1219                            source.clone(),
1220                            convert_format(file_format),
1221                            new_waves,
1222                            load_options,
1223                        );
1224                        // body is already being parsed on the server, we need to request the time table though
1225                        get_time_table_from_server(
1226                            self.channels.msg_sender.clone(),
1227                            server,
1228                            file_index,
1229                        );
1230                    }
1231                }
1232            }
1233            Message::WaveBodyLoaded(start, source, body) => {
1234                // for files using the `wellen` backend, parse the body in a second step
1235                info!("Loaded the body of {source} in {:?}", start.elapsed());
1236                self.progress_tracker = None;
1237                let waves = self
1238                    .user
1239                    .waves
1240                    .as_mut()
1241                    .expect("Waves should be loaded at this point!");
1242                // add source and time table
1243                let maybe_cmd = waves // TODO
1244                    .inner
1245                    .as_waves_mut()?
1246                    .wellen_add_body(body)
1247                    .map_err(|err| {
1248                        error!("While getting commands to lazy-load signals: {err:?}");
1249                    })
1250                    .ok()
1251                    .flatten();
1252                // Pre-load parameters
1253                let param_cmd = waves
1254                    .inner
1255                    .as_waves_mut()?
1256                    .load_parameters()
1257                    .map_err(|err| {
1258                        error!("While getting commands to lazy-load parameters: {err:?}");
1259                    })
1260                    .ok()
1261                    .flatten();
1262
1263                if self.wcp_greeted_signal.load(Ordering::Relaxed)
1264                    && self.wcp_client_capabilities.waveforms_loaded
1265                {
1266                    let source = match source {
1267                        WaveSource::File(path) => path.to_string(),
1268                        WaveSource::Url(url) => url,
1269                        _ => String::new(),
1270                    };
1271                    self.channels.wcp_s2c_sender.as_ref().map(|ch| {
1272                        block_on(
1273                            ch.send(WcpSCMessage::event(WcpEvent::waveforms_loaded { source })),
1274                        )
1275                    });
1276                }
1277
1278                // update viewports, now that we have the time table
1279                waves.update_viewports();
1280                // make sure we redraw
1281                self.invalidate_draw_commands();
1282                // start loading parameters
1283                if let Some(cmd) = param_cmd {
1284                    self.load_variables(cmd);
1285                }
1286                // start loading variables
1287                if let Some(cmd) = maybe_cmd {
1288                    self.load_variables(cmd);
1289                }
1290            }
1291            Message::SignalsLoaded(start, res) => {
1292                info!("Loaded {} variables in {:?}", res.len(), start.elapsed());
1293                self.progress_tracker = None;
1294                let waves = self
1295                    .user
1296                    .waves
1297                    .as_mut()
1298                    .expect("Waves should be loaded at this point!");
1299                match waves.inner.as_waves_mut()?.on_signals_loaded(res) {
1300                    Err(err) => error!("{err:?}"),
1301                    Ok(Some(cmd)) => self.load_variables(cmd),
1302                    _ => {}
1303                }
1304                // make sure we redraw since now more variable data is available
1305                self.invalidate_draw_commands();
1306            }
1307            Message::WavesLoaded(filename, format, new_waves, load_options) => {
1308                self.on_waves_loaded(filename, format, new_waves, load_options);
1309                // here, the body and thus the number of timestamps is already loaded!
1310                self.user
1311                    .waves
1312                    .as_mut()
1313                    .expect("Waves should be loaded at this point!")
1314                    .update_viewports();
1315                self.progress_tracker = None;
1316            }
1317            Message::TransactionStreamsLoaded(filename, format, new_ftr, loaded_options) => {
1318                self.on_transaction_streams_loaded(filename, format, new_ftr, loaded_options);
1319                self.user
1320                    .waves
1321                    .as_mut()
1322                    .expect("Waves should be loaded at this point!")
1323                    .update_viewports();
1324            }
1325            Message::BlacklistTranslator(idx, translator) => {
1326                self.user.blacklisted_translators.insert((idx, translator));
1327            }
1328            Message::Error(e) => {
1329                error!("{e:?}");
1330                self.user.show_logs = true;
1331            }
1332            Message::TranslatorLoaded(t) => {
1333                info!("Translator {} loaded", t.name());
1334                t.set_wave_source(
1335                    self.user
1336                        .waves
1337                        .as_ref()
1338                        .map(|waves| waves.source.into_translation_type()),
1339                );
1340
1341                self.translators.add_or_replace(AnyTranslator::Full(t));
1342            }
1343            Message::SetSidePanelVisible(v) => self.user.show_hierarchy = Some(v),
1344            Message::SetMenuVisible(v) => self.user.show_menu = Some(v),
1345            Message::ToggleMenu => {
1346                self.user.show_menu = Some(!self.show_menu());
1347            }
1348            Message::SetToolbarVisible(v) => self.user.show_toolbar = Some(v),
1349            Message::SetShowEmptyScopes(v) => self.user.show_empty_scopes = Some(v),
1350            Message::SetShowHierarchyIcons(v) => self.user.show_hierarchy_icons = Some(v),
1351            Message::SetParameterDisplayLocation(location) => {
1352                self.user.parameter_display_location = Some(location);
1353            }
1354            Message::SetStatusbarVisible(v) => self.user.show_statusbar = Some(v),
1355            Message::SetTickLines(v) => self.user.show_ticks = Some(v),
1356            Message::SetVariableTooltip(v) => self.user.show_tooltip = Some(v),
1357            Message::SetScopeTooltip(v) => self.user.show_scope_tooltip = Some(v),
1358            Message::SetOverviewVisible(v) => self.user.show_overview = Some(v),
1359            Message::SetShowVariableDirection(v) => self.user.show_variable_direction = Some(v),
1360            Message::SetTransitionValue(v) => self.user.transition_value = Some(v),
1361            Message::SetShowIndices(v) => {
1362                let new = v;
1363                self.user.show_variable_indices = Some(new);
1364                let waves = self.user.waves.as_mut()?;
1365                waves.display_variable_indices = new;
1366                waves.compute_variable_display_names();
1367            }
1368            Message::SetHighlightFocused(highlight) => {
1369                self.user.highlight_focused = Some(highlight);
1370            }
1371            Message::HideCommandPrompt => {
1372                *self.command_prompt_text.borrow_mut() = String::new();
1373                self.command_prompt.suggestions = vec![];
1374                self.command_prompt.selected = self.command_prompt.previous_commands.len();
1375                self.command_prompt.visible = false;
1376            }
1377            Message::ShowCommandPrompt(text, selected) => {
1378                self.command_prompt.new_text = Some((text, selected.unwrap_or(String::new())));
1379                self.command_prompt.visible = true;
1380            }
1381            Message::FileDownloaded(url, bytes, load_options) => {
1382                self.load_from_bytes(WaveSource::Url(url), bytes.to_vec(), load_options);
1383            }
1384            Message::CommandFileDownloaded(_url, bytes) => {
1385                self.add_batch_commands(read_command_bytes(bytes.to_vec()));
1386                self.progress_tracker = None;
1387            }
1388            Message::SetConfigFromString(s) => {
1389                // FIXME think about a structured way to collect errors
1390                let config = SurferConfig::new_from_toml(&s)
1391                    .with_context(|| "Failed to load config file")
1392                    .ok()?;
1393
1394                self.user.config = config;
1395
1396                let ctx = &self.context.as_ref()?;
1397                ctx.set_visuals(self.get_visuals());
1398            }
1399            Message::ReloadConfig => {
1400                // FIXME think about a structured way to collect errors
1401                let config = SurferConfig::new(false)
1402                    .with_context(|| "Failed to load config file")
1403                    .ok()?;
1404                self.translators = all_translators();
1405                self.user.config = config;
1406
1407                let ctx = &self.context.as_ref()?;
1408                ctx.set_visuals(self.get_visuals());
1409            }
1410            Message::ReloadWaveform(keep_unavailable) => {
1411                let waves = self.user.waves.as_ref()?;
1412                let options = if keep_unavailable {
1413                    LoadOptions::KeepAll
1414                } else {
1415                    LoadOptions::KeepAvailable
1416                };
1417                match &waves.source {
1418                    WaveSource::File(filename) => {
1419                        self.load_from_file(filename.clone(), options).ok();
1420                    }
1421                    WaveSource::Data => {}       // can't reload
1422                    WaveSource::Cxxrtl(..) => {} // can't reload
1423                    WaveSource::DragAndDrop(filename) => {
1424                        filename
1425                            .clone()
1426                            .and_then(|filename| self.load_from_file(filename, options).ok());
1427                    }
1428                    WaveSource::Url(url) => {
1429                        self.load_wave_from_url(url.clone(), options, false);
1430                    }
1431                }
1432
1433                for translator in self.translators.all_translators() {
1434                    translator.reload(self.channels.msg_sender.clone());
1435                }
1436                self.variable_name_info_cache.borrow_mut().clear();
1437
1438                if let Some(waves) = self.user.waves.as_mut() {
1439                    waves.compute_variable_display_names();
1440                }
1441            }
1442            Message::SuggestReloadWaveform => match self.autoreload_files() {
1443                AutoLoad::Always => self.update(Message::ReloadWaveform(true))?,
1444                AutoLoad::Never => (),
1445                AutoLoad::Ask => {
1446                    self.user.show_reload_suggestion = Some(ReloadWaveformDialog::default());
1447                }
1448            },
1449            Message::CloseReloadWaveformDialog {
1450                reload_file,
1451                do_not_show_again,
1452            } => {
1453                if do_not_show_again {
1454                    // FIXME: This is currently saved in state, but could be persisted in
1455                    // some setting.
1456                    self.user.autoreload_files = Some(AutoLoad::from_bool(reload_file));
1457                }
1458                self.user.show_reload_suggestion = None;
1459                if reload_file {
1460                    self.update(Message::ReloadWaveform(true));
1461                }
1462            }
1463            Message::UpdateReloadWaveformDialog(dialog) => {
1464                self.user.show_reload_suggestion = Some(dialog);
1465            }
1466            Message::OpenSiblingStateFile(open) => {
1467                if !open {
1468                    return None;
1469                }
1470                let waves = self.user.waves.as_ref()?;
1471                let state_file_path = waves.source.sibling_state_file()?;
1472                self.load_state_file(Some(state_file_path.clone().into_std_path_buf()));
1473            }
1474            Message::SuggestOpenSiblingStateFile => match self.autoload_sibling_state_files() {
1475                AutoLoad::Always => {
1476                    self.update(Message::OpenSiblingStateFile(true));
1477                }
1478                AutoLoad::Never => {}
1479                AutoLoad::Ask => {
1480                    self.user.show_open_sibling_state_file_suggestion =
1481                        Some(OpenSiblingStateFileDialog::default());
1482                }
1483            },
1484            Message::CloseOpenSiblingStateFileDialog {
1485                load_state,
1486                do_not_show_again,
1487            } => {
1488                if do_not_show_again {
1489                    self.user.autoload_sibling_state_files = Some(AutoLoad::from_bool(load_state));
1490                }
1491                self.user.show_open_sibling_state_file_suggestion = None;
1492                if load_state {
1493                    self.update(Message::OpenSiblingStateFile(true));
1494                }
1495            }
1496            Message::UpdateOpenSiblingStateFileDialog(dialog) => {
1497                self.user.show_open_sibling_state_file_suggestion = Some(dialog);
1498            }
1499            Message::RemovePlaceholders => {
1500                let waves = self.user.waves.as_mut()?;
1501                waves.remove_placeholders();
1502            }
1503            Message::SetClockHighlightType(new_type) => {
1504                self.user.clock_highlight_type = Some(new_type);
1505            }
1506            Message::SetFillHighValues(fill) => self.user.fill_high_values = Some(fill),
1507            Message::SetDinotraceStyle(dino_style) => {
1508                self.user.use_dinotrace_style = Some(dino_style);
1509                self.invalidate_draw_commands();
1510            }
1511            Message::AddMarker {
1512                time,
1513                name,
1514                move_focus,
1515            } => {
1516                if let Some(name) = &name {
1517                    self.save_current_canvas(format!("Add marker {name} at {time}"));
1518                } else {
1519                    self.save_current_canvas(format!("Add marker at {time}"));
1520                }
1521                let waves = self.user.waves.as_mut()?;
1522                waves.add_marker(&time, name, move_focus);
1523            }
1524            Message::SetMarker { id, time } => {
1525                self.save_current_canvas(format!("Set marker {id} to {time}"));
1526                let waves = self.user.waves.as_mut()?;
1527                waves.set_marker_position(id, &time);
1528            }
1529            Message::RemoveMarker(id) => {
1530                let waves = self.user.waves.as_mut()?;
1531                waves.remove_marker(id);
1532            }
1533            Message::MoveMarkerToCursor(idx) => {
1534                self.save_current_canvas("Move marker".into());
1535                let waves = self.user.waves.as_mut()?;
1536                waves.move_marker_to_cursor(idx);
1537            }
1538            Message::GoToCursorIfNotInView => {
1539                let waves = self.user.waves.as_mut()?;
1540                if waves.go_to_cursor_if_not_in_view() {
1541                    self.invalidate_draw_commands();
1542                }
1543            }
1544            Message::GoToMarkerPosition(idx, viewport_idx) => {
1545                let waves = self.user.waves.as_mut()?;
1546                // If there are no timestamps, the file is not fully loaded
1547                if let Some(num_timestamps) = waves.num_timestamps() {
1548                    let cursor = waves.markers.get(&idx)?;
1549                    waves.viewports[viewport_idx].go_to_time(cursor, &num_timestamps);
1550                    self.invalidate_draw_commands();
1551                } else {
1552                    warn!(
1553                        "Go to marker position: No timestamps count, even though waveforms should be loaded"
1554                    );
1555                }
1556            }
1557            Message::ChangeVariableNameType(target, name_type) => {
1558                let waves = self.user.waves.as_mut()?;
1559                let recompute_names = waves.change_variable_name_type(target, name_type);
1560
1561                if recompute_names {
1562                    waves.compute_variable_display_names();
1563                }
1564            }
1565            Message::ForceVariableNameTypes(name_type) => {
1566                let waves = self.user.waves.as_mut()?;
1567                waves.force_variable_name_type(name_type);
1568            }
1569            Message::CommandPromptClear => {
1570                *self.command_prompt_text.borrow_mut() = String::new();
1571                self.command_prompt.suggestions = vec![];
1572                // self.command_prompt.selected = self.command_prompt.previous_commands.len();
1573                self.command_prompt.selected = if self.command_prompt_text.borrow().is_empty() {
1574                    self.command_prompt.previous_commands.len().clamp(0, 3)
1575                } else {
1576                    0
1577                };
1578            }
1579            Message::CommandPromptUpdate { suggestions } => {
1580                self.command_prompt.suggestions = suggestions;
1581                self.command_prompt.selected = if self.command_prompt_text.borrow().is_empty() {
1582                    self.command_prompt.previous_commands.len().clamp(0, 3)
1583                } else {
1584                    0
1585                };
1586                self.command_prompt.new_selection =
1587                    Some(if self.command_prompt_text.borrow().is_empty() {
1588                        self.command_prompt.previous_commands.len().clamp(0, 3)
1589                    } else {
1590                        0
1591                    });
1592            }
1593            Message::CommandPromptPushPrevious(cmd) => {
1594                let len = cmd.len();
1595                self.command_prompt
1596                    .previous_commands
1597                    .insert(0, (cmd, vec![false; len]));
1598            }
1599            Message::OpenFileDialog(mode) => {
1600                self.open_file_dialog(mode);
1601            }
1602            Message::OpenCommandFileDialog => {
1603                self.open_command_file_dialog();
1604            }
1605            #[cfg(feature = "python")]
1606            Message::OpenPythonPluginDialog => {
1607                self.open_python_file_dialog();
1608            }
1609            #[cfg(feature = "python")]
1610            Message::ReloadPythonPlugin => {
1611                try_log_error!(
1612                    self.translators.reload_python_translator(),
1613                    "Error reloading Python translator"
1614                );
1615                self.invalidate_draw_commands();
1616            }
1617            Message::SaveStateFile(path) => self.save_state_file(path),
1618            Message::LoadStateFromData(bytes) => self.load_state_from_bytes(bytes),
1619            Message::LoadStateFile(path) => self.load_state_file(path),
1620            Message::LoadState(state, path) => self.load_state(state, path),
1621            Message::SetStateFile(path) => {
1622                // since in wasm we can't support "save", only "save as" - never set the `state_file`
1623                #[cfg(not(target_arch = "wasm32"))]
1624                {
1625                    self.user.state_file = Some(path);
1626                }
1627                #[cfg(target_arch = "wasm32")]
1628                {
1629                    error!("Failed to load {path:?}. Loading state files is unsupported on wasm")
1630                }
1631            }
1632            Message::SetAboutVisible(s) => self.user.show_about = s,
1633            Message::SetKeyHelpVisible(s) => self.user.show_keys = s,
1634            Message::SetGestureHelpVisible(s) => self.user.show_gestures = s,
1635            Message::SetUrlEntryVisible(s, f) => {
1636                self.user.show_url_entry = s;
1637                self.url_callback = f;
1638            }
1639            Message::SetLicenseVisible(s) => self.user.show_license = s,
1640            Message::SetQuickStartVisible(s) => self.user.show_quick_start = s,
1641            Message::SetPerformanceVisible(s) => {
1642                if !s {
1643                    self.continuous_redraw = false;
1644                }
1645                self.user.show_performance = s;
1646            }
1647            Message::SetContinuousRedraw(s) => self.continuous_redraw = s,
1648            Message::SetMouseGestureDragStart(pos) => self.gesture_start_location = pos,
1649            Message::SetMeasureDragStart(pos) => self.measure_start_location = pos,
1650            Message::SetFilterFocused(s) => self.user.variable_name_filter_focused = s,
1651            Message::SetTimeEditFocused(s) => self.time_edit_focused = s,
1652            Message::SetRequestTimeEditFocus(s) => self.request_time_edit_focus = s,
1653            Message::SetVariableNameFilterType(variable_name_filter_type) => {
1654                self.user.variable_filter.name_filter_type = variable_name_filter_type;
1655            }
1656            Message::SetVariableNameFilterCaseInsensitive(s) => {
1657                self.user.variable_filter.name_filter_case_insensitive = s;
1658            }
1659            Message::SetVariableIOFilter(t, b) => match t {
1660                VariableIOFilterType::Output => self.user.variable_filter.include_outputs = b,
1661                VariableIOFilterType::Input => self.user.variable_filter.include_inputs = b,
1662                VariableIOFilterType::InOut => self.user.variable_filter.include_inouts = b,
1663                VariableIOFilterType::Other => self.user.variable_filter.include_others = b,
1664            },
1665            Message::SetVariableGroupByDirection(b) => {
1666                self.user.variable_filter.group_by_direction = b;
1667            }
1668            Message::SetUIZoomFactor(scale) => {
1669                if let Some(ctx) = &mut self.context.as_ref() {
1670                    ctx.set_zoom_factor(scale);
1671                }
1672                self.user.ui_zoom_factor = Some(scale);
1673            }
1674            Message::SelectPrevCommand => {
1675                self.command_prompt.new_selection = Some(
1676                    self.command_prompt
1677                        .new_selection
1678                        .unwrap_or(self.command_prompt.selected)
1679                        .saturating_sub(1)
1680                        .max(0),
1681                );
1682            }
1683            Message::SelectNextCommand => {
1684                self.command_prompt.new_selection = Some(
1685                    self.command_prompt
1686                        .new_selection
1687                        .unwrap_or(self.command_prompt.selected)
1688                        .saturating_add(1)
1689                        .min(self.command_prompt.suggestions.len().saturating_sub(1)),
1690                );
1691            }
1692            Message::SetHierarchyStyle(style) => self.user.hierarchy_style = Some(style),
1693            Message::SetArrowKeyBindings(bindings) => {
1694                self.user.arrow_key_bindings = Some(bindings);
1695            }
1696            Message::SetPrimaryMouseDragBehavior(behavior) => {
1697                self.user.primary_button_drag_behavior = Some(behavior);
1698            }
1699            Message::InvalidateDrawCommands => self.invalidate_draw_commands(),
1700            Message::UnpauseSimulation => {
1701                let waves = self.user.waves.as_ref()?;
1702                waves.inner.as_waves()?.unpause_simulation();
1703            }
1704            Message::PauseSimulation => {
1705                let waves = self.user.waves.as_ref()?;
1706                waves.inner.as_waves()?.pause_simulation();
1707            }
1708            Message::Batch(messages) => {
1709                for message in messages {
1710                    self.update(message);
1711                }
1712            }
1713            Message::AddDraggedVariables(variables) => {
1714                let waves = self.user.waves.as_mut()?;
1715
1716                waves.focused_item = None;
1717                self.user.drag_source_idx = None;
1718                let target = self.user.drag_target_idx.take();
1719
1720                if let (Some(cmd), _) =
1721                    waves.add_variables(&self.translators, variables, target, true, false, None)
1722                {
1723                    self.load_variables(cmd);
1724                }
1725                self.invalidate_draw_commands();
1726            }
1727            Message::VariableDragStarted(vidx) => {
1728                self.user.drag_started = true;
1729                self.user.drag_source_idx = Some(vidx);
1730                self.user.drag_target_idx = None;
1731            }
1732            Message::VariableDragTargetChanged(position) => {
1733                self.user.drag_target_idx = Some(position);
1734            }
1735            Message::VariableDragFinished => {
1736                self.user.drag_started = false;
1737
1738                let source_vidx = self.user.drag_source_idx.take()?;
1739                let target_position = self.user.drag_target_idx.take()?;
1740
1741                // reordering
1742                self.save_current_canvas("Drag item".to_string());
1743                self.invalidate_draw_commands();
1744                let waves = self.user.waves.as_mut()?;
1745
1746                let focused_index = waves
1747                    .focused_item
1748                    .and_then(|vidx| waves.items_tree.to_displayed(vidx));
1749                let focused_item_ref = focused_index
1750                    .and_then(|idx| waves.items_tree.get(idx))
1751                    .map(|node| node.item_ref);
1752
1753                let mut to_move = waves
1754                    .items_tree
1755                    .iter_visible_extra()
1756                    .filter_map(|info| info.node.selected.then_some(info.idx))
1757                    .collect::<Vec<_>>();
1758                if let Some(idx) = focused_index {
1759                    to_move.push(idx);
1760                }
1761                if let Some(vidx) = waves.items_tree.to_displayed(source_vidx) {
1762                    to_move.push(vidx);
1763                }
1764
1765                let _ = waves.items_tree.move_items(to_move, target_position);
1766
1767                waves.focused_item = focused_item_ref
1768                    .and_then(|item_ref| {
1769                        waves
1770                            .items_tree
1771                            .iter_visible()
1772                            .position(|node| node.item_ref == item_ref)
1773                    })
1774                    .map(VisibleItemIndex);
1775            }
1776            Message::VariableValueToClipbord(vidx) => {
1777                self.handle_variable_clipboard_operation(
1778                    vidx,
1779                    |waves, item_ref: DisplayedItemRef| {
1780                        if let Some(DisplayedItem::Variable(_)) =
1781                            waves.displayed_items.get(&item_ref)
1782                        {
1783                            let field_ref = item_ref.into();
1784                            self.get_variable_value(
1785                                waves,
1786                                &field_ref,
1787                                waves
1788                                    .cursor
1789                                    .as_ref()
1790                                    .and_then(num::BigInt::to_biguint)
1791                                    .as_ref(),
1792                            )
1793                        } else {
1794                            None
1795                        }
1796                    },
1797                );
1798            }
1799            Message::VariableNameToClipboard(vidx) => {
1800                self.handle_variable_clipboard_operation(
1801                    vidx,
1802                    |waves, item_ref: DisplayedItemRef| {
1803                        if let Some(DisplayedItem::Variable(variable)) =
1804                            waves.displayed_items.get(&item_ref)
1805                        {
1806                            Some(variable.variable_ref.name.clone())
1807                        } else {
1808                            None
1809                        }
1810                    },
1811                );
1812            }
1813            Message::VariableFullNameToClipboard(vidx) => {
1814                self.handle_variable_clipboard_operation(
1815                    vidx,
1816                    |waves, item_ref: DisplayedItemRef| {
1817                        if let Some(DisplayedItem::Variable(variable)) =
1818                            waves.displayed_items.get(&item_ref)
1819                        {
1820                            Some(variable.variable_ref.full_path_string())
1821                        } else {
1822                            None
1823                        }
1824                    },
1825                );
1826            }
1827            Message::SetViewportStrategy(s) => {
1828                if let Some(waves) = &mut self.user.waves {
1829                    for vp in &mut waves.viewports {
1830                        vp.move_strategy = s;
1831                    }
1832                }
1833            }
1834            Message::Undo(count) => {
1835                let waves = self.user.waves.as_mut()?;
1836                for _ in 0..count {
1837                    if let Some(prev_state) = self.undo_stack.pop() {
1838                        self.redo_stack
1839                            .push(SystemState::current_canvas_state(waves, prev_state.message));
1840                        waves.focused_item = prev_state.focused_item;
1841                        waves.focused_transaction = prev_state.focused_transaction;
1842                        waves.items_tree = prev_state.items_tree;
1843                        waves.displayed_items = prev_state.displayed_items;
1844                        waves.markers = prev_state.markers;
1845                    } else {
1846                        break;
1847                    }
1848                }
1849                self.invalidate_draw_commands();
1850            }
1851            Message::Redo(count) => {
1852                let waves = self.user.waves.as_mut()?;
1853                for _ in 0..count {
1854                    if let Some(prev_state) = self.redo_stack.pop() {
1855                        self.undo_stack
1856                            .push(SystemState::current_canvas_state(waves, prev_state.message));
1857                        waves.focused_item = prev_state.focused_item;
1858                        waves.focused_transaction = prev_state.focused_transaction;
1859                        waves.items_tree = prev_state.items_tree;
1860                        waves.displayed_items = prev_state.displayed_items;
1861                        waves.markers = prev_state.markers;
1862                    } else {
1863                        break;
1864                    }
1865                }
1866                self.invalidate_draw_commands();
1867            }
1868            Message::DumpTree => {
1869                let waves = self.user.waves.as_ref()?;
1870                dump_tree(waves);
1871            }
1872            Message::GroupNew {
1873                name,
1874                before,
1875                items,
1876            } => {
1877                self.save_current_canvas(format!(
1878                    "Create group {}",
1879                    name.clone().unwrap_or(String::new())
1880                ));
1881                self.invalidate_draw_commands();
1882                let waves = self.user.waves.as_mut()?;
1883
1884                let passed_or_focused = before
1885                    .and_then(|before| {
1886                        waves
1887                            .items_tree
1888                            .get(before)
1889                            .map(|node| node.level)
1890                            .map(|level| TargetPosition { before, level })
1891                    })
1892                    .or_else(|| waves.insert_position(waves.focused_item));
1893                let final_target = passed_or_focused.unwrap_or_else(|| waves.end_insert_position());
1894
1895                let mut item_refs = items.unwrap_or_else(|| {
1896                    waves
1897                        .items_tree
1898                        .iter_visible_selected()
1899                        .map(|node| node.item_ref)
1900                        .collect::<Vec<_>>()
1901                });
1902
1903                // if we are using the focus as the insert anchor, then move that as well
1904                let item_refs = if before.is_none() & passed_or_focused.is_some() {
1905                    let focus_index = waves
1906                        .items_tree
1907                        .to_displayed(waves.focused_item.expect("Inconsistent state"))
1908                        .expect("Inconsistent state");
1909                    item_refs.push(
1910                        waves
1911                            .items_tree
1912                            .get(focus_index)
1913                            .expect("Inconsistent state")
1914                            .item_ref,
1915                    );
1916                    item_refs
1917                } else {
1918                    item_refs
1919                };
1920
1921                if item_refs.is_empty() {
1922                    return None;
1923                }
1924
1925                let group_ref =
1926                    waves.add_group(name.unwrap_or("Group".to_owned()), Some(final_target));
1927
1928                let item_idxs = waves
1929                    .items_tree
1930                    .iter()
1931                    .enumerate()
1932                    .filter_map(|(idx, node)| {
1933                        item_refs
1934                            .contains(&node.item_ref)
1935                            .then_some(crate::displayed_item_tree::ItemIndex(idx))
1936                    })
1937                    .collect::<Vec<_>>();
1938
1939                if let Err(e) = waves.items_tree.move_items(
1940                    item_idxs,
1941                    crate::displayed_item_tree::TargetPosition {
1942                        before: ItemIndex(final_target.before.0 + 1),
1943                        level: final_target.level.saturating_add(1),
1944                    },
1945                ) {
1946                    dump_tree(waves);
1947                    error!("failed to move items into group: {e:?}");
1948                }
1949                waves.items_tree.xselect_all_visible(false);
1950                waves.focused_item = waves
1951                    .items_tree
1952                    .iter_visible_extra()
1953                    .find_map(|info| (info.node.item_ref == group_ref).then_some(info.vidx));
1954            }
1955            Message::GroupDissolve(item_ref) => {
1956                self.save_current_canvas("Dissolve group".to_owned());
1957                self.invalidate_draw_commands();
1958                let waves = self.user.waves.as_mut()?;
1959                let item_index = waves.index_for_ref_or_focus(item_ref)?;
1960
1961                let removed = waves.items_tree.remove_dissolve(item_index);
1962                waves.displayed_items.remove(&removed);
1963            }
1964            Message::GroupFold(item_ref)
1965            | Message::GroupUnfold(item_ref)
1966            | Message::GroupFoldRecursive(item_ref)
1967            | Message::GroupUnfoldRecursive(item_ref) => {
1968                let unfold = matches!(
1969                    message,
1970                    Message::GroupUnfold(..) | Message::GroupUnfoldRecursive(..)
1971                );
1972                let recursive = matches!(
1973                    message,
1974                    Message::GroupFoldRecursive(..) | Message::GroupUnfoldRecursive(..)
1975                );
1976
1977                let undo_msg = if unfold {
1978                    "Unfold group".to_owned()
1979                } else {
1980                    "Fold group".to_owned()
1981                } + &(if recursive {
1982                    " recursive".to_owned()
1983                } else {
1984                    String::new()
1985                });
1986                // TODO add group name? would have to break the pattern that we insert an
1987                // undo message even if no waves are available
1988                self.save_current_canvas(undo_msg);
1989                self.invalidate_draw_commands();
1990
1991                let waves = self.user.waves.as_mut()?;
1992                let item = waves.index_for_ref_or_focus(item_ref)?;
1993
1994                if let Some(focused_item) = waves.focused_item {
1995                    let info = waves
1996                        .items_tree
1997                        .get_visible_extra(focused_item)
1998                        .expect("Inconsistent state");
1999                    if waves.items_tree.subtree_contains(item, info.idx) {
2000                        waves.focused_item = None;
2001                    }
2002                }
2003                if recursive {
2004                    waves.items_tree.xfold_recursive(item, unfold);
2005                } else {
2006                    waves.items_tree.xfold(item, unfold);
2007                }
2008            }
2009            Message::GroupFoldAll | Message::GroupUnfoldAll => {
2010                let unfold = matches!(message, Message::GroupUnfoldAll);
2011                let undo_msg = if unfold {
2012                    "Fold all groups".to_owned()
2013                } else {
2014                    "Unfold all groups".to_owned()
2015                };
2016                self.save_current_canvas(undo_msg);
2017                self.invalidate_draw_commands();
2018
2019                let waves = self.user.waves.as_mut()?;
2020
2021                // remove focus if focused item is folded away -> prevent future waveform
2022                // adds being invisibly inserted
2023                if let Some(focused_item) = waves.focused_item {
2024                    let focused_level = waves
2025                        .items_tree
2026                        .get_visible(focused_item)
2027                        .expect("Inconsistent state")
2028                        .level;
2029                    if !unfold & (focused_level > 0) {
2030                        waves.focused_item = None;
2031                    }
2032                }
2033                waves.items_tree.xfold_all(unfold);
2034            }
2035            #[cfg(target_arch = "wasm32")]
2036            Message::StartWcpServer { .. } => {
2037                error!("Wcp is not supported on wasm")
2038            }
2039            #[cfg(target_arch = "wasm32")]
2040            Message::StopWcpServer => {
2041                error!("Wcp is not supported on wasm")
2042            }
2043            #[cfg(not(target_arch = "wasm32"))]
2044            Message::StartWcpServer { address, initiate } => {
2045                self.start_wcp_server(address, initiate);
2046            }
2047            #[cfg(not(target_arch = "wasm32"))]
2048            Message::StopWcpServer => {
2049                self.stop_wcp_server();
2050            }
2051            Message::SetupChannelWCP => {
2052                #[cfg(target_arch = "wasm32")]
2053                {
2054                    use futures::executor::block_on;
2055                    self.channels.wcp_c2s_receiver = block_on(WCP_CS_HANDLER.rx.write()).take();
2056                    if self.channels.wcp_c2s_receiver.is_none() {
2057                        error!("Failed to claim wasm tx, was SetupWasmWCP executed twice?");
2058                    }
2059                    self.channels.wcp_s2c_sender = Some(WCP_SC_HANDLER.tx.clone());
2060                }
2061            }
2062            Message::BuildAnalogCache {
2063                display_id,
2064                cache_key,
2065            } => {
2066                let waves = self.user.waves.as_mut()?;
2067                let generation = waves.cache_generation;
2068
2069                // Check if already have valid entry (building or ready)
2070                let item = waves.displayed_items.get(&display_id)?;
2071                let DisplayedItem::Variable(var) = item else {
2072                    return None;
2073                };
2074                if var
2075                    .analog
2076                    .as_ref()?
2077                    .cache
2078                    .as_ref()
2079                    .is_some_and(|e| e.generation == generation && e.cache_key == cache_key)
2080                {
2081                    return None;
2082                }
2083
2084                // Try to share from in-flight builds first (handles removed-but-still-building case)
2085                if let Some(entry) = waves.inflight_caches.get(&cache_key)
2086                    && entry.generation == generation
2087                {
2088                    if let DisplayedItem::Variable(var) =
2089                        waves.displayed_items.get_mut(&display_id)?
2090                    {
2091                        var.analog.as_mut()?.cache = Some(entry.clone());
2092                    }
2093                    return None; // Shared from in-flight build
2094                }
2095
2096                // Try to share from another displayed variable (O(n) scan - only during cache build)
2097                let existing = waves
2098                    .displayed_items
2099                    .values()
2100                    .filter_map(|item| match item {
2101                        DisplayedItem::Variable(v) => v.analog.as_ref()?.cache.as_ref(),
2102                        _ => None,
2103                    })
2104                    .find(|e| e.cache_key == cache_key && e.generation == generation)
2105                    .cloned();
2106
2107                if let Some(entry) = existing {
2108                    if let DisplayedItem::Variable(var) =
2109                        waves.displayed_items.get_mut(&display_id)?
2110                    {
2111                        var.analog.as_mut()?.cache = Some(entry);
2112                    }
2113                    return None; // Shared existing entry (may still be building)
2114                }
2115
2116                // Clone variable_ref only when we need to spawn builder
2117                let variable_ref = match waves.displayed_items.get(&display_id)? {
2118                    DisplayedItem::Variable(v) => v.variable_ref.clone(),
2119                    _ => return None,
2120                };
2121
2122                // Create new entry and spawn builder
2123                let entry = std::sync::Arc::new(crate::analog_signal_cache::AnalogCacheEntry::new(
2124                    cache_key.clone(),
2125                    generation,
2126                ));
2127
2128                if let DisplayedItem::Variable(var) = waves.displayed_items.get_mut(&display_id)? {
2129                    var.analog.as_mut()?.cache = Some(entry.clone());
2130                }
2131
2132                let translator = self.translators.clone_translator(&cache_key.1);
2133
2134                // Track in-flight build for sharing with other variables
2135                waves
2136                    .inflight_caches
2137                    .insert(cache_key.clone(), entry.clone());
2138
2139                waves.build_analog_cache_async(
2140                    entry,
2141                    &variable_ref,
2142                    translator,
2143                    &self.channels.msg_sender,
2144                );
2145            }
2146            Message::AnalogCacheBuilt { entry, result } => {
2147                OUTSTANDING_TRANSACTIONS.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
2148                // Remove from in-flight registry (may already be gone if generation changed)
2149                if let Some(waves) = self.user.waves.as_mut() {
2150                    waves.inflight_caches.remove(&entry.cache_key);
2151                }
2152                match result {
2153                    Ok(cache) => {
2154                        entry.set(cache);
2155                    }
2156                    Err(err) => {
2157                        warn!("Failed to build analog cache: {err}");
2158                    }
2159                }
2160                self.invalidate_draw_commands();
2161            }
2162            Message::Exit | Message::ToggleFullscreen => {} // Handled in eframe::update
2163            Message::AddViewport => {
2164                let waves = self.user.waves.as_mut()?;
2165                let viewport = Viewport::new();
2166                waves.viewports.push(viewport);
2167                self.draw_data.borrow_mut().push(None);
2168            }
2169            Message::RemoveViewport => {
2170                let waves = self.user.waves.as_mut()?;
2171                if waves.viewports.len() > 1 {
2172                    waves.viewports.pop();
2173                    self.draw_data.borrow_mut().pop();
2174                }
2175            }
2176            Message::SelectTheme(theme_name) => {
2177                let theme = SurferTheme::new(theme_name)
2178                    .with_context(|| "Failed to set theme")
2179                    .ok()?;
2180                self.user.config.theme = theme;
2181                let ctx = self.context.as_ref()?;
2182                ctx.set_visuals(self.get_visuals());
2183            }
2184            Message::EnableAnimations(enable) => {
2185                let ctx = self.context.as_ref()?;
2186                self.user.animation_enabled = Some(enable);
2187                ctx.all_styles_mut(|style| {
2188                    style.animation_time = if self.animation_enabled() {
2189                        self.user.config.animation_time
2190                    } else {
2191                        0.0
2192                    };
2193                });
2194            }
2195            Message::AsyncDone(_) => (),
2196            Message::AddGraphic(id, g) => {
2197                let waves = self.user.waves.as_mut()?;
2198                waves.graphics.insert(id, g);
2199            }
2200            Message::RemoveGraphic(id) => {
2201                let waves = self.user.waves.as_mut()?;
2202                waves.graphics.retain(|k, _| k != &id);
2203            }
2204            Message::ExpandDrawnItem { item, levels } => {
2205                self.items_to_expand.borrow_mut().push((item, levels));
2206            }
2207            Message::AddCharToPrompt(c) => *self.char_to_add_to_prompt.borrow_mut() = Some(c),
2208        }
2209        Some(())
2210    }
2211
2212    pub fn add_scope_as_group(
2213        &mut self,
2214        scope: &ScopeRef,
2215        pos: TargetPosition,
2216        recursive: bool,
2217        variable_name_type: Option<VariableNameType>,
2218    ) -> TargetPosition {
2219        let Some(waves) = self.user.waves.as_mut() else {
2220            return pos;
2221        };
2222        let Some(container) = waves.inner.as_waves() else {
2223            return pos;
2224        };
2225
2226        let variables = container
2227            .variables_in_scope(scope)
2228            .iter()
2229            .sorted_by(|a, b| numeric_sort::cmp(&a.name, &b.name))
2230            .cloned()
2231            .collect_vec();
2232        let child_scopes = container.child_scopes(scope);
2233        let variable_name_type = variable_name_type.or_else(|| {
2234            container
2235                .scope_is_variable(scope)
2236                .then_some(VariableNameType::Local)
2237        });
2238
2239        waves.add_group(scope.name(), Some(pos));
2240        let into_group_pos = TargetPosition {
2241            before: ItemIndex(pos.before.0 + 1),
2242            level: pos.level + 1,
2243        };
2244
2245        let (cmd, variable_refs) = waves.add_variables(
2246            &self.translators,
2247            variables,
2248            Some(into_group_pos),
2249            false,
2250            false,
2251            variable_name_type,
2252        );
2253        let mut into_group_pos = TargetPosition {
2254            before: ItemIndex(into_group_pos.before.0 + variable_refs.len()),
2255            level: pos.level + 1,
2256        };
2257
2258        if let Some(cmd) = cmd {
2259            self.load_variables(cmd);
2260        }
2261
2262        if recursive {
2263            for child in child_scopes.unwrap_or(vec![]) {
2264                into_group_pos =
2265                    self.add_scope_as_group(&child, into_group_pos, recursive, variable_name_type);
2266                into_group_pos.level = pos.level + 1;
2267            }
2268        }
2269        into_group_pos
2270    }
2271
2272    fn handle_variable_clipboard_operation<F>(
2273        &self,
2274        vidx: MessageTarget<VisibleItemIndex>,
2275        get_text: F,
2276    ) where
2277        F: FnOnce(&WaveData, DisplayedItemRef) -> Option<String>,
2278    {
2279        let Some(waves) = &self.user.waves else {
2280            return;
2281        };
2282        let vidx = if let MessageTarget::Explicit(vidx) = vidx {
2283            vidx
2284        } else if let Some(focused) = waves.focused_item {
2285            focused
2286        } else {
2287            return;
2288        };
2289        let Some(item_ref) = waves.items_tree.get_visible(vidx).map(|node| node.item_ref) else {
2290            return;
2291        };
2292
2293        if let Some(text) = get_text(waves, item_ref)
2294            && let Some(ctx) = &self.context
2295        {
2296            ctx.copy_text(text);
2297        }
2298    }
2299}
2300
2301pub fn dump_tree(waves: &WaveData) {
2302    let mut result = String::new();
2303    for (idx, node) in waves.items_tree.iter().enumerate() {
2304        for _ in 0..node.level.saturating_sub(1) {
2305            result.push(' ');
2306        }
2307
2308        if node.level > 0 {
2309            match waves.items_tree.get(ItemIndex(idx + 1)) {
2310                Some(next) if next.level < node.level => result.push_str("╰╴"),
2311                _ => result.push_str("├╴"),
2312            }
2313        }
2314
2315        result.push_str(
2316            &waves
2317                .displayed_items
2318                .get(&node.item_ref)
2319                .map_or("?".to_owned(), displayed_item::DisplayedItem::name),
2320        );
2321        result.push_str(&format!("   ({:?})", node.item_ref));
2322        if node.selected {
2323            result.push_str(" !SEL! ");
2324        }
2325        result.push('\n');
2326    }
2327    info!("tree: \n{}", &result);
2328}
2329
2330pub struct StateWrapper(Arc<RwLock<SystemState>>);
2331impl App for StateWrapper {
2332    fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
2333        App::ui(&mut *self.0.write().unwrap(), ui, frame);
2334    }
2335}