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
37static 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#[derive(Clone, Copy, Debug, Deserialize, Display, FromStr, PartialEq, Eq, Sequence, Serialize)]
66pub enum ArrowKeyBindings {
67 Edge,
69
70 Scroll,
72}
73
74#[derive(Clone, Copy, Debug, Deserialize, Display, FromStr, PartialEq, Eq, Sequence, Serialize)]
75pub enum TransitionValue {
76 Previous,
78 Next,
80 Both,
82}
83
84#[derive(Debug, Deserialize, Display, PartialEq, Eq, Sequence, Serialize, Clone, Copy)]
86pub enum PrimaryMouseDrag {
87 #[display("Measure time")]
89 Measure,
90
91 #[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 pub gesture: SurferGesture,
121 pub behavior: SurferBehavior,
122 pub default_time_format: TimeFormat,
124 pub default_variable_name_type: VariableNameType,
125 default_clock_highlight_type: ClockHighlightType,
126 pub snap_distance: f32,
128 pub undo_stack_size: usize,
130 autoreload_files: AutoLoad,
132 autoload_sibling_state_files: AutoLoad,
134 pub wcp: WcpConfig,
136 pub server: ServerConfig,
138 pub animation_time: f32,
140 pub animation_enabled: bool,
142 pub max_url_length: u16,
145 #[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 show_hierarchy: bool,
176 show_menu: bool,
178 show_toolbar: bool,
180 show_ticks: bool,
182 show_tooltip: bool,
184 show_scope_tooltip: bool,
186 show_overview: bool,
188 show_statusbar: bool,
190 show_variable_indices: bool,
192 show_variable_direction: bool,
194 show_default_timeline: bool,
196 show_empty_scopes: bool,
198 show_hierarchy_icons: bool,
200 parameter_display_location: ParameterDisplayLocation,
202 pub window_height: usize,
204 pub window_width: usize,
206 align_names_right: bool,
208 hierarchy_style: HierarchyStyle,
210 pub waveforms_text_size: f32,
212 pub waveforms_line_height: f32,
214 pub waveforms_line_height_multiples: Vec<f32>,
216 pub transactions_line_height: f32,
218 pub zoom_factors: Vec<f32>,
220 pub default_zoom_factor: f32,
222 #[serde(default)]
223 highlight_focused: bool,
225 move_focus_on_inserted_marker: bool,
227 #[serde(default = "default_true")]
229 fill_high_values: bool,
230 #[serde(default)]
232 use_dinotrace_style: bool,
233 #[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 pub keep_during_reload: bool,
341 pub arrow_key_bindings: ArrowKeyBindings,
343 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)]
361pub struct SurferGesture {
363 pub size: f32,
365 pub deadzone: f32,
367 pub background_radius: f32,
369 pub background_gamma: f32,
371 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)]
407pub struct SurferTicks {
409 pub density: f32,
411 pub style: SurferLineStyle,
413}
414
415#[derive(Debug, Deserialize)]
416pub struct SurferRelationArrow {
417 pub style: SurferLineStyle,
419
420 pub head_angle: f32,
422
423 pub head_length: f32,
425}
426
427#[derive(Debug, Deserialize)]
428pub struct SurferTheme {
429 #[serde(deserialize_with = "deserialize_hex_color")]
431 pub foreground: Color32,
432 #[serde(deserialize_with = "deserialize_hex_color")]
433 pub border_color: Color32,
435 #[serde(deserialize_with = "deserialize_hex_color")]
437 pub alt_text_color: Color32,
438 pub canvas_colors: ThemeColorTriple,
440 pub primary_ui_color: ThemeColorPair,
442 pub secondary_ui_color: ThemeColorPair,
445 pub selected_elements_colors: ThemeColorPair,
447
448 pub accent_info: ThemeColorPair,
449 pub accent_warn: ThemeColorPair,
450 pub accent_error: ThemeColorPair,
451
452 pub cursor: SurferLineStyle,
454
455 pub gesture: SurferLineStyle,
457
458 pub measure: SurferLineStyle,
460
461 pub clock_highlight_line: SurferLineStyle,
463 #[serde(deserialize_with = "deserialize_hex_color")]
464 pub clock_highlight_cycle: Color32,
465 pub clock_rising_marker: bool,
467
468 #[serde(deserialize_with = "deserialize_hex_color")]
469 pub variable_default: Color32,
471 #[serde(deserialize_with = "deserialize_hex_color")]
472 pub variable_highimp: Color32,
474 #[serde(deserialize_with = "deserialize_hex_color")]
475 pub variable_undef: Color32,
477 #[serde(deserialize_with = "deserialize_hex_color")]
478 pub variable_dontcare: Color32,
480 #[serde(deserialize_with = "deserialize_hex_color")]
481 pub variable_weak: Color32,
483 #[serde(deserialize_with = "deserialize_hex_color")]
484 pub variable_parameter: Color32,
486 #[serde(deserialize_with = "deserialize_hex_color")]
487 pub transaction_default: Color32,
489 pub relation_arrow: SurferRelationArrow,
491 #[serde(deserialize_with = "deserialize_hex_color")]
492 pub variable_event: Color32,
494
495 pub waveform_opacity: f32,
498 #[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 pub linewidth: f32,
509
510 pub thick_linewidth: f32,
512
513 pub vector_transition_width: f32,
515
516 pub alt_frequency: usize,
519
520 pub viewport_separator: SurferLineStyle,
522
523 #[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 pub ticks: SurferTicks,
531
532 #[serde(default = "Vec::new")]
534 pub theme_names: Vec<String>,
535
536 #[serde(default)]
538 pub scope_icons: ScopeIcons,
539
540 #[serde(default)]
542 pub variable_icons: VariableIcons,
543}
544
545#[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), 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), unknown: Color32::from_rgb(0x9E, 0x9E, 0x9E), }
630 }
631}
632
633#[derive(Clone, Debug, Deserialize)]
636#[serde(default)]
637pub struct ScopeIcons {
638 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 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 #[serde(default)]
668 pub colors: ScopeIconColors,
669}
670
671impl Default for ScopeIcons {
672 fn default() -> Self {
673 use egui_remixicon::icons;
674 Self {
675 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_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 #[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#[derive(Clone, Debug, Deserialize)]
754#[serde(default)]
755pub struct VariableIconColors {
756 #[serde(deserialize_with = "deserialize_hex_color")]
758 pub wire: Color32,
759 #[serde(deserialize_with = "deserialize_hex_color")]
761 pub bus: Color32,
762 #[serde(deserialize_with = "deserialize_hex_color")]
764 pub string: Color32,
765 #[serde(deserialize_with = "deserialize_hex_color")]
767 pub event: Color32,
768 #[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), 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), }
782 }
783}
784
785#[derive(Clone, Debug, Deserialize)]
788#[serde(default)]
789pub struct VariableIcons {
790 pub wire: String,
792 pub bus: String,
794 pub string: String,
796 pub event: String,
798 pub other: String,
800 #[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 #[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 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 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 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 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 let local_config_dirs = find_local_configs();
958
959 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 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 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 pub autostart: bool,
1023 pub address: String,
1025}
1026
1027#[derive(Debug, Deserialize)]
1028pub struct ServerConfig {
1029 pub bind_address: String,
1031 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 config = config.add_source(File::from(old_config_path).required(false));
1090
1091 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")) };
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#[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()) .rev() .collect(),
1184 Err(_) => vec![],
1185 }
1186}