1use config::builder::DefaultState;
2use config::{Config, ConfigBuilder};
3#[cfg(not(target_arch = "wasm32"))]
4use config::{Environment, File};
5use derive_more::{Display, FromStr};
6#[cfg(not(target_arch = "wasm32"))]
7use directories::ProjectDirs;
8use ecolor::Color32;
9use enum_iterator::Sequence;
10use epaint::{PathStroke, Stroke};
11use eyre::{Report, Result, WrapErr as _, anyhow};
12use serde::de;
13use serde::{Deserialize, Deserializer, Serialize};
14use std::collections::HashMap;
15#[cfg(not(target_arch = "wasm32"))]
16use std::path::{Path, PathBuf};
17use std::sync::LazyLock;
18use surver::SurverConfig;
19use tracing::info;
20
21use crate::hierarchy::{HierarchyStyle, ParameterDisplayLocation};
22use crate::keyboard_shortcuts::{SurferShortcuts, deserialize_shortcuts};
23use crate::mousegestures::GestureZones;
24use crate::time::TimeFormat;
25use crate::trace_style::TraceStyle;
26use crate::wave_container::VariableMeta;
27use crate::{clock_highlighting::ClockHighlightType, variable_name_type::VariableNameType};
28use surfer_translation_types::{VariableEncoding, VariableType};
29
30macro_rules! theme {
31 ($name:expr) => {
32 (
33 $name,
34 include_str!(concat!("../../themes/", $name, ".toml")),
35 )
36 };
37}
38
39macro_rules! named_theme {
40 ($name:expr, $file:expr) => {
41 (
42 $name,
43 include_str!(concat!("../../themes/", $file, ".toml")),
44 )
45 };
46}
47
48static BUILTIN_THEMES: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
50 HashMap::from([
51 theme!("dark+"),
52 theme!("dark-high-contrast"),
53 named_theme!("IBM", "ibm"),
54 theme!("light+"),
55 theme!("light-high-contrast"),
56 named_theme!("Okabe/Ito", "okabe-ito"),
57 named_theme!("Petroff Dark", "petroff-dark"),
58 named_theme!("Petroff Light", "petroff-light"),
59 named_theme!("Rosé Pine", "rose-pine"),
60 named_theme!("Rosé Pine Moon", "rose-pine-moon"),
61 named_theme!("Rosé Pine Dawn", "rose-pine-dawn"),
62 named_theme!("Solarized", "solarized"),
63 ])
64});
65
66#[cfg(not(target_arch = "wasm32"))]
67pub static PROJECT_DIR: LazyLock<Option<ProjectDirs>> =
68 LazyLock::new(|| ProjectDirs::from("org", "surfer-project", "surfer"));
69#[cfg(not(target_arch = "wasm32"))]
70const OLD_CONFIG_FILE: &str = "surfer.toml";
71#[cfg(not(target_arch = "wasm32"))]
72const CONFIG_FILE: &str = "config.toml";
73#[cfg(not(target_arch = "wasm32"))]
74const THEMES_DIR: &str = "themes";
75#[cfg(not(target_arch = "wasm32"))]
76pub const LOCAL_DIR: &str = ".surfer";
77
78#[derive(Clone, Copy, Debug, Deserialize, Display, FromStr, PartialEq, Eq, Sequence, Serialize)]
80pub enum ArrowKeyBindings {
81 Edge,
83
84 Scroll,
86}
87
88#[derive(Clone, Copy, Debug, Deserialize, Display, FromStr, PartialEq, Eq, Sequence, Serialize)]
89pub enum TransitionValue {
90 Previous,
92 Next,
94 Both,
96}
97
98#[derive(Debug, Deserialize, Display, PartialEq, Eq, Sequence, Serialize, Clone, Copy)]
100pub enum PrimaryMouseDrag {
101 #[display("Measure time")]
103 Measure,
104
105 #[display("Move cursor")]
107 Cursor,
108}
109
110#[derive(Debug, Deserialize, Display, PartialEq, Eq, Sequence, Serialize, Clone, Copy)]
111pub enum AutoLoad {
112 Always,
113 Never,
114 Ask,
115}
116
117impl AutoLoad {
118 #[must_use]
119 pub fn from_bool(auto_load: bool) -> Self {
120 if auto_load {
121 AutoLoad::Always
122 } else {
123 AutoLoad::Never
124 }
125 }
126}
127
128#[derive(Clone, Copy, Debug, Deserialize, Display, FromStr, PartialEq, Eq, Sequence, Serialize)]
129pub enum FocusHighlight {
130 Off,
131 Background,
132 LineWidth,
133 BrightnessShift,
134 LineWidthAndBrightnessShift,
135}
136
137#[derive(Debug, Deserialize)]
138pub struct SurferConfig {
139 pub layout: SurferLayout,
140 #[serde(deserialize_with = "deserialize_theme")]
141 pub theme: SurferTheme,
142 pub gesture: SurferGesture,
146 pub behavior: SurferBehavior,
147 pub default_time_format: TimeFormat,
149 pub default_variable_name_type: VariableNameType,
150 default_clock_highlight_type: ClockHighlightType,
151 #[serde(deserialize_with = "deserialize_non_negative_f32")]
153 pub snap_distance: f32,
154 pub undo_stack_size: usize,
156 autoreload_files: AutoLoad,
158 autoload_sibling_state_files: AutoLoad,
160 pub plugin: PluginConfig,
162 pub wcp: WcpConfig,
164 pub server: SurverConfig,
166 #[serde(deserialize_with = "deserialize_non_negative_f32")]
168 pub animation_time: f32,
169 pub animation_enabled: bool,
171 pub max_url_length: u16,
174 #[serde(deserialize_with = "deserialize_shortcuts")]
176 pub shortcuts: SurferShortcuts,
177 pub show_divider_text: bool,
179}
180
181impl SurferConfig {
182 #[must_use]
183 pub fn default_clock_highlight_type(&self) -> ClockHighlightType {
184 self.default_clock_highlight_type
185 }
186
187 #[must_use]
188 pub fn autoload_sibling_state_files(&self) -> AutoLoad {
189 self.autoload_sibling_state_files
190 }
191
192 #[must_use]
193 pub fn autoreload_files(&self) -> AutoLoad {
194 self.autoreload_files
195 }
196
197 #[must_use]
198 pub fn animation_enabled(&self) -> bool {
199 self.animation_enabled
200 }
201
202 #[must_use]
203 pub fn show_divider_text(&self) -> bool {
204 self.show_divider_text
205 }
206}
207
208#[derive(Debug, Deserialize)]
209pub struct SurferLayout {
210 show_hierarchy: bool,
212 show_menu: bool,
214 show_toolbar: bool,
216 show_ticks: bool,
218 show_tooltip: bool,
220 show_scope_tooltip: bool,
222 show_overview: bool,
224 show_statusbar: bool,
226 show_variable_indices: bool,
228 show_variable_direction: bool,
230 show_default_timeline: bool,
232 show_empty_scopes: bool,
234 show_hierarchy_icons: bool,
236 parameter_display_location: ParameterDisplayLocation,
238 pub window_height: usize,
240 pub window_width: usize,
242 pub window_x_position: usize,
244 pub window_y_position: usize,
246 align_names_right: bool,
248 hierarchy_style: HierarchyStyle,
250 #[serde(deserialize_with = "deserialize_non_negative_f32")]
252 pub waveforms_text_size: f32,
253 #[serde(deserialize_with = "deserialize_non_negative_f32")]
255 pub waveforms_line_height: f32,
256 #[serde(deserialize_with = "deserialize_non_negative_f32")]
258 pub waveforms_gap: f32,
259 #[serde(deserialize_with = "deserialize_non_negative_f32_vec")]
261 pub waveforms_line_height_multiples: Vec<f32>,
262 #[serde(deserialize_with = "deserialize_non_negative_f32")]
264 pub analog_waveform_multiplier: f32,
265 #[serde(deserialize_with = "deserialize_non_negative_f32")]
267 pub transactions_line_height: f32,
268 #[serde(deserialize_with = "deserialize_non_negative_f32_vec")]
270 pub zoom_factors: Vec<f32>,
271 #[serde(deserialize_with = "deserialize_non_negative_f32")]
273 default_zoom_factor: f32,
274 focus_highlight: FocusHighlight,
276 move_focus_on_inserted_marker: bool,
278 fill_high_values: bool,
280 draw_vector_unknowns_as_line: bool,
282 trace_style: TraceStyle,
284 transition_value: TransitionValue,
286 #[serde(default)]
288 toolbar: ToolbarLayout,
289 #[serde(default)]
292 pub enable_time_offset: bool,
293}
294
295#[derive(Debug, Deserialize, Default)]
296struct ToolbarLayout {
297 #[serde(default)]
299 row: HashMap<String, u8>,
300 #[serde(default)]
302 visibility: HashMap<String, bool>,
303}
304
305impl SurferLayout {
306 #[must_use]
307 pub fn show_hierarchy(&self) -> bool {
308 self.show_hierarchy
309 }
310 #[must_use]
311 pub fn show_menu(&self) -> bool {
312 self.show_menu
313 }
314 #[must_use]
315 pub fn show_ticks(&self) -> bool {
316 self.show_ticks
317 }
318 #[must_use]
319 pub fn show_tooltip(&self) -> bool {
320 self.show_tooltip
321 }
322 #[must_use]
323 pub fn show_scope_tooltip(&self) -> bool {
324 self.show_scope_tooltip
325 }
326 #[must_use]
327 pub fn show_default_timeline(&self) -> bool {
328 self.show_default_timeline
329 }
330 #[must_use]
331 pub fn show_toolbar(&self) -> bool {
332 self.show_toolbar
333 }
334 #[must_use]
335 pub fn show_overview(&self) -> bool {
336 self.show_overview
337 }
338 #[must_use]
339 pub fn show_statusbar(&self) -> bool {
340 self.show_statusbar
341 }
342 #[must_use]
343 pub fn align_names_right(&self) -> bool {
344 self.align_names_right
345 }
346 #[must_use]
347 pub fn show_variable_indices(&self) -> bool {
348 self.show_variable_indices
349 }
350 #[must_use]
351 pub fn show_variable_direction(&self) -> bool {
352 self.show_variable_direction
353 }
354 #[must_use]
355 pub fn default_zoom_factor(&self) -> f32 {
356 self.default_zoom_factor
357 }
358 #[must_use]
359 pub fn show_empty_scopes(&self) -> bool {
360 self.show_empty_scopes
361 }
362 #[must_use]
363 pub fn show_hierarchy_icons(&self) -> bool {
364 self.show_hierarchy_icons
365 }
366 #[must_use]
367 pub fn parameter_display_location(&self) -> ParameterDisplayLocation {
368 self.parameter_display_location
369 }
370 #[must_use]
371 pub fn focus_highlight(&self) -> FocusHighlight {
372 self.focus_highlight
373 }
374 #[must_use]
375 pub fn move_focus_on_inserted_marker(&self) -> bool {
376 self.move_focus_on_inserted_marker
377 }
378 #[must_use]
379 pub fn fill_high_values(&self) -> bool {
380 self.fill_high_values
381 }
382 #[must_use]
383 pub fn draw_vector_unknowns_as_line(&self) -> bool {
384 self.draw_vector_unknowns_as_line
385 }
386 #[must_use]
387 pub fn hierarchy_style(&self) -> HierarchyStyle {
388 self.hierarchy_style
389 }
390 #[must_use]
391 pub fn trace_style(&self) -> TraceStyle {
392 self.trace_style
393 }
394 #[must_use]
395 pub fn transition_value(&self) -> TransitionValue {
396 self.transition_value
397 }
398 #[must_use]
399 pub fn toolbar_group_row(&self, id: &str) -> Option<u8> {
400 self.toolbar.row.get(id).copied()
401 }
402 #[must_use]
403 pub fn toolbar_group_visibility(&self, id: &str) -> Option<bool> {
404 self.toolbar.visibility.get(id).copied()
405 }
406 #[must_use]
407 pub fn enable_time_offset(&self) -> bool {
408 self.enable_time_offset
409 }
410}
411
412#[derive(Debug, Deserialize)]
413pub struct SurferBehavior {
414 pub keep_during_reload: bool,
416 pub file_history_size: usize,
418 arrow_key_bindings: ArrowKeyBindings,
420 primary_button_drag_behavior: PrimaryMouseDrag,
423}
424
425impl SurferBehavior {
426 #[must_use]
427 pub fn file_history_size(&self) -> usize {
428 self.file_history_size
429 }
430
431 #[must_use]
432 pub fn primary_button_drag_behavior(&self) -> PrimaryMouseDrag {
433 self.primary_button_drag_behavior
434 }
435
436 #[must_use]
437 pub fn arrow_key_bindings(&self) -> ArrowKeyBindings {
438 self.arrow_key_bindings
439 }
440}
441
442#[derive(Debug, Deserialize)]
443pub struct SurferGesture {
447 #[serde(deserialize_with = "deserialize_non_negative_f32")]
449 pub size: f32,
450 #[serde(deserialize_with = "deserialize_non_negative_f32")]
452 pub deadzone: f32,
453 #[serde(deserialize_with = "deserialize_non_negative_f32")]
455 pub background_radius: f32,
456 #[serde(deserialize_with = "deserialize_unit_interval_f32")]
458 pub background_gamma: f32,
459 pub mapping: GestureZones,
461}
462
463#[derive(Clone, Debug, Deserialize)]
464pub struct SurferLineStyle {
465 #[serde(deserialize_with = "deserialize_hex_color")]
466 pub color: Color32,
467 #[serde(deserialize_with = "deserialize_non_negative_f32")]
468 pub width: f32,
469}
470
471impl From<SurferLineStyle> for Stroke {
472 fn from(style: SurferLineStyle) -> Self {
473 Stroke {
474 color: style.color,
475 width: style.width,
476 }
477 }
478}
479
480impl From<&SurferLineStyle> for Stroke {
481 fn from(style: &SurferLineStyle) -> Self {
482 Stroke {
483 color: style.color,
484 width: style.width,
485 }
486 }
487}
488
489impl From<&SurferLineStyle> for PathStroke {
490 fn from(style: &SurferLineStyle) -> Self {
491 PathStroke::new(style.width, style.color)
492 }
493}
494
495#[derive(Debug, Deserialize)]
496pub struct SurferTicks {
498 #[serde(deserialize_with = "deserialize_unit_interval_f32")]
500 pub density: f32,
501 pub style: SurferLineStyle,
503}
504
505#[derive(Debug, Deserialize)]
506pub struct SurferRelationArrow {
507 pub style: SurferLineStyle,
509
510 #[serde(deserialize_with = "deserialize_non_negative_f32")]
512 pub head_angle: f32,
513
514 #[serde(deserialize_with = "deserialize_non_negative_f32")]
516 pub head_length: f32,
517}
518
519#[derive(Debug, Deserialize)]
520pub struct SurferTheme {
521 #[serde(deserialize_with = "deserialize_hex_color")]
523 pub foreground: Color32,
524 #[serde(deserialize_with = "deserialize_hex_color")]
525 pub border_color: Color32,
527 #[serde(deserialize_with = "deserialize_hex_color")]
529 pub alt_text_color: Color32,
530 pub canvas_colors: ThemeColorTriple,
532 pub primary_ui_color: ThemeColorPair,
534 pub secondary_ui_color: ThemeColorPair,
537 pub selected_elements_colors: ThemeColorPair,
539
540 pub accent_info: ThemeColorPair,
541 pub accent_warn: ThemeColorPair,
542 pub accent_error: ThemeColorPair,
543
544 pub cursor: SurferLineStyle,
546
547 pub gesture: SurferLineStyle,
549
550 pub measure: SurferLineStyle,
552
553 pub annotation_rectangle: SurferLineStyle,
555
556 pub annotation_arrow: SurferLineStyle,
558
559 pub clock_highlight_line: SurferLineStyle,
561 #[serde(deserialize_with = "deserialize_hex_color_vec")]
562 pub clock_highlight_line_colors: Vec<Color32>,
564 #[serde(deserialize_with = "deserialize_hex_color")]
565 pub clock_highlight_cycle: Color32,
566 #[serde(deserialize_with = "deserialize_hex_color_vec")]
567 pub clock_highlight_cycle_colors: Vec<Color32>,
569 pub clock_rising_marker: bool,
571
572 #[serde(deserialize_with = "deserialize_hex_color")]
573 pub variable_default: Color32,
575 #[serde(deserialize_with = "deserialize_hex_color")]
576 pub variable_highimp: Color32,
578 #[serde(deserialize_with = "deserialize_hex_color")]
579 pub variable_undef: Color32,
581 #[serde(deserialize_with = "deserialize_hex_color")]
582 pub variable_dontcare: Color32,
584 #[serde(deserialize_with = "deserialize_hex_color")]
585 pub variable_weak: Color32,
587 #[serde(deserialize_with = "deserialize_hex_color")]
588 pub variable_parameter: Color32,
590 #[serde(deserialize_with = "deserialize_hex_color")]
591 pub transaction_default: Color32,
593 pub relation_arrow: SurferRelationArrow,
595 #[serde(deserialize_with = "deserialize_hex_color")]
596 pub variable_event: Color32,
598
599 #[serde(deserialize_with = "deserialize_unit_interval_f32")]
602 pub waveform_opacity: f32,
603 #[serde(deserialize_with = "deserialize_unit_interval_f32")]
605 pub wide_opacity: f32,
606
607 #[serde(deserialize_with = "deserialize_color_map")]
608 pub colors: HashMap<String, Color32>,
609 #[serde(deserialize_with = "deserialize_hex_color")]
610 pub highlight_background: Color32,
611
612 #[serde(deserialize_with = "deserialize_non_negative_f32")]
614 pub linewidth: f32,
615
616 #[serde(deserialize_with = "deserialize_non_negative_f32")]
618 pub thick_linewidth: f32,
619 #[serde(deserialize_with = "deserialize_non_negative_f32")]
621 pub focus_highlight_line_width_multiplier: f32,
622 #[serde(deserialize_with = "deserialize_unit_interval_f32")]
624 pub focus_highlight_brightness_shift: f32,
625
626 #[serde(deserialize_with = "deserialize_non_negative_f32")]
628 pub vector_transition_width: f32,
629
630 pub alt_frequency: usize,
633
634 pub viewport_separator: SurferLineStyle,
636
637 #[serde(deserialize_with = "deserialize_hex_color")]
639 pub drag_hint_color: Color32,
640 #[serde(deserialize_with = "deserialize_non_negative_f32")]
641 pub drag_hint_width: f32,
642 #[serde(deserialize_with = "deserialize_non_negative_f32")]
643 pub drag_threshold: f32,
644
645 pub ticks: SurferTicks,
647
648 pub theme_names: Vec<String>,
650
651 #[serde(default)]
653 pub theme_name: String,
654
655 #[serde(default)]
657 pub scope_icons: ScopeIcons,
658
659 #[serde(default)]
661 pub variable_icons: VariableIcons,
662}
663
664#[derive(Clone, Debug, Deserialize)]
666#[serde(default)]
667pub struct ScopeIconColors {
668 #[serde(deserialize_with = "deserialize_hex_color")]
669 pub module: Color32,
670 #[serde(deserialize_with = "deserialize_hex_color")]
671 pub task: Color32,
672 #[serde(deserialize_with = "deserialize_hex_color")]
673 pub function: Color32,
674 #[serde(deserialize_with = "deserialize_hex_color")]
675 pub begin: Color32,
676 #[serde(deserialize_with = "deserialize_hex_color")]
677 pub fork: Color32,
678 #[serde(deserialize_with = "deserialize_hex_color")]
679 pub generate: Color32,
680 #[serde(rename = "struct", deserialize_with = "deserialize_hex_color")]
681 pub struct_: Color32,
682 #[serde(deserialize_with = "deserialize_hex_color")]
683 pub union: Color32,
684 #[serde(deserialize_with = "deserialize_hex_color")]
685 pub class: Color32,
686 #[serde(deserialize_with = "deserialize_hex_color")]
687 pub interface: Color32,
688 #[serde(deserialize_with = "deserialize_hex_color")]
689 pub package: Color32,
690 #[serde(deserialize_with = "deserialize_hex_color")]
691 pub program: Color32,
692 #[serde(deserialize_with = "deserialize_hex_color")]
693 pub vhdl_architecture: Color32,
694 #[serde(deserialize_with = "deserialize_hex_color")]
695 pub vhdl_procedure: Color32,
696 #[serde(deserialize_with = "deserialize_hex_color")]
697 pub vhdl_function: Color32,
698 #[serde(deserialize_with = "deserialize_hex_color")]
699 pub vhdl_record: Color32,
700 #[serde(deserialize_with = "deserialize_hex_color")]
701 pub vhdl_process: Color32,
702 #[serde(deserialize_with = "deserialize_hex_color")]
703 pub vhdl_block: Color32,
704 #[serde(deserialize_with = "deserialize_hex_color")]
705 pub vhdl_for_generate: Color32,
706 #[serde(deserialize_with = "deserialize_hex_color")]
707 pub vhdl_if_generate: Color32,
708 #[serde(deserialize_with = "deserialize_hex_color")]
709 pub vhdl_generate: Color32,
710 #[serde(deserialize_with = "deserialize_hex_color")]
711 pub vhdl_package: Color32,
712 #[serde(deserialize_with = "deserialize_hex_color")]
713 pub ghw_generic: Color32,
714 #[serde(deserialize_with = "deserialize_hex_color")]
715 pub vhdl_array: Color32,
716 #[serde(deserialize_with = "deserialize_hex_color")]
717 pub unknown: Color32,
718 #[serde(deserialize_with = "deserialize_hex_color")]
719 pub clocking: Color32,
720 #[serde(deserialize_with = "deserialize_hex_color")]
721 pub sv_array: Color32,
722}
723
724impl Default for ScopeIconColors {
725 fn default() -> Self {
726 Self {
727 module: Color32::from_rgb(0x4F, 0xC3, 0xF7), task: Color32::from_rgb(0xFF, 0xB7, 0x4D), function: Color32::from_rgb(0xBA, 0x68, 0xC8), begin: Color32::from_rgb(0x81, 0xC7, 0x84), fork: Color32::from_rgb(0xFF, 0x80, 0x80), generate: Color32::from_rgb(0x64, 0xB5, 0xF6), struct_: Color32::from_rgb(0x4D, 0xD0, 0xE1), union: Color32::from_rgb(0x4D, 0xD0, 0xE1), class: Color32::from_rgb(0xF0, 0x62, 0x92), interface: Color32::from_rgb(0xAE, 0xD5, 0x81), package: Color32::from_rgb(0xFF, 0xD5, 0x4F), program: Color32::from_rgb(0xA1, 0x88, 0x7F), vhdl_architecture: Color32::from_rgb(0x4F, 0xC3, 0xF7), vhdl_procedure: Color32::from_rgb(0xFF, 0xB7, 0x4D), vhdl_function: Color32::from_rgb(0xBA, 0x68, 0xC8), vhdl_record: Color32::from_rgb(0x4D, 0xD0, 0xE1), vhdl_process: Color32::from_rgb(0x81, 0xC7, 0x84), vhdl_block: Color32::from_rgb(0x90, 0xA4, 0xAE), vhdl_for_generate: Color32::from_rgb(0x64, 0xB5, 0xF6), vhdl_if_generate: Color32::from_rgb(0x64, 0xB5, 0xF6), vhdl_generate: Color32::from_rgb(0x64, 0xB5, 0xF6), vhdl_package: Color32::from_rgb(0xFF, 0xD5, 0x4F), ghw_generic: Color32::from_rgb(0xB0, 0xBE, 0xC5), vhdl_array: Color32::from_rgb(0xCE, 0x93, 0xD8), clocking: Color32::from_rgb(0xF0, 0x62, 0x92), sv_array: Color32::from_rgb(0xCE, 0x93, 0xD8), unknown: Color32::from_rgb(0x9E, 0x9E, 0x9E), }
755 }
756}
757
758#[derive(Clone, Debug, Deserialize)]
761#[serde(default)]
762pub struct ScopeIcons {
763 pub module: String,
765 pub task: String,
766 pub function: String,
767 pub begin: String,
768 pub fork: String,
769 pub generate: String,
770 #[serde(rename = "struct")]
771 pub struct_: String,
772 pub union: String,
773 pub class: String,
774 pub interface: String,
775 pub package: String,
776 pub program: String,
777 pub vhdl_architecture: String,
779 pub vhdl_procedure: String,
780 pub vhdl_function: String,
781 pub vhdl_record: String,
782 pub vhdl_process: String,
783 pub vhdl_block: String,
784 pub vhdl_for_generate: String,
785 pub vhdl_if_generate: String,
786 pub vhdl_generate: String,
787 pub vhdl_package: String,
788 pub ghw_generic: String,
789 pub vhdl_array: String,
790 pub unknown: String,
791 pub clocking: String,
792 pub sv_array: String,
793 #[serde(default)]
795 pub colors: ScopeIconColors,
796}
797
798impl Default for ScopeIcons {
799 fn default() -> Self {
800 use egui_remixicon::icons;
801 Self {
802 module: icons::CPU_LINE.to_string(),
804 task: icons::TASK_LINE.to_string(),
805 function: icons::BRACES_LINE.to_string(),
806 begin: icons::CODE_BOX_LINE.to_string(),
807 fork: icons::GIT_BRANCH_LINE.to_string(),
808 generate: icons::REPEAT_LINE.to_string(),
809 struct_: icons::TABLE_LINE.to_string(),
810 union: icons::MERGE_CELLS_HORIZONTAL.to_string(),
811 class: icons::TABLE_LINE.to_string(),
812 interface: icons::PLUG_LINE.to_string(),
813 package: icons::BOX_3_LINE.to_string(),
814 program: icons::FILE_CODE_LINE.to_string(),
815 vhdl_architecture: icons::CPU_LINE.to_string(),
817 vhdl_procedure: icons::TERMINAL_LINE.to_string(),
818 vhdl_function: icons::BRACES_LINE.to_string(),
819 vhdl_record: icons::TABLE_LINE.to_string(),
820 vhdl_process: icons::FLASHLIGHT_LINE.to_string(),
821 vhdl_block: icons::CODE_BLOCK.to_string(),
822 vhdl_for_generate: icons::REPEAT_LINE.to_string(),
823 vhdl_if_generate: icons::QUESTION_LINE.to_string(),
824 vhdl_generate: icons::REPEAT_LINE.to_string(),
825 vhdl_package: icons::BOX_3_LINE.to_string(),
826 ghw_generic: icons::SETTINGS_3_LINE.to_string(),
827 vhdl_array: icons::BRACKETS_LINE.to_string(),
828 sv_array: icons::BRACKETS_LINE.to_string(),
829 clocking: icons::TIME_LINE.to_string(),
830 unknown: icons::QUESTION_LINE.to_string(),
831 colors: ScopeIconColors::default(),
832 }
833 }
834}
835
836impl ScopeIcons {
837 #[must_use]
840 pub fn get_icon(&self, scope_type: Option<wellen::ScopeType>) -> (&str, Color32) {
841 use wellen::ScopeType;
842 match scope_type {
843 None => (&self.module, self.colors.module),
844 Some(st) => match st {
845 ScopeType::Module => (&self.module, self.colors.module),
846 ScopeType::Task => (&self.task, self.colors.task),
847 ScopeType::Function => (&self.function, self.colors.function),
848 ScopeType::Begin => (&self.begin, self.colors.begin),
849 ScopeType::Fork => (&self.fork, self.colors.fork),
850 ScopeType::Generate => (&self.generate, self.colors.generate),
851 ScopeType::Struct => (&self.struct_, self.colors.struct_),
852 ScopeType::Union => (&self.union, self.colors.union),
853 ScopeType::Class => (&self.class, self.colors.class),
854 ScopeType::Interface => (&self.interface, self.colors.interface),
855 ScopeType::Package => (&self.package, self.colors.package),
856 ScopeType::Program => (&self.program, self.colors.program),
857 ScopeType::VhdlArchitecture => {
858 (&self.vhdl_architecture, self.colors.vhdl_architecture)
859 }
860 ScopeType::VhdlProcedure => (&self.vhdl_procedure, self.colors.vhdl_procedure),
861 ScopeType::VhdlFunction => (&self.vhdl_function, self.colors.vhdl_function),
862 ScopeType::VhdlRecord => (&self.vhdl_record, self.colors.vhdl_record),
863 ScopeType::VhdlProcess => (&self.vhdl_process, self.colors.vhdl_process),
864 ScopeType::VhdlBlock => (&self.vhdl_block, self.colors.vhdl_block),
865 ScopeType::VhdlForGenerate => {
866 (&self.vhdl_for_generate, self.colors.vhdl_for_generate)
867 }
868 ScopeType::VhdlIfGenerate => (&self.vhdl_if_generate, self.colors.vhdl_if_generate),
869 ScopeType::VhdlGenerate => (&self.vhdl_generate, self.colors.vhdl_generate),
870 ScopeType::VhdlPackage => (&self.vhdl_package, self.colors.vhdl_package),
871 ScopeType::GhwGeneric => (&self.ghw_generic, self.colors.ghw_generic),
872 ScopeType::VhdlArray => (&self.vhdl_array, self.colors.vhdl_array),
873 ScopeType::Unknown => (&self.unknown, self.colors.unknown),
874 ScopeType::SvArray => (&self.sv_array, self.colors.sv_array),
875 ScopeType::Clocking => (&self.clocking, self.colors.clocking),
876 _ => (&self.unknown, self.colors.unknown),
877 },
878 }
879 }
880}
881
882#[derive(Clone, Debug, Deserialize)]
885#[serde(default)]
886pub struct VariableIconColors {
887 #[serde(deserialize_with = "deserialize_hex_color")]
889 pub wire: Color32,
890 #[serde(deserialize_with = "deserialize_hex_color")]
892 pub bus: Color32,
893 #[serde(deserialize_with = "deserialize_hex_color")]
895 pub string: Color32,
896 #[serde(deserialize_with = "deserialize_hex_color")]
898 pub event: Color32,
899 #[serde(deserialize_with = "deserialize_hex_color")]
901 pub other: Color32,
902}
903
904impl Default for VariableIconColors {
905 fn default() -> Self {
906 Self {
907 wire: Color32::from_rgb(0x81, 0xC7, 0x84), bus: Color32::from_rgb(0x64, 0xB5, 0xF6), string: Color32::from_rgb(0xFF, 0xB7, 0x4D), event: Color32::from_rgb(0xF0, 0x62, 0x92), other: Color32::from_rgb(0xBA, 0x68, 0xC8), }
913 }
914}
915
916#[derive(Clone, Debug, Deserialize)]
919#[serde(default)]
920pub struct VariableIcons {
921 pub wire: String,
923 pub bus: String,
925 pub string: String,
927 pub event: String,
929 pub other: String,
931 #[serde(default)]
933 pub colors: VariableIconColors,
934}
935
936impl Default for VariableIcons {
937 fn default() -> Self {
938 use egui_remixicon::icons;
939 Self {
940 wire: icons::GIT_COMMIT_LINE.to_string(),
941 bus: icons::BRACKETS_LINE.to_string(),
942 string: icons::TEXT.to_string(),
943 event: icons::ARROW_UP_LONG_LINE.to_string(),
944 other: icons::NUMBERS_LINE.to_string(),
945 colors: VariableIconColors::default(),
946 }
947 }
948}
949
950impl VariableIcons {
951 #[must_use]
954 pub fn get_icon(&self, meta: Option<&VariableMeta>) -> (&str, Color32) {
955 let Some(meta) = meta else {
956 return (&self.other, self.colors.other);
957 };
958
959 if matches!(
960 meta.variable_type,
961 Some(VariableType::VCDEvent | VariableType::EventParameter)
962 ) {
963 return (&self.event, self.colors.event);
964 }
965
966 match meta.encoding {
967 VariableEncoding::String => (&self.string, self.colors.string),
968 VariableEncoding::Real => (&self.other, self.colors.other),
969 VariableEncoding::BitVector => match meta.num_bits {
970 Some(1) => (&self.wire, self.colors.wire),
971 Some(n) if n > 1 => (&self.bus, self.colors.bus),
972 _ => (&self.other, self.colors.other),
973 },
974 }
975 }
976}
977
978fn gamma_correction(value: u8) -> f32 {
979 const INV_3294: f32 = 1.0 / 3294.0;
980 const INV_269: f32 = 1.0 / 269.0;
981
982 let v = f32::from(value);
983 if value < 10 {
984 v * INV_3294
985 } else {
986 (v * INV_269 + 0.0513).powf(2.4)
987 }
988}
989
990pub(crate) fn get_luminance(color: Color32) -> f32 {
991 0.2126 * gamma_correction(color.r())
992 + 0.7152 * gamma_correction(color.g())
993 + 0.0722 * gamma_correction(color.b())
994}
995
996impl SurferTheme {
997 #[must_use]
998 pub fn get_color(&self, color: &str) -> Option<Color32> {
999 self.colors.get(color).copied()
1000 }
1001
1002 #[must_use]
1003 pub fn get_best_text_color(&self, backgroundcolor: Color32) -> Color32 {
1004 let l_foreground = get_luminance(self.foreground);
1008 let l_alt_text_color = get_luminance(self.alt_text_color);
1009 let l_background = get_luminance(backgroundcolor);
1010
1011 let mut cr_foreground = (l_foreground + 0.05) / (l_background + 0.05);
1013 cr_foreground = cr_foreground.max(1. / cr_foreground);
1014 let mut cr_alt_text_color = (l_alt_text_color + 0.05) / (l_background + 0.05);
1015 cr_alt_text_color = cr_alt_text_color.max(1. / cr_alt_text_color);
1016
1017 if cr_foreground > cr_alt_text_color {
1019 self.foreground
1020 } else {
1021 self.alt_text_color
1022 }
1023 }
1024
1025 fn generate_defaults(
1026 theme_name: Option<&String>,
1027 ) -> (ConfigBuilder<DefaultState>, Vec<String>) {
1028 let default_theme = String::from(include_str!("../../default_theme.toml"));
1029
1030 let mut theme = Config::builder().add_source(config::File::from_str(
1031 &default_theme,
1032 config::FileFormat::Toml,
1033 ));
1034
1035 let theme_names = all_theme_names();
1036
1037 let override_theme = theme_name
1038 .as_ref()
1039 .and_then(|name| BUILTIN_THEMES.get(name.as_str()).copied())
1040 .unwrap_or("");
1041
1042 theme = theme.add_source(config::File::from_str(
1043 override_theme,
1044 config::FileFormat::Toml,
1045 ));
1046 (theme, theme_names)
1047 }
1048
1049 #[cfg(target_arch = "wasm32")]
1050 pub fn new(theme_name: Option<String>) -> Result<Self> {
1051 let theme_name = theme_name.filter(|s| !s.is_empty());
1052 let (theme, _) = Self::generate_defaults(theme_name.as_ref());
1053
1054 let theme = theme.set_override("theme_names", all_theme_names())?;
1055
1056 let mut result: SurferTheme = theme
1057 .build()?
1058 .try_deserialize()
1059 .map_err(|e| anyhow!("Failed to parse config {e}"))?;
1060 result.theme_name = theme_name.unwrap_or_default();
1061 Ok(result)
1062 }
1063
1064 #[cfg(not(target_arch = "wasm32"))]
1065 pub fn new(theme_name: Option<String>) -> Result<Self> {
1066 use std::fs::ReadDir;
1067
1068 let theme_name = theme_name.filter(|s| !s.is_empty());
1069 let (mut theme, mut theme_names) = Self::generate_defaults(theme_name.as_ref());
1070
1071 let mut add_themes_from_dir = |dir: ReadDir| {
1072 for theme in dir.flatten() {
1073 if let Ok(theme_path) = theme.file_name().into_string()
1074 && let Some(fname_str) = theme_path.strip_suffix(".toml")
1075 {
1076 let fname = fname_str.to_string();
1077 if !fname.is_empty() && !theme_names.contains(&fname) {
1078 theme_names.push(fname);
1079 }
1080 }
1081 }
1082 };
1083
1084 if let Some(proj_dirs) = &*PROJECT_DIR {
1086 let config_themes_dir = proj_dirs.config_dir().join(THEMES_DIR);
1087 if let Ok(config_themes_dir) = std::fs::read_dir(config_themes_dir) {
1088 add_themes_from_dir(config_themes_dir);
1089 }
1090 }
1091
1092 let local_config_dirs = find_local_configs();
1094
1095 local_config_dirs
1098 .iter()
1099 .filter_map(|p| std::fs::read_dir(p.join(THEMES_DIR)).ok())
1100 .for_each(add_themes_from_dir);
1101
1102 if let Some(name) = theme_name.as_ref()
1103 && !name.is_empty()
1104 {
1105 let theme_path = Path::new(THEMES_DIR).join(name.to_owned() + ".toml");
1106
1107 let local_themes: Vec<PathBuf> = local_config_dirs
1110 .iter()
1111 .map(|p| p.join(&theme_path))
1112 .filter(|p| p.exists())
1113 .collect();
1114 if local_themes.is_empty() {
1115 if let Some(proj_dirs) = &*PROJECT_DIR {
1117 let config_theme_path = proj_dirs.config_dir().join(theme_path);
1118 if config_theme_path.exists() {
1119 theme = theme.add_source(File::from(config_theme_path).required(false));
1120 }
1121 }
1122 } else {
1123 theme = local_themes
1124 .into_iter()
1125 .fold(theme, |t, p| t.add_source(File::from(p).required(false)));
1126 }
1127 }
1128
1129 let theme = theme.set_override("theme_names", theme_names)?;
1130
1131 let mut result: SurferTheme = theme
1132 .build()?
1133 .try_deserialize()
1134 .map_err(|e| anyhow!("Failed to parse theme {e}"))?;
1135 result.theme_name = theme_name.unwrap_or_default();
1136 Ok(result)
1137 }
1138}
1139
1140#[derive(Debug, Deserialize)]
1141pub struct ThemeColorPair {
1142 #[serde(deserialize_with = "deserialize_hex_color")]
1143 pub foreground: Color32,
1144 #[serde(deserialize_with = "deserialize_hex_color")]
1145 pub background: Color32,
1146}
1147
1148#[derive(Debug, Deserialize)]
1149pub struct ThemeColorTriple {
1150 #[serde(deserialize_with = "deserialize_hex_color")]
1151 pub foreground: Color32,
1152 #[serde(deserialize_with = "deserialize_hex_color")]
1153 pub background: Color32,
1154 #[serde(deserialize_with = "deserialize_hex_color")]
1155 pub alt_background: Color32,
1156}
1157
1158#[derive(Debug, Deserialize)]
1159pub struct PluginConfig {
1160 pub max_memory_mib: u64,
1162}
1163
1164#[derive(Debug, Deserialize)]
1165pub struct WcpConfig {
1166 pub autostart: bool,
1168 pub address: String,
1170}
1171
1172impl SurferConfig {
1173 #[cfg(target_arch = "wasm32")]
1174 pub fn new(_force_default_config: bool) -> Result<Self> {
1175 Self::new_from_toml(&include_str!("../../default_config.toml"))
1176 }
1177
1178 #[cfg(not(target_arch = "wasm32"))]
1179 pub fn new(force_default_config: bool) -> Result<Self> {
1180 use tracing::warn;
1181
1182 let default_config = String::from(include_str!("../../default_config.toml"));
1183
1184 let mut config = Config::builder().add_source(config::File::from_str(
1185 &default_config,
1186 config::FileFormat::Toml,
1187 ));
1188
1189 let config = if force_default_config {
1190 config
1191 } else {
1192 if let Some(proj_dirs) = &*PROJECT_DIR {
1193 let config_file = proj_dirs.config_dir().join(CONFIG_FILE);
1194 config = config.add_source(File::from(config_file).required(false));
1195 }
1196
1197 let old_config_path = Path::new(OLD_CONFIG_FILE);
1198 if old_config_path.exists() {
1199 warn!(
1200 "Configuration in 'surfer.toml' is deprecated. Please move your configuration to '.surfer/config.toml'."
1201 );
1202 }
1203
1204 config = config.add_source(File::from(old_config_path).required(false));
1206
1207 find_local_configs()
1210 .into_iter()
1211 .fold(config, |c, p| {
1212 c.add_source(File::from(p.join(CONFIG_FILE)).required(false))
1213 })
1214 .add_source(Environment::with_prefix("surfer")) };
1216
1217 config
1218 .build()?
1219 .try_deserialize()
1220 .map_err(|e| anyhow!("Failed to parse config {e}"))
1221 }
1222
1223 pub fn new_from_toml(config: &str) -> Result<Self> {
1224 Ok(toml::from_str(config)?)
1225 }
1226}
1227
1228impl Default for SurferConfig {
1229 fn default() -> Self {
1230 Self::new(false).expect("Failed to load default config")
1231 }
1232}
1233
1234fn hex_string_to_color32(str: &str) -> Result<Color32> {
1235 let str = str.strip_prefix('#').unwrap_or(str).to_string();
1236 let str = if str.len() == 3 {
1237 str.chars().flat_map(|c| [c, c]).collect()
1238 } else {
1239 str
1240 };
1241 if str.len() == 6 {
1242 let r = u8::from_str_radix(&str[0..2], 16)
1243 .with_context(|| format!("'{str}' is not a valid RGB hex color"))?;
1244 let g = u8::from_str_radix(&str[2..4], 16)
1245 .with_context(|| format!("'{str}' is not a valid RGB hex color"))?;
1246 let b = u8::from_str_radix(&str[4..6], 16)
1247 .with_context(|| format!("'{str}' is not a valid RGB hex color"))?;
1248 Ok(Color32::from_rgb(r, g, b))
1249 } else {
1250 Result::Err(Report::msg(format!("'{str}' is not a valid RGB hex color")))
1251 }
1252}
1253
1254fn all_theme_names() -> Vec<String> {
1255 let mut names: Vec<String> = BUILTIN_THEMES.keys().map(ToString::to_string).collect();
1256 names.sort();
1257 names
1258}
1259
1260fn deserialize_hex_color<'de, D>(deserializer: D) -> Result<Color32, D::Error>
1261where
1262 D: Deserializer<'de>,
1263{
1264 let buf = String::deserialize(deserializer)?;
1265 hex_string_to_color32(&buf).map_err(de::Error::custom)
1266}
1267
1268fn deserialize_color_map<'de, D>(deserializer: D) -> Result<HashMap<String, Color32>, D::Error>
1269where
1270 D: Deserializer<'de>,
1271{
1272 #[derive(Deserialize)]
1273 struct Wrapper(#[serde(deserialize_with = "deserialize_hex_color")] Color32);
1274
1275 let v = HashMap::<String, Wrapper>::deserialize(deserializer)?;
1276 Ok(v.into_iter().map(|(k, Wrapper(v))| (k, v)).collect())
1277}
1278
1279fn deserialize_hex_color_vec<'de, D>(deserializer: D) -> Result<Vec<Color32>, D::Error>
1280where
1281 D: Deserializer<'de>,
1282{
1283 #[derive(Deserialize)]
1284 struct Wrapper(#[serde(deserialize_with = "deserialize_hex_color")] Color32);
1285
1286 let v = Vec::<Wrapper>::deserialize(deserializer)?;
1287 Ok(v.into_iter().map(|Wrapper(v)| v).collect())
1288}
1289
1290fn deserialize_theme<'de, D>(deserializer: D) -> Result<SurferTheme, D::Error>
1291where
1292 D: Deserializer<'de>,
1293{
1294 let buf = String::deserialize(deserializer)?;
1295 SurferTheme::new(Some(buf)).map_err(de::Error::custom)
1296}
1297
1298fn deserialize_non_negative_f32<'de, D>(deserializer: D) -> Result<f32, D::Error>
1299where
1300 D: Deserializer<'de>,
1301{
1302 let value = f32::deserialize(deserializer)?;
1303 Ok(value.max(0.0))
1304}
1305
1306fn deserialize_non_negative_f32_vec<'de, D>(deserializer: D) -> Result<Vec<f32>, D::Error>
1307where
1308 D: Deserializer<'de>,
1309{
1310 let values = Vec::<f32>::deserialize(deserializer)?;
1311 Ok(values.into_iter().map(|v| v.max(0.0)).collect())
1312}
1313
1314fn deserialize_unit_interval_f32<'de, D>(deserializer: D) -> Result<f32, D::Error>
1315where
1316 D: Deserializer<'de>,
1317{
1318 let value = f32::deserialize(deserializer)?;
1319 Ok(value.clamp(0.0, 1.0))
1320}
1321
1322#[cfg(not(target_arch = "wasm32"))]
1328#[must_use]
1329pub fn find_local_configs() -> Vec<PathBuf> {
1330 use crate::util::search_upward;
1331 match std::env::current_dir() {
1332 Ok(dir) => {
1333 let root = dir
1334 .ancestors()
1335 .last()
1336 .map_or_else(|| PathBuf::from("/"), Path::to_path_buf);
1337 search_upward(dir, root, LOCAL_DIR)
1338 .into_iter()
1339 .filter(|p| p.is_dir()) .rev() .collect()
1342 }
1343 Err(_) => vec![],
1344 }
1345}
1346
1347#[cfg(not(target_arch = "wasm32"))]
1348pub fn write_default_config() -> eyre::Result<()> {
1349 use std::fs;
1350
1351 let default_config = include_str!("../../default_config.toml");
1352
1353 if let Some(proj_dirs) = &*PROJECT_DIR {
1354 let config_dir = proj_dirs.config_dir();
1355 let config_path = config_dir.join(CONFIG_FILE);
1356
1357 if config_path.exists() {
1358 return Err(eyre::eyre!(
1359 "Config file already exists at {}. Delete it first if you want to recreate it.",
1360 config_path.display()
1361 ));
1362 }
1363
1364 fs::create_dir_all(config_dir)?;
1365
1366 fs::write(&config_path, default_config)?;
1367
1368 info!("Default config written to {}", config_path.display());
1369 }
1370
1371 Ok(())
1372}
1373
1374#[cfg(test)]
1375mod tests {
1376 use super::*;
1377
1378 #[test]
1379 fn test_hex_string_3_chars() {
1380 let result = hex_string_to_color32("abc").unwrap();
1382 let expected = Color32::from_rgb(0xaa, 0xbb, 0xcc);
1383 assert_eq!(result, expected);
1384 }
1385
1386 #[test]
1387 fn test_hex_string_6_chars() {
1388 let result = hex_string_to_color32("a7e47e").unwrap();
1390 let expected = Color32::from_rgb(0xa7, 0xe4, 0x7e);
1391 assert_eq!(result, expected);
1392 }
1393
1394 #[test]
1395 fn test_hex_string_black() {
1396 let result = hex_string_to_color32("000000").unwrap();
1398 let expected = Color32::from_rgb(0x00, 0x00, 0x00);
1399 assert_eq!(result, expected);
1400 }
1401
1402 #[test]
1403 fn test_hex_string_white() {
1404 let result = hex_string_to_color32("ffffff").unwrap();
1406 let expected = Color32::from_rgb(0xff, 0xff, 0xff);
1407 assert_eq!(result, expected);
1408 }
1409
1410 #[test]
1411 fn test_hex_string_uppercase() {
1412 let result = hex_string_to_color32("ABCDEF").unwrap();
1414 let expected = Color32::from_rgb(0xab, 0xcd, 0xef);
1415 assert_eq!(result, expected);
1416 }
1417
1418 #[test]
1419 fn test_hex_string_mixed_case() {
1420 let result = hex_string_to_color32("Ab5DeF").unwrap();
1422 let expected = Color32::from_rgb(0xab, 0x5d, 0xef);
1423 assert_eq!(result, expected);
1424 }
1425
1426 #[test]
1427 fn test_hex_string_invalid_length() {
1428 let result = hex_string_to_color32("ab");
1430 assert!(result.is_err());
1431
1432 let result = hex_string_to_color32("abcde");
1433 assert!(result.is_err());
1434
1435 let result = hex_string_to_color32("abcdefgh");
1436 assert!(result.is_err());
1437 }
1438
1439 #[test]
1440 fn test_hex_string_invalid_characters() {
1441 let result = hex_string_to_color32("GGGGGG");
1443 assert!(result.is_err());
1444
1445 let result = hex_string_to_color32("12345g");
1446 assert!(result.is_err());
1447
1448 let result = hex_string_to_color32("zzzzzz");
1449 assert!(result.is_err());
1450 }
1451
1452 #[test]
1453 fn test_hex_string_empty() {
1454 let result = hex_string_to_color32("");
1456 assert!(result.is_err());
1457 }
1458
1459 #[test]
1460 fn test_hex_string_3_chars_doubling() {
1461 let result = hex_string_to_color32("050").unwrap();
1463 let expected = Color32::from_rgb(0x00, 0x55, 0x00);
1464 assert_eq!(result, expected);
1465 }
1466
1467 #[test]
1468 fn test_hex_string_with_hash_3_chars() {
1469 let result = hex_string_to_color32("#abc").unwrap();
1470 let expected = Color32::from_rgb(0xaa, 0xbb, 0xcc);
1471 assert_eq!(result, expected);
1472 }
1473
1474 #[test]
1475 fn test_hex_string_with_hash_6_chars() {
1476 let result = hex_string_to_color32("#ABCDEF").unwrap();
1477 let expected = Color32::from_rgb(0xab, 0xcd, 0xef);
1478 assert_eq!(result, expected);
1479 }
1480}