rat_theme2/
dark_theme.rs

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