Skip to main content

libsurfer/
config.rs

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;
12use eyre::{Context, Result};
13use serde::de;
14use serde::{Deserialize, Deserializer, Serialize};
15use std::collections::HashMap;
16#[cfg(not(target_arch = "wasm32"))]
17use std::path::{Path, PathBuf};
18use std::sync::LazyLock;
19
20use crate::hierarchy::{HierarchyStyle, ParameterDisplayLocation};
21use crate::keyboard_shortcuts::{SurferShortcuts, deserialize_shortcuts};
22use crate::mousegestures::GestureZones;
23use crate::time::TimeFormat;
24use crate::wave_container::VariableMeta;
25use crate::{clock_highlighting::ClockHighlightType, variable_name_type::VariableNameType};
26use surfer_translation_types::VariableEncoding;
27
28macro_rules! theme {
29    ($name:expr) => {
30        (
31            $name,
32            include_str!(concat!("../../themes/", $name, ".toml")),
33        )
34    };
35}
36
37/// Built-in theme names and their corresponding embedded content
38static BUILTIN_THEMES: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
39    HashMap::from([
40        theme!("dark+"),
41        theme!("dark-high-contrast"),
42        theme!("ibm"),
43        theme!("light+"),
44        theme!("light-high-contrast"),
45        ("okabe/ito", include_str!("../../themes/okabe-ito.toml")),
46        theme!("petroff-dark"),
47        theme!("petroff-light"),
48        theme!("solarized"),
49    ])
50});
51
52#[cfg(not(target_arch = "wasm32"))]
53pub static PROJECT_DIR: LazyLock<Option<ProjectDirs>> =
54    LazyLock::new(|| ProjectDirs::from("org", "surfer-project", "surfer"));
55#[cfg(not(target_arch = "wasm32"))]
56const OLD_CONFIG_FILE: &str = "surfer.toml";
57#[cfg(not(target_arch = "wasm32"))]
58const CONFIG_FILE: &str = "config.toml";
59#[cfg(not(target_arch = "wasm32"))]
60const THEMES_DIR: &str = "themes";
61#[cfg(not(target_arch = "wasm32"))]
62pub const LOCAL_DIR: &str = ".surfer";
63
64/// Select the function of the arrow keys
65#[derive(Clone, Copy, Debug, Deserialize, Display, FromStr, PartialEq, Eq, Sequence, Serialize)]
66pub enum ArrowKeyBindings {
67    /// The left/right arrow keys step to the next edge
68    Edge,
69
70    /// The left/right arrow keys scroll the viewport left/right
71    Scroll,
72}
73
74#[derive(Clone, Copy, Debug, Deserialize, Display, FromStr, PartialEq, Eq, Sequence, Serialize)]
75pub enum TransitionValue {
76    /// Transition value is the previous value
77    Previous,
78    /// Transition value is the next value
79    Next,
80    /// Transition value is both previous and next value
81    Both,
82}
83
84/// Select the function when dragging with primary mouse button
85#[derive(Debug, Deserialize, Display, PartialEq, Eq, Sequence, Serialize, Clone, Copy)]
86pub enum PrimaryMouseDrag {
87    /// The left/right arrow keys step to the next edge
88    #[display("Measure time")]
89    Measure,
90
91    /// The left/right arrow keys scroll the viewport left/right
92    #[display("Move cursor")]
93    Cursor,
94}
95
96#[derive(Debug, Deserialize, Display, PartialEq, Eq, Sequence, Serialize, Clone, Copy)]
97pub enum AutoLoad {
98    Always,
99    Never,
100    Ask,
101}
102
103impl AutoLoad {
104    #[must_use]
105    pub fn from_bool(auto_load: bool) -> Self {
106        if auto_load {
107            AutoLoad::Always
108        } else {
109            AutoLoad::Never
110        }
111    }
112}
113
114#[derive(Debug, Deserialize)]
115pub struct SurferConfig {
116    pub layout: SurferLayout,
117    #[serde(deserialize_with = "deserialize_theme")]
118    pub theme: SurferTheme,
119    /// Mouse gesture configurations. Color and linewidth are configured in the theme using [`SurferTheme::gesture`].
120    pub gesture: SurferGesture,
121    pub behavior: SurferBehavior,
122    /// Time stamp format
123    pub default_time_format: TimeFormat,
124    pub default_variable_name_type: VariableNameType,
125    default_clock_highlight_type: ClockHighlightType,
126    /// Distance in pixels for cursor snap
127    pub snap_distance: f32,
128    /// Maximum size of the undo stack
129    pub undo_stack_size: usize,
130    /// Reload changed waves
131    autoreload_files: AutoLoad,
132    /// Load state file
133    autoload_sibling_state_files: AutoLoad,
134    /// WCP Configuration
135    pub wcp: WcpConfig,
136    /// HTTP Server Configuration
137    pub server: ServerConfig,
138    /// Animation time for UI elements in seconds
139    pub animation_time: f32,
140    /// UI animation enabled
141    pub animation_enabled: bool,
142    /// Maximum URL length for remote connections.
143    /// Should only be changed in case you are behind a proxy that limits the URL length
144    pub max_url_length: u16,
145    /// Keyboard shortcuts
146    #[serde(deserialize_with = "deserialize_shortcuts")]
147    pub shortcuts: SurferShortcuts,
148}
149
150impl SurferConfig {
151    #[must_use]
152    pub fn default_clock_highlight_type(&self) -> ClockHighlightType {
153        self.default_clock_highlight_type
154    }
155
156    #[must_use]
157    pub fn autoload_sibling_state_files(&self) -> AutoLoad {
158        self.autoload_sibling_state_files
159    }
160
161    #[must_use]
162    pub fn autoreload_files(&self) -> AutoLoad {
163        self.autoreload_files
164    }
165
166    #[must_use]
167    pub fn animation_enabled(&self) -> bool {
168        self.animation_enabled
169    }
170}
171
172#[derive(Debug, Deserialize)]
173pub struct SurferLayout {
174    /// Flag to show/hide the hierarchy view
175    show_hierarchy: bool,
176    /// Flag to show/hide the menu
177    show_menu: bool,
178    /// Flag to show/hide toolbar
179    show_toolbar: bool,
180    /// Flag to show/hide tick lines
181    show_ticks: bool,
182    /// Flag to show/hide tooltip for variables
183    show_tooltip: bool,
184    /// Flag to show/hide tooltip for scopes
185    show_scope_tooltip: bool,
186    /// Flag to show/hide the overview
187    show_overview: bool,
188    /// Flag to show/hide the statusbar
189    show_statusbar: bool,
190    /// Flag to show/hide the indices of variables in the variable list
191    show_variable_indices: bool,
192    /// Flag to show/hide the variable direction icon
193    show_variable_direction: bool,
194    /// Flag to show/hide a default timeline
195    show_default_timeline: bool,
196    /// Flag to show/hide empty scopes
197    show_empty_scopes: bool,
198    /// Flag to show/hide scope and variable type icons in the hierarchy
199    show_hierarchy_icons: bool,
200    /// Where to show parameters in the hierarchy
201    parameter_display_location: ParameterDisplayLocation,
202    /// Initial window height
203    pub window_height: usize,
204    /// Initial window width
205    pub window_width: usize,
206    /// Align variable names right
207    align_names_right: bool,
208    /// Set style of hierarchy
209    hierarchy_style: HierarchyStyle,
210    /// Text size in points for values in waves
211    pub waveforms_text_size: f32,
212    /// Line height in points for waves
213    pub waveforms_line_height: f32,
214    /// Line height multiples for higher variables
215    pub waveforms_line_height_multiples: Vec<f32>,
216    /// Line height in points for transaction streams
217    pub transactions_line_height: f32,
218    /// UI zoom factors
219    pub zoom_factors: Vec<f32>,
220    /// Default UI zoom factor
221    pub default_zoom_factor: f32,
222    #[serde(default)]
223    /// Highlight the waveform of the focused item?
224    highlight_focused: bool,
225    /// Move the focus to the newly inserted marker?
226    move_focus_on_inserted_marker: bool,
227    /// Fill high values in boolean waveforms
228    #[serde(default = "default_true")]
229    fill_high_values: bool,
230    /// Dinotrace drawing style (thick upper line for all-ones, no upper line for all-zeros)
231    #[serde(default)]
232    use_dinotrace_style: bool,
233    /// Value to display when cursor is on a transition
234    #[serde(default = "default_next")]
235    transition_value: TransitionValue,
236}
237
238fn default_true() -> bool {
239    true
240}
241
242fn default_next() -> TransitionValue {
243    TransitionValue::Next
244}
245
246impl SurferLayout {
247    #[must_use]
248    pub fn show_hierarchy(&self) -> bool {
249        self.show_hierarchy
250    }
251    #[must_use]
252    pub fn show_menu(&self) -> bool {
253        self.show_menu
254    }
255    #[must_use]
256    pub fn show_ticks(&self) -> bool {
257        self.show_ticks
258    }
259    #[must_use]
260    pub fn show_tooltip(&self) -> bool {
261        self.show_tooltip
262    }
263    #[must_use]
264    pub fn show_scope_tooltip(&self) -> bool {
265        self.show_scope_tooltip
266    }
267    #[must_use]
268    pub fn show_default_timeline(&self) -> bool {
269        self.show_default_timeline
270    }
271    #[must_use]
272    pub fn show_toolbar(&self) -> bool {
273        self.show_toolbar
274    }
275    #[must_use]
276    pub fn show_overview(&self) -> bool {
277        self.show_overview
278    }
279    #[must_use]
280    pub fn show_statusbar(&self) -> bool {
281        self.show_statusbar
282    }
283    #[must_use]
284    pub fn align_names_right(&self) -> bool {
285        self.align_names_right
286    }
287    #[must_use]
288    pub fn show_variable_indices(&self) -> bool {
289        self.show_variable_indices
290    }
291    #[must_use]
292    pub fn show_variable_direction(&self) -> bool {
293        self.show_variable_direction
294    }
295    #[must_use]
296    pub fn default_zoom_factor(&self) -> f32 {
297        self.default_zoom_factor
298    }
299    #[must_use]
300    pub fn show_empty_scopes(&self) -> bool {
301        self.show_empty_scopes
302    }
303    #[must_use]
304    pub fn show_hierarchy_icons(&self) -> bool {
305        self.show_hierarchy_icons
306    }
307    #[must_use]
308    pub fn parameter_display_location(&self) -> ParameterDisplayLocation {
309        self.parameter_display_location
310    }
311    #[must_use]
312    pub fn highlight_focused(&self) -> bool {
313        self.highlight_focused
314    }
315    #[must_use]
316    pub fn move_focus_on_inserted_marker(&self) -> bool {
317        self.move_focus_on_inserted_marker
318    }
319    #[must_use]
320    pub fn fill_high_values(&self) -> bool {
321        self.fill_high_values
322    }
323    #[must_use]
324    pub fn hierarchy_style(&self) -> HierarchyStyle {
325        self.hierarchy_style
326    }
327    #[must_use]
328    pub fn use_dinotrace_style(&self) -> bool {
329        self.use_dinotrace_style
330    }
331    #[must_use]
332    pub fn transition_value(&self) -> TransitionValue {
333        self.transition_value
334    }
335}
336
337#[derive(Debug, Deserialize)]
338pub struct SurferBehavior {
339    /// Keep or remove variables if unavailable during reload
340    pub keep_during_reload: bool,
341    /// Select the functionality bound to the arrow keys
342    pub arrow_key_bindings: ArrowKeyBindings,
343    /// Whether dragging with primary mouse button will measure time or move cursor
344    /// (press shift for the other)
345    primary_button_drag_behavior: PrimaryMouseDrag,
346}
347
348impl SurferBehavior {
349    #[must_use]
350    pub fn primary_button_drag_behavior(&self) -> PrimaryMouseDrag {
351        self.primary_button_drag_behavior
352    }
353
354    #[must_use]
355    pub fn arrow_key_bindings(&self) -> ArrowKeyBindings {
356        self.arrow_key_bindings
357    }
358}
359
360#[derive(Debug, Deserialize)]
361/// Mouse gesture configurations. Color and linewidth are configured in the theme using [`SurferTheme::gesture`].
362pub struct SurferGesture {
363    /// Size of the overlay help
364    pub size: f32,
365    /// (Squared) minimum distance to move to remove the overlay help and perform gesture
366    pub deadzone: f32,
367    /// Circle radius for background as a factor of size/2
368    pub background_radius: f32,
369    /// Gamma factor for background circle, between 0 (opaque) and 1 (transparent)
370    pub background_gamma: f32,
371    /// Mapping between the eight directions and actions
372    pub mapping: GestureZones,
373}
374
375#[derive(Clone, Debug, Deserialize)]
376pub struct SurferLineStyle {
377    #[serde(deserialize_with = "deserialize_hex_color")]
378    pub color: Color32,
379    pub width: f32,
380}
381
382impl From<SurferLineStyle> for Stroke {
383    fn from(style: SurferLineStyle) -> Self {
384        Stroke {
385            color: style.color,
386            width: style.width,
387        }
388    }
389}
390
391impl From<&SurferLineStyle> for Stroke {
392    fn from(style: &SurferLineStyle) -> Self {
393        Stroke {
394            color: style.color,
395            width: style.width,
396        }
397    }
398}
399
400impl From<&SurferLineStyle> for PathStroke {
401    fn from(style: &SurferLineStyle) -> Self {
402        PathStroke::new(style.width, style.color)
403    }
404}
405
406#[derive(Debug, Deserialize)]
407/// Tick mark configuration
408pub struct SurferTicks {
409    /// 0 to 1, where 1 means as many ticks that can fit without overlap
410    pub density: f32,
411    /// Line style to use for ticks
412    pub style: SurferLineStyle,
413}
414
415#[derive(Debug, Deserialize)]
416pub struct SurferRelationArrow {
417    /// Arrow line style
418    pub style: SurferLineStyle,
419
420    /// Arrowhead angle in degrees
421    pub head_angle: f32,
422
423    /// Arrowhead length
424    pub head_length: f32,
425}
426
427#[derive(Debug, Deserialize)]
428pub struct SurferTheme {
429    /// Color used for text across the UI
430    #[serde(deserialize_with = "deserialize_hex_color")]
431    pub foreground: Color32,
432    #[serde(deserialize_with = "deserialize_hex_color")]
433    /// Color of borders between UI elements
434    pub border_color: Color32,
435    /// Color used for text across the markers
436    #[serde(deserialize_with = "deserialize_hex_color")]
437    pub alt_text_color: Color32,
438    /// Colors used for the background and text of the wave view
439    pub canvas_colors: ThemeColorTriple,
440    /// Colors used for most UI elements not on the variable canvas
441    pub primary_ui_color: ThemeColorPair,
442    /// Colors used for the variable and value list, as well as secondary elements
443    /// like text fields
444    pub secondary_ui_color: ThemeColorPair,
445    /// Color used for selected ui elements such as the currently selected hierarchy
446    pub selected_elements_colors: ThemeColorPair,
447
448    pub accent_info: ThemeColorPair,
449    pub accent_warn: ThemeColorPair,
450    pub accent_error: ThemeColorPair,
451
452    ///  Line style for cursor
453    pub cursor: SurferLineStyle,
454
455    /// Line style for mouse gesture lines
456    pub gesture: SurferLineStyle,
457
458    /// Line style for measurement lines
459    pub measure: SurferLineStyle,
460
461    ///  Line style for clock highlight lines
462    pub clock_highlight_line: SurferLineStyle,
463    #[serde(deserialize_with = "deserialize_hex_color")]
464    pub clock_highlight_cycle: Color32,
465    /// Draw arrows on rising clock edges
466    pub clock_rising_marker: bool,
467
468    #[serde(deserialize_with = "deserialize_hex_color")]
469    /// Default variable color
470    pub variable_default: Color32,
471    #[serde(deserialize_with = "deserialize_hex_color")]
472    /// Color used for high-impedance variables
473    pub variable_highimp: Color32,
474    #[serde(deserialize_with = "deserialize_hex_color")]
475    /// Color used for undefined variables
476    pub variable_undef: Color32,
477    #[serde(deserialize_with = "deserialize_hex_color")]
478    /// Color used for don't-care variables
479    pub variable_dontcare: Color32,
480    #[serde(deserialize_with = "deserialize_hex_color")]
481    /// Color used for weak variables
482    pub variable_weak: Color32,
483    #[serde(deserialize_with = "deserialize_hex_color")]
484    /// Color used for constant variables (parameters)
485    pub variable_parameter: Color32,
486    #[serde(deserialize_with = "deserialize_hex_color")]
487    /// Default transaction color
488    pub transaction_default: Color32,
489    // Relation arrows of transactions
490    pub relation_arrow: SurferRelationArrow,
491    #[serde(deserialize_with = "deserialize_hex_color")]
492    /// Color used for constant variables (parameters)
493    pub variable_event: Color32,
494
495    /// Opacity with which variable backgrounds are drawn. 0 is fully transparent and 1 is fully
496    /// opaque.
497    pub waveform_opacity: f32,
498    /// Opacity of variable backgrounds for wide signals (signals with more than one bit)
499    #[serde(default)]
500    pub wide_opacity: f32,
501
502    #[serde(default = "default_colors", deserialize_with = "deserialize_color_map")]
503    pub colors: HashMap<String, Color32>,
504    #[serde(deserialize_with = "deserialize_hex_color")]
505    pub highlight_background: Color32,
506
507    /// Variable line width
508    pub linewidth: f32,
509
510    /// Variable line width for accented variables
511    pub thick_linewidth: f32,
512
513    /// Vector transition max width
514    pub vector_transition_width: f32,
515
516    /// Number of lines using standard background before changing to
517    /// alternate background and so on, set to zero to disable
518    pub alt_frequency: usize,
519
520    /// Viewport separator line
521    pub viewport_separator: SurferLineStyle,
522
523    // Drag hint and threshold parameters
524    #[serde(deserialize_with = "deserialize_hex_color")]
525    pub drag_hint_color: Color32,
526    pub drag_hint_width: f32,
527    pub drag_threshold: f32,
528
529    /// Tick information
530    pub ticks: SurferTicks,
531
532    /// List of theme names
533    #[serde(default = "Vec::new")]
534    pub theme_names: Vec<String>,
535
536    /// Icons for scope types in the hierarchy view
537    #[serde(default)]
538    pub scope_icons: ScopeIcons,
539
540    /// Icons for variable types in the hierarchy view
541    #[serde(default)]
542    pub variable_icons: VariableIcons,
543}
544
545/// Colors for different scope type icons in the hierarchy view.
546#[derive(Clone, Debug, Deserialize)]
547#[serde(default)]
548pub struct ScopeIconColors {
549    #[serde(deserialize_with = "deserialize_hex_color")]
550    pub module: Color32,
551    #[serde(deserialize_with = "deserialize_hex_color")]
552    pub task: Color32,
553    #[serde(deserialize_with = "deserialize_hex_color")]
554    pub function: Color32,
555    #[serde(deserialize_with = "deserialize_hex_color")]
556    pub begin: Color32,
557    #[serde(deserialize_with = "deserialize_hex_color")]
558    pub fork: Color32,
559    #[serde(deserialize_with = "deserialize_hex_color")]
560    pub generate: Color32,
561    #[serde(rename = "struct", deserialize_with = "deserialize_hex_color")]
562    pub struct_: Color32,
563    #[serde(deserialize_with = "deserialize_hex_color")]
564    pub union: Color32,
565    #[serde(deserialize_with = "deserialize_hex_color")]
566    pub class: Color32,
567    #[serde(deserialize_with = "deserialize_hex_color")]
568    pub interface: Color32,
569    #[serde(deserialize_with = "deserialize_hex_color")]
570    pub package: Color32,
571    #[serde(deserialize_with = "deserialize_hex_color")]
572    pub program: Color32,
573    #[serde(deserialize_with = "deserialize_hex_color")]
574    pub vhdl_architecture: Color32,
575    #[serde(deserialize_with = "deserialize_hex_color")]
576    pub vhdl_procedure: Color32,
577    #[serde(deserialize_with = "deserialize_hex_color")]
578    pub vhdl_function: Color32,
579    #[serde(deserialize_with = "deserialize_hex_color")]
580    pub vhdl_record: Color32,
581    #[serde(deserialize_with = "deserialize_hex_color")]
582    pub vhdl_process: Color32,
583    #[serde(deserialize_with = "deserialize_hex_color")]
584    pub vhdl_block: Color32,
585    #[serde(deserialize_with = "deserialize_hex_color")]
586    pub vhdl_for_generate: Color32,
587    #[serde(deserialize_with = "deserialize_hex_color")]
588    pub vhdl_if_generate: Color32,
589    #[serde(deserialize_with = "deserialize_hex_color")]
590    pub vhdl_generate: Color32,
591    #[serde(deserialize_with = "deserialize_hex_color")]
592    pub vhdl_package: Color32,
593    #[serde(deserialize_with = "deserialize_hex_color")]
594    pub ghw_generic: Color32,
595    #[serde(deserialize_with = "deserialize_hex_color")]
596    pub vhdl_array: Color32,
597    #[serde(deserialize_with = "deserialize_hex_color")]
598    pub unknown: Color32,
599}
600
601impl Default for ScopeIconColors {
602    fn default() -> Self {
603        Self {
604            module: Color32::from_rgb(0x4F, 0xC3, 0xF7), // Light Blue
605            task: Color32::from_rgb(0xFF, 0xB7, 0x4D),   // Orange
606            function: Color32::from_rgb(0xBA, 0x68, 0xC8), // Purple
607            begin: Color32::from_rgb(0x81, 0xC7, 0x84),  // Green
608            fork: Color32::from_rgb(0xFF, 0x80, 0x80),   // Red
609            generate: Color32::from_rgb(0x64, 0xB5, 0xF6), // Blue
610            struct_: Color32::from_rgb(0x4D, 0xD0, 0xE1), // Cyan
611            union: Color32::from_rgb(0x4D, 0xD0, 0xE1),  // Cyan
612            class: Color32::from_rgb(0xF0, 0x62, 0x92),  // Pink
613            interface: Color32::from_rgb(0xAE, 0xD5, 0x81), // Light Green
614            package: Color32::from_rgb(0xFF, 0xD5, 0x4F), // Yellow
615            program: Color32::from_rgb(0xA1, 0x88, 0x7F), // Brown
616            vhdl_architecture: Color32::from_rgb(0x4F, 0xC3, 0xF7), // Light Blue (like module)
617            vhdl_procedure: Color32::from_rgb(0xFF, 0xB7, 0x4D), // Orange (like task)
618            vhdl_function: Color32::from_rgb(0xBA, 0x68, 0xC8), // Purple (like function)
619            vhdl_record: Color32::from_rgb(0x4D, 0xD0, 0xE1), // Cyan (like struct)
620            vhdl_process: Color32::from_rgb(0x81, 0xC7, 0x84), // Green (like begin)
621            vhdl_block: Color32::from_rgb(0x90, 0xA4, 0xAE), // Blue Grey
622            vhdl_for_generate: Color32::from_rgb(0x64, 0xB5, 0xF6), // Blue (like generate)
623            vhdl_if_generate: Color32::from_rgb(0x64, 0xB5, 0xF6), // Blue (like generate)
624            vhdl_generate: Color32::from_rgb(0x64, 0xB5, 0xF6), // Blue (like generate)
625            vhdl_package: Color32::from_rgb(0xFF, 0xD5, 0x4F), // Yellow (like package)
626            ghw_generic: Color32::from_rgb(0xB0, 0xBE, 0xC5), // Blue Grey Light
627            vhdl_array: Color32::from_rgb(0xCE, 0x93, 0xD8), // Light Purple
628            unknown: Color32::from_rgb(0x9E, 0x9E, 0x9E), // Grey
629        }
630    }
631}
632
633/// Icons for different scope types in the hierarchy view.
634/// Each field maps to a wellen::ScopeType and contains a Remix icon string.
635#[derive(Clone, Debug, Deserialize)]
636#[serde(default)]
637pub struct ScopeIcons {
638    // Verilog/SystemVerilog scope types
639    pub module: String,
640    pub task: String,
641    pub function: String,
642    pub begin: String,
643    pub fork: String,
644    pub generate: String,
645    #[serde(rename = "struct")]
646    pub struct_: String,
647    pub union: String,
648    pub class: String,
649    pub interface: String,
650    pub package: String,
651    pub program: String,
652    // VHDL scope types
653    pub vhdl_architecture: String,
654    pub vhdl_procedure: String,
655    pub vhdl_function: String,
656    pub vhdl_record: String,
657    pub vhdl_process: String,
658    pub vhdl_block: String,
659    pub vhdl_for_generate: String,
660    pub vhdl_if_generate: String,
661    pub vhdl_generate: String,
662    pub vhdl_package: String,
663    pub ghw_generic: String,
664    pub vhdl_array: String,
665    pub unknown: String,
666    /// Colors for scope icons
667    #[serde(default)]
668    pub colors: ScopeIconColors,
669}
670
671impl Default for ScopeIcons {
672    fn default() -> Self {
673        use egui_remixicon::icons;
674        Self {
675            // Verilog/SystemVerilog scope types
676            module: icons::CPU_LINE.to_string(),
677            task: icons::TASK_LINE.to_string(),
678            function: icons::BRACES_LINE.to_string(),
679            begin: icons::CODE_BOX_LINE.to_string(),
680            fork: icons::GIT_BRANCH_LINE.to_string(),
681            generate: icons::REPEAT_LINE.to_string(),
682            struct_: icons::TABLE_LINE.to_string(),
683            union: icons::MERGE_CELLS_HORIZONTAL.to_string(),
684            class: icons::TABLE_LINE.to_string(),
685            interface: icons::PLUG_LINE.to_string(),
686            package: icons::BOX_3_LINE.to_string(),
687            program: icons::FILE_CODE_LINE.to_string(),
688            // VHDL scope types
689            vhdl_architecture: icons::CPU_LINE.to_string(),
690            vhdl_procedure: icons::TERMINAL_LINE.to_string(),
691            vhdl_function: icons::BRACES_LINE.to_string(),
692            vhdl_record: icons::TABLE_LINE.to_string(),
693            vhdl_process: icons::FLASHLIGHT_LINE.to_string(),
694            vhdl_block: icons::CODE_BLOCK.to_string(),
695            vhdl_for_generate: icons::REPEAT_LINE.to_string(),
696            vhdl_if_generate: icons::QUESTION_LINE.to_string(),
697            vhdl_generate: icons::REPEAT_LINE.to_string(),
698            vhdl_package: icons::BOX_3_LINE.to_string(),
699            ghw_generic: icons::SETTINGS_3_LINE.to_string(),
700            vhdl_array: icons::BRACKETS_LINE.to_string(),
701            unknown: icons::QUESTION_LINE.to_string(),
702            colors: ScopeIconColors::default(),
703        }
704    }
705}
706
707impl ScopeIcons {
708    /// Returns the icon and color for a given scope type.
709    /// If `scope_type` is `None`, returns the default module icon and color.
710    #[must_use]
711    pub fn get_icon(&self, scope_type: Option<wellen::ScopeType>) -> (&str, Color32) {
712        use wellen::ScopeType;
713        match scope_type {
714            None => (&self.module, self.colors.module),
715            Some(st) => match st {
716                ScopeType::Module => (&self.module, self.colors.module),
717                ScopeType::Task => (&self.task, self.colors.task),
718                ScopeType::Function => (&self.function, self.colors.function),
719                ScopeType::Begin => (&self.begin, self.colors.begin),
720                ScopeType::Fork => (&self.fork, self.colors.fork),
721                ScopeType::Generate => (&self.generate, self.colors.generate),
722                ScopeType::Struct => (&self.struct_, self.colors.struct_),
723                ScopeType::Union => (&self.union, self.colors.union),
724                ScopeType::Class => (&self.class, self.colors.class),
725                ScopeType::Interface => (&self.interface, self.colors.interface),
726                ScopeType::Package => (&self.package, self.colors.package),
727                ScopeType::Program => (&self.program, self.colors.program),
728                ScopeType::VhdlArchitecture => {
729                    (&self.vhdl_architecture, self.colors.vhdl_architecture)
730                }
731                ScopeType::VhdlProcedure => (&self.vhdl_procedure, self.colors.vhdl_procedure),
732                ScopeType::VhdlFunction => (&self.vhdl_function, self.colors.vhdl_function),
733                ScopeType::VhdlRecord => (&self.vhdl_record, self.colors.vhdl_record),
734                ScopeType::VhdlProcess => (&self.vhdl_process, self.colors.vhdl_process),
735                ScopeType::VhdlBlock => (&self.vhdl_block, self.colors.vhdl_block),
736                ScopeType::VhdlForGenerate => {
737                    (&self.vhdl_for_generate, self.colors.vhdl_for_generate)
738                }
739                ScopeType::VhdlIfGenerate => (&self.vhdl_if_generate, self.colors.vhdl_if_generate),
740                ScopeType::VhdlGenerate => (&self.vhdl_generate, self.colors.vhdl_generate),
741                ScopeType::VhdlPackage => (&self.vhdl_package, self.colors.vhdl_package),
742                ScopeType::GhwGeneric => (&self.ghw_generic, self.colors.ghw_generic),
743                ScopeType::VhdlArray => (&self.vhdl_array, self.colors.vhdl_array),
744                ScopeType::Unknown => (&self.unknown, self.colors.unknown),
745                _ => (&self.unknown, self.colors.unknown),
746            },
747        }
748    }
749}
750
751/// Colors for different variable type icons in the hierarchy view.
752/// Each field contains a Color32 value for the corresponding variable type.
753#[derive(Clone, Debug, Deserialize)]
754#[serde(default)]
755pub struct VariableIconColors {
756    /// Color for 1-bit wire signals
757    #[serde(deserialize_with = "deserialize_hex_color")]
758    pub wire: Color32,
759    /// Color for multi-bit bus signals
760    #[serde(deserialize_with = "deserialize_hex_color")]
761    pub bus: Color32,
762    /// Color for string variables
763    #[serde(deserialize_with = "deserialize_hex_color")]
764    pub string: Color32,
765    /// Color for event variables
766    #[serde(deserialize_with = "deserialize_hex_color")]
767    pub event: Color32,
768    /// Color for other types (integers, floats, enums)
769    #[serde(deserialize_with = "deserialize_hex_color")]
770    pub other: Color32,
771}
772
773impl Default for VariableIconColors {
774    fn default() -> Self {
775        Self {
776            wire: Color32::from_rgb(0x81, 0xC7, 0x84),   // Green
777            bus: Color32::from_rgb(0x64, 0xB5, 0xF6),    // Blue
778            string: Color32::from_rgb(0xFF, 0xB7, 0x4D), // Orange
779            event: Color32::from_rgb(0xF0, 0x62, 0x92),  // Pink
780            other: Color32::from_rgb(0xBA, 0x68, 0xC8),  // Purple
781        }
782    }
783}
784
785/// Icons for different variable types in the hierarchy view.
786/// Each field contains a Remix icon string.
787#[derive(Clone, Debug, Deserialize)]
788#[serde(default)]
789pub struct VariableIcons {
790    /// 1-bit wire signals
791    pub wire: String,
792    /// Multi-bit bus signals
793    pub bus: String,
794    /// String variables
795    pub string: String,
796    /// Event variables
797    pub event: String,
798    /// Other types (integers, floats, enums)
799    pub other: String,
800    /// Colors for variable icons
801    #[serde(default)]
802    pub colors: VariableIconColors,
803}
804
805impl Default for VariableIcons {
806    fn default() -> Self {
807        use egui_remixicon::icons;
808        Self {
809            wire: icons::GIT_COMMIT_LINE.to_string(),
810            bus: icons::BRACKETS_LINE.to_string(),
811            string: icons::TEXT.to_string(),
812            event: icons::ARROW_UP_LONG_LINE.to_string(),
813            other: icons::NUMBERS_LINE.to_string(),
814            colors: VariableIconColors::default(),
815        }
816    }
817}
818
819impl VariableIcons {
820    /// Returns the icon and color for a given variable meta.
821    /// If `meta` is `None`, returns the default "other" icon and color.
822    #[must_use]
823    pub fn get_icon(&self, meta: Option<&VariableMeta>) -> (&str, Color32) {
824        let Some(meta) = meta else {
825            return (&self.other, self.colors.other);
826        };
827
828        match meta.encoding {
829            VariableEncoding::String => (&self.string, self.colors.string),
830            VariableEncoding::Event => (&self.event, self.colors.event),
831            VariableEncoding::Real => (&self.other, self.colors.other),
832            VariableEncoding::BitVector => match meta.num_bits {
833                Some(1) => (&self.wire, self.colors.wire),
834                Some(n) if n > 1 => (&self.bus, self.colors.bus),
835                _ => (&self.other, self.colors.other),
836            },
837        }
838    }
839}
840
841fn get_luminance(color: Color32) -> f32 {
842    let rg = if color.r() < 10 {
843        f32::from(color.r()) / 3294.0
844    } else {
845        (f32::from(color.r()) / 269.0 + 0.0513).powf(2.4)
846    };
847    let gg = if color.g() < 10 {
848        f32::from(color.g()) / 3294.0
849    } else {
850        (f32::from(color.g()) / 269.0 + 0.0513).powf(2.4)
851    };
852    let bg = if color.b() < 10 {
853        f32::from(color.b()) / 3294.0
854    } else {
855        (f32::from(color.b()) / 269.0 + 0.0513).powf(2.4)
856    };
857    0.2126 * rg + 0.7152 * gg + 0.0722 * bg
858}
859
860impl SurferTheme {
861    #[must_use]
862    pub fn get_color(&self, color: &str) -> Option<Color32> {
863        self.colors.get(color).copied()
864    }
865
866    #[must_use]
867    pub fn get_best_text_color(&self, backgroundcolor: Color32) -> Color32 {
868        // Based on https://ux.stackexchange.com/questions/82056/how-to-measure-the-contrast-between-any-given-color-and-white
869
870        // Compute luminance
871        let l_foreground = get_luminance(self.foreground);
872        let l_alt_text_color = get_luminance(self.alt_text_color);
873        let l_background = get_luminance(backgroundcolor);
874
875        // Compute contrast ratio
876        let mut cr_foreground = (l_foreground + 0.05) / (l_background + 0.05);
877        cr_foreground = cr_foreground.max(1. / cr_foreground);
878        let mut cr_alt_text_color = (l_alt_text_color + 0.05) / (l_background + 0.05);
879        cr_alt_text_color = cr_alt_text_color.max(1. / cr_alt_text_color);
880
881        // Return color with highest contrast
882        if cr_foreground > cr_alt_text_color {
883            self.foreground
884        } else {
885            self.alt_text_color
886        }
887    }
888
889    fn generate_defaults(
890        theme_name: Option<&String>,
891    ) -> (ConfigBuilder<DefaultState>, Vec<String>) {
892        let default_theme = String::from(include_str!("../../default_theme.toml"));
893
894        let mut theme = Config::builder().add_source(config::File::from_str(
895            &default_theme,
896            config::FileFormat::Toml,
897        ));
898
899        let theme_names = all_theme_names();
900
901        let override_theme = theme_name
902            .as_ref()
903            .and_then(|name| BUILTIN_THEMES.get(name.as_str()).copied())
904            .unwrap_or("");
905
906        theme = theme.add_source(config::File::from_str(
907            override_theme,
908            config::FileFormat::Toml,
909        ));
910        (theme, theme_names)
911    }
912
913    #[cfg(target_arch = "wasm32")]
914    pub fn new(theme_name: Option<String>) -> Result<Self> {
915        use eyre::anyhow;
916
917        let (theme, _) = Self::generate_defaults(theme_name.as_ref());
918
919        let theme = theme.set_override("theme_names", all_theme_names())?;
920
921        theme
922            .build()?
923            .try_deserialize()
924            .map_err(|e| anyhow!("Failed to parse config {e}"))
925    }
926
927    #[cfg(not(target_arch = "wasm32"))]
928    pub fn new(theme_name: Option<String>) -> eyre::Result<Self> {
929        use std::fs::ReadDir;
930
931        use eyre::anyhow;
932
933        let (mut theme, mut theme_names) = Self::generate_defaults(theme_name.as_ref());
934
935        let mut add_themes_from_dir = |dir: ReadDir| {
936            for theme in dir.flatten() {
937                if let Ok(theme_path) = theme.file_name().into_string()
938                    && let Some(fname_str) = theme_path.strip_suffix(".toml")
939                {
940                    let fname = fname_str.to_string();
941                    if !fname.is_empty() && !theme_names.contains(&fname) {
942                        theme_names.push(fname);
943                    }
944                }
945            }
946        };
947
948        // read themes from config directory
949        if let Some(proj_dirs) = &*PROJECT_DIR {
950            let config_themes_dir = proj_dirs.config_dir().join(THEMES_DIR);
951            if let Ok(config_themes_dir) = std::fs::read_dir(config_themes_dir) {
952                add_themes_from_dir(config_themes_dir);
953            }
954        }
955
956        // Read themes from local directories.
957        let local_config_dirs = find_local_configs();
958
959        // Add any existing themes from most top-level to most local. This allows overwriting of
960        // higher-level theme settings with a local `.surfer` directory.
961        local_config_dirs
962            .iter()
963            .filter_map(|p| std::fs::read_dir(p.join(THEMES_DIR)).ok())
964            .for_each(add_themes_from_dir);
965
966        if matches!(theme_name, Some(ref name) if !name.is_empty()) {
967            let theme_path =
968                Path::new(THEMES_DIR).join(theme_name.as_ref().unwrap().to_owned() + ".toml");
969
970            // First filter out all the existing local themes and add them in the aforementioned
971            // order.
972            let local_themes: Vec<PathBuf> = local_config_dirs
973                .iter()
974                .map(|p| p.join(&theme_path))
975                .filter(|p| p.exists())
976                .collect();
977            if local_themes.is_empty() {
978                // If no local themes exist, search in the config directory.
979                if let Some(proj_dirs) = &*PROJECT_DIR {
980                    let config_theme_path = proj_dirs.config_dir().join(theme_path);
981                    if config_theme_path.exists() {
982                        theme = theme.add_source(File::from(config_theme_path).required(false));
983                    }
984                }
985            } else {
986                theme = local_themes
987                    .into_iter()
988                    .fold(theme, |t, p| t.add_source(File::from(p).required(false)));
989            }
990        }
991
992        let theme = theme.set_override("theme_names", theme_names)?;
993
994        theme
995            .build()?
996            .try_deserialize()
997            .map_err(|e| anyhow!("Failed to parse theme {e}"))
998    }
999}
1000
1001#[derive(Debug, Deserialize)]
1002pub struct ThemeColorPair {
1003    #[serde(deserialize_with = "deserialize_hex_color")]
1004    pub foreground: Color32,
1005    #[serde(deserialize_with = "deserialize_hex_color")]
1006    pub background: Color32,
1007}
1008
1009#[derive(Debug, Deserialize)]
1010pub struct ThemeColorTriple {
1011    #[serde(deserialize_with = "deserialize_hex_color")]
1012    pub foreground: Color32,
1013    #[serde(deserialize_with = "deserialize_hex_color")]
1014    pub background: Color32,
1015    #[serde(deserialize_with = "deserialize_hex_color")]
1016    pub alt_background: Color32,
1017}
1018
1019#[derive(Debug, Deserialize)]
1020pub struct WcpConfig {
1021    /// Controls if a server is started after Surfer is launched
1022    pub autostart: bool,
1023    /// Address to bind to (address:port)
1024    pub address: String,
1025}
1026
1027#[derive(Debug, Deserialize)]
1028pub struct ServerConfig {
1029    /// IP address to bind the HTTP server to
1030    pub bind_address: String,
1031    /// Default port for the HTTP server
1032    pub port: u16,
1033}
1034
1035fn default_colors() -> HashMap<String, Color32> {
1036    [
1037        ("Green", "a7e47e"),
1038        ("Red", "c52e2e"),
1039        ("Yellow", "f3d54a"),
1040        ("Blue", "81a2be"),
1041        ("Purple", "b294bb"),
1042        ("Aqua", "8abeb7"),
1043        ("Gray", "c5c8c6"),
1044    ]
1045    .iter()
1046    .map(|(name, hexcode)| {
1047        (
1048            (*name).to_string(),
1049            hex_string_to_color32((*hexcode).to_string()).unwrap(),
1050        )
1051    })
1052    .collect()
1053}
1054
1055impl SurferConfig {
1056    #[cfg(target_arch = "wasm32")]
1057    pub fn new(_force_default_config: bool) -> Result<Self> {
1058        Self::new_from_toml(&include_str!("../../default_config.toml"))
1059    }
1060
1061    #[cfg(not(target_arch = "wasm32"))]
1062    pub fn new(force_default_config: bool) -> eyre::Result<Self> {
1063        use eyre::anyhow;
1064        use tracing::warn;
1065
1066        let default_config = String::from(include_str!("../../default_config.toml"));
1067
1068        let mut config = Config::builder().add_source(config::File::from_str(
1069            &default_config,
1070            config::FileFormat::Toml,
1071        ));
1072
1073        let config = if force_default_config {
1074            config
1075        } else {
1076            if let Some(proj_dirs) = &*PROJECT_DIR {
1077                let config_file = proj_dirs.config_dir().join(CONFIG_FILE);
1078                config = config.add_source(File::from(config_file).required(false));
1079            }
1080
1081            let old_config_path = Path::new(OLD_CONFIG_FILE);
1082            if old_config_path.exists() {
1083                warn!(
1084                    "Configuration in 'surfer.toml' is deprecated. Please move your configuration to '.surfer/config.toml'."
1085                );
1086            }
1087
1088            // `surfer.toml` will not be searched for upward, as it is deprecated.
1089            config = config.add_source(File::from(old_config_path).required(false));
1090
1091            // Add configs from most top-level to most local. This allows overwriting of
1092            // higher-level settings with a local `.surfer` directory.
1093            find_local_configs()
1094                .into_iter()
1095                .fold(config, |c, p| {
1096                    c.add_source(File::from(p.join(CONFIG_FILE)).required(false))
1097                })
1098                .add_source(Environment::with_prefix("surfer")) // Add environment finally
1099        };
1100
1101        config
1102            .build()?
1103            .try_deserialize()
1104            .map_err(|e| anyhow!("Failed to parse config {e}"))
1105    }
1106
1107    pub fn new_from_toml(config: &str) -> Result<Self> {
1108        Ok(toml::from_str(config)?)
1109    }
1110}
1111
1112impl Default for SurferConfig {
1113    fn default() -> Self {
1114        Self::new(false).expect("Failed to load default config")
1115    }
1116}
1117
1118fn hex_string_to_color32(mut str: String) -> Result<Color32> {
1119    let mut hex_str = String::new();
1120    if str.len() == 3 {
1121        for c in str.chars() {
1122            hex_str.push(c);
1123            hex_str.push(c);
1124        }
1125        str = hex_str;
1126    }
1127    if str.len() == 6 {
1128        let r = u8::from_str_radix(&str[0..2], 16)
1129            .with_context(|| format!("'{str}' is not a valid RGB hex color"))?;
1130        let g = u8::from_str_radix(&str[2..4], 16)
1131            .with_context(|| format!("'{str}' is not a valid RGB hex color"))?;
1132        let b = u8::from_str_radix(&str[4..6], 16)
1133            .with_context(|| format!("'{str}' is not a valid RGB hex color"))?;
1134        Ok(Color32::from_rgb(r, g, b))
1135    } else {
1136        Result::Err(Report::msg(format!("'{str}' is not a valid RGB hex color")))
1137    }
1138}
1139
1140fn all_theme_names() -> Vec<String> {
1141    BUILTIN_THEMES.keys().map(ToString::to_string).collect()
1142}
1143
1144fn deserialize_hex_color<'de, D>(deserializer: D) -> Result<Color32, D::Error>
1145where
1146    D: Deserializer<'de>,
1147{
1148    let buf = String::deserialize(deserializer)?;
1149    hex_string_to_color32(buf).map_err(de::Error::custom)
1150}
1151
1152fn deserialize_color_map<'de, D>(deserializer: D) -> Result<HashMap<String, Color32>, D::Error>
1153where
1154    D: Deserializer<'de>,
1155{
1156    #[derive(Deserialize)]
1157    struct Wrapper(#[serde(deserialize_with = "deserialize_hex_color")] Color32);
1158
1159    let v = HashMap::<String, Wrapper>::deserialize(deserializer)?;
1160    Ok(v.into_iter().map(|(k, Wrapper(v))| (k, v)).collect())
1161}
1162
1163fn deserialize_theme<'de, D>(deserializer: D) -> Result<SurferTheme, D::Error>
1164where
1165    D: Deserializer<'de>,
1166{
1167    let buf = String::deserialize(deserializer)?;
1168    SurferTheme::new(Some(buf)).map_err(de::Error::custom)
1169}
1170
1171/// Searches for `.surfer` directories upward from the current location until it reaches root.
1172/// Returns an empty vector in case the search fails in any way. If any `.surfer` directories
1173/// are found, they will be returned in a `Vec<PathBuf>` in a pre-order of most top-level to most
1174/// local. All plain files are ignored.
1175#[cfg(not(target_arch = "wasm32"))]
1176pub fn find_local_configs() -> Vec<PathBuf> {
1177    use crate::util::search_upward;
1178    match std::env::current_dir() {
1179        Ok(dir) => search_upward(dir, "/", LOCAL_DIR)
1180            .into_iter()
1181            .filter(|p| p.is_dir()) // Only keep directories and ignore plain files.
1182            .rev() // Reverse for pre-order traversal of directories.
1183            .collect(),
1184        Err(_) => vec![],
1185    }
1186}