1use crate::{Contrast, Palette, SalsaTheme};
2use rat_widget::button::ButtonStyle;
3use rat_widget::calendar::CalendarStyle;
4use rat_widget::checkbox::CheckboxStyle;
5use rat_widget::choice::ChoiceStyle;
6use rat_widget::clipper::ClipperStyle;
7use rat_widget::file_dialog::FileDialogStyle;
8use rat_widget::form::FormStyle;
9use rat_widget::line_number::LineNumberStyle;
10use rat_widget::list::ListStyle;
11use rat_widget::menu::MenuStyle;
12use rat_widget::msgdialog::MsgDialogStyle;
13use rat_widget::paragraph::ParagraphStyle;
14use rat_widget::popup::PopupStyle;
15use rat_widget::radio::{RadioLayout, RadioStyle};
16use rat_widget::scrolled::ScrollStyle;
17use rat_widget::shadow::{ShadowDirection, ShadowStyle};
18use rat_widget::slider::SliderStyle;
19use rat_widget::splitter::SplitStyle;
20use rat_widget::statusline::StatusLineStyle;
21use rat_widget::tabbed::TabbedStyle;
22use rat_widget::table::TableStyle;
23use rat_widget::text::TextStyle;
24use rat_widget::view::ViewStyle;
25use ratatui::layout::Alignment;
26use ratatui::prelude::Style;
27use ratatui::style::{Color, Stylize};
28use ratatui::widgets::{Block, Borders};
29use std::borrow::Cow;
30use std::time::Duration;
31
32#[derive(Debug, Clone)]
34pub struct ShellTheme {
35 p: Palette,
36 name: Box<str>,
37}
38
39impl ShellTheme {
40 pub fn new(name: &str, p: Palette) -> Self {
41 Self {
42 p,
43 name: Box::from(name),
44 }
45 }
46
47 fn fg_style(&self, color: Color) -> Style {
49 Style::new().fg(color)
50 }
51}
52
53impl SalsaTheme for ShellTheme {
54 fn name(&self) -> &str {
55 &self.name
56 }
57
58 fn palette(&self) -> &Palette {
59 &self.p
60 }
61
62 fn white(&self, n: usize) -> Style {
65 self.p.white(n, Contrast::Normal)
66 }
67
68 fn black(&self, n: usize) -> Style {
71 self.p.black(n, Contrast::Normal)
72 }
73
74 fn gray(&self, n: usize) -> Style {
77 self.p.gray(n, Contrast::Normal)
78 }
79
80 fn red(&self, n: usize) -> Style {
83 self.p.red(n, Contrast::Normal)
84 }
85
86 fn orange(&self, n: usize) -> Style {
89 self.p.orange(n, Contrast::Normal)
90 }
91
92 fn yellow(&self, n: usize) -> Style {
95 self.p.yellow(n, Contrast::Normal)
96 }
97
98 fn limegreen(&self, n: usize) -> Style {
101 self.p.limegreen(n, Contrast::Normal)
102 }
103
104 fn green(&self, n: usize) -> Style {
107 self.p.green(n, Contrast::Normal)
108 }
109
110 fn bluegreen(&self, n: usize) -> Style {
113 self.p.bluegreen(n, Contrast::Normal)
114 }
115
116 fn cyan(&self, n: usize) -> Style {
119 self.p.cyan(n, Contrast::Normal)
120 }
121
122 fn blue(&self, n: usize) -> Style {
125 self.p.blue(n, Contrast::Normal)
126 }
127
128 fn deepblue(&self, n: usize) -> Style {
131 self.p.deepblue(n, Contrast::Normal)
132 }
133
134 fn purple(&self, n: usize) -> Style {
137 self.p.purple(n, Contrast::Normal)
138 }
139
140 fn magenta(&self, n: usize) -> Style {
143 self.p.magenta(n, Contrast::Normal)
144 }
145
146 fn redpink(&self, n: usize) -> Style {
149 self.p.redpink(n, Contrast::Normal)
150 }
151
152 fn primary(&self, n: usize) -> Style {
155 self.p.primary(n, Contrast::Normal)
156 }
157
158 fn secondary(&self, n: usize) -> Style {
161 self.p.secondary(n, Contrast::Normal)
162 }
163
164 fn focus(&self) -> Style {
165 self.p.high_contrast(self.p.primary[Palette::BRIGHT_2])
166 }
167
168 fn select(&self) -> Style {
169 self.p.high_contrast(self.p.secondary[Palette::BRIGHT_2])
170 }
171
172 fn text_input(&self) -> Style {
173 self.p.normal_contrast(self.p.gray[Palette::BRIGHT_0])
174 }
175
176 fn text_focus(&self) -> Style {
177 self.p.normal_contrast(self.p.gray[Palette::BRIGHT_3])
178 }
179
180 fn text_select(&self) -> Style {
181 self.p.normal_contrast(self.p.secondary[Palette::BRIGHT_0])
182 }
183
184 fn container_base(&self) -> Style {
186 Default::default()
187 }
188
189 fn container_border(&self) -> Style {
191 Default::default()
192 }
193
194 fn container_arrow(&self) -> Style {
196 Default::default()
197 }
198
199 fn popup_base(&self) -> Style {
201 self.p
202 .style(self.p.gray[Palette::BRIGHT_0], Contrast::Normal)
203 }
204
205 fn popup_border(&self) -> Style {
207 self.popup_base().fg(self.p.gray[Palette::BRIGHT_0])
208 }
209
210 fn popup_arrow(&self) -> Style {
212 self.popup_base().fg(self.p.gray[Palette::BRIGHT_0])
213 }
214
215 fn dialog_base(&self) -> Style {
217 self.p
218 .style(self.p.gray[Palette::BRIGHT_1], Contrast::Normal)
219 }
220
221 fn dialog_border(&self) -> Style {
223 self.dialog_base().fg(self.p.white[Palette::BRIGHT_0])
224 }
225
226 fn dialog_arrow(&self) -> Style {
228 self.dialog_base().fg(self.p.white[Palette::BRIGHT_0])
229 }
230
231 fn status_base(&self) -> Style {
233 Default::default()
234 }
235
236 fn button_base(&self) -> Style {
238 self.p
239 .style(self.p.gray[Palette::BRIGHT_2], Contrast::Normal)
240 }
241
242 fn button_armed(&self) -> Style {
244 self.p
245 .style(self.p.secondary[Palette::BRIGHT_0], Contrast::Normal)
246 }
247
248 fn month_style(&self) -> CalendarStyle {
250 CalendarStyle {
251 style: Default::default(),
252 title: None,
253 weeknum: Some(Style::new().fg(self.p.limegreen[Palette::BRIGHT_0])),
254 weekday: Some(Style::new().fg(self.p.limegreen[Palette::BRIGHT_0])),
255 day: None,
256 select: Some(self.select()),
257 focus: Some(self.focus()),
258 ..CalendarStyle::default()
259 }
260 }
261
262 fn shadow_style(&self) -> ShadowStyle {
264 ShadowStyle {
265 style: Style::new().bg(self.p.black[Palette::DARK_0]),
266 dir: ShadowDirection::BottomRight,
267 ..ShadowStyle::default()
268 }
269 }
270
271 fn line_nr_style(&self) -> LineNumberStyle {
273 LineNumberStyle {
274 style: self.container_base(),
275 cursor: Some(self.text_select()),
276 ..LineNumberStyle::default()
277 }
278 }
279
280 fn textarea_style(&self) -> TextStyle {
282 TextStyle {
283 style: self.text_input(),
284 select: Some(self.text_select()),
285 scroll: Some(self.scroll_style()),
286 border_style: Some(self.container_border()),
287 ..TextStyle::default()
288 }
289 }
290
291 fn text_style(&self) -> TextStyle {
293 TextStyle {
294 style: self.text_input(),
295 focus: Some(self.text_focus()),
296 select: Some(self.text_select()),
297 invalid: Some(self.fg_style(self.p.red[Palette::BRIGHT_3])),
298 ..TextStyle::default()
299 }
300 }
301
302 fn label_style(&self) -> Style {
304 self.container_base()
305 }
306
307 fn paragraph_style(&self) -> ParagraphStyle {
308 ParagraphStyle {
309 style: self.container_base(),
310 focus: Some(self.focus()),
311 scroll: Some(self.scroll_style()),
312 ..Default::default()
313 }
314 }
315
316 fn choice_style(&self) -> ChoiceStyle {
317 ChoiceStyle {
318 style: self.text_input(),
319 select: Some(self.text_select()),
320 focus: Some(self.text_focus()),
321 popup: PopupStyle::default(),
322 popup_style: Some(self.popup_base()),
323 popup_border: Some(self.popup_border()),
324 popup_scroll: Some(self.popup_scroll_style()),
325 popup_block: Some(
326 Block::bordered()
327 .borders(Borders::LEFT)
328 .border_style(self.popup_border()),
329 ),
330 ..Default::default()
331 }
332 }
333
334 fn radio_style(&self) -> RadioStyle {
335 RadioStyle {
336 layout: Some(RadioLayout::Stacked),
337 style: self.text_input(),
338 focus: Some(self.text_focus()),
339 ..Default::default()
340 }
341 }
342
343 fn checkbox_style(&self) -> CheckboxStyle {
345 CheckboxStyle {
346 style: self.text_input(),
347 focus: Some(self.text_focus()),
348 ..Default::default()
349 }
350 }
351
352 fn slider_style(&self) -> SliderStyle {
354 SliderStyle {
355 style: self.text_input(),
356 bounds: Some(self.gray(2)),
357 knob: Some(self.select()),
358 focus: Some(self.focus()),
359 text_align: Some(Alignment::Center),
360 ..Default::default()
361 }
362 }
363
364 fn menu_style(&self) -> MenuStyle {
366 MenuStyle {
367 style: self.status_base(),
368 title: Some(self.fg_style(self.p.yellow[Palette::BRIGHT_2])),
369 focus: Some(self.focus()),
370 right: Some(self.fg_style(self.p.green[Palette::BRIGHT_3])),
371 disabled: Some(self.fg_style(self.p.gray[Palette::BRIGHT_2])),
372 highlight: Some(Style::default().underlined()),
373 block: Some(Block::bordered().style(self.popup_border())),
374 ..Default::default()
375 }
376 }
377
378 fn button_style(&self) -> ButtonStyle {
380 ButtonStyle {
381 style: self.button_base(),
382 focus: Some(self.focus()),
383 armed: Some(self.select()),
384 armed_delay: Some(Duration::from_millis(50)),
385 ..Default::default()
386 }
387 }
388
389 fn table_style(&self) -> TableStyle {
391 TableStyle {
392 style: self.container_base(),
393 select_row: Some(self.select()),
394 show_row_focus: true,
395 focus_style: Some(self.focus()),
396 border_style: Some(self.container_border()),
397 scroll: Some(self.scroll_style()),
398 header: Some(self.fg_style(self.p.green[Palette::BRIGHT_2])),
399 footer: Some(self.fg_style(self.p.green[Palette::BRIGHT_2])),
400 ..Default::default()
401 }
402 }
403
404 fn list_style(&self) -> ListStyle {
406 ListStyle {
407 style: self.container_base(),
408 select: Some(self.select()),
409 focus: Some(self.focus()),
410 scroll: Some(self.scroll_style()),
411 ..Default::default()
412 }
413 }
414
415 fn scroll_style(&self) -> ScrollStyle {
417 ScrollStyle {
418 thumb_style: Some(self.container_border()),
419 track_style: Some(self.container_border()),
420 min_style: Some(self.container_border()),
421 begin_style: Some(self.container_arrow()),
422 end_style: Some(self.container_arrow()),
423 ..Default::default()
424 }
425 }
426
427 fn popup_scroll_style(&self) -> ScrollStyle {
429 ScrollStyle {
430 thumb_style: Some(self.popup_border()),
431 track_style: Some(self.popup_border()),
432 min_style: Some(self.popup_border()),
433 begin_style: Some(self.popup_arrow()),
434 end_style: Some(self.popup_arrow()),
435 ..Default::default()
436 }
437 }
438
439 fn dialog_scroll_style(&self) -> ScrollStyle {
441 ScrollStyle {
442 thumb_style: Some(self.dialog_border()),
443 track_style: Some(self.dialog_border()),
444 min_style: Some(self.dialog_border()),
445 begin_style: Some(self.dialog_arrow()),
446 end_style: Some(self.dialog_arrow()),
447 ..Default::default()
448 }
449 }
450
451 fn split_style(&self) -> SplitStyle {
453 SplitStyle {
454 style: self.container_border(),
455 arrow_style: Some(self.container_arrow()),
456 drag_style: Some(self.focus()),
457 ..Default::default()
458 }
459 }
460
461 fn view_style(&self) -> ViewStyle {
463 ViewStyle {
464 scroll: Some(self.scroll_style()),
465 ..Default::default()
466 }
467 }
468
469 fn tabbed_style(&self) -> TabbedStyle {
471 TabbedStyle {
472 style: self.container_base(),
473 tab: Some(self.button_base()),
474 select: Some(self.button_armed()),
475 focus: Some(self.focus()),
476 ..Default::default()
477 }
478 }
479
480 fn statusline_style(&self) -> Vec<Style> {
482 vec![
483 self.status_base(),
484 self.fg_style(self.p.blue[Palette::BRIGHT_2]),
485 self.fg_style(self.p.blue[Palette::BRIGHT_2]),
486 self.fg_style(self.p.blue[Palette::BRIGHT_2]),
487 ]
488 }
489
490 fn statusline_style_ext(&self) -> StatusLineStyle {
492 StatusLineStyle {
493 sep: Some(Cow::Borrowed("|")),
494 styles: vec![
495 self.status_base(),
496 self.status_base().fg(self.p.blue[Palette::BRIGHT_2]),
497 self.status_base().fg(self.p.blue[Palette::BRIGHT_2]),
498 self.status_base().fg(self.p.blue[Palette::BRIGHT_2]),
499 ],
500 ..Default::default()
501 }
502 }
503
504 fn file_dialog_style(&self) -> FileDialogStyle {
506 FileDialogStyle {
507 style: self.dialog_base(),
508 list: Some(self.list_style()),
509 roots: Some(ListStyle {
510 style: self.dialog_base(),
511 ..self.list_style()
512 }),
513 text: Some(self.text_style()),
514 button: Some(self.button_style()),
515 block: Some(Block::bordered()),
516 ..Default::default()
517 }
518 }
519
520 fn msg_dialog_style(&self) -> MsgDialogStyle {
522 MsgDialogStyle {
523 style: self.dialog_base(),
524 button: Some(self.button_style()),
525 ..Default::default()
526 }
527 }
528
529 fn form_style(&self) -> FormStyle {
531 FormStyle {
532 style: self.container_base(),
533 navigation: Some(self.container_arrow()),
534 block: Some(
535 Block::default()
536 .borders(Borders::TOP)
537 .border_style(self.container_border()),
538 ),
539 ..Default::default()
540 }
541 }
542
543 fn clipper_style(&self) -> ClipperStyle {
545 ClipperStyle {
546 style: self.container_base(),
547 scroll: Some(self.scroll_style()),
548 ..Default::default()
549 }
550 }
551
552 fn textview_style(&self) -> TextStyle {
553 TextStyle {
554 style: self.container_base(),
555 focus: Some(self.container_base()),
556 select: Some(self.text_select()),
557 scroll: Some(self.scroll_style()),
558 border_style: Some(self.container_border()),
559 ..TextStyle::default()
560 }
561 }
562}