rust_kanban/ui/
theme.rs

1use crate::{
2    app::App,
3    constants::SAMPLE_TEXT,
4    ui::inbuilt_themes::{
5        cyberpunk_theme, default_theme, dracula_theme, light_theme, matrix_theme, metro_theme,
6        midnight_blue_theme, slate_theme,
7    },
8};
9use ratatui::{
10    style::{Color, Modifier, Style},
11    text::Span,
12    widgets::{Cell, Row},
13};
14use serde::{Deserialize, Serialize};
15use strum::{Display, EnumIter, IntoEnumIterator};
16
17#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
18pub struct Theme {
19    pub card_due_default_style: Style,
20    pub card_due_overdue_style: Style,
21    pub card_due_warning_style: Style,
22    pub card_priority_high_style: Style,
23    pub card_priority_low_style: Style,
24    pub card_priority_medium_style: Style,
25    pub card_status_active_style: Style,
26    pub card_status_completed_style: Style,
27    pub card_status_stale_style: Style,
28    pub error_text_style: Style,
29    pub general_style: Style,
30    pub help_key_style: Style,
31    pub help_text_style: Style,
32    pub inactive_text_style: Style,
33    pub keyboard_focus_style: Style,
34    pub list_select_style: Style,
35    pub log_debug_style: Style,
36    pub log_error_style: Style,
37    pub log_info_style: Style,
38    pub log_trace_style: Style,
39    pub log_warn_style: Style,
40    pub mouse_focus_style: Style,
41    pub name: String,
42    pub progress_bar_style: Style,
43}
44
45impl Default for Theme {
46    fn default() -> Self {
47        default_theme()
48    }
49}
50
51impl Theme {
52    pub fn all_default_themes() -> Vec<Theme> {
53        vec![
54            cyberpunk_theme(),
55            default_theme(),
56            dracula_theme(),
57            light_theme(),
58            matrix_theme(),
59            metro_theme(),
60            midnight_blue_theme(),
61            slate_theme(),
62        ]
63    }
64
65    pub fn get_style(&self, theme_enum: ThemeEnum) -> Style {
66        match theme_enum {
67            ThemeEnum::Name => self.general_style,
68            ThemeEnum::General => self.general_style,
69            ThemeEnum::ListSelect => self.list_select_style,
70            ThemeEnum::MouseFocus => self.mouse_focus_style,
71            ThemeEnum::CardDueDefault => self.card_due_default_style,
72            ThemeEnum::CardDueOverdue => self.card_due_overdue_style,
73            ThemeEnum::CardDueWarning => self.card_due_warning_style,
74            ThemeEnum::CardPriorityHigh => self.card_priority_high_style,
75            ThemeEnum::CardPriorityLow => self.card_priority_low_style,
76            ThemeEnum::CardPriorityMedium => self.card_priority_medium_style,
77            ThemeEnum::CardStatusActive => self.card_status_active_style,
78            ThemeEnum::CardStatusCompleted => self.card_status_completed_style,
79            ThemeEnum::CardStatusStale => self.card_status_stale_style,
80            ThemeEnum::ProgressBar => self.progress_bar_style,
81            ThemeEnum::ErrorText => self.error_text_style,
82            ThemeEnum::HelpKey => self.help_key_style,
83            ThemeEnum::HelpText => self.help_text_style,
84            ThemeEnum::InactiveText => self.inactive_text_style,
85            ThemeEnum::KeyboardFocus => self.keyboard_focus_style,
86            ThemeEnum::LogDebug => self.log_debug_style,
87            ThemeEnum::LogError => self.log_error_style,
88            ThemeEnum::LogInfo => self.log_info_style,
89            ThemeEnum::LogTrace => self.log_trace_style,
90            ThemeEnum::LogWarn => self.log_warn_style,
91        }
92    }
93
94    pub fn get_mut_style(&mut self, theme_enum: ThemeEnum) -> &mut Style {
95        match theme_enum {
96            ThemeEnum::Name => &mut self.general_style,
97            ThemeEnum::General => &mut self.general_style,
98            ThemeEnum::ListSelect => &mut self.list_select_style,
99            ThemeEnum::MouseFocus => &mut self.mouse_focus_style,
100            ThemeEnum::CardDueDefault => &mut self.card_due_default_style,
101            ThemeEnum::CardDueOverdue => &mut self.card_due_overdue_style,
102            ThemeEnum::CardDueWarning => &mut self.card_due_warning_style,
103            ThemeEnum::CardPriorityHigh => &mut self.card_priority_high_style,
104            ThemeEnum::CardPriorityLow => &mut self.card_priority_low_style,
105            ThemeEnum::CardPriorityMedium => &mut self.card_priority_medium_style,
106            ThemeEnum::CardStatusActive => &mut self.card_status_active_style,
107            ThemeEnum::CardStatusCompleted => &mut self.card_status_completed_style,
108            ThemeEnum::CardStatusStale => &mut self.card_status_stale_style,
109            ThemeEnum::ProgressBar => &mut self.progress_bar_style,
110            ThemeEnum::ErrorText => &mut self.error_text_style,
111            ThemeEnum::HelpKey => &mut self.help_key_style,
112            ThemeEnum::HelpText => &mut self.help_text_style,
113            ThemeEnum::InactiveText => &mut self.inactive_text_style,
114            ThemeEnum::KeyboardFocus => &mut self.keyboard_focus_style,
115            ThemeEnum::LogDebug => &mut self.log_debug_style,
116            ThemeEnum::LogError => &mut self.log_error_style,
117            ThemeEnum::LogInfo => &mut self.log_info_style,
118            ThemeEnum::LogTrace => &mut self.log_trace_style,
119            ThemeEnum::LogWarn => &mut self.log_warn_style,
120        }
121    }
122
123    pub fn name(&self) -> &str {
124        &self.name
125    }
126
127    fn create_styled_spans(&self, app: &App, is_active: bool, sample_mode: bool) -> Vec<Span> {
128        ThemeEnum::iter()
129            .map(|theme_enum| {
130                let text = if sample_mode {
131                    if theme_enum == ThemeEnum::Name {
132                        self.name.clone()
133                    } else {
134                        SAMPLE_TEXT.to_string()
135                    }
136                } else {
137                    theme_enum.to_string()
138                };
139                let style = if !is_active {
140                    app.current_theme.inactive_text_style
141                } else if sample_mode {
142                    self.get_style(theme_enum)
143                } else {
144                    app.current_theme.general_style
145                };
146                Span::styled(text, style)
147            })
148            .collect::<Vec<Span>>()
149    }
150
151    pub fn to_rows(&self, app: &App, is_active: bool) -> (Vec<Row>, Vec<Row>) {
152        let theme_title_list = self.create_styled_spans(app, is_active, false);
153        let theme_style_list = self.create_styled_spans(app, is_active, true);
154
155        let rows_title = theme_title_list
156            .iter()
157            .map(|span| Row::new(vec![Cell::from(span.clone())]))
158            .collect();
159        let rows_elements = theme_style_list
160            .iter()
161            .map(|span| Row::new(vec![Cell::from(span.clone())]))
162            .collect();
163
164        (rows_title, rows_elements)
165    }
166
167    pub fn update_style(
168        style: &mut Style,
169        fg_color: Option<Color>,
170        bg_color: Option<Color>,
171        modifier: Option<Modifier>,
172    ) {
173        if let Some(fg) = fg_color {
174            style.fg = Some(fg);
175        }
176
177        if let Some(bg) = bg_color {
178            style.bg = Some(bg);
179        }
180
181        if let Some(modifier) = modifier {
182            Self::add_modifier_to_style(style, modifier);
183        }
184    }
185
186    pub fn add_modifier_to_style(style: &mut Style, modifier: Modifier) {
187        style.sub_modifier = style.sub_modifier.difference(modifier);
188        style.add_modifier = style.add_modifier.union(modifier);
189    }
190}
191
192#[derive(EnumIter, Display, PartialEq, Clone, Copy)]
193#[strum(serialize_all = "title_case")]
194pub enum ThemeEnum {
195    Name,
196    General,
197    ListSelect,
198    MouseFocus,
199    CardDueDefault,
200    CardDueOverdue,
201    CardDueWarning,
202    CardPriorityHigh,
203    CardPriorityLow,
204    CardPriorityMedium,
205    CardStatusActive,
206    CardStatusCompleted,
207    CardStatusStale,
208    ProgressBar,
209    ErrorText,
210    HelpKey,
211    HelpText,
212    InactiveText,
213    KeyboardFocus,
214    LogDebug,
215    LogError,
216    LogInfo,
217    LogTrace,
218    LogWarn,
219}