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