rat_theme3/
dark_theme.rs

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