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::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/// 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            block: Some(Block::bordered().style(self.popup_border())),
374            ..Default::default()
375        }
376    }
377
378    /// Complete ButtonStyle
379    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    /// Complete TableStyle
390    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    /// Complete ListStyle
405    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    /// Scroll style
416    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    /// Popup scroll style
428    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    /// Dialog scroll style
440    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    /// Split style
452    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    /// View style
462    fn view_style(&self) -> ViewStyle {
463        ViewStyle {
464            scroll: Some(self.scroll_style()),
465            ..Default::default()
466        }
467    }
468
469    /// Tabbed style
470    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    /// Complete StatusLineStyle for a StatusLine with 3 indicator fields.
481    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    /// StatusLineStyle for a StatusLine with 3 indicator fields.
491    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    /// FileDialog style.
505    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    /// Complete MsgDialogStyle.
521    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    /// Form style.
530    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    /// Clipper style.
544    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}