1use std::{
2 collections::{HashMap, HashSet, VecDeque},
3 mem,
4 path::PathBuf,
5};
6
7use crate::{
8 clock_highlighting::ClockHighlightType,
9 config::{ArrowKeyBindings, AutoLoad, PrimaryMouseDrag, SurferConfig},
10 data_container::DataContainer,
11 dialog::{OpenSiblingStateFileDialog, ReloadWaveformDialog},
12 displayed_item_tree::{DisplayedItemTree, VisibleItemIndex},
13 hierarchy::HierarchyStyle,
14 message::Message,
15 system_state::SystemState,
16 time::{TimeStringFormatting, TimeUnit},
17 transaction_container::TransactionContainer,
18 variable_filter::VariableFilter,
19 viewport::Viewport,
20 wave_container::{ScopeRef, VariableRef, WaveContainer},
21 wave_data::WaveData,
22 wave_source::{LoadOptions, WaveFormat, WaveSource},
23 CanvasState, StartupParams,
24};
25use egui::{
26 style::{Selection, WidgetVisuals, Widgets},
27 CornerRadius, Stroke, Visuals,
28};
29use itertools::Itertools;
30use log::{error, info, trace, warn};
31use serde::{Deserialize, Serialize};
32use surfer_translation_types::Translator;
33
34#[derive(Serialize, Deserialize)]
36pub struct UserState {
37 #[serde(skip)]
38 pub config: SurferConfig,
39
40 pub(crate) show_hierarchy: Option<bool>,
42 pub(crate) show_menu: Option<bool>,
43 pub(crate) show_ticks: Option<bool>,
44 pub(crate) show_toolbar: Option<bool>,
45 pub(crate) show_tooltip: Option<bool>,
46 pub(crate) show_scope_tooltip: Option<bool>,
47 pub(crate) show_default_timeline: Option<bool>,
48 pub(crate) show_overview: Option<bool>,
49 pub(crate) show_statusbar: Option<bool>,
50 pub(crate) align_names_right: Option<bool>,
51 pub(crate) show_variable_indices: Option<bool>,
52 pub(crate) show_variable_direction: Option<bool>,
53 pub(crate) show_empty_scopes: Option<bool>,
54 pub(crate) show_parameters_in_scopes: Option<bool>,
55 #[serde(default)]
56 pub(crate) highlight_focused: Option<bool>,
57 #[serde(default)]
58 pub(crate) fill_high_values: Option<bool>,
59 #[serde(default)]
60 pub(crate) primary_button_drag_behavior: Option<PrimaryMouseDrag>,
61 #[serde(default)]
62 pub(crate) arrow_key_bindings: Option<ArrowKeyBindings>,
63 #[serde(default)]
64 pub(crate) clock_highlight_type: Option<ClockHighlightType>,
65 #[serde(default)]
66 pub(crate) hierarchy_style: Option<HierarchyStyle>,
67 #[serde(default)]
68 pub(crate) autoload_sibling_state_files: Option<AutoLoad>,
69 #[serde(default)]
70 pub(crate) autoreload_files: Option<AutoLoad>,
71
72 pub(crate) waves: Option<WaveData>,
73 pub(crate) drag_started: bool,
74 pub(crate) drag_source_idx: Option<VisibleItemIndex>,
75 pub(crate) drag_target_idx: Option<crate::displayed_item_tree::TargetPosition>,
76
77 pub(crate) previous_waves: Option<WaveData>,
78
79 pub(crate) count: Option<String>,
81
82 pub(crate) blacklisted_translators: HashSet<(VariableRef, String)>,
84
85 pub(crate) show_about: bool,
86 pub(crate) show_keys: bool,
87 pub(crate) show_gestures: bool,
88 pub(crate) show_quick_start: bool,
89 pub(crate) show_license: bool,
90 pub(crate) show_performance: bool,
91 pub(crate) show_logs: bool,
92 pub(crate) show_cursor_window: bool,
93 pub(crate) wanted_timeunit: TimeUnit,
94 pub(crate) time_string_format: Option<TimeStringFormatting>,
95 pub(crate) show_url_entry: bool,
96 #[serde(skip, default)]
99 pub(crate) show_reload_suggestion: Option<ReloadWaveformDialog>,
100 #[serde(skip, default)]
101 pub(crate) show_open_sibling_state_file_suggestion: Option<OpenSiblingStateFileDialog>,
102 pub(crate) variable_name_filter_focused: bool,
103 pub(crate) variable_filter: VariableFilter,
104 pub(crate) rename_target: Option<VisibleItemIndex>,
105 pub(crate) sidepanel_width: Option<f32>,
107 pub(crate) ui_zoom_factor: Option<f32>,
109
110 #[serde(skip)]
116 pub state_file: Option<PathBuf>,
117}
118
119impl std::fmt::Debug for UserState {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 write!(f, "SharedState {{ <snipped> }}")
124 }
125}
126
127impl SystemState {
128 pub fn with_params(mut self, args: StartupParams) -> Self {
129 self.user.previous_waves = self.user.waves;
130 self.user.waves = None;
131
132 self.batch_messages = VecDeque::new();
134
135 match args.waves {
136 Some(WaveSource::Url(url)) => {
137 self.add_batch_message(Message::LoadWaveformFileFromUrl(url, LoadOptions::clean()));
138 }
139 Some(WaveSource::File(file)) => {
140 self.add_batch_message(Message::LoadFile(file, LoadOptions::clean()));
141 }
142 Some(WaveSource::Data) => error!("Attempted to load data at startup"),
143 Some(WaveSource::Cxxrtl(url)) => {
144 self.add_batch_message(Message::SetupCxxrtl(url));
145 }
146 Some(WaveSource::DragAndDrop(_)) => {
147 error!("Attempted to load from drag and drop at startup (how?)");
148 }
149 None => {}
150 }
151
152 if let Some(port) = args.wcp_initiate {
153 let addr = format!("127.0.0.1:{port}");
154 self.add_batch_message(Message::StartWcpServer {
155 address: Some(addr),
156 initiate: true,
157 });
158 }
159
160 self.add_batch_commands(args.startup_commands);
161
162 self
163 }
164
165 pub fn wcp(&mut self) {
166 self.handle_wcp_commands();
167 }
168
169 pub(crate) fn get_scope(&mut self, scope: ScopeRef, recursive: bool) -> Vec<VariableRef> {
170 let Some(waves) = self.user.waves.as_mut() else {
171 return vec![];
172 };
173
174 let wave_cont = waves.inner.as_waves().unwrap();
175
176 let children = wave_cont.child_scopes(&scope);
177 let mut variables = wave_cont
178 .variables_in_scope(&scope)
179 .iter()
180 .sorted_by(|a, b| numeric_sort::cmp(&a.name, &b.name))
181 .cloned()
182 .collect_vec();
183
184 if recursive {
185 if let Ok(children) = children {
186 for child in children {
187 variables.append(&mut self.get_scope(child, true));
188 }
189 }
190 }
191
192 variables
193 }
194
195 pub(crate) fn on_waves_loaded(
196 &mut self,
197 filename: WaveSource,
198 format: WaveFormat,
199 new_waves: Box<WaveContainer>,
200 load_options: LoadOptions,
201 ) {
202 info!("{format} file loaded");
203 let viewport = Viewport::new();
204 let viewports = [viewport].to_vec();
205
206 for translator in self.translators.all_translators() {
207 translator.set_wave_source(Some(filename.into_translation_type()));
208 }
209
210 let ((new_wave, load_commands), is_reload) =
211 if load_options.keep_variables && self.user.waves.is_some() {
212 (
213 self.user.waves.take().unwrap().update_with_waves(
214 new_waves,
215 filename,
216 format,
217 &self.translators,
218 load_options.keep_unavailable,
219 ),
220 true,
221 )
222 } else if let Some(old) = self.user.previous_waves.take() {
223 (
224 old.update_with_waves(
225 new_waves,
226 filename,
227 format,
228 &self.translators,
229 load_options.keep_unavailable,
230 ),
231 true,
232 )
233 } else {
234 (
235 (
236 WaveData {
237 inner: DataContainer::Waves(*new_waves),
238 source: filename,
239 format,
240 active_scope: None,
241 items_tree: DisplayedItemTree::default(),
242 displayed_items: HashMap::new(),
243 viewports,
244 cursor: None,
245 markers: HashMap::new(),
246 focused_item: None,
247 focused_transaction: (None, None),
248 default_variable_name_type: self.user.config.default_variable_name_type,
249 display_variable_indices: self.show_variable_indices(),
250 scroll_offset: 0.,
251 drawing_infos: vec![],
252 top_item_draw_offset: 0.,
253 total_height: 0.,
254 display_item_ref_counter: 0,
255 old_num_timestamps: None,
256 graphics: HashMap::new(),
257 },
258 None,
259 ),
260 false,
261 )
262 };
263
264 if let Some(cmd) = load_commands {
265 self.load_variables(cmd);
266 }
267 self.invalidate_draw_commands();
268
269 self.user.waves = Some(new_wave);
270
271 if !is_reload {
272 if let Some(waves) = &mut self.user.waves {
273 self.user.wanted_timeunit = waves.inner.metadata().timescale.unit;
275 if waves.source.sibling_state_file().is_some() {
277 self.update(Message::SuggestOpenSiblingStateFile);
278 }
279 }
280 }
281 }
282
283 pub(crate) fn on_transaction_streams_loaded(
284 &mut self,
285 filename: WaveSource,
286 format: WaveFormat,
287 new_ftr: TransactionContainer,
288 _loaded_options: LoadOptions,
289 ) {
290 info!("Transaction streams are loaded.");
291
292 let viewport = Viewport::new();
293 let viewports = [viewport].to_vec();
294
295 let new_transaction_streams = WaveData {
296 inner: DataContainer::Transactions(new_ftr),
297 source: filename,
298 format,
299 active_scope: None,
300 items_tree: DisplayedItemTree::default(),
301 displayed_items: HashMap::new(),
302 viewports,
303 cursor: None,
304 markers: HashMap::new(),
305 focused_item: None,
306 focused_transaction: (None, None),
307 default_variable_name_type: self.user.config.default_variable_name_type,
308 display_variable_indices: self.show_variable_indices(),
309 scroll_offset: 0.,
310 drawing_infos: vec![],
311 top_item_draw_offset: 0.,
312 total_height: 0.,
313 display_item_ref_counter: 0,
314 old_num_timestamps: None,
315 graphics: HashMap::new(),
316 };
317
318 self.invalidate_draw_commands();
319
320 self.user.config.theme.alt_frequency = 0;
321 self.user.wanted_timeunit = new_transaction_streams.inner.metadata().timescale.unit;
322 self.user.waves = Some(new_transaction_streams);
323 }
324
325 pub(crate) fn handle_async_messages(&mut self) {
326 let mut msgs = vec![];
327 loop {
328 match self.channels.msg_receiver.try_recv() {
329 Ok(msg) => msgs.push(msg),
330 Err(std::sync::mpsc::TryRecvError::Empty) => break,
331 Err(std::sync::mpsc::TryRecvError::Disconnected) => {
332 trace!("Message sender disconnected");
333 break;
334 }
335 }
336 }
337
338 while let Some(msg) = msgs.pop() {
339 self.update(msg);
340 }
341 }
342
343 pub fn get_visuals(&self) -> Visuals {
344 let widget_style = WidgetVisuals {
345 bg_fill: self.user.config.theme.secondary_ui_color.background,
346 fg_stroke: Stroke {
347 color: self.user.config.theme.secondary_ui_color.foreground,
348 width: 1.0,
349 },
350 weak_bg_fill: self.user.config.theme.secondary_ui_color.background,
351 bg_stroke: Stroke {
352 color: self.user.config.theme.border_color,
353 width: 1.0,
354 },
355 corner_radius: CornerRadius::same(2),
356 expansion: 0.0,
357 };
358
359 Visuals {
360 override_text_color: Some(self.user.config.theme.foreground),
361 extreme_bg_color: self.user.config.theme.secondary_ui_color.background,
362 panel_fill: self.user.config.theme.secondary_ui_color.background,
363 window_fill: self.user.config.theme.primary_ui_color.background,
364 window_stroke: Stroke {
365 width: 1.0,
366 color: self.user.config.theme.border_color,
367 },
368 selection: Selection {
369 bg_fill: self.user.config.theme.selected_elements_colors.background,
370 stroke: Stroke {
371 color: self.user.config.theme.selected_elements_colors.foreground,
372 width: 1.0,
373 },
374 },
375 widgets: Widgets {
376 noninteractive: widget_style,
377 inactive: widget_style,
378 hovered: widget_style,
379 active: widget_style,
380 open: widget_style,
381 },
382 ..Visuals::dark()
383 }
384 }
385
386 pub(crate) fn load_state(&mut self, mut loaded_state: Box<UserState>, path: Option<PathBuf>) {
387 mem::swap(&mut self.user, &mut loaded_state);
389
390 mem::swap(&mut loaded_state.waves, &mut self.user.waves);
393 let load_commands = if let (Some(waves), Some(new_waves)) =
394 (&mut self.user.waves, &mut loaded_state.waves)
395 {
396 mem::swap(&mut waves.active_scope, &mut new_waves.active_scope);
397 let items = std::mem::take(&mut new_waves.displayed_items);
398 let items_tree = std::mem::take(&mut new_waves.items_tree);
399 let load_commands = waves.update_with_items(&items, items_tree, &self.translators);
400
401 mem::swap(&mut waves.viewports, &mut new_waves.viewports);
402 mem::swap(&mut waves.cursor, &mut new_waves.cursor);
403 mem::swap(&mut waves.markers, &mut new_waves.markers);
404 mem::swap(&mut waves.focused_item, &mut new_waves.focused_item);
405 waves.default_variable_name_type = new_waves.default_variable_name_type;
406 waves.scroll_offset = new_waves.scroll_offset;
407 load_commands
408 } else {
409 None
410 };
411 if let Some(load_commands) = load_commands {
412 self.load_variables(load_commands);
413 };
414
415 self.user.drag_started = false;
417 self.user.drag_source_idx = None;
418 self.user.drag_target_idx = None;
419
420 self.user.previous_waves = None;
422 self.user.count = None;
423
424 self.user.state_file = path;
426 self.user.rename_target = None;
427
428 self.invalidate_draw_commands();
429 if let Some(waves) = &mut self.user.waves {
430 waves.update_viewports();
431 }
432 }
433
434 pub fn waves_fully_loaded(&self) -> bool {
438 self.user
439 .waves
440 .as_ref()
441 .is_some_and(|w| w.inner.is_fully_loaded())
442 }
443
444 pub(crate) fn current_canvas_state(waves: &WaveData, message: String) -> CanvasState {
446 CanvasState {
447 message,
448 focused_item: waves.focused_item,
449 focused_transaction: waves.focused_transaction.clone(),
450 items_tree: waves.items_tree.clone(),
451 displayed_items: waves.displayed_items.clone(),
452 markers: waves.markers.clone(),
453 }
454 }
455
456 pub(crate) fn save_current_canvas(&mut self, message: String) {
458 if let Some(waves) = &self.user.waves {
459 self.undo_stack
460 .push(SystemState::current_canvas_state(waves, message));
461
462 if self.undo_stack.len() > self.user.config.undo_stack_size {
463 self.undo_stack.remove(0);
464 }
465 self.redo_stack.clear();
466 }
467 }
468
469 #[cfg(not(target_arch = "wasm32"))]
470 pub(crate) fn start_wcp_server(&mut self, address: Option<String>, initiate: bool) {
471 use wcp::wcp_server::WcpServer;
472
473 use crate::wcp;
474
475 if self.wcp_server_thread.as_ref().is_some()
476 || self
477 .wcp_running_signal
478 .load(std::sync::atomic::Ordering::Relaxed)
479 {
480 warn!("WCP HTTP server is already running");
481 return;
482 }
483 let (wcp_s2c_sender, wcp_s2c_receiver) = tokio::sync::mpsc::channel(100);
485 let (wcp_c2s_sender, wcp_c2s_receiver) = tokio::sync::mpsc::channel(100);
486
487 self.channels.wcp_c2s_receiver = Some(wcp_c2s_receiver);
488 self.channels.wcp_s2c_sender = Some(wcp_s2c_sender);
489 let stop_signal_copy = self.wcp_stop_signal.clone();
490 stop_signal_copy.store(false, std::sync::atomic::Ordering::Relaxed);
491 let running_signal_copy = self.wcp_running_signal.clone();
492 running_signal_copy.store(true, std::sync::atomic::Ordering::Relaxed);
493 let greeted_signal_copy = self.wcp_greeted_signal.clone();
494 greeted_signal_copy.store(true, std::sync::atomic::Ordering::Relaxed);
495
496 let ctx = self.context.clone();
497 let address = address.unwrap_or(self.user.config.wcp.address.clone());
498 self.wcp_server_address = Some(address.clone());
499 self.wcp_server_thread = Some(tokio::spawn(async move {
500 let server = WcpServer::new(
501 address,
502 initiate,
503 wcp_c2s_sender,
504 wcp_s2c_receiver,
505 stop_signal_copy,
506 running_signal_copy,
507 greeted_signal_copy,
508 ctx,
509 )
510 .await;
511 match server {
512 Ok(mut server) => server.run().await,
513 Err(m) => {
514 error!("Could not start WCP server. {m:?}")
515 }
516 }
517 }));
518 }
519
520 #[cfg_attr(target_arch = "wasm32", allow(dead_code))]
521 pub(crate) fn stop_wcp_server(&mut self) {
522 if self.wcp_server_address.is_some() && self.wcp_server_thread.is_some() {
525 self.wcp_stop_signal
527 .store(true, std::sync::atomic::Ordering::Relaxed);
528
529 self.wcp_server_thread = None;
530 self.wcp_server_address = None;
531 self.channels.wcp_s2c_sender = None;
532 self.channels.wcp_c2s_receiver = None;
533 info!("Stopped WCP server");
534 }
535 }
536}