eulumdat_ui/
theme.rs

1//! Theme settings for diagrams and UI
2
3use egui::Color32;
4
5/// Theme for diagram rendering
6#[derive(Debug, Clone)]
7#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
8pub struct Theme {
9    /// Background color
10    pub background: Color32,
11    /// Grid line color
12    pub grid: Color32,
13    /// Primary curve color (C0-C180)
14    pub primary_curve: Color32,
15    /// Secondary curve color (C90-C270)
16    pub secondary_curve: Color32,
17    /// Text/label color
18    pub text: Color32,
19    /// Axis color
20    pub axis: Color32,
21    /// Whether this is a dark theme
22    pub is_dark: bool,
23}
24
25impl Default for Theme {
26    fn default() -> Self {
27        Self::light()
28    }
29}
30
31impl Theme {
32    /// Light theme (white background)
33    pub fn light() -> Self {
34        Self {
35            background: Color32::WHITE,
36            grid: Color32::from_gray(220),
37            primary_curve: Color32::from_rgb(220, 60, 60), // Red
38            secondary_curve: Color32::from_rgb(60, 60, 220), // Blue
39            text: Color32::from_gray(40),
40            axis: Color32::from_gray(100),
41            is_dark: false,
42        }
43    }
44
45    /// Dark theme (dark background)
46    pub fn dark() -> Self {
47        Self {
48            background: Color32::from_gray(30),
49            grid: Color32::from_gray(60),
50            primary_curve: Color32::from_rgb(255, 100, 100), // Light red
51            secondary_curve: Color32::from_rgb(100, 150, 255), // Light blue
52            text: Color32::from_gray(220),
53            axis: Color32::from_gray(150),
54            is_dark: true,
55        }
56    }
57
58    /// Create theme from egui's visuals
59    pub fn from_egui(ctx: &egui::Context) -> Self {
60        if ctx.style().visuals.dark_mode {
61            Self::dark()
62        } else {
63            Self::light()
64        }
65    }
66
67    /// Get a color for a specific C-plane (for multi-curve diagrams)
68    pub fn c_plane_color(&self, c_angle: f64, _total_planes: usize) -> Color32 {
69        let hue = (c_angle / 360.0) as f32;
70        let (r, g, b) = hsl_to_rgb(hue, 0.7, if self.is_dark { 0.6 } else { 0.45 });
71        Color32::from_rgb(r, g, b)
72    }
73
74    /// Get heatmap color for normalized intensity (0.0 - 1.0)
75    pub fn heatmap_color(&self, normalized: f64) -> Color32 {
76        let n = normalized.clamp(0.0, 1.0) as f32;
77
78        // Blue -> Cyan -> Green -> Yellow -> Red
79        let (r, g, b) = if n < 0.25 {
80            let t = n / 0.25;
81            (0.0, t, 1.0) // Blue to Cyan
82        } else if n < 0.5 {
83            let t = (n - 0.25) / 0.25;
84            (0.0, 1.0, 1.0 - t) // Cyan to Green
85        } else if n < 0.75 {
86            let t = (n - 0.5) / 0.25;
87            (t, 1.0, 0.0) // Green to Yellow
88        } else {
89            let t = (n - 0.75) / 0.25;
90            (1.0, 1.0 - t, 0.0) // Yellow to Red
91        };
92
93        Color32::from_rgb((r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8)
94    }
95}
96
97/// Convert HSL to RGB
98fn hsl_to_rgb(h: f32, s: f32, l: f32) -> (u8, u8, u8) {
99    let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
100    let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs());
101    let m = l - c / 2.0;
102
103    let (r, g, b) = match (h * 6.0) as u32 {
104        0 => (c, x, 0.0),
105        1 => (x, c, 0.0),
106        2 => (0.0, c, x),
107        3 => (0.0, x, c),
108        4 => (x, 0.0, c),
109        _ => (c, 0.0, x),
110    };
111
112    (
113        ((r + m) * 255.0) as u8,
114        ((g + m) * 255.0) as u8,
115        ((b + m) * 255.0) as u8,
116    )
117}