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}