1use crate::buffer::CellStyle;
2use crate::geom::Insets;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum Theme {
7 Dark,
8 Light,
9}
10
11thread_local! {
12 pub static CURRENT_THEME: std::cell::RefCell<Theme> = std::cell::RefCell::new(Theme::Dark);
14}
15
16pub fn set_theme(theme: Theme) {
18 CURRENT_THEME.with(|t| *t.borrow_mut() = theme);
19 crate::runtime::FORCE_FULL_PAINT.with(|f| *f.borrow_mut() = true);
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum Color {
26 Black,
28 Red,
29 Green,
30 Yellow,
31 Blue,
32 Magenta,
33 Cyan,
34 White,
35 Gray,
36 LightRed,
37 LightGreen,
38 LightYellow,
39 LightBlue,
40 LightMagenta,
41 LightCyan,
42 DarkGray,
43 Indexed(u8),
45 Rgb(u8, u8, u8),
47}
48
49impl Color {
50 pub fn hex(hex: &str) -> Option<Color> {
52 let s = hex.trim().trim_start_matches('#');
53 if s.is_empty() {
54 return None;
55 }
56 match s.len() {
57 3 => {
58 let chars: Vec<char> = s.chars().collect();
59 let r = u8::from_str_radix(&chars[0].to_string().repeat(2), 16).ok()?;
60 let g = u8::from_str_radix(&chars[1].to_string().repeat(2), 16).ok()?;
61 let b = u8::from_str_radix(&chars[2].to_string().repeat(2), 16).ok()?;
62 Some(Color::Rgb(r, g, b))
63 }
64 6 => {
65 let r = u8::from_str_radix(&s[0..2], 16).ok()?;
66 let g = u8::from_str_radix(&s[2..4], 16).ok()?;
67 let b = u8::from_str_radix(&s[4..6], 16).ok()?;
68 Some(Color::Rgb(r, g, b))
69 }
70 _ => None,
71 }
72 }
73
74 pub fn rgb(r: u8, g: u8, b: u8) -> Color {
75 Color::Rgb(r, g, b)
76 }
77
78 pub fn indexed(i: u8) -> Color {
79 Color::Indexed(i)
80 }
81}
82
83impl From<Color> for crossterm::style::Color {
84 fn from(c: Color) -> Self {
85 let dark = CURRENT_THEME.with(|t| *t.borrow() == Theme::Dark);
86 match c {
87 Color::Rgb(r, g, b) => crossterm::style::Color::Rgb { r, g, b },
88 Color::Indexed(i) => crossterm::style::Color::AnsiValue(i),
89 Color::Black => crossterm::style::Color::Black,
90 Color::Red if dark => crossterm::style::Color::DarkRed,
91 Color::Red => crossterm::style::Color::Red,
92 Color::Green if dark => crossterm::style::Color::DarkGreen,
93 Color::Green => crossterm::style::Color::Green,
94 Color::Yellow if dark => crossterm::style::Color::DarkYellow,
95 Color::Yellow => crossterm::style::Color::Yellow,
96 Color::Blue if dark => crossterm::style::Color::DarkBlue,
97 Color::Blue => crossterm::style::Color::Blue,
98 Color::Magenta if dark => crossterm::style::Color::DarkMagenta,
99 Color::Magenta => crossterm::style::Color::Magenta,
100 Color::Cyan if dark => crossterm::style::Color::DarkCyan,
101 Color::Cyan => crossterm::style::Color::Cyan,
102 Color::White => crossterm::style::Color::Grey,
103 Color::Gray => crossterm::style::Color::DarkGrey,
104 Color::LightRed => crossterm::style::Color::Red,
105 Color::LightGreen => crossterm::style::Color::Green,
106 Color::LightYellow => crossterm::style::Color::Yellow,
107 Color::LightBlue => crossterm::style::Color::Blue,
108 Color::LightMagenta => crossterm::style::Color::Magenta,
109 Color::LightCyan => crossterm::style::Color::Cyan,
110 Color::DarkGray => crossterm::style::Color::DarkGrey,
111 }
112 }
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub enum Length {
118 Auto,
120 Fixed(u16),
122 Percent(u16),
124 Fraction(u16),
126}
127
128impl Default for Length {
129 fn default() -> Self {
130 Length::Auto
131 }
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136pub enum Layout {
137 None,
138 Vertical,
139 Horizontal,
140}
141
142impl Default for Layout {
143 fn default() -> Self {
144 Layout::None
145 }
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
150pub enum TextWrap {
151 #[default]
153 None,
154 Char,
156 Word,
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
162pub enum TextAlign {
163 #[default]
164 Left,
165 Center,
166 Right,
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
171pub enum TextTruncate {
172 #[default]
174 None,
175 Ellipsis,
177}
178
179#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
181pub enum Border {
182 #[default]
183 None,
184 Plain,
185 Rounded,
186 Double,
187}
188
189#[derive(Debug, Clone, PartialEq, Eq)]
191pub struct Style {
192 pub fg: Option<Color>,
194 pub bg: Option<Color>,
195 pub bold: bool,
196 pub italic: bool,
197 pub underline: bool,
198
199 pub width: Length,
201 pub height: Length,
202 pub padding: Insets,
203 pub margin: Insets,
204 pub layout: Layout,
205 pub gap: u16,
206 pub flex_grow: u16,
207 pub flex_shrink: bool,
208 pub border: Border,
209}
210
211impl Default for Style {
212 fn default() -> Self {
213 Self {
214 fg: None,
215 bg: None,
216 bold: false,
217 italic: false,
218 underline: false,
219 width: Length::Auto,
220 height: Length::Auto,
221 padding: Insets::ZERO,
222 margin: Insets::ZERO,
223 layout: Layout::None,
224 gap: 0,
225 flex_grow: 0,
226 flex_shrink: true,
227 border: Border::None,
228 }
229 }
230}
231
232impl Style {
233 pub fn fg(mut self, color: Color) -> Self {
235 self.fg = Some(color);
236 self
237 }
238
239 pub fn bg(mut self, color: Color) -> Self {
241 self.bg = Some(color);
242 self
243 }
244
245 pub fn bold(mut self) -> Self {
247 self.bold = true;
248 self
249 }
250
251 pub fn italic(mut self) -> Self {
253 self.italic = true;
254 self
255 }
256
257 pub fn underline(mut self) -> Self {
259 self.underline = true;
260 self
261 }
262
263 pub fn width(mut self, width: Length) -> Self {
265 self.width = width;
266 self
267 }
268
269 pub fn height(mut self, height: Length) -> Self {
271 self.height = height;
272 self
273 }
274
275 pub fn padding(mut self, value: u16) -> Self {
277 self.padding = Insets::all(value);
278 self
279 }
280
281 pub fn padding_each(mut self, top: u16, right: u16, bottom: u16, left: u16) -> Self {
283 self.padding = Insets { top, right, bottom, left };
284 self
285 }
286
287 pub fn margin(mut self, value: u16) -> Self {
289 self.margin = Insets::all(value);
290 self
291 }
292
293 pub fn layout(mut self, layout: Layout) -> Self {
295 self.layout = layout;
296 self
297 }
298
299 pub fn gap(mut self, gap: u16) -> Self {
301 self.gap = gap;
302 self
303 }
304
305 pub fn flex_grow(mut self, grow: u16) -> Self {
307 self.flex_grow = grow;
308 self
309 }
310
311 pub fn flex_shrink(mut self, shrink: bool) -> Self {
313 self.flex_shrink = shrink;
314 self
315 }
316
317 pub fn border(mut self, border: Border) -> Self {
319 self.border = border;
320 self
321 }
322
323 pub fn into_cell_style(&self) -> CellStyle {
325 CellStyle {
326 fg: self.fg,
327 bg: self.bg,
328 bold: self.bold,
329 italic: self.italic,
330 underline: self.underline,
331 }
332 }
333}