1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13pub enum Color {
14 Reset,
16 Black,
18 Red,
20 Green,
22 Yellow,
24 Blue,
26 Magenta,
28 Cyan,
30 White,
32 Rgb(u8, u8, u8),
34 Indexed(u8),
36}
37
38impl Color {
39 fn to_rgb(self) -> (u8, u8, u8) {
44 match self {
45 Color::Rgb(r, g, b) => (r, g, b),
46 Color::Black => (0, 0, 0),
47 Color::Red => (205, 49, 49),
48 Color::Green => (13, 188, 121),
49 Color::Yellow => (229, 229, 16),
50 Color::Blue => (36, 114, 200),
51 Color::Magenta => (188, 63, 188),
52 Color::Cyan => (17, 168, 205),
53 Color::White => (229, 229, 229),
54 Color::Reset => (0, 0, 0),
55 Color::Indexed(idx) => xterm256_to_rgb(idx),
56 }
57 }
58
59 pub fn luminance(self) -> f32 {
77 let (r, g, b) = self.to_rgb();
78 let rf = r as f32 / 255.0;
79 let gf = g as f32 / 255.0;
80 let bf = b as f32 / 255.0;
81 0.2126 * rf + 0.7152 * gf + 0.0722 * bf
82 }
83
84 pub fn contrast_fg(bg: Color) -> Color {
100 if bg.luminance() > 0.5 {
101 Color::Rgb(0, 0, 0)
102 } else {
103 Color::Rgb(255, 255, 255)
104 }
105 }
106
107 pub fn blend(self, other: Color, alpha: f32) -> Color {
123 let alpha = alpha.clamp(0.0, 1.0);
124 let (r1, g1, b1) = self.to_rgb();
125 let (r2, g2, b2) = other.to_rgb();
126 let r = (r1 as f32 * alpha + r2 as f32 * (1.0 - alpha)) as u8;
127 let g = (g1 as f32 * alpha + g2 as f32 * (1.0 - alpha)) as u8;
128 let b = (b1 as f32 * alpha + b2 as f32 * (1.0 - alpha)) as u8;
129 Color::Rgb(r, g, b)
130 }
131
132 pub fn lighten(self, amount: f32) -> Color {
137 Color::Rgb(255, 255, 255).blend(self, 1.0 - amount.clamp(0.0, 1.0))
138 }
139
140 pub fn darken(self, amount: f32) -> Color {
145 Color::Rgb(0, 0, 0).blend(self, 1.0 - amount.clamp(0.0, 1.0))
146 }
147
148 pub fn downsampled(self, depth: ColorDepth) -> Color {
156 match depth {
157 ColorDepth::TrueColor => self,
158 ColorDepth::EightBit => match self {
159 Color::Rgb(r, g, b) => Color::Indexed(rgb_to_ansi256(r, g, b)),
160 other => other,
161 },
162 ColorDepth::Basic => match self {
163 Color::Rgb(r, g, b) => rgb_to_ansi16(r, g, b),
164 Color::Indexed(i) => {
165 let (r, g, b) = xterm256_to_rgb(i);
166 rgb_to_ansi16(r, g, b)
167 }
168 other => other,
169 },
170 }
171 }
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
180#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
181pub enum ColorDepth {
182 TrueColor,
184 EightBit,
186 Basic,
188}
189
190impl ColorDepth {
191 pub fn detect() -> Self {
196 if let Ok(ct) = std::env::var("COLORTERM") {
197 let ct = ct.to_lowercase();
198 if ct == "truecolor" || ct == "24bit" {
199 return Self::TrueColor;
200 }
201 }
202 if let Ok(term) = std::env::var("TERM") {
203 if term.contains("256color") {
204 return Self::EightBit;
205 }
206 }
207 Self::Basic
208 }
209}
210
211fn rgb_to_ansi256(r: u8, g: u8, b: u8) -> u8 {
212 if r == g && g == b {
213 if r < 8 {
214 return 16;
215 }
216 if r > 248 {
217 return 231;
218 }
219 return 232 + (((r as u16 - 8) * 24 / 240) as u8);
220 }
221
222 let ri = if r < 48 {
223 0
224 } else {
225 ((r as u16 - 35) / 40) as u8
226 };
227 let gi = if g < 48 {
228 0
229 } else {
230 ((g as u16 - 35) / 40) as u8
231 };
232 let bi = if b < 48 {
233 0
234 } else {
235 ((b as u16 - 35) / 40) as u8
236 };
237 16 + 36 * ri.min(5) + 6 * gi.min(5) + bi.min(5)
238}
239
240fn rgb_to_ansi16(r: u8, g: u8, b: u8) -> Color {
241 let lum =
242 0.2126 * (r as f32 / 255.0) + 0.7152 * (g as f32 / 255.0) + 0.0722 * (b as f32 / 255.0);
243
244 let max = r.max(g).max(b);
245 let min = r.min(g).min(b);
246 let saturation = if max == 0 {
247 0.0
248 } else {
249 (max - min) as f32 / max as f32
250 };
251
252 if saturation < 0.2 {
253 return if lum < 0.15 {
254 Color::Black
255 } else {
256 Color::White
257 };
258 }
259
260 let rf = r as f32;
261 let gf = g as f32;
262 let bf = b as f32;
263
264 if rf >= gf && rf >= bf {
265 if gf > bf * 1.5 {
266 Color::Yellow
267 } else if bf > gf * 1.5 {
268 Color::Magenta
269 } else {
270 Color::Red
271 }
272 } else if gf >= rf && gf >= bf {
273 if bf > rf * 1.5 {
274 Color::Cyan
275 } else {
276 Color::Green
277 }
278 } else if rf > gf * 1.5 {
279 Color::Magenta
280 } else if gf > rf * 1.5 {
281 Color::Cyan
282 } else {
283 Color::Blue
284 }
285}
286
287fn xterm256_to_rgb(idx: u8) -> (u8, u8, u8) {
288 match idx {
289 0 => (0, 0, 0),
290 1 => (128, 0, 0),
291 2 => (0, 128, 0),
292 3 => (128, 128, 0),
293 4 => (0, 0, 128),
294 5 => (128, 0, 128),
295 6 => (0, 128, 128),
296 7 => (192, 192, 192),
297 8 => (128, 128, 128),
298 9 => (255, 0, 0),
299 10 => (0, 255, 0),
300 11 => (255, 255, 0),
301 12 => (0, 0, 255),
302 13 => (255, 0, 255),
303 14 => (0, 255, 255),
304 15 => (255, 255, 255),
305 16..=231 => {
306 let n = idx - 16;
307 let b_idx = n % 6;
308 let g_idx = (n / 6) % 6;
309 let r_idx = n / 36;
310 let to_val = |i: u8| if i == 0 { 0u8 } else { 55 + 40 * i };
311 (to_val(r_idx), to_val(g_idx), to_val(b_idx))
312 }
313 232..=255 => {
314 let v = 8 + 10 * (idx - 232);
315 (v, v, v)
316 }
317 }
318}
319
320#[derive(Debug, Clone, Copy)]
326#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
327pub struct Theme {
328 pub primary: Color,
330 pub secondary: Color,
332 pub accent: Color,
334 pub text: Color,
336 pub text_dim: Color,
338 pub border: Color,
340 pub bg: Color,
342 pub success: Color,
344 pub warning: Color,
346 pub error: Color,
348 pub selected_bg: Color,
350 pub selected_fg: Color,
352 pub surface: Color,
354 pub surface_hover: Color,
359 pub surface_text: Color,
365}
366
367impl Theme {
368 pub fn dark() -> Self {
370 Self {
371 primary: Color::Cyan,
372 secondary: Color::Blue,
373 accent: Color::Magenta,
374 text: Color::White,
375 text_dim: Color::Indexed(245),
376 border: Color::Indexed(240),
377 bg: Color::Reset,
378 success: Color::Green,
379 warning: Color::Yellow,
380 error: Color::Red,
381 selected_bg: Color::Cyan,
382 selected_fg: Color::Black,
383 surface: Color::Indexed(236),
384 surface_hover: Color::Indexed(238),
385 surface_text: Color::Indexed(250),
386 }
387 }
388
389 pub fn light() -> Self {
391 Self {
392 primary: Color::Blue,
393 secondary: Color::Cyan,
394 accent: Color::Magenta,
395 text: Color::Black,
396 text_dim: Color::Indexed(240),
397 border: Color::Indexed(245),
398 bg: Color::Reset,
399 success: Color::Green,
400 warning: Color::Yellow,
401 error: Color::Red,
402 selected_bg: Color::Blue,
403 selected_fg: Color::White,
404 surface: Color::Indexed(254),
405 surface_hover: Color::Indexed(252),
406 surface_text: Color::Indexed(238),
407 }
408 }
409
410 pub fn dracula() -> Self {
412 Self {
413 primary: Color::Rgb(189, 147, 249),
414 secondary: Color::Rgb(139, 233, 253),
415 accent: Color::Rgb(255, 121, 198),
416 text: Color::Rgb(248, 248, 242),
417 text_dim: Color::Rgb(98, 114, 164),
418 border: Color::Rgb(68, 71, 90),
419 bg: Color::Rgb(40, 42, 54),
420 success: Color::Rgb(80, 250, 123),
421 warning: Color::Rgb(241, 250, 140),
422 error: Color::Rgb(255, 85, 85),
423 selected_bg: Color::Rgb(189, 147, 249),
424 selected_fg: Color::Rgb(40, 42, 54),
425 surface: Color::Rgb(68, 71, 90),
426 surface_hover: Color::Rgb(98, 100, 120),
427 surface_text: Color::Rgb(191, 194, 210),
428 }
429 }
430
431 pub fn catppuccin() -> Self {
433 Self {
434 primary: Color::Rgb(180, 190, 254),
435 secondary: Color::Rgb(137, 180, 250),
436 accent: Color::Rgb(245, 194, 231),
437 text: Color::Rgb(205, 214, 244),
438 text_dim: Color::Rgb(127, 132, 156),
439 border: Color::Rgb(88, 91, 112),
440 bg: Color::Rgb(30, 30, 46),
441 success: Color::Rgb(166, 227, 161),
442 warning: Color::Rgb(249, 226, 175),
443 error: Color::Rgb(243, 139, 168),
444 selected_bg: Color::Rgb(180, 190, 254),
445 selected_fg: Color::Rgb(30, 30, 46),
446 surface: Color::Rgb(49, 50, 68),
447 surface_hover: Color::Rgb(69, 71, 90),
448 surface_text: Color::Rgb(166, 173, 200),
449 }
450 }
451
452 pub fn nord() -> Self {
454 Self {
455 primary: Color::Rgb(136, 192, 208),
456 secondary: Color::Rgb(129, 161, 193),
457 accent: Color::Rgb(180, 142, 173),
458 text: Color::Rgb(236, 239, 244),
459 text_dim: Color::Rgb(76, 86, 106),
460 border: Color::Rgb(76, 86, 106),
461 bg: Color::Rgb(46, 52, 64),
462 success: Color::Rgb(163, 190, 140),
463 warning: Color::Rgb(235, 203, 139),
464 error: Color::Rgb(191, 97, 106),
465 selected_bg: Color::Rgb(136, 192, 208),
466 selected_fg: Color::Rgb(46, 52, 64),
467 surface: Color::Rgb(59, 66, 82),
468 surface_hover: Color::Rgb(67, 76, 94),
469 surface_text: Color::Rgb(216, 222, 233),
470 }
471 }
472
473 pub fn solarized_dark() -> Self {
475 Self {
476 primary: Color::Rgb(38, 139, 210),
477 secondary: Color::Rgb(42, 161, 152),
478 accent: Color::Rgb(211, 54, 130),
479 text: Color::Rgb(131, 148, 150),
480 text_dim: Color::Rgb(88, 110, 117),
481 border: Color::Rgb(88, 110, 117),
482 bg: Color::Rgb(0, 43, 54),
483 success: Color::Rgb(133, 153, 0),
484 warning: Color::Rgb(181, 137, 0),
485 error: Color::Rgb(220, 50, 47),
486 selected_bg: Color::Rgb(38, 139, 210),
487 selected_fg: Color::Rgb(253, 246, 227),
488 surface: Color::Rgb(7, 54, 66),
489 surface_hover: Color::Rgb(23, 72, 85),
490 surface_text: Color::Rgb(147, 161, 161),
491 }
492 }
493
494 pub fn tokyo_night() -> Self {
496 Self {
497 primary: Color::Rgb(122, 162, 247),
498 secondary: Color::Rgb(125, 207, 255),
499 accent: Color::Rgb(187, 154, 247),
500 text: Color::Rgb(169, 177, 214),
501 text_dim: Color::Rgb(86, 95, 137),
502 border: Color::Rgb(54, 58, 79),
503 bg: Color::Rgb(26, 27, 38),
504 success: Color::Rgb(158, 206, 106),
505 warning: Color::Rgb(224, 175, 104),
506 error: Color::Rgb(247, 118, 142),
507 selected_bg: Color::Rgb(122, 162, 247),
508 selected_fg: Color::Rgb(26, 27, 38),
509 surface: Color::Rgb(36, 40, 59),
510 surface_hover: Color::Rgb(41, 46, 66),
511 surface_text: Color::Rgb(192, 202, 245),
512 }
513 }
514}
515
516impl Default for Theme {
517 fn default() -> Self {
518 Self::dark()
519 }
520}
521
522#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
527pub enum Breakpoint {
528 Xs,
530 Sm,
532 Md,
534 Lg,
536 Xl,
538}
539
540#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
545#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
546pub enum Border {
547 Single,
549 Double,
551 Rounded,
553 Thick,
555 Dashed,
557 DashedThick,
559}
560
561#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
566pub struct BorderChars {
567 pub tl: char,
569 pub tr: char,
571 pub bl: char,
573 pub br: char,
575 pub h: char,
577 pub v: char,
579}
580
581#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
583#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
584pub struct BorderSides {
585 pub top: bool,
586 pub right: bool,
587 pub bottom: bool,
588 pub left: bool,
589}
590
591impl BorderSides {
592 pub const fn all() -> Self {
593 Self {
594 top: true,
595 right: true,
596 bottom: true,
597 left: true,
598 }
599 }
600
601 pub const fn none() -> Self {
602 Self {
603 top: false,
604 right: false,
605 bottom: false,
606 left: false,
607 }
608 }
609
610 pub const fn horizontal() -> Self {
611 Self {
612 top: true,
613 right: false,
614 bottom: true,
615 left: false,
616 }
617 }
618
619 pub const fn vertical() -> Self {
620 Self {
621 top: false,
622 right: true,
623 bottom: false,
624 left: true,
625 }
626 }
627
628 pub fn has_horizontal(&self) -> bool {
629 self.top || self.bottom
630 }
631
632 pub fn has_vertical(&self) -> bool {
633 self.left || self.right
634 }
635}
636
637impl Default for BorderSides {
638 fn default() -> Self {
639 Self::all()
640 }
641}
642
643impl Border {
644 pub const fn chars(self) -> BorderChars {
646 match self {
647 Self::Single => BorderChars {
648 tl: '┌',
649 tr: '┐',
650 bl: '└',
651 br: '┘',
652 h: '─',
653 v: '│',
654 },
655 Self::Double => BorderChars {
656 tl: '╔',
657 tr: '╗',
658 bl: '╚',
659 br: '╝',
660 h: '═',
661 v: '║',
662 },
663 Self::Rounded => BorderChars {
664 tl: '╭',
665 tr: '╮',
666 bl: '╰',
667 br: '╯',
668 h: '─',
669 v: '│',
670 },
671 Self::Thick => BorderChars {
672 tl: '┏',
673 tr: '┓',
674 bl: '┗',
675 br: '┛',
676 h: '━',
677 v: '┃',
678 },
679 Self::Dashed => BorderChars {
680 tl: '┌',
681 tr: '┐',
682 bl: '└',
683 br: '┘',
684 h: '┄',
685 v: '┆',
686 },
687 Self::DashedThick => BorderChars {
688 tl: '┏',
689 tr: '┓',
690 bl: '┗',
691 br: '┛',
692 h: '┅',
693 v: '┇',
694 },
695 }
696 }
697}
698
699#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
704#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
705pub struct Padding {
706 pub top: u32,
708 pub right: u32,
710 pub bottom: u32,
712 pub left: u32,
714}
715
716impl Padding {
717 pub const fn all(v: u32) -> Self {
719 Self::new(v, v, v, v)
720 }
721
722 pub const fn xy(x: u32, y: u32) -> Self {
724 Self::new(y, x, y, x)
725 }
726
727 pub const fn new(top: u32, right: u32, bottom: u32, left: u32) -> Self {
729 Self {
730 top,
731 right,
732 bottom,
733 left,
734 }
735 }
736
737 pub const fn horizontal(self) -> u32 {
739 self.left + self.right
740 }
741
742 pub const fn vertical(self) -> u32 {
744 self.top + self.bottom
745 }
746}
747
748#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
753#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
754pub struct Margin {
755 pub top: u32,
757 pub right: u32,
759 pub bottom: u32,
761 pub left: u32,
763}
764
765impl Margin {
766 pub const fn all(v: u32) -> Self {
768 Self::new(v, v, v, v)
769 }
770
771 pub const fn xy(x: u32, y: u32) -> Self {
773 Self::new(y, x, y, x)
774 }
775
776 pub const fn new(top: u32, right: u32, bottom: u32, left: u32) -> Self {
778 Self {
779 top,
780 right,
781 bottom,
782 left,
783 }
784 }
785
786 pub const fn horizontal(self) -> u32 {
788 self.left + self.right
789 }
790
791 pub const fn vertical(self) -> u32 {
793 self.top + self.bottom
794 }
795}
796
797#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
810#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
811#[must_use = "configure constraints using the returned value"]
812pub struct Constraints {
813 pub min_width: Option<u32>,
815 pub max_width: Option<u32>,
817 pub min_height: Option<u32>,
819 pub max_height: Option<u32>,
821 pub width_pct: Option<u8>,
823 pub height_pct: Option<u8>,
825}
826
827impl Constraints {
828 pub const fn min_w(mut self, min_width: u32) -> Self {
830 self.min_width = Some(min_width);
831 self
832 }
833
834 pub const fn max_w(mut self, max_width: u32) -> Self {
836 self.max_width = Some(max_width);
837 self
838 }
839
840 pub const fn min_h(mut self, min_height: u32) -> Self {
842 self.min_height = Some(min_height);
843 self
844 }
845
846 pub const fn max_h(mut self, max_height: u32) -> Self {
848 self.max_height = Some(max_height);
849 self
850 }
851
852 pub const fn w_pct(mut self, pct: u8) -> Self {
854 self.width_pct = Some(pct);
855 self
856 }
857
858 pub const fn h_pct(mut self, pct: u8) -> Self {
860 self.height_pct = Some(pct);
861 self
862 }
863}
864
865#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
871#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
872pub enum Align {
873 #[default]
875 Start,
876 Center,
878 End,
880}
881
882#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
891#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
892pub enum Justify {
893 #[default]
895 Start,
896 Center,
898 End,
900 SpaceBetween,
902 SpaceAround,
904 SpaceEvenly,
906}
907
908#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
913#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
914#[cfg_attr(feature = "serde", serde(transparent))]
915pub struct Modifiers(pub u8);
916
917impl Modifiers {
918 pub const NONE: Self = Self(0);
920 pub const BOLD: Self = Self(1 << 0);
922 pub const DIM: Self = Self(1 << 1);
924 pub const ITALIC: Self = Self(1 << 2);
926 pub const UNDERLINE: Self = Self(1 << 3);
928 pub const REVERSED: Self = Self(1 << 4);
930 pub const STRIKETHROUGH: Self = Self(1 << 5);
932
933 #[inline]
935 pub fn contains(self, other: Self) -> bool {
936 (self.0 & other.0) == other.0
937 }
938
939 #[inline]
941 pub fn insert(&mut self, other: Self) {
942 self.0 |= other.0;
943 }
944
945 #[inline]
947 pub fn is_empty(self) -> bool {
948 self.0 == 0
949 }
950}
951
952impl std::ops::BitOr for Modifiers {
953 type Output = Self;
954 #[inline]
955 fn bitor(self, rhs: Self) -> Self {
956 Self(self.0 | rhs.0)
957 }
958}
959
960impl std::ops::BitOrAssign for Modifiers {
961 #[inline]
962 fn bitor_assign(&mut self, rhs: Self) {
963 self.0 |= rhs.0;
964 }
965}
966
967#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
981#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
982#[must_use = "build and pass the returned Style value"]
983pub struct Style {
984 pub fg: Option<Color>,
986 pub bg: Option<Color>,
988 pub modifiers: Modifiers,
990}
991
992impl Style {
993 pub const fn new() -> Self {
995 Self {
996 fg: None,
997 bg: None,
998 modifiers: Modifiers::NONE,
999 }
1000 }
1001
1002 pub const fn fg(mut self, color: Color) -> Self {
1004 self.fg = Some(color);
1005 self
1006 }
1007
1008 pub const fn bg(mut self, color: Color) -> Self {
1010 self.bg = Some(color);
1011 self
1012 }
1013
1014 pub fn bold(mut self) -> Self {
1016 self.modifiers |= Modifiers::BOLD;
1017 self
1018 }
1019
1020 pub fn dim(mut self) -> Self {
1022 self.modifiers |= Modifiers::DIM;
1023 self
1024 }
1025
1026 pub fn italic(mut self) -> Self {
1028 self.modifiers |= Modifiers::ITALIC;
1029 self
1030 }
1031
1032 pub fn underline(mut self) -> Self {
1034 self.modifiers |= Modifiers::UNDERLINE;
1035 self
1036 }
1037
1038 pub fn reversed(mut self) -> Self {
1040 self.modifiers |= Modifiers::REVERSED;
1041 self
1042 }
1043
1044 pub fn strikethrough(mut self) -> Self {
1046 self.modifiers |= Modifiers::STRIKETHROUGH;
1047 self
1048 }
1049}