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 builder() -> ThemeBuilder {
423 ThemeBuilder {
424 primary: None,
425 secondary: None,
426 accent: None,
427 text: None,
428 text_dim: None,
429 border: None,
430 bg: None,
431 success: None,
432 warning: None,
433 error: None,
434 selected_bg: None,
435 selected_fg: None,
436 surface: None,
437 surface_hover: None,
438 surface_text: None,
439 }
440 }
441
442 pub fn dracula() -> Self {
444 Self {
445 primary: Color::Rgb(189, 147, 249),
446 secondary: Color::Rgb(139, 233, 253),
447 accent: Color::Rgb(255, 121, 198),
448 text: Color::Rgb(248, 248, 242),
449 text_dim: Color::Rgb(98, 114, 164),
450 border: Color::Rgb(68, 71, 90),
451 bg: Color::Rgb(40, 42, 54),
452 success: Color::Rgb(80, 250, 123),
453 warning: Color::Rgb(241, 250, 140),
454 error: Color::Rgb(255, 85, 85),
455 selected_bg: Color::Rgb(189, 147, 249),
456 selected_fg: Color::Rgb(40, 42, 54),
457 surface: Color::Rgb(68, 71, 90),
458 surface_hover: Color::Rgb(98, 100, 120),
459 surface_text: Color::Rgb(191, 194, 210),
460 }
461 }
462
463 pub fn catppuccin() -> Self {
465 Self {
466 primary: Color::Rgb(180, 190, 254),
467 secondary: Color::Rgb(137, 180, 250),
468 accent: Color::Rgb(245, 194, 231),
469 text: Color::Rgb(205, 214, 244),
470 text_dim: Color::Rgb(127, 132, 156),
471 border: Color::Rgb(88, 91, 112),
472 bg: Color::Rgb(30, 30, 46),
473 success: Color::Rgb(166, 227, 161),
474 warning: Color::Rgb(249, 226, 175),
475 error: Color::Rgb(243, 139, 168),
476 selected_bg: Color::Rgb(180, 190, 254),
477 selected_fg: Color::Rgb(30, 30, 46),
478 surface: Color::Rgb(49, 50, 68),
479 surface_hover: Color::Rgb(69, 71, 90),
480 surface_text: Color::Rgb(166, 173, 200),
481 }
482 }
483
484 pub fn nord() -> Self {
486 Self {
487 primary: Color::Rgb(136, 192, 208),
488 secondary: Color::Rgb(129, 161, 193),
489 accent: Color::Rgb(180, 142, 173),
490 text: Color::Rgb(236, 239, 244),
491 text_dim: Color::Rgb(76, 86, 106),
492 border: Color::Rgb(76, 86, 106),
493 bg: Color::Rgb(46, 52, 64),
494 success: Color::Rgb(163, 190, 140),
495 warning: Color::Rgb(235, 203, 139),
496 error: Color::Rgb(191, 97, 106),
497 selected_bg: Color::Rgb(136, 192, 208),
498 selected_fg: Color::Rgb(46, 52, 64),
499 surface: Color::Rgb(59, 66, 82),
500 surface_hover: Color::Rgb(67, 76, 94),
501 surface_text: Color::Rgb(216, 222, 233),
502 }
503 }
504
505 pub fn solarized_dark() -> Self {
507 Self {
508 primary: Color::Rgb(38, 139, 210),
509 secondary: Color::Rgb(42, 161, 152),
510 accent: Color::Rgb(211, 54, 130),
511 text: Color::Rgb(131, 148, 150),
512 text_dim: Color::Rgb(88, 110, 117),
513 border: Color::Rgb(88, 110, 117),
514 bg: Color::Rgb(0, 43, 54),
515 success: Color::Rgb(133, 153, 0),
516 warning: Color::Rgb(181, 137, 0),
517 error: Color::Rgb(220, 50, 47),
518 selected_bg: Color::Rgb(38, 139, 210),
519 selected_fg: Color::Rgb(253, 246, 227),
520 surface: Color::Rgb(7, 54, 66),
521 surface_hover: Color::Rgb(23, 72, 85),
522 surface_text: Color::Rgb(147, 161, 161),
523 }
524 }
525
526 pub fn tokyo_night() -> Self {
528 Self {
529 primary: Color::Rgb(122, 162, 247),
530 secondary: Color::Rgb(125, 207, 255),
531 accent: Color::Rgb(187, 154, 247),
532 text: Color::Rgb(169, 177, 214),
533 text_dim: Color::Rgb(86, 95, 137),
534 border: Color::Rgb(54, 58, 79),
535 bg: Color::Rgb(26, 27, 38),
536 success: Color::Rgb(158, 206, 106),
537 warning: Color::Rgb(224, 175, 104),
538 error: Color::Rgb(247, 118, 142),
539 selected_bg: Color::Rgb(122, 162, 247),
540 selected_fg: Color::Rgb(26, 27, 38),
541 surface: Color::Rgb(36, 40, 59),
542 surface_hover: Color::Rgb(41, 46, 66),
543 surface_text: Color::Rgb(192, 202, 245),
544 }
545 }
546}
547
548pub struct ThemeBuilder {
550 primary: Option<Color>,
551 secondary: Option<Color>,
552 accent: Option<Color>,
553 text: Option<Color>,
554 text_dim: Option<Color>,
555 border: Option<Color>,
556 bg: Option<Color>,
557 success: Option<Color>,
558 warning: Option<Color>,
559 error: Option<Color>,
560 selected_bg: Option<Color>,
561 selected_fg: Option<Color>,
562 surface: Option<Color>,
563 surface_hover: Option<Color>,
564 surface_text: Option<Color>,
565}
566
567impl ThemeBuilder {
568 pub fn primary(mut self, color: Color) -> Self {
569 self.primary = Some(color);
570 self
571 }
572
573 pub fn secondary(mut self, color: Color) -> Self {
574 self.secondary = Some(color);
575 self
576 }
577
578 pub fn accent(mut self, color: Color) -> Self {
579 self.accent = Some(color);
580 self
581 }
582
583 pub fn text(mut self, color: Color) -> Self {
584 self.text = Some(color);
585 self
586 }
587
588 pub fn text_dim(mut self, color: Color) -> Self {
589 self.text_dim = Some(color);
590 self
591 }
592
593 pub fn border(mut self, color: Color) -> Self {
594 self.border = Some(color);
595 self
596 }
597
598 pub fn bg(mut self, color: Color) -> Self {
599 self.bg = Some(color);
600 self
601 }
602
603 pub fn success(mut self, color: Color) -> Self {
604 self.success = Some(color);
605 self
606 }
607
608 pub fn warning(mut self, color: Color) -> Self {
609 self.warning = Some(color);
610 self
611 }
612
613 pub fn error(mut self, color: Color) -> Self {
614 self.error = Some(color);
615 self
616 }
617
618 pub fn selected_bg(mut self, color: Color) -> Self {
619 self.selected_bg = Some(color);
620 self
621 }
622
623 pub fn selected_fg(mut self, color: Color) -> Self {
624 self.selected_fg = Some(color);
625 self
626 }
627
628 pub fn surface(mut self, color: Color) -> Self {
629 self.surface = Some(color);
630 self
631 }
632
633 pub fn surface_hover(mut self, color: Color) -> Self {
634 self.surface_hover = Some(color);
635 self
636 }
637
638 pub fn surface_text(mut self, color: Color) -> Self {
639 self.surface_text = Some(color);
640 self
641 }
642
643 pub fn build(self) -> Theme {
644 let defaults = Theme::dark();
645 Theme {
646 primary: self.primary.unwrap_or(defaults.primary),
647 secondary: self.secondary.unwrap_or(defaults.secondary),
648 accent: self.accent.unwrap_or(defaults.accent),
649 text: self.text.unwrap_or(defaults.text),
650 text_dim: self.text_dim.unwrap_or(defaults.text_dim),
651 border: self.border.unwrap_or(defaults.border),
652 bg: self.bg.unwrap_or(defaults.bg),
653 success: self.success.unwrap_or(defaults.success),
654 warning: self.warning.unwrap_or(defaults.warning),
655 error: self.error.unwrap_or(defaults.error),
656 selected_bg: self.selected_bg.unwrap_or(defaults.selected_bg),
657 selected_fg: self.selected_fg.unwrap_or(defaults.selected_fg),
658 surface: self.surface.unwrap_or(defaults.surface),
659 surface_hover: self.surface_hover.unwrap_or(defaults.surface_hover),
660 surface_text: self.surface_text.unwrap_or(defaults.surface_text),
661 }
662 }
663}
664
665impl Default for Theme {
666 fn default() -> Self {
667 Self::dark()
668 }
669}
670
671#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
676pub enum Breakpoint {
677 Xs,
679 Sm,
681 Md,
683 Lg,
685 Xl,
687}
688
689#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
694#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
695pub enum Border {
696 Single,
698 Double,
700 Rounded,
702 Thick,
704 Dashed,
706 DashedThick,
708}
709
710#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
715pub struct BorderChars {
716 pub tl: char,
718 pub tr: char,
720 pub bl: char,
722 pub br: char,
724 pub h: char,
726 pub v: char,
728}
729
730#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
732#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
733pub struct BorderSides {
734 pub top: bool,
735 pub right: bool,
736 pub bottom: bool,
737 pub left: bool,
738}
739
740impl BorderSides {
741 pub const fn all() -> Self {
742 Self {
743 top: true,
744 right: true,
745 bottom: true,
746 left: true,
747 }
748 }
749
750 pub const fn none() -> Self {
751 Self {
752 top: false,
753 right: false,
754 bottom: false,
755 left: false,
756 }
757 }
758
759 pub const fn horizontal() -> Self {
760 Self {
761 top: true,
762 right: false,
763 bottom: true,
764 left: false,
765 }
766 }
767
768 pub const fn vertical() -> Self {
769 Self {
770 top: false,
771 right: true,
772 bottom: false,
773 left: true,
774 }
775 }
776
777 pub fn has_horizontal(&self) -> bool {
778 self.top || self.bottom
779 }
780
781 pub fn has_vertical(&self) -> bool {
782 self.left || self.right
783 }
784}
785
786impl Default for BorderSides {
787 fn default() -> Self {
788 Self::all()
789 }
790}
791
792impl Border {
793 pub const fn chars(self) -> BorderChars {
795 match self {
796 Self::Single => BorderChars {
797 tl: '┌',
798 tr: '┐',
799 bl: '└',
800 br: '┘',
801 h: '─',
802 v: '│',
803 },
804 Self::Double => BorderChars {
805 tl: '╔',
806 tr: '╗',
807 bl: '╚',
808 br: '╝',
809 h: '═',
810 v: '║',
811 },
812 Self::Rounded => BorderChars {
813 tl: '╭',
814 tr: '╮',
815 bl: '╰',
816 br: '╯',
817 h: '─',
818 v: '│',
819 },
820 Self::Thick => BorderChars {
821 tl: '┏',
822 tr: '┓',
823 bl: '┗',
824 br: '┛',
825 h: '━',
826 v: '┃',
827 },
828 Self::Dashed => BorderChars {
829 tl: '┌',
830 tr: '┐',
831 bl: '└',
832 br: '┘',
833 h: '┄',
834 v: '┆',
835 },
836 Self::DashedThick => BorderChars {
837 tl: '┏',
838 tr: '┓',
839 bl: '┗',
840 br: '┛',
841 h: '┅',
842 v: '┇',
843 },
844 }
845 }
846}
847
848#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
853#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
854pub struct Padding {
855 pub top: u32,
857 pub right: u32,
859 pub bottom: u32,
861 pub left: u32,
863}
864
865impl Padding {
866 pub const fn all(v: u32) -> Self {
868 Self::new(v, v, v, v)
869 }
870
871 pub const fn xy(x: u32, y: u32) -> Self {
873 Self::new(y, x, y, x)
874 }
875
876 pub const fn new(top: u32, right: u32, bottom: u32, left: u32) -> Self {
878 Self {
879 top,
880 right,
881 bottom,
882 left,
883 }
884 }
885
886 pub const fn horizontal(self) -> u32 {
888 self.left + self.right
889 }
890
891 pub const fn vertical(self) -> u32 {
893 self.top + self.bottom
894 }
895}
896
897#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
902#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
903pub struct Margin {
904 pub top: u32,
906 pub right: u32,
908 pub bottom: u32,
910 pub left: u32,
912}
913
914impl Margin {
915 pub const fn all(v: u32) -> Self {
917 Self::new(v, v, v, v)
918 }
919
920 pub const fn xy(x: u32, y: u32) -> Self {
922 Self::new(y, x, y, x)
923 }
924
925 pub const fn new(top: u32, right: u32, bottom: u32, left: u32) -> Self {
927 Self {
928 top,
929 right,
930 bottom,
931 left,
932 }
933 }
934
935 pub const fn horizontal(self) -> u32 {
937 self.left + self.right
938 }
939
940 pub const fn vertical(self) -> u32 {
942 self.top + self.bottom
943 }
944}
945
946#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
959#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
960#[must_use = "configure constraints using the returned value"]
961pub struct Constraints {
962 pub min_width: Option<u32>,
964 pub max_width: Option<u32>,
966 pub min_height: Option<u32>,
968 pub max_height: Option<u32>,
970 pub width_pct: Option<u8>,
972 pub height_pct: Option<u8>,
974}
975
976impl Constraints {
977 pub const fn min_w(mut self, min_width: u32) -> Self {
979 self.min_width = Some(min_width);
980 self
981 }
982
983 pub const fn max_w(mut self, max_width: u32) -> Self {
985 self.max_width = Some(max_width);
986 self
987 }
988
989 pub const fn min_h(mut self, min_height: u32) -> Self {
991 self.min_height = Some(min_height);
992 self
993 }
994
995 pub const fn max_h(mut self, max_height: u32) -> Self {
997 self.max_height = Some(max_height);
998 self
999 }
1000
1001 pub const fn w_pct(mut self, pct: u8) -> Self {
1003 self.width_pct = Some(pct);
1004 self
1005 }
1006
1007 pub const fn h_pct(mut self, pct: u8) -> Self {
1009 self.height_pct = Some(pct);
1010 self
1011 }
1012}
1013
1014#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
1020#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1021pub enum Align {
1022 #[default]
1024 Start,
1025 Center,
1027 End,
1029}
1030
1031#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
1040#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1041pub enum Justify {
1042 #[default]
1044 Start,
1045 Center,
1047 End,
1049 SpaceBetween,
1051 SpaceAround,
1053 SpaceEvenly,
1055}
1056
1057#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
1062#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1063#[cfg_attr(feature = "serde", serde(transparent))]
1064pub struct Modifiers(pub u8);
1065
1066impl Modifiers {
1067 pub const NONE: Self = Self(0);
1069 pub const BOLD: Self = Self(1 << 0);
1071 pub const DIM: Self = Self(1 << 1);
1073 pub const ITALIC: Self = Self(1 << 2);
1075 pub const UNDERLINE: Self = Self(1 << 3);
1077 pub const REVERSED: Self = Self(1 << 4);
1079 pub const STRIKETHROUGH: Self = Self(1 << 5);
1081
1082 #[inline]
1084 pub fn contains(self, other: Self) -> bool {
1085 (self.0 & other.0) == other.0
1086 }
1087
1088 #[inline]
1090 pub fn insert(&mut self, other: Self) {
1091 self.0 |= other.0;
1092 }
1093
1094 #[inline]
1096 pub fn is_empty(self) -> bool {
1097 self.0 == 0
1098 }
1099}
1100
1101impl std::ops::BitOr for Modifiers {
1102 type Output = Self;
1103 #[inline]
1104 fn bitor(self, rhs: Self) -> Self {
1105 Self(self.0 | rhs.0)
1106 }
1107}
1108
1109impl std::ops::BitOrAssign for Modifiers {
1110 #[inline]
1111 fn bitor_assign(&mut self, rhs: Self) {
1112 self.0 |= rhs.0;
1113 }
1114}
1115
1116#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
1130#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1131#[must_use = "build and pass the returned Style value"]
1132pub struct Style {
1133 pub fg: Option<Color>,
1135 pub bg: Option<Color>,
1137 pub modifiers: Modifiers,
1139}
1140
1141impl Style {
1142 pub const fn new() -> Self {
1144 Self {
1145 fg: None,
1146 bg: None,
1147 modifiers: Modifiers::NONE,
1148 }
1149 }
1150
1151 pub const fn fg(mut self, color: Color) -> Self {
1153 self.fg = Some(color);
1154 self
1155 }
1156
1157 pub const fn bg(mut self, color: Color) -> Self {
1159 self.bg = Some(color);
1160 self
1161 }
1162
1163 pub fn bold(mut self) -> Self {
1165 self.modifiers |= Modifiers::BOLD;
1166 self
1167 }
1168
1169 pub fn dim(mut self) -> Self {
1171 self.modifiers |= Modifiers::DIM;
1172 self
1173 }
1174
1175 pub fn italic(mut self) -> Self {
1177 self.modifiers |= Modifiers::ITALIC;
1178 self
1179 }
1180
1181 pub fn underline(mut self) -> Self {
1183 self.modifiers |= Modifiers::UNDERLINE;
1184 self
1185 }
1186
1187 pub fn reversed(mut self) -> Self {
1189 self.modifiers |= Modifiers::REVERSED;
1190 self
1191 }
1192
1193 pub fn strikethrough(mut self) -> Self {
1195 self.modifiers |= Modifiers::STRIKETHROUGH;
1196 self
1197 }
1198}