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;
13#[allow(deprecated)]
14use rat_widget::pager::PagerStyle;
15use rat_widget::paragraph::ParagraphStyle;
16use rat_widget::popup::PopupStyle;
17use rat_widget::radio::{RadioLayout, RadioStyle};
18use rat_widget::scrolled::ScrollStyle;
19use rat_widget::shadow::{ShadowDirection, ShadowStyle};
20use rat_widget::slider::SliderStyle;
21use rat_widget::splitter::SplitStyle;
22use rat_widget::statusline::StatusLineStyle;
23use rat_widget::tabbed::TabbedStyle;
24use rat_widget::table::TableStyle;
25use rat_widget::text::TextStyle;
26use rat_widget::view::ViewStyle;
27use ratatui::layout::Alignment;
28use ratatui::prelude::Style;
29use ratatui::style::{Color, Stylize};
30use ratatui::widgets::{Block, Borders};
31use std::borrow::Cow;
32use std::time::Duration;
33
34/// A sample theme for shell usage.
35#[derive(Debug, Clone)]
36pub struct ShellTheme {
37    p: Palette,
38    name: Box<str>,
39}
40
41impl ShellTheme {
42    pub fn new(name: &str, p: Palette) -> Self {
43        Self {
44            p,
45            name: Box::from(name),
46        }
47    }
48
49    /// Create a style with only a text foreground color
50    fn fg_style(&self, color: Color) -> Style {
51        Style::new().fg(color)
52    }
53}
54
55impl SalsaTheme for ShellTheme {
56    fn name(&self) -> &str {
57        &self.name
58    }
59
60    fn palette(&self) -> &Palette {
61        &self.p
62    }
63
64    /// Create a style from the given white shade.
65    /// n is `0..8`
66    fn white(&self, n: usize) -> Style {
67        self.p.white(n, Contrast::Normal)
68    }
69
70    /// Create a style from the given black shade.
71    /// n is `0..8`
72    fn black(&self, n: usize) -> Style {
73        self.p.black(n, Contrast::Normal)
74    }
75
76    /// Create a style from the given gray shade.
77    /// n is `0..8`
78    fn gray(&self, n: usize) -> Style {
79        self.p.gray(n, Contrast::Normal)
80    }
81
82    /// Create a style from the given red shade.
83    /// n is `0..8`
84    fn red(&self, n: usize) -> Style {
85        self.p.red(n, Contrast::Normal)
86    }
87
88    /// Create a style from the given orange shade.
89    /// n is `0..8`
90    fn orange(&self, n: usize) -> Style {
91        self.p.orange(n, Contrast::Normal)
92    }
93
94    /// Create a style from the given yellow shade.
95    /// n is `0..8`
96    fn yellow(&self, n: usize) -> Style {
97        self.p.yellow(n, Contrast::Normal)
98    }
99
100    /// Create a style from the given limegreen shade.
101    /// n is `0..8`
102    fn limegreen(&self, n: usize) -> Style {
103        self.p.limegreen(n, Contrast::Normal)
104    }
105
106    /// Create a style from the given green shade.
107    /// n is `0..8`
108    fn green(&self, n: usize) -> Style {
109        self.p.green(n, Contrast::Normal)
110    }
111
112    /// Create a style from the given bluegreen shade.
113    /// n is `0..8`
114    fn bluegreen(&self, n: usize) -> Style {
115        self.p.bluegreen(n, Contrast::Normal)
116    }
117
118    /// Create a style from the given cyan shade.
119    /// n is `0..8`
120    fn cyan(&self, n: usize) -> Style {
121        self.p.cyan(n, Contrast::Normal)
122    }
123
124    /// Create a style from the given blue shade.
125    /// n is `0..8`
126    fn blue(&self, n: usize) -> Style {
127        self.p.blue(n, Contrast::Normal)
128    }
129
130    /// Create a style from the given deepblue shade.
131    /// n is `0..8`
132    fn deepblue(&self, n: usize) -> Style {
133        self.p.deepblue(n, Contrast::Normal)
134    }
135
136    /// Create a style from the given purple shade.
137    /// n is `0..8`
138    fn purple(&self, n: usize) -> Style {
139        self.p.purple(n, Contrast::Normal)
140    }
141
142    /// Create a style from the given magenta shade.
143    /// n is `0..8`
144    fn magenta(&self, n: usize) -> Style {
145        self.p.magenta(n, Contrast::Normal)
146    }
147
148    /// Create a style from the given redpink shade.
149    /// n is `0..8`
150    fn redpink(&self, n: usize) -> Style {
151        self.p.redpink(n, Contrast::Normal)
152    }
153
154    /// Create a style from the given primary shade.
155    /// n is `0..8`
156    fn primary(&self, n: usize) -> Style {
157        self.p.primary(n, Contrast::Normal)
158    }
159
160    /// Create a style from the given secondary shade.
161    /// n is `0..8`
162    fn secondary(&self, n: usize) -> Style {
163        self.p.secondary(n, Contrast::Normal)
164    }
165
166    fn focus(&self) -> Style {
167        self.p.high_contrast(self.p.primary[Palette::BRIGHT_2])
168    }
169
170    fn select(&self) -> Style {
171        self.p.high_contrast(self.p.secondary[Palette::BRIGHT_2])
172    }
173
174    fn text_input(&self) -> Style {
175        self.p.normal_contrast(self.p.gray[Palette::BRIGHT_0])
176    }
177
178    fn text_focus(&self) -> Style {
179        self.p.normal_contrast(self.p.gray[Palette::BRIGHT_3])
180    }
181
182    fn text_select(&self) -> Style {
183        self.p.normal_contrast(self.p.secondary[Palette::BRIGHT_0])
184    }
185
186    /// Container base
187    fn container_base(&self) -> Style {
188        Default::default()
189    }
190
191    /// Container border
192    fn container_border(&self) -> Style {
193        Default::default()
194    }
195
196    /// Container arrows
197    fn container_arrow(&self) -> Style {
198        Default::default()
199    }
200
201    /// Background for popups.
202    fn popup_base(&self) -> Style {
203        self.p
204            .style(self.p.gray[Palette::BRIGHT_0], Contrast::Normal)
205    }
206
207    /// Dialog arrows
208    fn popup_border(&self) -> Style {
209        self.popup_base().fg(self.p.gray[Palette::BRIGHT_0])
210    }
211
212    /// Dialog arrows
213    fn popup_arrow(&self) -> Style {
214        self.popup_base().fg(self.p.gray[Palette::BRIGHT_0])
215    }
216
217    /// Background for dialogs.
218    fn dialog_base(&self) -> Style {
219        self.p
220            .style(self.p.gray[Palette::BRIGHT_1], Contrast::Normal)
221    }
222
223    /// Dialog arrows
224    fn dialog_border(&self) -> Style {
225        self.dialog_base().fg(self.p.white[Palette::BRIGHT_0])
226    }
227
228    /// Dialog arrows
229    fn dialog_arrow(&self) -> Style {
230        self.dialog_base().fg(self.p.white[Palette::BRIGHT_0])
231    }
232
233    /// Style for the status line.
234    fn status_base(&self) -> Style {
235        Default::default()
236    }
237
238    /// Base style for buttons.
239    fn button_base(&self) -> Style {
240        self.p
241            .style(self.p.gray[Palette::BRIGHT_2], Contrast::Normal)
242    }
243
244    /// Armed style for buttons.
245    fn button_armed(&self) -> Style {
246        self.p
247            .style(self.p.secondary[Palette::BRIGHT_0], Contrast::Normal)
248    }
249
250    /// Complete MonthStyle.
251    fn month_style(&self) -> CalendarStyle {
252        CalendarStyle {
253            style: Default::default(),
254            title: None,
255            weeknum: Some(Style::new().fg(self.p.limegreen[Palette::BRIGHT_0])),
256            weekday: Some(Style::new().fg(self.p.limegreen[Palette::BRIGHT_0])),
257            day: None,
258            select: Some(self.select()),
259            focus: Some(self.focus()),
260            ..CalendarStyle::default()
261        }
262    }
263
264    /// Style for shadows.
265    fn shadow_style(&self) -> ShadowStyle {
266        ShadowStyle {
267            style: Style::new().bg(self.p.black[Palette::DARK_0]),
268            dir: ShadowDirection::BottomRight,
269            ..ShadowStyle::default()
270        }
271    }
272
273    /// Style for LineNumbers.
274    fn line_nr_style(&self) -> LineNumberStyle {
275        LineNumberStyle {
276            style: self.container_base(),
277            cursor: Some(self.text_select()),
278            ..LineNumberStyle::default()
279        }
280    }
281
282    /// Complete TextAreaStyle
283    fn textarea_style(&self) -> TextStyle {
284        TextStyle {
285            style: self.text_input(),
286            select: Some(self.text_select()),
287            scroll: Some(self.scroll_style()),
288            border_style: Some(self.container_border()),
289            ..TextStyle::default()
290        }
291    }
292
293    /// Complete TextInputStyle
294    fn text_style(&self) -> TextStyle {
295        TextStyle {
296            style: self.text_input(),
297            focus: Some(self.text_focus()),
298            select: Some(self.text_select()),
299            invalid: Some(self.fg_style(self.p.red[Palette::BRIGHT_3])),
300            ..TextStyle::default()
301        }
302    }
303
304    /// Text-label style.
305    fn label_style(&self) -> Style {
306        self.container_base()
307    }
308
309    fn paragraph_style(&self) -> ParagraphStyle {
310        ParagraphStyle {
311            style: self.container_base(),
312            focus: Some(self.focus()),
313            scroll: Some(self.scroll_style()),
314            ..Default::default()
315        }
316    }
317
318    #[allow(deprecated)]
319    fn choice_style(&self) -> ChoiceStyle {
320        ChoiceStyle {
321            style: self.text_input(),
322            select: Some(self.text_select()),
323            focus: Some(self.text_focus()),
324            popup: PopupStyle::default(),
325            popup_style: Some(self.popup_base()),
326            popup_border: Some(self.popup_border()),
327            popup_scroll: Some(self.popup_scroll_style()),
328            popup_block: Some(
329                Block::bordered()
330                    .borders(Borders::LEFT)
331                    .border_style(self.popup_border()),
332            ),
333            ..Default::default()
334        }
335    }
336
337    fn radio_style(&self) -> RadioStyle {
338        RadioStyle {
339            layout: Some(RadioLayout::Stacked),
340            style: self.text_input(),
341            focus: Some(self.text_focus()),
342            ..Default::default()
343        }
344    }
345
346    /// Complete CheckboxStyle
347    fn checkbox_style(&self) -> CheckboxStyle {
348        CheckboxStyle {
349            style: self.text_input(),
350            focus: Some(self.text_focus()),
351            ..Default::default()
352        }
353    }
354
355    /// Slider Style
356    fn slider_style(&self) -> SliderStyle {
357        SliderStyle {
358            style: self.text_input(),
359            bounds: Some(self.gray(2)),
360            knob: Some(self.select()),
361            focus: Some(self.focus()),
362            text_align: Some(Alignment::Center),
363            ..Default::default()
364        }
365    }
366
367    /// Complete MenuStyle
368    fn menu_style(&self) -> MenuStyle {
369        MenuStyle {
370            style: self.status_base(),
371            title: Some(self.fg_style(self.p.yellow[Palette::BRIGHT_2])),
372            focus: Some(self.focus()),
373            right: Some(self.fg_style(self.p.green[Palette::BRIGHT_3])),
374            disabled: Some(self.fg_style(self.p.gray[Palette::BRIGHT_2])),
375            highlight: Some(Style::default().underlined()),
376            block: Some(Block::bordered().style(self.popup_border())),
377            ..Default::default()
378        }
379    }
380
381    /// Complete ButtonStyle
382    fn button_style(&self) -> ButtonStyle {
383        ButtonStyle {
384            style: self.button_base(),
385            focus: Some(self.focus()),
386            armed: Some(self.select()),
387            armed_delay: Some(Duration::from_millis(50)),
388            ..Default::default()
389        }
390    }
391
392    /// Complete TableStyle
393    fn table_style(&self) -> TableStyle {
394        TableStyle {
395            style: self.container_base(),
396            select_row: Some(self.select()),
397            show_row_focus: true,
398            focus_style: Some(self.focus()),
399            border_style: Some(self.container_border()),
400            scroll: Some(self.scroll_style()),
401            header: Some(self.fg_style(self.p.green[Palette::BRIGHT_2])),
402            footer: Some(self.fg_style(self.p.green[Palette::BRIGHT_2])),
403            ..Default::default()
404        }
405    }
406
407    /// Complete ListStyle
408    fn list_style(&self) -> ListStyle {
409        ListStyle {
410            style: self.container_base(),
411            select: Some(self.select()),
412            focus: Some(self.focus()),
413            scroll: Some(self.scroll_style()),
414            ..Default::default()
415        }
416    }
417
418    /// Scroll style
419    fn scroll_style(&self) -> ScrollStyle {
420        ScrollStyle {
421            thumb_style: Some(self.container_border()),
422            track_style: Some(self.container_border()),
423            min_style: Some(self.container_border()),
424            begin_style: Some(self.container_arrow()),
425            end_style: Some(self.container_arrow()),
426            ..Default::default()
427        }
428    }
429
430    /// Popup scroll style
431    fn popup_scroll_style(&self) -> ScrollStyle {
432        ScrollStyle {
433            thumb_style: Some(self.popup_border()),
434            track_style: Some(self.popup_border()),
435            min_style: Some(self.popup_border()),
436            begin_style: Some(self.popup_arrow()),
437            end_style: Some(self.popup_arrow()),
438            ..Default::default()
439        }
440    }
441
442    /// Dialog scroll style
443    fn dialog_scroll_style(&self) -> ScrollStyle {
444        ScrollStyle {
445            thumb_style: Some(self.dialog_border()),
446            track_style: Some(self.dialog_border()),
447            min_style: Some(self.dialog_border()),
448            begin_style: Some(self.dialog_arrow()),
449            end_style: Some(self.dialog_arrow()),
450            ..Default::default()
451        }
452    }
453
454    /// Split style
455    fn split_style(&self) -> SplitStyle {
456        SplitStyle {
457            style: self.container_border(),
458            arrow_style: Some(self.container_arrow()),
459            drag_style: Some(self.focus()),
460            ..Default::default()
461        }
462    }
463
464    /// View style
465    fn view_style(&self) -> ViewStyle {
466        ViewStyle {
467            scroll: Some(self.scroll_style()),
468            ..Default::default()
469        }
470    }
471
472    /// Tabbed style
473    fn tabbed_style(&self) -> TabbedStyle {
474        TabbedStyle {
475            style: self.container_base(),
476            tab: Some(self.button_base()),
477            select: Some(self.button_armed()),
478            focus: Some(self.focus()),
479            ..Default::default()
480        }
481    }
482
483    /// Complete StatusLineStyle for a StatusLine with 3 indicator fields.
484    fn statusline_style(&self) -> Vec<Style> {
485        vec![
486            self.status_base(),
487            self.fg_style(self.p.blue[Palette::BRIGHT_2]),
488            self.fg_style(self.p.blue[Palette::BRIGHT_2]),
489            self.fg_style(self.p.blue[Palette::BRIGHT_2]),
490        ]
491    }
492
493    /// StatusLineStyle for a StatusLine with 3 indicator fields.
494    fn statusline_style_ext(&self) -> StatusLineStyle {
495        StatusLineStyle {
496            sep: Some(Cow::Borrowed("|")),
497            styles: vec![
498                self.status_base(),
499                self.status_base().fg(self.p.blue[Palette::BRIGHT_2]),
500                self.status_base().fg(self.p.blue[Palette::BRIGHT_2]),
501                self.status_base().fg(self.p.blue[Palette::BRIGHT_2]),
502            ],
503            ..Default::default()
504        }
505    }
506
507    /// FileDialog style.
508    fn file_dialog_style(&self) -> FileDialogStyle {
509        FileDialogStyle {
510            style: self.dialog_base(),
511            list: Some(self.list_style()),
512            roots: Some(ListStyle {
513                style: self.dialog_base(),
514                ..self.list_style()
515            }),
516            text: Some(self.text_style()),
517            button: Some(self.button_style()),
518            block: Some(Block::bordered()),
519            ..Default::default()
520        }
521    }
522
523    /// Complete MsgDialogStyle.
524    fn msg_dialog_style(&self) -> MsgDialogStyle {
525        MsgDialogStyle {
526            style: self.dialog_base(),
527            button: Some(self.button_style()),
528            ..Default::default()
529        }
530    }
531
532    /// Pager style.
533    #[allow(deprecated)]
534    fn pager_style(&self) -> PagerStyle {
535        PagerStyle {
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    /// Form style.
548    fn form_style(&self) -> FormStyle {
549        FormStyle {
550            style: self.container_base(),
551            navigation: Some(self.container_arrow()),
552            block: Some(
553                Block::default()
554                    .borders(Borders::TOP)
555                    .border_style(self.container_border()),
556            ),
557            ..Default::default()
558        }
559    }
560
561    /// Clipper style.
562    fn clipper_style(&self) -> ClipperStyle {
563        ClipperStyle {
564            style: self.container_base(),
565            scroll: Some(self.scroll_style()),
566            ..Default::default()
567        }
568    }
569
570    fn textview_style(&self) -> TextStyle {
571        TextStyle {
572            style: self.container_base(),
573            focus: Some(self.container_base()),
574            select: Some(self.text_select()),
575            scroll: Some(self.scroll_style()),
576            border_style: Some(self.container_border()),
577            ..TextStyle::default()
578        }
579    }
580}