rat_theme3/
shell_theme.rs

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_core::layout::Alignment;
26use ratatui_core::style::{Color, Style};
27use ratatui_widgets::block::Block;
28use ratatui_widgets::borders::Borders;
29use std::borrow::Cow;
30use std::time::Duration;
31
32/// A sample theme for shell usage.
33#[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    /// Create a style with only a text foreground color
48    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    /// Create a style from the given white shade.
63    /// n is `0..8`
64    fn white(&self, n: usize) -> Style {
65        self.p.white(n, Contrast::Normal)
66    }
67
68    /// Create a style from the given black shade.
69    /// n is `0..8`
70    fn black(&self, n: usize) -> Style {
71        self.p.black(n, Contrast::Normal)
72    }
73
74    /// Create a style from the given gray shade.
75    /// n is `0..8`
76    fn gray(&self, n: usize) -> Style {
77        self.p.gray(n, Contrast::Normal)
78    }
79
80    /// Create a style from the given red shade.
81    /// n is `0..8`
82    fn red(&self, n: usize) -> Style {
83        self.p.red(n, Contrast::Normal)
84    }
85
86    /// Create a style from the given orange shade.
87    /// n is `0..8`
88    fn orange(&self, n: usize) -> Style {
89        self.p.orange(n, Contrast::Normal)
90    }
91
92    /// Create a style from the given yellow shade.
93    /// n is `0..8`
94    fn yellow(&self, n: usize) -> Style {
95        self.p.yellow(n, Contrast::Normal)
96    }
97
98    /// Create a style from the given limegreen shade.
99    /// n is `0..8`
100    fn limegreen(&self, n: usize) -> Style {
101        self.p.limegreen(n, Contrast::Normal)
102    }
103
104    /// Create a style from the given green shade.
105    /// n is `0..8`
106    fn green(&self, n: usize) -> Style {
107        self.p.green(n, Contrast::Normal)
108    }
109
110    /// Create a style from the given bluegreen shade.
111    /// n is `0..8`
112    fn bluegreen(&self, n: usize) -> Style {
113        self.p.bluegreen(n, Contrast::Normal)
114    }
115
116    /// Create a style from the given cyan shade.
117    /// n is `0..8`
118    fn cyan(&self, n: usize) -> Style {
119        self.p.cyan(n, Contrast::Normal)
120    }
121
122    /// Create a style from the given blue shade.
123    /// n is `0..8`
124    fn blue(&self, n: usize) -> Style {
125        self.p.blue(n, Contrast::Normal)
126    }
127
128    /// Create a style from the given deepblue shade.
129    /// n is `0..8`
130    fn deepblue(&self, n: usize) -> Style {
131        self.p.deepblue(n, Contrast::Normal)
132    }
133
134    /// Create a style from the given purple shade.
135    /// n is `0..8`
136    fn purple(&self, n: usize) -> Style {
137        self.p.purple(n, Contrast::Normal)
138    }
139
140    /// Create a style from the given magenta shade.
141    /// n is `0..8`
142    fn magenta(&self, n: usize) -> Style {
143        self.p.magenta(n, Contrast::Normal)
144    }
145
146    /// Create a style from the given redpink shade.
147    /// n is `0..8`
148    fn redpink(&self, n: usize) -> Style {
149        self.p.redpink(n, Contrast::Normal)
150    }
151
152    /// Create a style from the given primary shade.
153    /// n is `0..8`
154    fn primary(&self, n: usize) -> Style {
155        self.p.primary(n, Contrast::Normal)
156    }
157
158    /// Create a style from the given secondary shade.
159    /// n is `0..8`
160    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    /// Container base
185    fn container_base(&self) -> Style {
186        Default::default()
187    }
188
189    /// Container border
190    fn container_border(&self) -> Style {
191        Default::default()
192    }
193
194    /// Container arrows
195    fn container_arrow(&self) -> Style {
196        Default::default()
197    }
198
199    /// Background for popups.
200    fn popup_base(&self) -> Style {
201        self.p
202            .style(self.p.gray[Palette::BRIGHT_0], Contrast::Normal)
203    }
204
205    /// Dialog arrows
206    fn popup_border(&self) -> Style {
207        self.popup_base().fg(self.p.gray[Palette::BRIGHT_0])
208    }
209
210    /// Dialog arrows
211    fn popup_arrow(&self) -> Style {
212        self.popup_base().fg(self.p.gray[Palette::BRIGHT_0])
213    }
214
215    /// Background for dialogs.
216    fn dialog_base(&self) -> Style {
217        self.p
218            .style(self.p.gray[Palette::BRIGHT_1], Contrast::Normal)
219    }
220
221    /// Dialog arrows
222    fn dialog_border(&self) -> Style {
223        self.dialog_base().fg(self.p.white[Palette::BRIGHT_0])
224    }
225
226    /// Dialog arrows
227    fn dialog_arrow(&self) -> Style {
228        self.dialog_base().fg(self.p.white[Palette::BRIGHT_0])
229    }
230
231    /// Style for the status line.
232    fn status_base(&self) -> Style {
233        Default::default()
234    }
235
236    /// Base style for buttons.
237    fn button_base(&self) -> Style {
238        self.p
239            .style(self.p.gray[Palette::BRIGHT_2], Contrast::Normal)
240    }
241
242    /// Armed style for buttons.
243    fn button_armed(&self) -> Style {
244        self.p
245            .style(self.p.secondary[Palette::BRIGHT_0], Contrast::Normal)
246    }
247
248    /// Complete MonthStyle.
249    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    /// Style for shadows.
263    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    /// Style for LineNumbers.
272    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    /// Complete TextAreaStyle
281    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    /// Complete TextInputStyle
292    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    /// Text-label style.
303    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    /// Complete CheckboxStyle
344    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    /// Slider Style
353    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    /// Complete MenuStyle
365    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            popup_block: Some(Block::bordered().style(self.popup_border())),
374            popup_focus: Some(self.focus()),
375            popup_right: Some(self.fg_style(self.p.green[Palette::BRIGHT_3])),
376            popup_disabled: Some(self.fg_style(self.p.gray[Palette::BRIGHT_2])),
377            popup_highlight: Some(Style::default().underlined()),
378            ..Default::default()
379        }
380    }
381
382    /// Complete ButtonStyle
383    fn button_style(&self) -> ButtonStyle {
384        ButtonStyle {
385            style: self.button_base(),
386            focus: Some(self.focus()),
387            armed: Some(self.select()),
388            armed_delay: Some(Duration::from_millis(50)),
389            ..Default::default()
390        }
391    }
392
393    /// Complete TableStyle
394    fn table_style(&self) -> TableStyle {
395        TableStyle {
396            style: self.container_base(),
397            select_row: Some(self.select()),
398            show_row_focus: true,
399            focus_style: Some(self.focus()),
400            border_style: Some(self.container_border()),
401            scroll: Some(self.scroll_style()),
402            header: Some(self.fg_style(self.p.green[Palette::BRIGHT_2])),
403            footer: Some(self.fg_style(self.p.green[Palette::BRIGHT_2])),
404            ..Default::default()
405        }
406    }
407
408    /// Complete ListStyle
409    fn list_style(&self) -> ListStyle {
410        ListStyle {
411            style: self.container_base(),
412            select: Some(self.select()),
413            focus: Some(self.focus()),
414            scroll: Some(self.scroll_style()),
415            ..Default::default()
416        }
417    }
418
419    /// Scroll style
420    fn scroll_style(&self) -> ScrollStyle {
421        ScrollStyle {
422            thumb_style: Some(self.container_border()),
423            track_style: Some(self.container_border()),
424            min_style: Some(self.container_border()),
425            begin_style: Some(self.container_arrow()),
426            end_style: Some(self.container_arrow()),
427            ..Default::default()
428        }
429    }
430
431    /// Popup scroll style
432    fn popup_scroll_style(&self) -> ScrollStyle {
433        ScrollStyle {
434            thumb_style: Some(self.popup_border()),
435            track_style: Some(self.popup_border()),
436            min_style: Some(self.popup_border()),
437            begin_style: Some(self.popup_arrow()),
438            end_style: Some(self.popup_arrow()),
439            ..Default::default()
440        }
441    }
442
443    /// Dialog scroll style
444    fn dialog_scroll_style(&self) -> ScrollStyle {
445        ScrollStyle {
446            thumb_style: Some(self.dialog_border()),
447            track_style: Some(self.dialog_border()),
448            min_style: Some(self.dialog_border()),
449            begin_style: Some(self.dialog_arrow()),
450            end_style: Some(self.dialog_arrow()),
451            ..Default::default()
452        }
453    }
454
455    /// Split style
456    fn split_style(&self) -> SplitStyle {
457        SplitStyle {
458            style: self.container_border(),
459            arrow_style: Some(self.container_arrow()),
460            drag_style: Some(self.focus()),
461            ..Default::default()
462        }
463    }
464
465    /// View style
466    fn view_style(&self) -> ViewStyle {
467        ViewStyle {
468            scroll: Some(self.scroll_style()),
469            ..Default::default()
470        }
471    }
472
473    /// Tabbed style
474    fn tabbed_style(&self) -> TabbedStyle {
475        TabbedStyle {
476            style: self.container_base(),
477            tab: Some(self.button_base()),
478            select: Some(self.button_armed()),
479            focus: Some(self.focus()),
480            ..Default::default()
481        }
482    }
483
484    /// Complete StatusLineStyle for a StatusLine with 3 indicator fields.
485    fn statusline_style(&self) -> Vec<Style> {
486        vec![
487            self.status_base(),
488            self.fg_style(self.p.blue[Palette::BRIGHT_2]),
489            self.fg_style(self.p.blue[Palette::BRIGHT_2]),
490            self.fg_style(self.p.blue[Palette::BRIGHT_2]),
491        ]
492    }
493
494    /// StatusLineStyle for a StatusLine with 3 indicator fields.
495    fn statusline_style_ext(&self) -> StatusLineStyle {
496        StatusLineStyle {
497            sep: Some(Cow::Borrowed("|")),
498            styles: vec![
499                self.status_base(),
500                self.status_base().fg(self.p.blue[Palette::BRIGHT_2]),
501                self.status_base().fg(self.p.blue[Palette::BRIGHT_2]),
502                self.status_base().fg(self.p.blue[Palette::BRIGHT_2]),
503            ],
504            ..Default::default()
505        }
506    }
507
508    /// FileDialog style.
509    fn file_dialog_style(&self) -> FileDialogStyle {
510        FileDialogStyle {
511            style: self.dialog_base(),
512            list: Some(self.list_style()),
513            roots: Some(ListStyle {
514                style: self.dialog_base(),
515                ..self.list_style()
516            }),
517            text: Some(self.text_style()),
518            button: Some(self.button_style()),
519            block: Some(Block::bordered()),
520            ..Default::default()
521        }
522    }
523
524    /// Complete MsgDialogStyle.
525    fn msg_dialog_style(&self) -> MsgDialogStyle {
526        MsgDialogStyle {
527            style: self.dialog_base(),
528            button: Some(self.button_style()),
529            ..Default::default()
530        }
531    }
532
533    /// Form style.
534    fn form_style(&self) -> FormStyle {
535        FormStyle {
536            style: self.container_base(),
537            navigation: Some(self.container_arrow()),
538            block: Some(
539                Block::default()
540                    .borders(Borders::TOP)
541                    .border_style(self.container_border()),
542            ),
543            ..Default::default()
544        }
545    }
546
547    /// Clipper style.
548    fn clipper_style(&self) -> ClipperStyle {
549        ClipperStyle {
550            style: self.container_base(),
551            scroll: Some(self.scroll_style()),
552            ..Default::default()
553        }
554    }
555
556    fn textview_style(&self) -> TextStyle {
557        TextStyle {
558            style: self.container_base(),
559            focus: Some(self.container_base()),
560            select: Some(self.text_select()),
561            scroll: Some(self.scroll_style()),
562            border_style: Some(self.container_border()),
563            ..TextStyle::default()
564        }
565    }
566}