rat_theme/
dark_theme.rs

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