1use aranet_types::Status;
4use ratatui::style::{Color, Modifier, Style};
5use ratatui::widgets::BorderType;
6
7use crate::tui::app::ConnectionStatus;
8
9#[derive(Debug, Clone, Copy)]
13pub struct AppTheme {
14 pub primary: Color,
16
17 pub success: Color,
19 pub warning: Color,
20 pub caution: Color,
21 pub danger: Color,
22 pub info: Color,
23
24 pub sensor_temperature: Color,
26 pub sensor_humidity: Color,
27 pub sensor_pressure: Color,
28 pub sensor_radiation: Color,
29 pub series_co2: Color,
30 pub series_radon: Color,
31 pub series_radiation: Color,
32
33 pub trend_rising: Color,
35 pub trend_falling: Color,
36 pub trend_stable: Color,
37 pub signal_excellent: Color,
38 pub signal_good: Color,
39 pub signal_fair: Color,
40 pub signal_weak: Color,
41 pub signal_offline: Color,
42
43 pub text_primary: Color,
45 pub text_secondary: Color,
46 pub text_muted: Color,
47
48 pub border_active: Color,
50 pub border_inactive: Color,
51
52 pub bg_selected: Color,
54 pub bg_header: Color,
55}
56
57impl Default for AppTheme {
58 fn default() -> Self {
59 Self::dark()
60 }
61}
62
63impl AppTheme {
64 #[must_use]
66 pub const fn dark() -> Self {
67 Self {
68 primary: Color::Rgb(34, 211, 238), success: Color::Rgb(74, 222, 128), warning: Color::Rgb(251, 191, 36), caution: Color::Rgb(251, 146, 60), danger: Color::Rgb(248, 113, 113), info: Color::Rgb(96, 165, 250), sensor_temperature: Color::Rgb(251, 191, 36), sensor_humidity: Color::Rgb(96, 165, 250), sensor_pressure: Color::Rgb(248, 250, 252), sensor_radiation: Color::Rgb(217, 70, 239), series_co2: Color::Rgb(74, 222, 128), series_radon: Color::Rgb(34, 211, 238), series_radiation: Color::Rgb(217, 70, 239), trend_rising: Color::Rgb(248, 113, 113), trend_falling: Color::Rgb(74, 222, 128), trend_stable: Color::Rgb(100, 116, 139), signal_excellent: Color::Rgb(74, 222, 128), signal_good: Color::Rgb(74, 222, 128), signal_fair: Color::Rgb(251, 191, 36), signal_weak: Color::Rgb(248, 113, 113), signal_offline: Color::Rgb(100, 116, 139), text_primary: Color::Rgb(248, 250, 252), text_secondary: Color::Rgb(148, 163, 184), text_muted: Color::Rgb(100, 116, 139), border_active: Color::Rgb(34, 211, 238), border_inactive: Color::Rgb(71, 85, 105), bg_selected: Color::Rgb(51, 65, 85), bg_header: Color::Rgb(30, 41, 59), }
110 }
111
112 #[must_use]
114 pub const fn light() -> Self {
115 Self {
116 primary: Color::Rgb(6, 182, 212), success: Color::Rgb(22, 163, 74), warning: Color::Rgb(217, 119, 6), caution: Color::Rgb(234, 88, 12), danger: Color::Rgb(220, 38, 38), info: Color::Rgb(37, 99, 235), sensor_temperature: Color::Rgb(217, 119, 6), sensor_humidity: Color::Rgb(37, 99, 235), sensor_pressure: Color::Rgb(15, 23, 42), sensor_radiation: Color::Rgb(147, 51, 234), series_co2: Color::Rgb(22, 163, 74), series_radon: Color::Rgb(8, 145, 178), series_radiation: Color::Rgb(147, 51, 234), trend_rising: Color::Rgb(220, 38, 38), trend_falling: Color::Rgb(22, 163, 74), trend_stable: Color::Rgb(148, 163, 184), signal_excellent: Color::Rgb(22, 163, 74), signal_good: Color::Rgb(22, 163, 74), signal_fair: Color::Rgb(217, 119, 6), signal_weak: Color::Rgb(220, 38, 38), signal_offline: Color::Rgb(148, 163, 184), text_primary: Color::Rgb(15, 23, 42), text_secondary: Color::Rgb(71, 85, 105), text_muted: Color::Rgb(148, 163, 184), border_active: Color::Rgb(6, 182, 212), border_inactive: Color::Rgb(203, 213, 225), bg_selected: Color::Rgb(226, 232, 240), bg_header: Color::Rgb(241, 245, 249), }
158 }
159
160 #[inline]
164 #[must_use]
165 pub fn border_active_style(&self) -> Style {
166 Style::default().fg(self.border_active)
167 }
168
169 #[inline]
171 #[must_use]
172 pub fn border_inactive_style(&self) -> Style {
173 Style::default().fg(self.border_inactive)
174 }
175
176 #[inline]
178 #[must_use]
179 pub fn selected_style(&self) -> Style {
180 Style::default()
181 .bg(self.bg_selected)
182 .fg(self.text_primary)
183 .add_modifier(Modifier::BOLD)
184 }
185
186 #[inline]
188 #[must_use]
189 pub fn title_style(&self) -> Style {
190 Style::default()
191 .fg(self.primary)
192 .add_modifier(Modifier::BOLD)
193 }
194
195 #[inline]
197 #[must_use]
198 pub fn header_style(&self) -> Style {
199 Style::default().bg(self.bg_header)
200 }
201
202 #[must_use]
204 pub fn co2_level_color(&self, ppm: u16) -> Color {
205 match ppm {
206 0..=800 => self.success,
207 801..=1000 => self.warning,
208 1001..=1500 => self.caution,
209 _ => self.danger,
210 }
211 }
212
213 #[must_use]
215 pub fn radon_level_color(&self, bq_m3: u32) -> Color {
216 match bq_m3 {
217 0..=100 => self.success,
218 101..=150 => self.warning,
219 151..=300 => self.caution,
220 _ => self.danger,
221 }
222 }
223
224 #[must_use]
226 pub fn battery_level_color(&self, percent: u8) -> Color {
227 match percent {
228 0..=20 => self.danger,
229 21..=50 => self.warning,
230 _ => self.success,
231 }
232 }
233
234 #[must_use]
236 pub fn sensor_status_color(&self, status: &Status) -> Color {
237 match status {
238 Status::Green => self.success,
239 Status::Yellow => self.warning,
240 Status::Red => self.danger,
241 Status::Error => self.signal_offline,
242 _ => self.signal_offline,
243 }
244 }
245
246 #[must_use]
248 pub fn connection_color(&self, status: &ConnectionStatus) -> Color {
249 match status {
250 ConnectionStatus::Disconnected => self.signal_offline,
251 ConnectionStatus::Connecting => self.warning,
252 ConnectionStatus::Connected => self.success,
253 ConnectionStatus::Error(_) => self.danger,
254 }
255 }
256
257 #[must_use]
259 pub fn signal_strength_display(&self, rssi: i16) -> (&'static str, Color) {
260 if rssi >= -50 {
261 ("▂▄▆█", self.signal_excellent)
262 } else if rssi >= -60 {
263 ("▂▄▆░", self.signal_good)
264 } else if rssi >= -70 {
265 ("▂▄░░", self.signal_fair)
266 } else if rssi >= -80 {
267 ("▂░░░", self.signal_weak)
268 } else {
269 ("░░░░", self.signal_offline)
270 }
271 }
272
273 #[must_use]
275 pub fn trend_color(&self, diff: i32, threshold: i32) -> Color {
276 if diff > threshold {
277 self.trend_rising
278 } else if diff < -threshold {
279 self.trend_falling
280 } else {
281 self.trend_stable
282 }
283 }
284}
285
286pub const BORDER_TYPE: BorderType = BorderType::Rounded;