1use crate::theme::Theme;
4use ratatui::{
5 buffer::Buffer,
6 layout::Rect,
7 style::{Color, Modifier, Style},
8};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum Status {
13 Success,
15 Warning,
17 Error,
19 Info,
21}
22
23impl Status {
24 pub const ALL: [Status; 4] = [
26 Status::Success,
27 Status::Warning,
28 Status::Error,
29 Status::Info,
30 ];
31
32 #[must_use]
34 pub fn label(self) -> &'static str {
35 match self {
36 Status::Success => "success",
37 Status::Warning => "warning",
38 Status::Error => "error",
39 Status::Info => "info",
40 }
41 }
42}
43
44impl Theme {
45 #[must_use]
47 pub fn style(&self) -> Style {
48 let s = Style::default().fg(self.foreground);
49 match self.background {
50 Some(bg) => s.bg(bg),
51 None => s,
52 }
53 }
54
55 #[must_use]
57 pub fn base_style(&self) -> Style {
58 self.style()
59 }
60
61 #[must_use]
63 pub fn surface_style(&self) -> Style {
64 let s = Style::default().fg(self.foreground);
65 match self.surface.or(self.background) {
66 Some(bg) => s.bg(bg),
67 None => s,
68 }
69 }
70
71 #[must_use]
73 pub fn muted_style(&self) -> Style {
74 Style::default().fg(self.muted)
75 }
76
77 #[must_use]
79 pub fn border_style(&self, focused: bool) -> Style {
80 if focused {
81 Style::default()
82 .fg(self.border_focused)
83 .add_modifier(Modifier::BOLD)
84 } else {
85 Style::default().fg(self.border)
86 }
87 }
88
89 #[must_use]
91 pub fn title_style(&self) -> Style {
92 Style::default().fg(self.title).add_modifier(Modifier::BOLD)
93 }
94
95 #[must_use]
97 pub fn selected_style(&self) -> Style {
98 Style::default().fg(self.selection_fg).bg(self.selection_bg)
99 }
100
101 #[must_use]
103 pub fn disabled_style(&self) -> Style {
104 Style::default().fg(self.disabled)
105 }
106
107 #[must_use]
109 pub fn highlight_style(&self) -> Style {
110 Style::default()
111 .fg(self.highlight)
112 .add_modifier(Modifier::BOLD)
113 }
114
115 #[must_use]
117 pub fn status_color(&self, status: Status) -> Color {
118 match status {
119 Status::Success => self.success,
120 Status::Warning => self.warning,
121 Status::Error => self.error,
122 Status::Info => self.info,
123 }
124 }
125
126 #[must_use]
128 pub fn status_style(&self, status: Status) -> Style {
129 Style::default().fg(self.status_color(status))
130 }
131
132 #[must_use]
134 pub fn axis_style(&self) -> Style {
135 Style::default().fg(self.axis)
136 }
137
138 #[must_use]
140 pub fn label_style(&self) -> Style {
141 self.muted_style()
142 }
143
144 #[must_use]
146 pub fn grid_style(&self) -> Style {
147 Style::default().fg(self.grid)
148 }
149
150 #[must_use]
152 pub fn tooltip_style(&self) -> Style {
153 Style::default().fg(self.tooltip_fg).bg(self.tooltip_bg)
154 }
155
156 pub fn fill(&self, area: Rect, buf: &mut Buffer) {
158 if let Some(bg) = self.background {
159 buf.set_style(area.intersection(buf.area), Style::default().bg(bg));
160 }
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn style_has_fg_and_bg() {
170 let t = Theme::dracula();
171 let s = t.style();
172 assert_eq!(s.fg, Some(t.foreground));
173 assert_eq!(s.bg, t.background);
174 assert_eq!(Theme::default().style().bg, None);
175 assert_eq!(t.base_style(), s);
176 }
177
178 #[test]
179 fn surface_style_falls_back() {
180 let t = Theme::dracula();
181 assert_eq!(t.surface_style().bg, t.surface);
182 let t = t.surface(None);
183 assert_eq!(t.surface_style().bg, t.background);
184 }
185
186 #[test]
187 fn border_focus_differs() {
188 let t = Theme::nord();
189 assert_eq!(t.border_style(false).fg, Some(t.border));
190 assert_eq!(t.border_style(true).fg, Some(t.border_focused));
191 assert!(t.border_style(true).add_modifier.contains(Modifier::BOLD));
192 }
193
194 #[test]
195 fn selected_uses_selection_colors() {
196 let t = Theme::gruvbox();
197 let s = t.selected_style();
198 assert_eq!((s.fg, s.bg), (Some(t.selection_fg), Some(t.selection_bg)));
199 }
200
201 #[test]
202 fn status_maps_each_variant() {
203 let t = Theme::one_dark();
204 assert_eq!(t.status_color(Status::Success), t.success);
205 assert_eq!(t.status_color(Status::Warning), t.warning);
206 assert_eq!(t.status_color(Status::Error), t.error);
207 assert_eq!(t.status_color(Status::Info), t.info);
208 assert_eq!(t.status_style(Status::Error).fg, Some(t.error));
209 }
210
211 #[test]
212 fn status_labels_unique() {
213 let mut l: Vec<_> = Status::ALL.iter().map(|s| s.label()).collect();
214 l.sort_unstable();
215 l.dedup();
216 assert_eq!(l.len(), 4);
217 }
218
219 #[test]
220 fn misc_styles() {
221 let t = Theme::monokai();
222 assert_eq!(t.muted_style().fg, Some(t.muted));
223 assert_eq!(t.label_style(), t.muted_style());
224 assert_eq!(t.disabled_style().fg, Some(t.disabled));
225 assert_eq!(t.axis_style().fg, Some(t.axis));
226 assert_eq!(t.grid_style().fg, Some(t.grid));
227 assert_eq!(t.highlight_style().fg, Some(t.highlight));
228 assert_eq!(t.tooltip_style().bg, Some(t.tooltip_bg));
229 assert!(t.title_style().add_modifier.contains(Modifier::BOLD));
230 }
231
232 #[test]
233 fn fill_paints_background_within_buffer() {
234 let t = Theme::dracula();
235 let mut buf = Buffer::empty(Rect::new(0, 0, 4, 2));
236 t.fill(Rect::new(0, 0, 10, 10), &mut buf);
237 assert_eq!(buf[(3, 1)].bg, t.background.unwrap());
238 let mut buf = Buffer::empty(Rect::new(0, 0, 4, 2));
239 Theme::default().fill(buf.area, &mut buf);
240 assert_eq!(buf[(0, 0)].bg, Color::Reset);
241 }
242}