telex/theme.rs
1//! Theming support for Telex applications.
2//!
3//! Provides customizable color schemes for UI elements.
4//!
5//! # Example
6//! ```rust,ignore
7//! use telex::theme::Theme;
8//!
9//! let theme = Theme::dark();
10//! // or customize:
11//! let custom = Theme::default()
12//! .with_primary(Color::Cyan)
13//! .with_background(Color::Black);
14//! ```
15
16use crossterm::style::Color;
17
18/// Color scheme for the application.
19#[derive(Debug, Clone)]
20pub struct Theme {
21 /// Primary accent color (focused elements, highlights)
22 pub primary: Color,
23 /// Secondary accent color
24 pub secondary: Color,
25 /// Background color
26 pub background: Color,
27 /// Foreground/text color
28 pub foreground: Color,
29 /// Muted/dimmed text color
30 pub muted: Color,
31 /// Error color
32 pub error: Color,
33 /// Success color
34 pub success: Color,
35 /// Warning color
36 pub warning: Color,
37 /// Border color
38 pub border: Color,
39 /// Focused border color
40 pub border_focused: Color,
41 /// Button background
42 pub button_bg: Color,
43 /// Button foreground
44 pub button_fg: Color,
45 /// Button focused background
46 pub button_focused_bg: Color,
47 /// Button focused foreground
48 pub button_focused_fg: Color,
49 /// Selection/highlight background
50 pub selection_bg: Color,
51 /// Selection/highlight foreground
52 pub selection_fg: Color,
53 /// Input field background
54 pub input_bg: Color,
55 /// Input field foreground
56 pub input_fg: Color,
57 /// Placeholder text color
58 pub placeholder: Color,
59 /// Cursor color (foreground of cursor character/block)
60 pub cursor: Color,
61 /// Cursor text color (background behind cursor, text color when on a character)
62 pub cursor_text: Color,
63}
64
65impl Default for Theme {
66 fn default() -> Self {
67 Self::dark()
68 }
69}
70
71impl Theme {
72 /// Create a dark theme (default).
73 pub fn dark() -> Self {
74 Self {
75 primary: Color::Cyan,
76 secondary: Color::Magenta,
77 background: Color::Reset,
78 foreground: Color::Reset,
79 muted: Color::DarkGrey,
80 error: Color::Red,
81 success: Color::Green,
82 warning: Color::Yellow,
83 border: Color::Reset,
84 border_focused: Color::Cyan,
85 button_bg: Color::Reset,
86 button_fg: Color::Grey,
87 button_focused_bg: Color::White,
88 button_focused_fg: Color::Black,
89 selection_bg: Color::Grey, // Softer than White
90 selection_fg: Color::Black,
91 input_bg: Color::Reset,
92 input_fg: Color::Reset,
93 placeholder: Color::DarkGrey,
94 // Cursor must contrast with selection_bg (the "general highlight" when widget has focus).
95 // Since selection_bg=Grey, cursor must NOT be Grey or it disappears.
96 cursor: Color::Black,
97 cursor_text: Color::Grey,
98 }
99 }
100
101 /// Create a light theme.
102 pub fn light() -> Self {
103 Self {
104 primary: Color::Blue,
105 secondary: Color::Magenta,
106 background: Color::White,
107 foreground: Color::Black,
108 muted: Color::DarkGrey,
109 error: Color::Red,
110 success: Color::Green,
111 warning: Color::Yellow,
112 border: Color::DarkGrey,
113 border_focused: Color::Blue,
114 button_bg: Color::DarkGrey,
115 button_fg: Color::White,
116 button_focused_bg: Color::Blue,
117 button_focused_fg: Color::White,
118 selection_bg: Color::Blue,
119 selection_fg: Color::White,
120 input_bg: Color::White,
121 input_fg: Color::Black,
122 placeholder: Color::DarkGrey,
123 cursor: Color::Black,
124 cursor_text: Color::White,
125 }
126 }
127
128 /// Create a nord-inspired theme.
129 pub fn nord() -> Self {
130 Self {
131 primary: Color::Rgb {
132 r: 136,
133 g: 192,
134 b: 208,
135 }, // Nord8 - frost
136 secondary: Color::Rgb {
137 r: 180,
138 g: 142,
139 b: 173,
140 }, // Nord15 - aurora
141 background: Color::Rgb {
142 r: 46,
143 g: 52,
144 b: 64,
145 }, // Nord0 - polar night
146 foreground: Color::Rgb {
147 r: 236,
148 g: 239,
149 b: 244,
150 }, // Nord6 - snow storm
151 muted: Color::Rgb {
152 r: 76,
153 g: 86,
154 b: 106,
155 }, // Nord3
156 error: Color::Rgb {
157 r: 191,
158 g: 97,
159 b: 106,
160 }, // Nord11
161 success: Color::Rgb {
162 r: 163,
163 g: 190,
164 b: 140,
165 }, // Nord14
166 warning: Color::Rgb {
167 r: 235,
168 g: 203,
169 b: 139,
170 }, // Nord13
171 border: Color::Rgb {
172 r: 76,
173 g: 86,
174 b: 106,
175 }, // Nord3
176 border_focused: Color::Rgb {
177 r: 136,
178 g: 192,
179 b: 208,
180 }, // Nord8
181 button_bg: Color::Rgb {
182 r: 59,
183 g: 66,
184 b: 82,
185 }, // Nord1
186 button_fg: Color::Rgb {
187 r: 236,
188 g: 239,
189 b: 244,
190 }, // Nord6
191 button_focused_bg: Color::Rgb {
192 r: 136,
193 g: 192,
194 b: 208,
195 }, // Nord8
196 button_focused_fg: Color::Rgb {
197 r: 46,
198 g: 52,
199 b: 64,
200 }, // Nord0
201 selection_bg: Color::Rgb {
202 r: 136,
203 g: 192,
204 b: 208,
205 }, // Nord8
206 selection_fg: Color::Rgb {
207 r: 46,
208 g: 52,
209 b: 64,
210 }, // Nord0
211 input_bg: Color::Rgb {
212 r: 59,
213 g: 66,
214 b: 82,
215 }, // Nord1
216 input_fg: Color::Rgb {
217 r: 236,
218 g: 239,
219 b: 244,
220 }, // Nord6
221 placeholder: Color::Rgb {
222 r: 76,
223 g: 86,
224 b: 106,
225 }, // Nord3
226 cursor: Color::Rgb {
227 r: 236,
228 g: 239,
229 b: 244,
230 }, // Nord6 - snow storm (foreground)
231 cursor_text: Color::Rgb {
232 r: 46,
233 g: 52,
234 b: 64,
235 }, // Nord0 - polar night
236 }
237 }
238
239 /// Create a monokai-inspired theme.
240 pub fn monokai() -> Self {
241 Self {
242 primary: Color::Rgb {
243 r: 102,
244 g: 217,
245 b: 239,
246 }, // Cyan
247 secondary: Color::Rgb {
248 r: 174,
249 g: 129,
250 b: 255,
251 }, // Purple
252 background: Color::Rgb {
253 r: 39,
254 g: 40,
255 b: 34,
256 }, // Dark bg
257 foreground: Color::Rgb {
258 r: 248,
259 g: 248,
260 b: 242,
261 }, // Light fg
262 muted: Color::Rgb {
263 r: 117,
264 g: 113,
265 b: 94,
266 }, // Comment grey
267 error: Color::Rgb {
268 r: 249,
269 g: 38,
270 b: 114,
271 }, // Pink/red
272 success: Color::Rgb {
273 r: 166,
274 g: 226,
275 b: 46,
276 }, // Green
277 warning: Color::Rgb {
278 r: 253,
279 g: 151,
280 b: 31,
281 }, // Orange
282 border: Color::Rgb {
283 r: 117,
284 g: 113,
285 b: 94,
286 },
287 border_focused: Color::Rgb {
288 r: 102,
289 g: 217,
290 b: 239,
291 },
292 button_bg: Color::Rgb {
293 r: 73,
294 g: 72,
295 b: 62,
296 },
297 button_fg: Color::Rgb {
298 r: 248,
299 g: 248,
300 b: 242,
301 },
302 button_focused_bg: Color::Rgb {
303 r: 102,
304 g: 217,
305 b: 239,
306 },
307 button_focused_fg: Color::Rgb {
308 r: 39,
309 g: 40,
310 b: 34,
311 },
312 selection_bg: Color::Rgb {
313 r: 102,
314 g: 217,
315 b: 239,
316 },
317 selection_fg: Color::Rgb {
318 r: 39,
319 g: 40,
320 b: 34,
321 },
322 input_bg: Color::Rgb {
323 r: 73,
324 g: 72,
325 b: 62,
326 },
327 input_fg: Color::Rgb {
328 r: 248,
329 g: 248,
330 b: 242,
331 },
332 placeholder: Color::Rgb {
333 r: 117,
334 g: 113,
335 b: 94,
336 },
337 cursor: Color::Rgb {
338 r: 248,
339 g: 248,
340 b: 242,
341 }, // Light fg (foreground)
342 cursor_text: Color::Rgb {
343 r: 39,
344 g: 40,
345 b: 34,
346 }, // Dark bg
347 }
348 }
349
350 /// Create a Catppuccin Mocha theme (dark).
351 pub fn catppuccin_mocha() -> Self {
352 Self {
353 primary: Color::Rgb {
354 r: 137,
355 g: 180,
356 b: 250,
357 }, // Blue
358 secondary: Color::Rgb {
359 r: 203,
360 g: 166,
361 b: 247,
362 }, // Mauve
363 background: Color::Rgb {
364 r: 30,
365 g: 30,
366 b: 46,
367 }, // Base
368 foreground: Color::Rgb {
369 r: 205,
370 g: 214,
371 b: 244,
372 }, // Text
373 muted: Color::Rgb {
374 r: 108,
375 g: 112,
376 b: 134,
377 }, // Overlay0
378 error: Color::Rgb {
379 r: 243,
380 g: 139,
381 b: 168,
382 }, // Red
383 success: Color::Rgb {
384 r: 166,
385 g: 227,
386 b: 161,
387 }, // Green
388 warning: Color::Rgb {
389 r: 249,
390 g: 226,
391 b: 175,
392 }, // Yellow
393 border: Color::Rgb {
394 r: 69,
395 g: 71,
396 b: 90,
397 }, // Surface1
398 border_focused: Color::Rgb {
399 r: 137,
400 g: 180,
401 b: 250,
402 }, // Blue
403 button_bg: Color::Rgb {
404 r: 49,
405 g: 50,
406 b: 68,
407 }, // Surface0
408 button_fg: Color::Rgb {
409 r: 205,
410 g: 214,
411 b: 244,
412 }, // Text
413 button_focused_bg: Color::Rgb {
414 r: 137,
415 g: 180,
416 b: 250,
417 }, // Blue
418 button_focused_fg: Color::Rgb {
419 r: 30,
420 g: 30,
421 b: 46,
422 }, // Base
423 selection_bg: Color::Rgb {
424 r: 137,
425 g: 180,
426 b: 250,
427 }, // Blue
428 selection_fg: Color::Rgb {
429 r: 30,
430 g: 30,
431 b: 46,
432 }, // Base
433 input_bg: Color::Rgb {
434 r: 49,
435 g: 50,
436 b: 68,
437 }, // Surface0
438 input_fg: Color::Rgb {
439 r: 205,
440 g: 214,
441 b: 244,
442 }, // Text
443 placeholder: Color::Rgb {
444 r: 108,
445 g: 112,
446 b: 134,
447 }, // Overlay0
448 cursor: Color::Rgb {
449 r: 205,
450 g: 214,
451 b: 244,
452 }, // Text (foreground)
453 cursor_text: Color::Rgb {
454 r: 30,
455 g: 30,
456 b: 46,
457 }, // Base
458 }
459 }
460
461 /// Create a Catppuccin Latte theme (light).
462 pub fn catppuccin_latte() -> Self {
463 Self {
464 primary: Color::Rgb {
465 r: 30,
466 g: 102,
467 b: 245,
468 }, // Blue
469 secondary: Color::Rgb {
470 r: 136,
471 g: 57,
472 b: 239,
473 }, // Mauve
474 background: Color::Rgb {
475 r: 239,
476 g: 241,
477 b: 245,
478 }, // Base
479 foreground: Color::Rgb {
480 r: 76,
481 g: 79,
482 b: 105,
483 }, // Text
484 muted: Color::Rgb {
485 r: 156,
486 g: 160,
487 b: 176,
488 }, // Overlay0
489 error: Color::Rgb {
490 r: 210,
491 g: 15,
492 b: 57,
493 }, // Red
494 success: Color::Rgb {
495 r: 64,
496 g: 160,
497 b: 43,
498 }, // Green
499 warning: Color::Rgb {
500 r: 223,
501 g: 142,
502 b: 29,
503 }, // Yellow
504 border: Color::Rgb {
505 r: 188,
506 g: 192,
507 b: 204,
508 }, // Surface1
509 border_focused: Color::Rgb {
510 r: 30,
511 g: 102,
512 b: 245,
513 }, // Blue
514 button_bg: Color::Rgb {
515 r: 204,
516 g: 208,
517 b: 218,
518 }, // Surface0
519 button_fg: Color::Rgb {
520 r: 76,
521 g: 79,
522 b: 105,
523 }, // Text
524 button_focused_bg: Color::Rgb {
525 r: 30,
526 g: 102,
527 b: 245,
528 }, // Blue
529 button_focused_fg: Color::Rgb {
530 r: 239,
531 g: 241,
532 b: 245,
533 }, // Base
534 selection_bg: Color::Rgb {
535 r: 30,
536 g: 102,
537 b: 245,
538 }, // Blue
539 selection_fg: Color::Rgb {
540 r: 239,
541 g: 241,
542 b: 245,
543 }, // Base
544 input_bg: Color::Rgb {
545 r: 204,
546 g: 208,
547 b: 218,
548 }, // Surface0
549 input_fg: Color::Rgb {
550 r: 76,
551 g: 79,
552 b: 105,
553 }, // Text
554 placeholder: Color::Rgb {
555 r: 156,
556 g: 160,
557 b: 176,
558 }, // Overlay0
559 cursor: Color::Rgb {
560 r: 76,
561 g: 79,
562 b: 105,
563 }, // Text (foreground)
564 cursor_text: Color::Rgb {
565 r: 239,
566 g: 241,
567 b: 245,
568 }, // Base
569 }
570 }
571
572 /// Create a Dracula theme.
573 pub fn dracula() -> Self {
574 Self {
575 primary: Color::Rgb {
576 r: 139,
577 g: 233,
578 b: 253,
579 }, // Cyan
580 secondary: Color::Rgb {
581 r: 189,
582 g: 147,
583 b: 249,
584 }, // Purple
585 background: Color::Rgb {
586 r: 40,
587 g: 42,
588 b: 54,
589 }, // Background
590 foreground: Color::Rgb {
591 r: 248,
592 g: 248,
593 b: 242,
594 }, // Foreground
595 muted: Color::Rgb {
596 r: 98,
597 g: 114,
598 b: 164,
599 }, // Comment
600 error: Color::Rgb {
601 r: 255,
602 g: 85,
603 b: 85,
604 }, // Red
605 success: Color::Rgb {
606 r: 80,
607 g: 250,
608 b: 123,
609 }, // Green
610 warning: Color::Rgb {
611 r: 255,
612 g: 184,
613 b: 108,
614 }, // Orange
615 border: Color::Rgb {
616 r: 68,
617 g: 71,
618 b: 90,
619 }, // Selection
620 border_focused: Color::Rgb {
621 r: 139,
622 g: 233,
623 b: 253,
624 }, // Cyan
625 button_bg: Color::Rgb {
626 r: 68,
627 g: 71,
628 b: 90,
629 }, // Selection
630 button_fg: Color::Rgb {
631 r: 248,
632 g: 248,
633 b: 242,
634 }, // Foreground
635 button_focused_bg: Color::Rgb {
636 r: 139,
637 g: 233,
638 b: 253,
639 }, // Cyan
640 button_focused_fg: Color::Rgb {
641 r: 40,
642 g: 42,
643 b: 54,
644 }, // Background
645 selection_bg: Color::Rgb {
646 r: 68,
647 g: 71,
648 b: 90,
649 }, // Selection
650 selection_fg: Color::Rgb {
651 r: 248,
652 g: 248,
653 b: 242,
654 }, // Foreground
655 input_bg: Color::Rgb {
656 r: 68,
657 g: 71,
658 b: 90,
659 }, // Selection
660 input_fg: Color::Rgb {
661 r: 248,
662 g: 248,
663 b: 242,
664 }, // Foreground
665 placeholder: Color::Rgb {
666 r: 98,
667 g: 114,
668 b: 164,
669 }, // Comment
670 cursor: Color::Rgb {
671 r: 248,
672 g: 248,
673 b: 242,
674 }, // Foreground
675 cursor_text: Color::Rgb {
676 r: 40,
677 g: 42,
678 b: 54,
679 }, // Background
680 }
681 }
682
683 /// Create a Gruvbox Dark theme.
684 pub fn gruvbox_dark() -> Self {
685 Self {
686 primary: Color::Rgb {
687 r: 131,
688 g: 165,
689 b: 152,
690 }, // Blue
691 secondary: Color::Rgb {
692 r: 211,
693 g: 134,
694 b: 155,
695 }, // Purple
696 background: Color::Rgb {
697 r: 40,
698 g: 40,
699 b: 40,
700 }, // bg0
701 foreground: Color::Rgb {
702 r: 235,
703 g: 219,
704 b: 178,
705 }, // fg (light1)
706 muted: Color::Rgb {
707 r: 146,
708 g: 131,
709 b: 116,
710 }, // gray
711 error: Color::Rgb {
712 r: 251,
713 g: 73,
714 b: 52,
715 }, // Red bright
716 success: Color::Rgb {
717 r: 184,
718 g: 187,
719 b: 38,
720 }, // Green bright
721 warning: Color::Rgb {
722 r: 250,
723 g: 189,
724 b: 47,
725 }, // Yellow bright
726 border: Color::Rgb {
727 r: 80,
728 g: 73,
729 b: 69,
730 }, // bg2
731 border_focused: Color::Rgb {
732 r: 131,
733 g: 165,
734 b: 152,
735 }, // Blue
736 button_bg: Color::Rgb {
737 r: 60,
738 g: 56,
739 b: 54,
740 }, // bg1
741 button_fg: Color::Rgb {
742 r: 235,
743 g: 219,
744 b: 178,
745 }, // fg
746 button_focused_bg: Color::Rgb {
747 r: 131,
748 g: 165,
749 b: 152,
750 }, // Blue
751 button_focused_fg: Color::Rgb {
752 r: 40,
753 g: 40,
754 b: 40,
755 }, // bg0
756 selection_bg: Color::Rgb {
757 r: 131,
758 g: 165,
759 b: 152,
760 }, // Blue
761 selection_fg: Color::Rgb {
762 r: 40,
763 g: 40,
764 b: 40,
765 }, // bg0
766 input_bg: Color::Rgb {
767 r: 60,
768 g: 56,
769 b: 54,
770 }, // bg1
771 input_fg: Color::Rgb {
772 r: 235,
773 g: 219,
774 b: 178,
775 }, // fg
776 placeholder: Color::Rgb {
777 r: 146,
778 g: 131,
779 b: 116,
780 }, // gray
781 cursor: Color::Rgb {
782 r: 235,
783 g: 219,
784 b: 178,
785 }, // fg (foreground)
786 cursor_text: Color::Rgb {
787 r: 40,
788 g: 40,
789 b: 40,
790 }, // bg0
791 }
792 }
793
794 /// Create a Solarized Dark theme.
795 pub fn solarized_dark() -> Self {
796 Self {
797 primary: Color::Rgb {
798 r: 38,
799 g: 139,
800 b: 210,
801 }, // Blue
802 secondary: Color::Rgb {
803 r: 108,
804 g: 113,
805 b: 196,
806 }, // Violet
807 background: Color::Rgb { r: 0, g: 43, b: 54 }, // base03
808 foreground: Color::Rgb {
809 r: 131,
810 g: 148,
811 b: 150,
812 }, // base0
813 muted: Color::Rgb {
814 r: 88,
815 g: 110,
816 b: 117,
817 }, // base01
818 error: Color::Rgb {
819 r: 220,
820 g: 50,
821 b: 47,
822 }, // Red
823 success: Color::Rgb {
824 r: 133,
825 g: 153,
826 b: 0,
827 }, // Green
828 warning: Color::Rgb {
829 r: 181,
830 g: 137,
831 b: 0,
832 }, // Yellow
833 border: Color::Rgb { r: 7, g: 54, b: 66 }, // base02
834 border_focused: Color::Rgb {
835 r: 38,
836 g: 139,
837 b: 210,
838 }, // Blue
839 button_bg: Color::Rgb { r: 7, g: 54, b: 66 }, // base02
840 button_fg: Color::Rgb {
841 r: 131,
842 g: 148,
843 b: 150,
844 }, // base0
845 button_focused_bg: Color::Rgb {
846 r: 38,
847 g: 139,
848 b: 210,
849 }, // Blue
850 button_focused_fg: Color::Rgb {
851 r: 253,
852 g: 246,
853 b: 227,
854 }, // base3
855 selection_bg: Color::Rgb {
856 r: 38,
857 g: 139,
858 b: 210,
859 }, // Blue
860 selection_fg: Color::Rgb {
861 r: 253,
862 g: 246,
863 b: 227,
864 }, // base3
865 input_bg: Color::Rgb { r: 7, g: 54, b: 66 }, // base02
866 input_fg: Color::Rgb {
867 r: 131,
868 g: 148,
869 b: 150,
870 }, // base0
871 placeholder: Color::Rgb {
872 r: 88,
873 g: 110,
874 b: 117,
875 }, // base01
876 cursor: Color::Rgb {
877 r: 131,
878 g: 148,
879 b: 150,
880 }, // base0 (foreground)
881 cursor_text: Color::Rgb {
882 r: 0,
883 g: 43,
884 b: 54,
885 }, // base03
886 }
887 }
888
889 /// Create a Rosé Pine theme (dark).
890 pub fn rose_pine() -> Self {
891 Self {
892 primary: Color::Rgb {
893 r: 156,
894 g: 207,
895 b: 216,
896 }, // Foam
897 secondary: Color::Rgb {
898 r: 196,
899 g: 167,
900 b: 231,
901 }, // Iris
902 background: Color::Rgb {
903 r: 25,
904 g: 23,
905 b: 36,
906 }, // Base
907 foreground: Color::Rgb {
908 r: 224,
909 g: 222,
910 b: 244,
911 }, // Text
912 muted: Color::Rgb {
913 r: 110,
914 g: 106,
915 b: 134,
916 }, // Muted
917 error: Color::Rgb {
918 r: 235,
919 g: 111,
920 b: 146,
921 }, // Love
922 success: Color::Rgb {
923 r: 49,
924 g: 116,
925 b: 143,
926 }, // Pine
927 warning: Color::Rgb {
928 r: 246,
929 g: 193,
930 b: 119,
931 }, // Gold
932 border: Color::Rgb {
933 r: 31,
934 g: 29,
935 b: 46,
936 }, // Surface
937 border_focused: Color::Rgb {
938 r: 156,
939 g: 207,
940 b: 216,
941 }, // Foam
942 button_bg: Color::Rgb {
943 r: 31,
944 g: 29,
945 b: 46,
946 }, // Surface
947 button_fg: Color::Rgb {
948 r: 224,
949 g: 222,
950 b: 244,
951 }, // Text
952 button_focused_bg: Color::Rgb {
953 r: 156,
954 g: 207,
955 b: 216,
956 }, // Foam
957 button_focused_fg: Color::Rgb {
958 r: 25,
959 g: 23,
960 b: 36,
961 }, // Base
962 selection_bg: Color::Rgb {
963 r: 156,
964 g: 207,
965 b: 216,
966 }, // Foam
967 selection_fg: Color::Rgb {
968 r: 25,
969 g: 23,
970 b: 36,
971 }, // Base
972 input_bg: Color::Rgb {
973 r: 31,
974 g: 29,
975 b: 46,
976 }, // Surface
977 input_fg: Color::Rgb {
978 r: 224,
979 g: 222,
980 b: 244,
981 }, // Text
982 placeholder: Color::Rgb {
983 r: 110,
984 g: 106,
985 b: 134,
986 }, // Muted
987 cursor: Color::Rgb {
988 r: 224,
989 g: 222,
990 b: 244,
991 }, // Text (foreground)
992 cursor_text: Color::Rgb {
993 r: 25,
994 g: 23,
995 b: 36,
996 }, // Base
997 }
998 }
999
1000 /// Create a HaX0R Blue theme (monochrome cyan/blue hacker style).
1001 pub fn hax0r_blue() -> Self {
1002 Self {
1003 primary: Color::Rgb {
1004 r: 16,
1005 g: 182,
1006 b: 255,
1007 }, // Bright cyan
1008 secondary: Color::Rgb {
1009 r: 0,
1010 g: 179,
1011 b: 247,
1012 }, // Cyan
1013 background: Color::Rgb { r: 1, g: 5, b: 21 }, // Dark navy
1014 foreground: Color::Rgb {
1015 r: 17,
1016 g: 183,
1017 b: 255,
1018 }, // Cyan text
1019 muted: Color::Rgb {
1020 r: 72,
1021 g: 65,
1022 b: 87,
1023 }, // Muted purple-gray
1024 error: Color::Rgb {
1025 r: 16,
1026 g: 182,
1027 b: 255,
1028 }, // Cyan (monochrome)
1029 success: Color::Rgb {
1030 r: 16,
1031 g: 182,
1032 b: 255,
1033 }, // Cyan (monochrome)
1034 warning: Color::Rgb {
1035 r: 16,
1036 g: 182,
1037 b: 255,
1038 }, // Cyan (monochrome)
1039 border: Color::Rgb {
1040 r: 16,
1041 g: 182,
1042 b: 255,
1043 }, // Cyan
1044 border_focused: Color::Rgb {
1045 r: 250,
1046 g: 250,
1047 b: 250,
1048 }, // White
1049 button_bg: Color::Rgb { r: 1, g: 9, b: 33 }, // Slightly lighter bg
1050 button_fg: Color::Rgb {
1051 r: 16,
1052 g: 182,
1053 b: 255,
1054 }, // Cyan
1055 button_focused_bg: Color::Rgb {
1056 r: 16,
1057 g: 182,
1058 b: 255,
1059 }, // Cyan
1060 button_focused_fg: Color::Rgb { r: 1, g: 5, b: 21 }, // Dark bg
1061 selection_bg: Color::Rgb {
1062 r: 193,
1063 g: 228,
1064 b: 255,
1065 }, // Light cyan
1066 selection_fg: Color::Rgb { r: 1, g: 5, b: 21 }, // Dark bg
1067 input_bg: Color::Rgb { r: 1, g: 9, b: 33 }, // Slightly lighter bg
1068 input_fg: Color::Rgb {
1069 r: 16,
1070 g: 182,
1071 b: 255,
1072 }, // Cyan
1073 placeholder: Color::Rgb {
1074 r: 72,
1075 g: 65,
1076 b: 87,
1077 }, // Muted
1078 cursor: Color::Rgb {
1079 r: 17,
1080 g: 183,
1081 b: 255,
1082 }, // Foreground cyan
1083 cursor_text: Color::Rgb { r: 1, g: 5, b: 21 }, // Dark navy
1084 }
1085 }
1086
1087 /// Create a HaX0R Green theme (monochrome green hacker style).
1088 pub fn hax0r_green() -> Self {
1089 Self {
1090 primary: Color::Rgb {
1091 r: 21,
1092 g: 208,
1093 b: 13,
1094 }, // Bright green
1095 secondary: Color::Rgb {
1096 r: 25,
1097 g: 226,
1098 b: 14,
1099 }, // Green
1100 background: Color::Rgb { r: 2, g: 15, b: 1 }, // Dark green-black
1101 foreground: Color::Rgb {
1102 r: 22,
1103 g: 177,
1104 b: 14,
1105 }, // Green text
1106 muted: Color::Rgb {
1107 r: 51,
1108 g: 72,
1109 b: 67,
1110 }, // Muted green-gray
1111 error: Color::Rgb {
1112 r: 21,
1113 g: 208,
1114 b: 13,
1115 }, // Green (monochrome)
1116 success: Color::Rgb {
1117 r: 21,
1118 g: 208,
1119 b: 13,
1120 }, // Green (monochrome)
1121 warning: Color::Rgb {
1122 r: 21,
1123 g: 208,
1124 b: 13,
1125 }, // Green (monochrome)
1126 border: Color::Rgb {
1127 r: 21,
1128 g: 208,
1129 b: 13,
1130 }, // Green
1131 border_focused: Color::Rgb {
1132 r: 250,
1133 g: 250,
1134 b: 250,
1135 }, // White
1136 button_bg: Color::Rgb { r: 0, g: 31, b: 11 }, // Slightly lighter bg
1137 button_fg: Color::Rgb {
1138 r: 21,
1139 g: 208,
1140 b: 13,
1141 }, // Green
1142 button_focused_bg: Color::Rgb {
1143 r: 21,
1144 g: 208,
1145 b: 13,
1146 }, // Green
1147 button_focused_fg: Color::Rgb { r: 2, g: 15, b: 1 }, // Dark bg
1148 selection_bg: Color::Rgb {
1149 r: 212,
1150 g: 255,
1151 b: 193,
1152 }, // Light green
1153 selection_fg: Color::Rgb { r: 2, g: 15, b: 1 }, // Dark bg
1154 input_bg: Color::Rgb { r: 0, g: 31, b: 11 }, // Slightly lighter bg
1155 input_fg: Color::Rgb {
1156 r: 21,
1157 g: 208,
1158 b: 13,
1159 }, // Green
1160 placeholder: Color::Rgb {
1161 r: 51,
1162 g: 72,
1163 b: 67,
1164 }, // Muted
1165 cursor: Color::Rgb {
1166 r: 22,
1167 g: 177,
1168 b: 14,
1169 }, // Foreground green
1170 cursor_text: Color::Rgb { r: 2, g: 15, b: 1 }, // Dark bg
1171 }
1172 }
1173
1174 /// Create a HaX0R Red theme (monochrome red hacker style).
1175 pub fn hax0r_red() -> Self {
1176 Self {
1177 primary: Color::Rgb {
1178 r: 176,
1179 g: 13,
1180 b: 13,
1181 }, // Dark red
1182 secondary: Color::Rgb {
1183 r: 255,
1184 g: 17,
1185 b: 17,
1186 }, // Bright red
1187 background: Color::Rgb { r: 32, g: 1, b: 1 }, // Dark red-black
1188 foreground: Color::Rgb {
1189 r: 177,
1190 g: 14,
1191 b: 14,
1192 }, // Red text
1193 muted: Color::Rgb {
1194 r: 85,
1195 g: 64,
1196 b: 64,
1197 }, // Muted red-gray
1198 error: Color::Rgb {
1199 r: 255,
1200 g: 17,
1201 b: 17,
1202 }, // Bright red
1203 success: Color::Rgb {
1204 r: 176,
1205 g: 13,
1206 b: 13,
1207 }, // Red (monochrome)
1208 warning: Color::Rgb {
1209 r: 176,
1210 g: 13,
1211 b: 13,
1212 }, // Red (monochrome)
1213 border: Color::Rgb {
1214 r: 176,
1215 g: 13,
1216 b: 13,
1217 }, // Red
1218 border_focused: Color::Rgb {
1219 r: 250,
1220 g: 250,
1221 b: 250,
1222 }, // White
1223 button_bg: Color::Rgb { r: 31, g: 0, b: 0 }, // Slightly lighter bg
1224 button_fg: Color::Rgb {
1225 r: 176,
1226 g: 13,
1227 b: 13,
1228 }, // Red
1229 button_focused_bg: Color::Rgb {
1230 r: 176,
1231 g: 13,
1232 b: 13,
1233 }, // Red
1234 button_focused_fg: Color::Rgb { r: 32, g: 1, b: 1 }, // Dark bg
1235 selection_bg: Color::Rgb {
1236 r: 235,
1237 g: 193,
1238 b: 255,
1239 }, // Light pink
1240 selection_fg: Color::Rgb { r: 32, g: 1, b: 1 }, // Dark bg
1241 input_bg: Color::Rgb { r: 31, g: 0, b: 0 }, // Slightly lighter bg
1242 input_fg: Color::Rgb {
1243 r: 176,
1244 g: 13,
1245 b: 13,
1246 }, // Red
1247 placeholder: Color::Rgb {
1248 r: 85,
1249 g: 64,
1250 b: 64,
1251 }, // Muted
1252 cursor: Color::Rgb {
1253 r: 177,
1254 g: 14,
1255 b: 14,
1256 }, // Foreground red
1257 cursor_text: Color::Rgb { r: 32, g: 1, b: 1 }, // Dark bg
1258 }
1259 }
1260
1261 /// Create a Tokyo Night theme (dark).
1262 pub fn tokyo_night() -> Self {
1263 Self {
1264 primary: Color::Rgb {
1265 r: 122,
1266 g: 162,
1267 b: 247,
1268 }, // Blue
1269 secondary: Color::Rgb {
1270 r: 187,
1271 g: 154,
1272 b: 247,
1273 }, // Magenta
1274 background: Color::Rgb {
1275 r: 26,
1276 g: 27,
1277 b: 38,
1278 }, // bg
1279 foreground: Color::Rgb {
1280 r: 192,
1281 g: 202,
1282 b: 245,
1283 }, // fg
1284 muted: Color::Rgb {
1285 r: 65,
1286 g: 72,
1287 b: 104,
1288 }, // Bright black
1289 error: Color::Rgb {
1290 r: 247,
1291 g: 118,
1292 b: 142,
1293 }, // Red
1294 success: Color::Rgb {
1295 r: 158,
1296 g: 206,
1297 b: 106,
1298 }, // Green
1299 warning: Color::Rgb {
1300 r: 224,
1301 g: 175,
1302 b: 104,
1303 }, // Yellow
1304 border: Color::Rgb {
1305 r: 41,
1306 g: 46,
1307 b: 66,
1308 }, // Surface
1309 border_focused: Color::Rgb {
1310 r: 122,
1311 g: 162,
1312 b: 247,
1313 }, // Blue
1314 button_bg: Color::Rgb {
1315 r: 41,
1316 g: 46,
1317 b: 66,
1318 }, // Surface
1319 button_fg: Color::Rgb {
1320 r: 192,
1321 g: 202,
1322 b: 245,
1323 }, // fg
1324 button_focused_bg: Color::Rgb {
1325 r: 122,
1326 g: 162,
1327 b: 247,
1328 }, // Blue
1329 button_focused_fg: Color::Rgb {
1330 r: 26,
1331 g: 27,
1332 b: 38,
1333 }, // bg
1334 selection_bg: Color::Rgb {
1335 r: 122,
1336 g: 162,
1337 b: 247,
1338 }, // Blue
1339 selection_fg: Color::Rgb {
1340 r: 26,
1341 g: 27,
1342 b: 38,
1343 }, // bg
1344 input_bg: Color::Rgb {
1345 r: 41,
1346 g: 46,
1347 b: 66,
1348 }, // Surface
1349 input_fg: Color::Rgb {
1350 r: 192,
1351 g: 202,
1352 b: 245,
1353 }, // fg
1354 placeholder: Color::Rgb {
1355 r: 65,
1356 g: 72,
1357 b: 104,
1358 }, // Bright black
1359 cursor: Color::Rgb {
1360 r: 192,
1361 g: 202,
1362 b: 245,
1363 }, // fg (foreground)
1364 cursor_text: Color::Rgb {
1365 r: 26,
1366 g: 27,
1367 b: 38,
1368 }, // bg
1369 }
1370 }
1371
1372 // Builder methods for customization
1373
1374 /// Set the primary accent color.
1375 pub fn with_primary(mut self, color: Color) -> Self {
1376 self.primary = color;
1377 self
1378 }
1379
1380 /// Set the secondary accent color.
1381 pub fn with_secondary(mut self, color: Color) -> Self {
1382 self.secondary = color;
1383 self
1384 }
1385
1386 /// Set the background color.
1387 pub fn with_background(mut self, color: Color) -> Self {
1388 self.background = color;
1389 self
1390 }
1391
1392 /// Set the foreground/text color.
1393 pub fn with_foreground(mut self, color: Color) -> Self {
1394 self.foreground = color;
1395 self
1396 }
1397
1398 /// Set the error color.
1399 pub fn with_error(mut self, color: Color) -> Self {
1400 self.error = color;
1401 self
1402 }
1403
1404 /// Set the success color.
1405 pub fn with_success(mut self, color: Color) -> Self {
1406 self.success = color;
1407 self
1408 }
1409
1410 /// Set the warning color.
1411 pub fn with_warning(mut self, color: Color) -> Self {
1412 self.warning = color;
1413 self
1414 }
1415}
1416
1417/// Global theme storage (thread-local for now).
1418use std::cell::RefCell;
1419
1420thread_local! {
1421 static CURRENT_THEME: RefCell<Theme> = RefCell::new(Theme::default());
1422}
1423
1424/// Set the current theme.
1425pub fn set_theme(theme: Theme) {
1426 CURRENT_THEME.with(|t| {
1427 *t.borrow_mut() = theme;
1428 });
1429}
1430
1431/// Get the current theme.
1432pub fn current_theme() -> Theme {
1433 CURRENT_THEME.with(|t| t.borrow().clone())
1434}
1435
1436/// Get a specific color from the current theme.
1437pub fn themed_color<F>(f: F) -> Color
1438where
1439 F: FnOnce(&Theme) -> Color,
1440{
1441 CURRENT_THEME.with(|t| f(&t.borrow()))
1442}
1443
1444/// Check if the terminal supports true color (24-bit RGB).
1445///
1446/// Returns `true` if the terminal likely supports RGB colors.
1447/// Returns `false` if running in a terminal known not to support true color
1448/// (e.g., Apple Terminal.app).
1449pub fn supports_true_color() -> bool {
1450 use std::env;
1451
1452 // Apple Terminal doesn't support true color
1453 if let Ok(term_program) = env::var("TERM_PROGRAM") {
1454 if term_program == "Apple_Terminal" {
1455 return false;
1456 }
1457 }
1458
1459 // Check for explicit true color support
1460 if let Ok(colorterm) = env::var("COLORTERM") {
1461 if colorterm == "truecolor" || colorterm == "24bit" {
1462 return true;
1463 }
1464 }
1465
1466 // Known true color terminals
1467 if let Ok(term) = env::var("TERM") {
1468 if term.contains("256color") || term.contains("truecolor") || term.contains("24bit") {
1469 // 256color doesn't guarantee true color, but many modern terminals
1470 // that set this also support true color
1471 }
1472 }
1473
1474 // Check for specific terminal programs known to support true color
1475 if let Ok(term_program) = env::var("TERM_PROGRAM") {
1476 let true_color_terminals = [
1477 "iTerm.app",
1478 "Hyper",
1479 "vscode",
1480 "Ghostty",
1481 "WezTerm",
1482 "Alacritty",
1483 "kitty",
1484 ];
1485 if true_color_terminals
1486 .iter()
1487 .any(|t| term_program.contains(t))
1488 {
1489 return true;
1490 }
1491 }
1492
1493 // Default: assume true color is supported (most modern terminals do)
1494 // User can check the warning if colors look wrong
1495 true
1496}
1497
1498/// Get the name of the current terminal, if detectable.
1499pub fn terminal_name() -> Option<String> {
1500 std::env::var("TERM_PROGRAM").ok()
1501}