Skip to main content

ringkernel_accnet/gui/
theme.rs

1//! Visual theme for the accounting network GUI.
2
3use eframe::egui::{self, Color32, Rounding, Stroke};
4
5/// Color theme for the application.
6#[derive(Debug, Clone)]
7pub struct AccNetTheme {
8    /// Background color for the main canvas.
9    pub canvas_bg: Color32,
10    /// Grid line color.
11    pub grid_color: Color32,
12    /// Panel background.
13    pub panel_bg: Color32,
14    /// Text color.
15    pub text_primary: Color32,
16    /// Secondary text color.
17    pub text_secondary: Color32,
18    /// Accent color for highlights.
19    pub accent: Color32,
20
21    // Account type colors
22    /// Asset account color.
23    pub asset_color: Color32,
24    /// Liability account color.
25    pub liability_color: Color32,
26    /// Equity account color.
27    pub equity_color: Color32,
28    /// Revenue account color.
29    pub revenue_color: Color32,
30    /// Expense account color.
31    pub expense_color: Color32,
32    /// Contra account color.
33    pub contra_color: Color32,
34
35    // Alert colors
36    /// Low severity.
37    pub alert_low: Color32,
38    /// Medium severity.
39    pub alert_medium: Color32,
40    /// High severity.
41    pub alert_high: Color32,
42    /// Critical severity.
43    pub alert_critical: Color32,
44
45    // Flow colors
46    /// Normal flow.
47    pub flow_normal: Color32,
48    /// Suspicious flow.
49    pub flow_suspicious: Color32,
50    /// Fraudulent flow.
51    pub flow_fraud: Color32,
52
53    // UI
54    /// Node radius.
55    pub node_radius: f32,
56    /// Edge width.
57    pub edge_width: f32,
58    /// Panel rounding.
59    pub panel_rounding: Rounding,
60}
61
62impl Default for AccNetTheme {
63    fn default() -> Self {
64        Self::dark()
65    }
66}
67
68impl AccNetTheme {
69    /// Create a dark theme (default).
70    pub fn dark() -> Self {
71        Self {
72            canvas_bg: Color32::from_rgb(18, 18, 24),
73            grid_color: Color32::from_rgb(40, 40, 50),
74            panel_bg: Color32::from_rgb(28, 28, 36),
75            text_primary: Color32::from_rgb(240, 240, 245),
76            text_secondary: Color32::from_rgb(140, 140, 155),
77            accent: Color32::from_rgb(100, 180, 255),
78
79            // Account types - professional palette
80            asset_color: Color32::from_rgb(66, 165, 245), // Blue
81            liability_color: Color32::from_rgb(239, 83, 80), // Red
82            equity_color: Color32::from_rgb(102, 187, 106), // Green
83            revenue_color: Color32::from_rgb(255, 202, 40), // Amber
84            expense_color: Color32::from_rgb(171, 71, 188), // Purple
85            contra_color: Color32::from_rgb(158, 158, 158), // Gray
86
87            // Alerts
88            alert_low: Color32::from_rgb(100, 180, 255),
89            alert_medium: Color32::from_rgb(255, 202, 40),
90            alert_high: Color32::from_rgb(255, 152, 0),
91            alert_critical: Color32::from_rgb(244, 67, 54),
92
93            // Flows
94            flow_normal: Color32::from_rgb(100, 255, 180),
95            flow_suspicious: Color32::from_rgb(255, 202, 40),
96            flow_fraud: Color32::from_rgb(244, 67, 54),
97
98            // UI
99            node_radius: 20.0,
100            edge_width: 2.0,
101            panel_rounding: Rounding::same(8.0),
102        }
103    }
104
105    /// Create a light theme.
106    pub fn light() -> Self {
107        Self {
108            canvas_bg: Color32::from_rgb(245, 245, 250),
109            grid_color: Color32::from_rgb(220, 220, 230),
110            panel_bg: Color32::from_rgb(255, 255, 255),
111            text_primary: Color32::from_rgb(30, 30, 40),
112            text_secondary: Color32::from_rgb(100, 100, 115),
113            accent: Color32::from_rgb(25, 118, 210),
114
115            // Account types
116            asset_color: Color32::from_rgb(25, 118, 210),
117            liability_color: Color32::from_rgb(211, 47, 47),
118            equity_color: Color32::from_rgb(56, 142, 60),
119            revenue_color: Color32::from_rgb(255, 160, 0),
120            expense_color: Color32::from_rgb(142, 36, 170),
121            contra_color: Color32::from_rgb(117, 117, 117),
122
123            // Alerts
124            alert_low: Color32::from_rgb(25, 118, 210),
125            alert_medium: Color32::from_rgb(255, 160, 0),
126            alert_high: Color32::from_rgb(230, 81, 0),
127            alert_critical: Color32::from_rgb(198, 40, 40),
128
129            // Flows
130            flow_normal: Color32::from_rgb(0, 150, 136),
131            flow_suspicious: Color32::from_rgb(255, 160, 0),
132            flow_fraud: Color32::from_rgb(198, 40, 40),
133
134            // UI
135            node_radius: 20.0,
136            edge_width: 2.0,
137            panel_rounding: Rounding::same(8.0),
138        }
139    }
140
141    /// Get color for account type.
142    pub fn account_color(&self, account_type: crate::models::AccountType) -> Color32 {
143        use crate::models::AccountType;
144        match account_type {
145            AccountType::Asset => self.asset_color,
146            AccountType::Liability => self.liability_color,
147            AccountType::Equity => self.equity_color,
148            AccountType::Revenue => self.revenue_color,
149            AccountType::Expense => self.expense_color,
150            AccountType::Contra => self.contra_color,
151        }
152    }
153
154    /// Get stroke for edges.
155    pub fn edge_stroke(&self, suspicious: bool, fraud: bool) -> Stroke {
156        let color = if fraud {
157            self.flow_fraud
158        } else if suspicious {
159            self.flow_suspicious
160        } else {
161            self.flow_normal
162        };
163        Stroke::new(self.edge_width, color)
164    }
165
166    /// Get futuristic edge color with transparency based on weight and flow type.
167    /// Returns (color, alpha) tuple.
168    pub fn edge_color_futuristic(
169        &self,
170        weight: f32,
171        suspicious: bool,
172        fraud: bool,
173        max_weight: f32,
174    ) -> Color32 {
175        // Normalize weight for alpha calculation
176        let normalized = (weight / max_weight.max(1.0)).min(1.0);
177
178        // Base color selection
179        let base = if fraud {
180            self.flow_fraud
181        } else if suspicious {
182            self.flow_suspicious
183        } else {
184            // Gradient from cyan to blue based on weight
185            let t = normalized;
186            Color32::from_rgb(
187                (60.0 + t * 40.0) as u8,  // 60-100
188                (180.0 + t * 40.0) as u8, // 180-220
189                (220.0 + t * 35.0) as u8, // 220-255
190            )
191        };
192
193        // Alpha: lighter for low weight, more visible for high weight
194        // Range from 40 (very transparent) to 160 (semi-transparent)
195        let alpha = (40.0 + normalized * 120.0) as u8;
196
197        Color32::from_rgba_unmultiplied(base.r(), base.g(), base.b(), alpha)
198    }
199
200    /// Apply theme to egui context.
201    pub fn apply(&self, ctx: &egui::Context) {
202        let mut style = (*ctx.style()).clone();
203
204        style.visuals.dark_mode = self.canvas_bg.r() < 128;
205        style.visuals.panel_fill = self.panel_bg;
206        style.visuals.window_fill = self.panel_bg;
207        style.visuals.widgets.noninteractive.fg_stroke.color = self.text_primary;
208        style.visuals.widgets.inactive.fg_stroke.color = self.text_secondary;
209        style.visuals.selection.bg_fill = self.accent;
210        style.visuals.window_rounding = self.panel_rounding;
211
212        ctx.set_style(style);
213    }
214}