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;
19use surver::SurverConfig;
20
21use crate::hierarchy::{HierarchyStyle, ParameterDisplayLocation};
22use crate::keyboard_shortcuts::{SurferShortcuts, deserialize_shortcuts};
23use crate::mousegestures::GestureZones;
24use crate::time::TimeFormat;
25use crate::wave_container::VariableMeta;
26use crate::{clock_highlighting::ClockHighlightType, variable_name_type::VariableNameType};
27use surfer_translation_types::VariableEncoding;
28
29macro_rules! theme {
30 ($name:expr) => {
31 (
32 $name,
33 include_str!(concat!("../../themes/", $name, ".toml")),
34 )
35 };
36}
37
38static BUILTIN_THEMES: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
40 HashMap::from([
41 theme!("dark+"),
42 theme!("dark-high-contrast"),
43 theme!("ibm"),
44 theme!("light+"),
45 theme!("light-high-contrast"),
46 ("okabe/ito", include_str!("../../themes/okabe-ito.toml")),
47 theme!("petroff-dark"),
48 theme!("petroff-light"),
49 ("Rosé Pine", include_str!("../../themes/rose-pine.toml")),
50 (
51 "Rosé Pine Moon",
52 include_str!("../../themes/rose-pine-moon.toml"),
53 ),
54 (
55 "Rosé Pine Dawn",
56 include_str!("../../themes/rose-pine-dawn.toml"),
57 ),
58 theme!("solarized"),
59 ])
60});
61
62#[cfg(not(target_arch = "wasm32"))]
63pub static PROJECT_DIR: LazyLock<Option<ProjectDirs>> =
64 LazyLock::new(|| ProjectDirs::from("org", "surfer-project", "surfer"));
65#[cfg(not(target_arch = "wasm32"))]
66const OLD_CONFIG_FILE: &str = "surfer.toml";
67#[cfg(not(target_arch = "wasm32"))]
68const CONFIG_FILE: &str = "config.toml";
69#[cfg(not(target_arch = "wasm32"))]
70const THEMES_DIR: &str = "themes";
71#[cfg(not(target_arch = "wasm32"))]
72pub const LOCAL_DIR: &str = ".surfer";
73
74#[derive(Clone, Copy, Debug, Deserialize, Display, FromStr, PartialEq, Eq, Sequence, Serialize)]
76pub enum ArrowKeyBindings {
77 Edge,
79
80 Scroll,
82}
83
84#[derive(Clone, Copy, Debug, Deserialize, Display, FromStr, PartialEq, Eq, Sequence, Serialize)]
85pub enum TransitionValue {
86 Previous,
88 Next,
90 Both,
92}
93
94#[derive(Debug, Deserialize, Display, PartialEq, Eq, Sequence, Serialize, Clone, Copy)]
96pub enum PrimaryMouseDrag {
97 #[display("Measure time")]
99 Measure,
100
101 #[display("Move cursor")]
103 Cursor,
104}
105
106#[derive(Debug, Deserialize, Display, PartialEq, Eq, Sequence, Serialize, Clone, Copy)]
107pub enum AutoLoad {
108 Always,
109 Never,
110 Ask,
111}
112
113impl AutoLoad {
114 #[must_use]
115 pub fn from_bool(auto_load: bool) -> Self {
116 if auto_load {
117 AutoLoad::Always
118 } else {
119 AutoLoad::Never
120 }
121 }
122}
123
124#[derive(Debug, Deserialize)]
125pub struct SurferConfig {
126 pub layout: SurferLayout,
127 #[serde(deserialize_with = "deserialize_theme")]
128 pub theme: SurferTheme,
129 pub gesture: SurferGesture,
131 pub behavior: SurferBehavior,
132 pub default_time_format: TimeFormat,
134 pub default_variable_name_type: VariableNameType,
135 default_clock_highlight_type: ClockHighlightType,
136 pub snap_distance: f32,
138 pub undo_stack_size: usize,
140 autoreload_files: AutoLoad,
142 autoload_sibling_state_files: AutoLoad,
144 pub wcp: WcpConfig,
146 pub server: SurverConfig,
148 pub animation_time: f32,
150 pub animation_enabled: bool,
152 pub max_url_length: u16,
155 #[serde(deserialize_with = "deserialize_shortcuts")]
157 pub shortcuts: SurferShortcuts,
158}
159
160impl SurferConfig {
161 #[must_use]
162 pub fn default_clock_highlight_type(&self) -> ClockHighlightType {
163 self.default_clock_highlight_type
164 }
165
166 #[must_use]
167 pub fn autoload_sibling_state_files(&self) -> AutoLoad {
168 self.autoload_sibling_state_files
169 }
170
171 #[must_use]
172 pub fn autoreload_files(&self) -> AutoLoad {
173 self.autoreload_files
174 }
175
176 #[must_use]
177 pub fn animation_enabled(&self) -> bool {
178 self.animation_enabled
179 }
180}
181
182#[derive(Debug, Deserialize)]
183pub struct SurferLayout {
184 show_hierarchy: bool,
186 show_menu: bool,
188 show_toolbar: bool,
190 show_ticks: bool,
192 show_tooltip: bool,
194 show_scope_tooltip: bool,
196 show_overview: bool,
198 show_statusbar: bool,
200 show_variable_indices: bool,
202 show_variable_direction: bool,
204 show_default_timeline: bool,
206 show_empty_scopes: bool,
208 show_hierarchy_icons: bool,
210 parameter_display_location: ParameterDisplayLocation,
212 pub window_height: usize,
214 pub window_width: usize,
216 align_names_right: bool,
218 hierarchy_style: HierarchyStyle,
220 pub waveforms_text_size: f32,
222 pub waveforms_line_height: f32,
224 pub waveforms_line_height_multiples: Vec<f32>,
226 pub transactions_line_height: f32,
228 pub zoom_factors: Vec<f32>,
230 pub default_zoom_factor: f32,
232 #[serde(default)]
233 highlight_focused: bool,
235 move_focus_on_inserted_marker: bool,
237 #[serde(default = "default_true")]
239 fill_high_values: bool,
240 #[serde(default)]
242 use_dinotrace_style: bool,
243 #[serde(default = "default_next")]
245 transition_value: TransitionValue,
246}
247
248fn default_true() -> bool {
249 true
250}
251
252fn default_next() -> TransitionValue {
253 TransitionValue::Next
254}
255
256impl SurferLayout {
257 #[must_use]
258 pub fn show_hierarchy(&self) -> bool {
259 self.show_hierarchy
260 }
261 #[must_use]
262 pub fn show_menu(&self) -> bool {
263 self.show_menu
264 }
265 #[must_use]
266 pub fn show_ticks(&self) -> bool {
267 self.show_ticks
268 }
269 #[must_use]
270 pub fn show_tooltip(&self) -> bool {
271 self.show_tooltip
272 }
273 #[must_use]
274 pub fn show_scope_tooltip(&self) -> bool {
275 self.show_scope_tooltip
276 }
277 #[must_use]
278 pub fn show_default_timeline(&self) -> bool {
279 self.show_default_timeline
280 }
281 #[must_use]
282 pub fn show_toolbar(&self) -> bool {
283 self.show_toolbar
284 }
285 #[must_use]
286 pub fn show_overview(&self) -> bool {
287 self.show_overview
288 }
289 #[must_use]
290 pub fn show_statusbar(&self) -> bool {
291 self.show_statusbar
292 }
293 #[must_use]
294 pub fn align_names_right(&self) -> bool {
295 self.align_names_right
296 }
297 #[must_use]
298 pub fn show_variable_indices(&self) -> bool {
299 self.show_variable_indices
300 }
301 #[must_use]
302 pub fn show_variable_direction(&self) -> bool {
303 self.show_variable_direction
304 }
305 #[must_use]
306 pub fn default_zoom_factor(&self) -> f32 {
307 self.default_zoom_factor
308 }
309 #[must_use]
310 pub fn show_empty_scopes(&self) -> bool {
311 self.show_empty_scopes
312 }
313 #[must_use]
314 pub fn show_hierarchy_icons(&self) -> bool {
315 self.show_hierarchy_icons
316 }
317 #[must_use]
318 pub fn parameter_display_location(&self) -> ParameterDisplayLocation {
319 self.parameter_display_location
320 }
321 #[must_use]
322 pub fn highlight_focused(&self) -> bool {
323 self.highlight_focused
324 }
325 #[must_use]
326 pub fn move_focus_on_inserted_marker(&self) -> bool {
327 self.move_focus_on_inserted_marker
328 }
329 #[must_use]
330 pub fn fill_high_values(&self) -> bool {
331 self.fill_high_values
332 }
333 #[must_use]
334 pub fn hierarchy_style(&self) -> HierarchyStyle {
335 self.hierarchy_style
336 }
337 #[must_use]
338 pub fn use_dinotrace_style(&self) -> bool {
339 self.use_dinotrace_style
340 }
341 #[must_use]
342 pub fn transition_value(&self) -> TransitionValue {
343 self.transition_value
344 }
345}
346
347#[derive(Debug, Deserialize)]
348pub struct SurferBehavior {
349 pub keep_during_reload: bool,
351 pub arrow_key_bindings: ArrowKeyBindings,
353 primary_button_drag_behavior: PrimaryMouseDrag,
356}
357
358impl SurferBehavior {
359 #[must_use]
360 pub fn primary_button_drag_behavior(&self) -> PrimaryMouseDrag {
361 self.primary_button_drag_behavior
362 }
363
364 #[must_use]
365 pub fn arrow_key_bindings(&self) -> ArrowKeyBindings {
366 self.arrow_key_bindings
367 }
368}
369
370#[derive(Debug, Deserialize)]
371pub struct SurferGesture {
373 pub size: f32,
375 pub deadzone: f32,
377 pub background_radius: f32,
379 pub background_gamma: f32,
381 pub mapping: GestureZones,
383}
384
385#[derive(Clone, Debug, Deserialize)]
386pub struct SurferLineStyle {
387 #[serde(deserialize_with = "deserialize_hex_color")]
388 pub color: Color32,
389 pub width: f32,
390}
391
392impl From<SurferLineStyle> for Stroke {
393 fn from(style: SurferLineStyle) -> Self {
394 Stroke {
395 color: style.color,
396 width: style.width,
397 }
398 }
399}
400
401impl From<&SurferLineStyle> for Stroke {
402 fn from(style: &SurferLineStyle) -> Self {
403 Stroke {
404 color: style.color,
405 width: style.width,
406 }
407 }
408}
409
410impl From<&SurferLineStyle> for PathStroke {
411 fn from(style: &SurferLineStyle) -> Self {
412 PathStroke::new(style.width, style.color)
413 }
414}
415
416#[derive(Debug, Deserialize)]
417pub struct SurferTicks {
419 pub density: f32,
421 pub style: SurferLineStyle,
423}
424
425#[derive(Debug, Deserialize)]
426pub struct SurferRelationArrow {
427 pub style: SurferLineStyle,
429
430 pub head_angle: f32,
432
433 pub head_length: f32,
435}
436
437#[derive(Debug, Deserialize)]
438pub struct SurferTheme {
439 #[serde(deserialize_with = "deserialize_hex_color")]
441 pub foreground: Color32,
442 #[serde(deserialize_with = "deserialize_hex_color")]
443 pub border_color: Color32,
445 #[serde(deserialize_with = "deserialize_hex_color")]
447 pub alt_text_color: Color32,
448 pub canvas_colors: ThemeColorTriple,
450 pub primary_ui_color: ThemeColorPair,
452 pub secondary_ui_color: ThemeColorPair,
455 pub selected_elements_colors: ThemeColorPair,
457
458 pub accent_info: ThemeColorPair,
459 pub accent_warn: ThemeColorPair,
460 pub accent_error: ThemeColorPair,
461
462 pub cursor: SurferLineStyle,
464
465 pub gesture: SurferLineStyle,
467
468 pub measure: SurferLineStyle,
470
471 pub clock_highlight_line: SurferLineStyle,
473 #[serde(deserialize_with = "deserialize_hex_color")]
474 pub clock_highlight_cycle: Color32,
475 pub clock_rising_marker: bool,
477
478 #[serde(deserialize_with = "deserialize_hex_color")]
479 pub variable_default: Color32,
481 #[serde(deserialize_with = "deserialize_hex_color")]
482 pub variable_highimp: Color32,
484 #[serde(deserialize_with = "deserialize_hex_color")]
485 pub variable_undef: Color32,
487 #[serde(deserialize_with = "deserialize_hex_color")]
488 pub variable_dontcare: Color32,
490 #[serde(deserialize_with = "deserialize_hex_color")]
491 pub variable_weak: Color32,
493 #[serde(deserialize_with = "deserialize_hex_color")]
494 pub variable_parameter: Color32,
496 #[serde(deserialize_with = "deserialize_hex_color")]
497 pub transaction_default: Color32,
499 pub relation_arrow: SurferRelationArrow,
501 #[serde(deserialize_with = "deserialize_hex_color")]
502 pub variable_event: Color32,
504
505 pub waveform_opacity: f32,
508 #[serde(default)]
510 pub wide_opacity: f32,
511
512 #[serde(default = "default_colors", deserialize_with = "deserialize_color_map")]
513 pub colors: HashMap<String, Color32>,
514 #[serde(deserialize_with = "deserialize_hex_color")]
515 pub highlight_background: Color32,
516
517 pub linewidth: f32,
519
520 pub thick_linewidth: f32,
522
523 pub vector_transition_width: f32,
525
526 pub alt_frequency: usize,
529
530 pub viewport_separator: SurferLineStyle,
532
533 #[serde(deserialize_with = "deserialize_hex_color")]
535 pub drag_hint_color: Color32,
536 pub drag_hint_width: f32,
537 pub drag_threshold: f32,
538
539 pub ticks: SurferTicks,
541
542 #[serde(default = "Vec::new")]
544 pub theme_names: Vec<String>,
545
546 #[serde(default)]
548 pub scope_icons: ScopeIcons,
549
550 #[serde(default)]
552 pub variable_icons: VariableIcons,
553}
554
555#[derive(Clone, Debug, Deserialize)]
557#[serde(default)]
558pub struct ScopeIconColors {
559 #[serde(deserialize_with = "deserialize_hex_color")]
560 pub module: Color32,
561 #[serde(deserialize_with = "deserialize_hex_color")]
562 pub task: Color32,
563 #[serde(deserialize_with = "deserialize_hex_color")]
564 pub function: Color32,
565 #[serde(deserialize_with = "deserialize_hex_color")]
566 pub begin: Color32,
567 #[serde(deserialize_with = "deserialize_hex_color")]
568 pub fork: Color32,
569 #[serde(deserialize_with = "deserialize_hex_color")]
570 pub generate: Color32,
571 #[serde(rename = "struct", deserialize_with = "deserialize_hex_color")]
572 pub struct_: Color32,
573 #[serde(deserialize_with = "deserialize_hex_color")]
574 pub union: Color32,
575 #[serde(deserialize_with = "deserialize_hex_color")]
576 pub class: Color32,
577 #[serde(deserialize_with = "deserialize_hex_color")]
578 pub interface: Color32,
579 #[serde(deserialize_with = "deserialize_hex_color")]
580 pub package: Color32,
581 #[serde(deserialize_with = "deserialize_hex_color")]
582 pub program: Color32,
583 #[serde(deserialize_with = "deserialize_hex_color")]
584 pub vhdl_architecture: Color32,
585 #[serde(deserialize_with = "deserialize_hex_color")]
586 pub vhdl_procedure: Color32,
587 #[serde(deserialize_with = "deserialize_hex_color")]
588 pub vhdl_function: Color32,
589 #[serde(deserialize_with = "deserialize_hex_color")]
590 pub vhdl_record: Color32,
591 #[serde(deserialize_with = "deserialize_hex_color")]
592 pub vhdl_process: Color32,
593 #[serde(deserialize_with = "deserialize_hex_color")]
594 pub vhdl_block: Color32,
595 #[serde(deserialize_with = "deserialize_hex_color")]
596 pub vhdl_for_generate: Color32,
597 #[serde(deserialize_with = "deserialize_hex_color")]
598 pub vhdl_if_generate: Color32,
599 #[serde(deserialize_with = "deserialize_hex_color")]
600 pub vhdl_generate: Color32,
601 #[serde(deserialize_with = "deserialize_hex_color")]
602 pub vhdl_package: Color32,
603 #[serde(deserialize_with = "deserialize_hex_color")]
604 pub ghw_generic: Color32,
605 #[serde(deserialize_with = "deserialize_hex_color")]
606 pub vhdl_array: Color32,
607 #[serde(deserialize_with = "deserialize_hex_color")]
608 pub unknown: Color32,
609 #[serde(deserialize_with = "deserialize_hex_color")]
610 pub clocking: Color32,
611 #[serde(deserialize_with = "deserialize_hex_color")]
612 pub sv_array: Color32,
613}
614
615impl Default for ScopeIconColors {
616 fn default() -> Self {
617 Self {
618 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), }
646 }
647}
648
649#[derive(Clone, Debug, Deserialize)]
652#[serde(default)]
653pub struct ScopeIcons {
654 pub module: String,
656 pub task: String,
657 pub function: String,
658 pub begin: String,
659 pub fork: String,
660 pub generate: String,
661 #[serde(rename = "struct")]
662 pub struct_: String,
663 pub union: String,
664 pub class: String,
665 pub interface: String,
666 pub package: String,
667 pub program: String,
668 pub vhdl_architecture: String,
670 pub vhdl_procedure: String,
671 pub vhdl_function: String,
672 pub vhdl_record: String,
673 pub vhdl_process: String,
674 pub vhdl_block: String,
675 pub vhdl_for_generate: String,
676 pub vhdl_if_generate: String,
677 pub vhdl_generate: String,
678 pub vhdl_package: String,
679 pub ghw_generic: String,
680 pub vhdl_array: String,
681 pub unknown: String,
682 pub clocking: String,
683 pub sv_array: String,
684 #[serde(default)]
686 pub colors: ScopeIconColors,
687}
688
689impl Default for ScopeIcons {
690 fn default() -> Self {
691 use egui_remixicon::icons;
692 Self {
693 module: icons::CPU_LINE.to_string(),
695 task: icons::TASK_LINE.to_string(),
696 function: icons::BRACES_LINE.to_string(),
697 begin: icons::CODE_BOX_LINE.to_string(),
698 fork: icons::GIT_BRANCH_LINE.to_string(),
699 generate: icons::REPEAT_LINE.to_string(),
700 struct_: icons::TABLE_LINE.to_string(),
701 union: icons::MERGE_CELLS_HORIZONTAL.to_string(),
702 class: icons::TABLE_LINE.to_string(),
703 interface: icons::PLUG_LINE.to_string(),
704 package: icons::BOX_3_LINE.to_string(),
705 program: icons::FILE_CODE_LINE.to_string(),
706 vhdl_architecture: icons::CPU_LINE.to_string(),
708 vhdl_procedure: icons::TERMINAL_LINE.to_string(),
709 vhdl_function: icons::BRACES_LINE.to_string(),
710 vhdl_record: icons::TABLE_LINE.to_string(),
711 vhdl_process: icons::FLASHLIGHT_LINE.to_string(),
712 vhdl_block: icons::CODE_BLOCK.to_string(),
713 vhdl_for_generate: icons::REPEAT_LINE.to_string(),
714 vhdl_if_generate: icons::QUESTION_LINE.to_string(),
715 vhdl_generate: icons::REPEAT_LINE.to_string(),
716 vhdl_package: icons::BOX_3_LINE.to_string(),
717 ghw_generic: icons::SETTINGS_3_LINE.to_string(),
718 vhdl_array: icons::BRACKETS_LINE.to_string(),
719 sv_array: icons::BRACKETS_LINE.to_string(),
720 clocking: icons::TIME_LINE.to_string(),
721 unknown: icons::QUESTION_LINE.to_string(),
722 colors: ScopeIconColors::default(),
723 }
724 }
725}
726
727impl ScopeIcons {
728 #[must_use]
731 pub fn get_icon(&self, scope_type: Option<wellen::ScopeType>) -> (&str, Color32) {
732 use wellen::ScopeType;
733 match scope_type {
734 None => (&self.module, self.colors.module),
735 Some(st) => match st {
736 ScopeType::Module => (&self.module, self.colors.module),
737 ScopeType::Task => (&self.task, self.colors.task),
738 ScopeType::Function => (&self.function, self.colors.function),
739 ScopeType::Begin => (&self.begin, self.colors.begin),
740 ScopeType::Fork => (&self.fork, self.colors.fork),
741 ScopeType::Generate => (&self.generate, self.colors.generate),
742 ScopeType::Struct => (&self.struct_, self.colors.struct_),
743 ScopeType::Union => (&self.union, self.colors.union),
744 ScopeType::Class => (&self.class, self.colors.class),
745 ScopeType::Interface => (&self.interface, self.colors.interface),
746 ScopeType::Package => (&self.package, self.colors.package),
747 ScopeType::Program => (&self.program, self.colors.program),
748 ScopeType::VhdlArchitecture => {
749 (&self.vhdl_architecture, self.colors.vhdl_architecture)
750 }
751 ScopeType::VhdlProcedure => (&self.vhdl_procedure, self.colors.vhdl_procedure),
752 ScopeType::VhdlFunction => (&self.vhdl_function, self.colors.vhdl_function),
753 ScopeType::VhdlRecord => (&self.vhdl_record, self.colors.vhdl_record),
754 ScopeType::VhdlProcess => (&self.vhdl_process, self.colors.vhdl_process),
755 ScopeType::VhdlBlock => (&self.vhdl_block, self.colors.vhdl_block),
756 ScopeType::VhdlForGenerate => {
757 (&self.vhdl_for_generate, self.colors.vhdl_for_generate)
758 }
759 ScopeType::VhdlIfGenerate => (&self.vhdl_if_generate, self.colors.vhdl_if_generate),
760 ScopeType::VhdlGenerate => (&self.vhdl_generate, self.colors.vhdl_generate),
761 ScopeType::VhdlPackage => (&self.vhdl_package, self.colors.vhdl_package),
762 ScopeType::GhwGeneric => (&self.ghw_generic, self.colors.ghw_generic),
763 ScopeType::VhdlArray => (&self.vhdl_array, self.colors.vhdl_array),
764 ScopeType::Unknown => (&self.unknown, self.colors.unknown),
765 ScopeType::SvArray => (&self.sv_array, self.colors.sv_array),
766 ScopeType::Clocking => (&self.clocking, self.colors.clocking),
767 _ => (&self.unknown, self.colors.unknown),
768 },
769 }
770 }
771}
772
773#[derive(Clone, Debug, Deserialize)]
776#[serde(default)]
777pub struct VariableIconColors {
778 #[serde(deserialize_with = "deserialize_hex_color")]
780 pub wire: Color32,
781 #[serde(deserialize_with = "deserialize_hex_color")]
783 pub bus: Color32,
784 #[serde(deserialize_with = "deserialize_hex_color")]
786 pub string: Color32,
787 #[serde(deserialize_with = "deserialize_hex_color")]
789 pub event: Color32,
790 #[serde(deserialize_with = "deserialize_hex_color")]
792 pub other: Color32,
793}
794
795impl Default for VariableIconColors {
796 fn default() -> Self {
797 Self {
798 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), }
804 }
805}
806
807#[derive(Clone, Debug, Deserialize)]
810#[serde(default)]
811pub struct VariableIcons {
812 pub wire: String,
814 pub bus: String,
816 pub string: String,
818 pub event: String,
820 pub other: String,
822 #[serde(default)]
824 pub colors: VariableIconColors,
825}
826
827impl Default for VariableIcons {
828 fn default() -> Self {
829 use egui_remixicon::icons;
830 Self {
831 wire: icons::GIT_COMMIT_LINE.to_string(),
832 bus: icons::BRACKETS_LINE.to_string(),
833 string: icons::TEXT.to_string(),
834 event: icons::ARROW_UP_LONG_LINE.to_string(),
835 other: icons::NUMBERS_LINE.to_string(),
836 colors: VariableIconColors::default(),
837 }
838 }
839}
840
841impl VariableIcons {
842 #[must_use]
845 pub fn get_icon(&self, meta: Option<&VariableMeta>) -> (&str, Color32) {
846 let Some(meta) = meta else {
847 return (&self.other, self.colors.other);
848 };
849
850 match meta.encoding {
851 VariableEncoding::String => (&self.string, self.colors.string),
852 VariableEncoding::Event => (&self.event, self.colors.event),
853 VariableEncoding::Real => (&self.other, self.colors.other),
854 VariableEncoding::BitVector => match meta.num_bits {
855 Some(1) => (&self.wire, self.colors.wire),
856 Some(n) if n > 1 => (&self.bus, self.colors.bus),
857 _ => (&self.other, self.colors.other),
858 },
859 }
860 }
861}
862
863fn get_luminance(color: Color32) -> f32 {
864 let rg = if color.r() < 10 {
865 f32::from(color.r()) / 3294.0
866 } else {
867 (f32::from(color.r()) / 269.0 + 0.0513).powf(2.4)
868 };
869 let gg = if color.g() < 10 {
870 f32::from(color.g()) / 3294.0
871 } else {
872 (f32::from(color.g()) / 269.0 + 0.0513).powf(2.4)
873 };
874 let bg = if color.b() < 10 {
875 f32::from(color.b()) / 3294.0
876 } else {
877 (f32::from(color.b()) / 269.0 + 0.0513).powf(2.4)
878 };
879 0.2126 * rg + 0.7152 * gg + 0.0722 * bg
880}
881
882impl SurferTheme {
883 #[must_use]
884 pub fn get_color(&self, color: &str) -> Option<Color32> {
885 self.colors.get(color).copied()
886 }
887
888 #[must_use]
889 pub fn get_best_text_color(&self, backgroundcolor: Color32) -> Color32 {
890 let l_foreground = get_luminance(self.foreground);
894 let l_alt_text_color = get_luminance(self.alt_text_color);
895 let l_background = get_luminance(backgroundcolor);
896
897 let mut cr_foreground = (l_foreground + 0.05) / (l_background + 0.05);
899 cr_foreground = cr_foreground.max(1. / cr_foreground);
900 let mut cr_alt_text_color = (l_alt_text_color + 0.05) / (l_background + 0.05);
901 cr_alt_text_color = cr_alt_text_color.max(1. / cr_alt_text_color);
902
903 if cr_foreground > cr_alt_text_color {
905 self.foreground
906 } else {
907 self.alt_text_color
908 }
909 }
910
911 fn generate_defaults(
912 theme_name: Option<&String>,
913 ) -> (ConfigBuilder<DefaultState>, Vec<String>) {
914 let default_theme = String::from(include_str!("../../default_theme.toml"));
915
916 let mut theme = Config::builder().add_source(config::File::from_str(
917 &default_theme,
918 config::FileFormat::Toml,
919 ));
920
921 let theme_names = all_theme_names();
922
923 let override_theme = theme_name
924 .as_ref()
925 .and_then(|name| BUILTIN_THEMES.get(name.as_str()).copied())
926 .unwrap_or("");
927
928 theme = theme.add_source(config::File::from_str(
929 override_theme,
930 config::FileFormat::Toml,
931 ));
932 (theme, theme_names)
933 }
934
935 #[cfg(target_arch = "wasm32")]
936 pub fn new(theme_name: Option<String>) -> Result<Self> {
937 use eyre::anyhow;
938
939 let (theme, _) = Self::generate_defaults(theme_name.as_ref());
940
941 let theme = theme.set_override("theme_names", all_theme_names())?;
942
943 theme
944 .build()?
945 .try_deserialize()
946 .map_err(|e| anyhow!("Failed to parse config {e}"))
947 }
948
949 #[cfg(not(target_arch = "wasm32"))]
950 pub fn new(theme_name: Option<String>) -> eyre::Result<Self> {
951 use std::fs::ReadDir;
952
953 use eyre::anyhow;
954
955 let (mut theme, mut theme_names) = Self::generate_defaults(theme_name.as_ref());
956
957 let mut add_themes_from_dir = |dir: ReadDir| {
958 for theme in dir.flatten() {
959 if let Ok(theme_path) = theme.file_name().into_string()
960 && let Some(fname_str) = theme_path.strip_suffix(".toml")
961 {
962 let fname = fname_str.to_string();
963 if !fname.is_empty() && !theme_names.contains(&fname) {
964 theme_names.push(fname);
965 }
966 }
967 }
968 };
969
970 if let Some(proj_dirs) = &*PROJECT_DIR {
972 let config_themes_dir = proj_dirs.config_dir().join(THEMES_DIR);
973 if let Ok(config_themes_dir) = std::fs::read_dir(config_themes_dir) {
974 add_themes_from_dir(config_themes_dir);
975 }
976 }
977
978 let local_config_dirs = find_local_configs();
980
981 local_config_dirs
984 .iter()
985 .filter_map(|p| std::fs::read_dir(p.join(THEMES_DIR)).ok())
986 .for_each(add_themes_from_dir);
987
988 if matches!(theme_name, Some(ref name) if !name.is_empty()) {
989 let theme_path =
990 Path::new(THEMES_DIR).join(theme_name.as_ref().unwrap().to_owned() + ".toml");
991
992 let local_themes: Vec<PathBuf> = local_config_dirs
995 .iter()
996 .map(|p| p.join(&theme_path))
997 .filter(|p| p.exists())
998 .collect();
999 if local_themes.is_empty() {
1000 if let Some(proj_dirs) = &*PROJECT_DIR {
1002 let config_theme_path = proj_dirs.config_dir().join(theme_path);
1003 if config_theme_path.exists() {
1004 theme = theme.add_source(File::from(config_theme_path).required(false));
1005 }
1006 }
1007 } else {
1008 theme = local_themes
1009 .into_iter()
1010 .fold(theme, |t, p| t.add_source(File::from(p).required(false)));
1011 }
1012 }
1013
1014 let theme = theme.set_override("theme_names", theme_names)?;
1015
1016 theme
1017 .build()?
1018 .try_deserialize()
1019 .map_err(|e| anyhow!("Failed to parse theme {e}"))
1020 }
1021}
1022
1023#[derive(Debug, Deserialize)]
1024pub struct ThemeColorPair {
1025 #[serde(deserialize_with = "deserialize_hex_color")]
1026 pub foreground: Color32,
1027 #[serde(deserialize_with = "deserialize_hex_color")]
1028 pub background: Color32,
1029}
1030
1031#[derive(Debug, Deserialize)]
1032pub struct ThemeColorTriple {
1033 #[serde(deserialize_with = "deserialize_hex_color")]
1034 pub foreground: Color32,
1035 #[serde(deserialize_with = "deserialize_hex_color")]
1036 pub background: Color32,
1037 #[serde(deserialize_with = "deserialize_hex_color")]
1038 pub alt_background: Color32,
1039}
1040
1041#[derive(Debug, Deserialize)]
1042pub struct WcpConfig {
1043 pub autostart: bool,
1045 pub address: String,
1047}
1048
1049fn default_colors() -> HashMap<String, Color32> {
1050 [
1051 ("Green", "a7e47e"),
1052 ("Red", "c52e2e"),
1053 ("Yellow", "f3d54a"),
1054 ("Blue", "81a2be"),
1055 ("Purple", "b294bb"),
1056 ("Aqua", "8abeb7"),
1057 ("Gray", "c5c8c6"),
1058 ]
1059 .iter()
1060 .map(|(name, hexcode)| {
1061 (
1062 (*name).to_string(),
1063 hex_string_to_color32((*hexcode).to_string()).unwrap(),
1064 )
1065 })
1066 .collect()
1067}
1068
1069impl SurferConfig {
1070 #[cfg(target_arch = "wasm32")]
1071 pub fn new(_force_default_config: bool) -> Result<Self> {
1072 Self::new_from_toml(&include_str!("../../default_config.toml"))
1073 }
1074
1075 #[cfg(not(target_arch = "wasm32"))]
1076 pub fn new(force_default_config: bool) -> eyre::Result<Self> {
1077 use eyre::anyhow;
1078 use tracing::warn;
1079
1080 let default_config = String::from(include_str!("../../default_config.toml"));
1081
1082 let mut config = Config::builder().add_source(config::File::from_str(
1083 &default_config,
1084 config::FileFormat::Toml,
1085 ));
1086
1087 let config = if force_default_config {
1088 config
1089 } else {
1090 if let Some(proj_dirs) = &*PROJECT_DIR {
1091 let config_file = proj_dirs.config_dir().join(CONFIG_FILE);
1092 config = config.add_source(File::from(config_file).required(false));
1093 }
1094
1095 let old_config_path = Path::new(OLD_CONFIG_FILE);
1096 if old_config_path.exists() {
1097 warn!(
1098 "Configuration in 'surfer.toml' is deprecated. Please move your configuration to '.surfer/config.toml'."
1099 );
1100 }
1101
1102 config = config.add_source(File::from(old_config_path).required(false));
1104
1105 find_local_configs()
1108 .into_iter()
1109 .fold(config, |c, p| {
1110 c.add_source(File::from(p.join(CONFIG_FILE)).required(false))
1111 })
1112 .add_source(Environment::with_prefix("surfer")) };
1114
1115 config
1116 .build()?
1117 .try_deserialize()
1118 .map_err(|e| anyhow!("Failed to parse config {e}"))
1119 }
1120
1121 pub fn new_from_toml(config: &str) -> Result<Self> {
1122 Ok(toml::from_str(config)?)
1123 }
1124}
1125
1126impl Default for SurferConfig {
1127 fn default() -> Self {
1128 Self::new(false).expect("Failed to load default config")
1129 }
1130}
1131
1132fn hex_string_to_color32(str: String) -> Result<Color32> {
1133 let str = if str.len() == 3 {
1134 str.chars().flat_map(|c| [c, c]).collect()
1135 } else {
1136 str
1137 };
1138 if str.len() == 6 {
1139 let r = u8::from_str_radix(&str[0..2], 16)
1140 .with_context(|| format!("'{str}' is not a valid RGB hex color"))?;
1141 let g = u8::from_str_radix(&str[2..4], 16)
1142 .with_context(|| format!("'{str}' is not a valid RGB hex color"))?;
1143 let b = u8::from_str_radix(&str[4..6], 16)
1144 .with_context(|| format!("'{str}' is not a valid RGB hex color"))?;
1145 Ok(Color32::from_rgb(r, g, b))
1146 } else {
1147 Result::Err(Report::msg(format!("'{str}' is not a valid RGB hex color")))
1148 }
1149}
1150
1151fn all_theme_names() -> Vec<String> {
1152 BUILTIN_THEMES.keys().map(ToString::to_string).collect()
1153}
1154
1155fn deserialize_hex_color<'de, D>(deserializer: D) -> Result<Color32, D::Error>
1156where
1157 D: Deserializer<'de>,
1158{
1159 let buf = String::deserialize(deserializer)?;
1160 hex_string_to_color32(buf).map_err(de::Error::custom)
1161}
1162
1163fn deserialize_color_map<'de, D>(deserializer: D) -> Result<HashMap<String, Color32>, D::Error>
1164where
1165 D: Deserializer<'de>,
1166{
1167 #[derive(Deserialize)]
1168 struct Wrapper(#[serde(deserialize_with = "deserialize_hex_color")] Color32);
1169
1170 let v = HashMap::<String, Wrapper>::deserialize(deserializer)?;
1171 Ok(v.into_iter().map(|(k, Wrapper(v))| (k, v)).collect())
1172}
1173
1174fn deserialize_theme<'de, D>(deserializer: D) -> Result<SurferTheme, D::Error>
1175where
1176 D: Deserializer<'de>,
1177{
1178 let buf = String::deserialize(deserializer)?;
1179 SurferTheme::new(Some(buf)).map_err(de::Error::custom)
1180}
1181
1182#[cfg(not(target_arch = "wasm32"))]
1187pub fn find_local_configs() -> Vec<PathBuf> {
1188 use crate::util::search_upward;
1189 match std::env::current_dir() {
1190 Ok(dir) => search_upward(dir, "/", LOCAL_DIR)
1191 .into_iter()
1192 .filter(|p| p.is_dir()) .rev() .collect(),
1195 Err(_) => vec![],
1196 }
1197}
1198
1199#[cfg(test)]
1200mod tests {
1201 use super::*;
1202
1203 #[test]
1204 fn test_hex_string_3_chars() {
1205 let result = hex_string_to_color32("abc".to_string()).unwrap();
1207 let expected = Color32::from_rgb(0xaa, 0xbb, 0xcc);
1208 assert_eq!(result, expected);
1209 }
1210
1211 #[test]
1212 fn test_hex_string_6_chars() {
1213 let result = hex_string_to_color32("a7e47e".to_string()).unwrap();
1215 let expected = Color32::from_rgb(0xa7, 0xe4, 0x7e);
1216 assert_eq!(result, expected);
1217 }
1218
1219 #[test]
1220 fn test_hex_string_black() {
1221 let result = hex_string_to_color32("000000".to_string()).unwrap();
1223 let expected = Color32::from_rgb(0x00, 0x00, 0x00);
1224 assert_eq!(result, expected);
1225 }
1226
1227 #[test]
1228 fn test_hex_string_white() {
1229 let result = hex_string_to_color32("ffffff".to_string()).unwrap();
1231 let expected = Color32::from_rgb(0xff, 0xff, 0xff);
1232 assert_eq!(result, expected);
1233 }
1234
1235 #[test]
1236 fn test_hex_string_uppercase() {
1237 let result = hex_string_to_color32("ABCDEF".to_string()).unwrap();
1239 let expected = Color32::from_rgb(0xab, 0xcd, 0xef);
1240 assert_eq!(result, expected);
1241 }
1242
1243 #[test]
1244 fn test_hex_string_mixed_case() {
1245 let result = hex_string_to_color32("Ab5DeF".to_string()).unwrap();
1247 let expected = Color32::from_rgb(0xab, 0x5d, 0xef);
1248 assert_eq!(result, expected);
1249 }
1250
1251 #[test]
1252 fn test_hex_string_invalid_length() {
1253 let result = hex_string_to_color32("ab".to_string());
1255 assert!(result.is_err());
1256
1257 let result = hex_string_to_color32("abcde".to_string());
1258 assert!(result.is_err());
1259
1260 let result = hex_string_to_color32("abcdefgh".to_string());
1261 assert!(result.is_err());
1262 }
1263
1264 #[test]
1265 fn test_hex_string_invalid_characters() {
1266 let result = hex_string_to_color32("GGGGGG".to_string());
1268 assert!(result.is_err());
1269
1270 let result = hex_string_to_color32("12345g".to_string());
1271 assert!(result.is_err());
1272
1273 let result = hex_string_to_color32("zzzzzz".to_string());
1274 assert!(result.is_err());
1275 }
1276
1277 #[test]
1278 fn test_hex_string_empty() {
1279 let result = hex_string_to_color32(String::new());
1281 assert!(result.is_err());
1282 }
1283
1284 #[test]
1285 fn test_hex_string_3_chars_doubling() {
1286 let result = hex_string_to_color32("050".to_string()).unwrap();
1288 let expected = Color32::from_rgb(0x00, 0x55, 0x00);
1289 assert_eq!(result, expected);
1290 }
1291}