Skip to main content

pito_tui/
palette.rs

1use std::time::Duration;
2
3use pito_footer::Styles as FooterStyles;
4use pito_header::Styles as HeaderStyles;
5use pito_hourglass::Hourglass;
6use pito_list::{Bar, Column, Count, List, ListView, Source, Styles as ListStyles};
7use ratatui::style::{Color, Modifier, Style};
8
9const GOOD: Color = Color::Rgb(0x8c, 0xe6, 0xb4);
10const BAD: Color = Color::Rgb(0xff, 0x6e, 0xc7);
11const WARN: Color = Color::Rgb(0xff, 0xc8, 0x57);
12
13#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
14#[non_exhaustive]
15pub enum Tone {
16    #[default]
17    Ink,
18    Strong,
19    Muted,
20    Accent,
21    Good,
22    Warn,
23    Bad,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27#[non_exhaustive]
28pub struct Palette {
29    pub base: Style,
30    pub ink: Style,
31    pub strong: Style,
32    pub muted: Style,
33    pub accent: Style,
34    pub selected: Style,
35    pub good: Style,
36    pub warn: Style,
37    pub bad: Style,
38    pub rule: Style,
39    pub line: Style,
40    pub glass: Style,
41    pub shimmer: Style,
42    pub title: Style,
43    pub inactive: Style,
44    pub focus: Style,
45    pub small: Style,
46    pub bar: Option<Bar<'static>>,
47    pub count: Option<Count<'static>>,
48    pub mono: bool,
49}
50
51const fn bare(style: Style) -> Style {
52    let mut style = style;
53    style.fg = None;
54    style.bg = None;
55    style
56}
57
58impl Palette {
59    pub const fn new(accent: Color) -> Self {
60        let lit = Style::new().fg(accent);
61        let muted = Style::new().add_modifier(Modifier::DIM);
62        Palette {
63            base: Style::new(),
64            ink: Style::new(),
65            strong: Style::new().add_modifier(Modifier::BOLD),
66            muted,
67            accent: lit,
68            selected: lit.add_modifier(Modifier::BOLD),
69            good: Style::new().fg(GOOD),
70            warn: Style::new().fg(WARN),
71            bad: Style::new().fg(BAD),
72            rule: lit.add_modifier(Modifier::BOLD),
73            line: muted,
74            glass: lit,
75            shimmer: lit,
76            title: lit.add_modifier(Modifier::BOLD),
77            inactive: muted,
78            focus: lit
79                .add_modifier(Modifier::BOLD)
80                .add_modifier(Modifier::REVERSED),
81            small: Style::new(),
82            bar: None,
83            count: None,
84            mono: false,
85        }
86    }
87
88    pub const fn mono(self) -> Self {
89        Palette {
90            base: bare(self.base),
91            ink: bare(self.ink),
92            strong: bare(self.strong).add_modifier(Modifier::BOLD),
93            muted: bare(self.muted).add_modifier(Modifier::DIM),
94            accent: bare(self.accent).add_modifier(Modifier::BOLD),
95            selected: bare(self.selected).add_modifier(Modifier::REVERSED),
96            good: bare(self.good).add_modifier(Modifier::BOLD),
97            warn: bare(self.warn)
98                .add_modifier(Modifier::BOLD)
99                .add_modifier(Modifier::UNDERLINED),
100            bad: bare(self.bad).add_modifier(Modifier::REVERSED),
101            rule: bare(self.rule).add_modifier(Modifier::DIM),
102            line: bare(self.line).add_modifier(Modifier::DIM),
103            glass: bare(self.glass).add_modifier(Modifier::BOLD),
104            shimmer: bare(self.shimmer).add_modifier(Modifier::BOLD),
105            title: bare(self.title).add_modifier(Modifier::BOLD),
106            inactive: bare(self.inactive).add_modifier(Modifier::DIM),
107            focus: bare(self.focus).add_modifier(Modifier::REVERSED),
108            small: bare(self.small),
109            bar: self.bar,
110            count: self.count,
111            mono: true,
112        }
113    }
114
115    pub const fn mono_style(style: Style) -> Style {
116        bare(style)
117    }
118
119    pub const fn base(mut self, style: Style) -> Self {
120        self.base = style;
121        self
122    }
123
124    pub const fn ink(mut self, style: Style) -> Self {
125        self.ink = style;
126        self
127    }
128
129    pub const fn strong(mut self, style: Style) -> Self {
130        self.strong = style;
131        self
132    }
133
134    pub const fn muted(mut self, style: Style) -> Self {
135        self.muted = style;
136        self
137    }
138
139    pub const fn accent(mut self, style: Style) -> Self {
140        self.accent = style;
141        self
142    }
143
144    pub const fn selected(mut self, style: Style) -> Self {
145        self.selected = style;
146        self
147    }
148
149    pub const fn good(mut self, style: Style) -> Self {
150        self.good = style;
151        self
152    }
153
154    pub const fn warn(mut self, style: Style) -> Self {
155        self.warn = style;
156        self
157    }
158
159    pub const fn bad(mut self, style: Style) -> Self {
160        self.bad = style;
161        self
162    }
163
164    pub const fn rule(mut self, style: Style) -> Self {
165        self.rule = style;
166        self
167    }
168
169    pub const fn line(mut self, style: Style) -> Self {
170        self.line = style;
171        self
172    }
173
174    pub const fn glass(mut self, style: Style) -> Self {
175        self.glass = style;
176        self
177    }
178
179    pub const fn shimmer(mut self, style: Style) -> Self {
180        self.shimmer = style;
181        self
182    }
183
184    pub const fn title(mut self, style: Style) -> Self {
185        self.title = style;
186        self
187    }
188
189    pub const fn inactive(mut self, style: Style) -> Self {
190        self.inactive = style;
191        self
192    }
193
194    pub const fn focus(mut self, style: Style) -> Self {
195        self.focus = style;
196        self
197    }
198
199    pub const fn small(mut self, style: Style) -> Self {
200        self.small = style;
201        self
202    }
203
204    pub const fn bar(mut self, bar: Option<Bar<'static>>) -> Self {
205        self.bar = bar;
206        self
207    }
208
209    pub const fn count(mut self, count: Option<Count<'static>>) -> Self {
210        self.count = count;
211        self
212    }
213
214    pub const fn tone(&self, tone: Tone) -> Style {
215        match tone {
216            Tone::Ink => self.ink,
217            Tone::Strong => self.strong,
218            Tone::Muted => self.muted,
219            Tone::Accent => self.accent.add_modifier(Modifier::BOLD),
220            Tone::Good => self.good,
221            Tone::Warn => self.warn,
222            Tone::Bad => self.bad,
223        }
224    }
225
226    pub const fn header(&self) -> HeaderStyles {
227        HeaderStyles::new()
228            .accent(self.accent)
229            .muted(self.muted)
230            .rule(self.rule)
231    }
232
233    pub const fn footer(&self) -> FooterStyles {
234        FooterStyles::new()
235            .accent(self.accent)
236            .muted(self.muted)
237            .alert(self.bad)
238            .ink(self.ink)
239            .rule(self.line)
240            .good(self.good)
241    }
242
243    pub const fn list(&self) -> ListStyles {
244        ListStyles::new()
245            .selected(self.selected)
246            .ink(self.ink)
247            .faint(self.muted)
248            .base(self.base)
249    }
250
251    pub fn view<'a, S: Source>(
252        &self,
253        list: &'a mut List<S>,
254        columns: &'a [Column<'a>],
255    ) -> ListView<'a, S> {
256        ListView::new(list, columns)
257            .styles(self.list())
258            .scrollbar(self.bar)
259            .count(self.count)
260    }
261
262    pub fn hourglass<'a>(
263        &self,
264        elapsed: Duration,
265        label: &'a str,
266        hint: Option<&'a str>,
267    ) -> Hourglass<'a> {
268        Hourglass::new(elapsed)
269            .label(label)
270            .hint(hint.filter(|hint| !hint.is_empty()))
271            .glass(self.glass)
272            .accent(self.shimmer)
273            .muted(self.muted)
274    }
275}