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_core::style::Color;
27use ratatui_core::style::Style;
28use ratatui_widgets::block::Block;
29use ratatui_widgets::borders::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_style: Some(self.popup_base()),
353            popup_border: Some(self.popup_border()),
354            popup_scroll: Some(self.popup_scroll_style()),
355            popup_block: Some(
356                Block::bordered()
357                    .borders(Borders::LEFT)
358                    .border_style(self.popup_border()),
359            ),
360            ..Default::default()
361        }
362    }
363
364    pub fn radio_style(&self) -> RadioStyle {
365        RadioStyle {
366            layout: Some(RadioLayout::Stacked),
367            style: self.text_input(),
368            focus: Some(self.text_focus()),
369            ..Default::default()
370        }
371    }
372
373    /// Complete CheckboxStyle
374    pub fn checkbox_style(&self) -> CheckboxStyle {
375        CheckboxStyle {
376            style: self.text_input(),
377            focus: Some(self.text_focus()),
378            ..Default::default()
379        }
380    }
381
382    /// Complete MenuStyle
383    pub fn menu_style(&self) -> MenuStyle {
384        let menu = Style::default().fg(self.s.white[3]).bg(self.s.black[2]);
385        MenuStyle {
386            style: menu,
387            title: Some(Style::default().fg(self.s.black[0]).bg(self.s.yellow[2])),
388            focus: Some(self.focus()),
389            right: Some(Style::default().fg(self.s.bluegreen[0])),
390            disabled: Some(Style::default().fg(self.s.gray[0])),
391            highlight: Some(Style::default().underlined()),
392            popup_block: Some(Block::bordered()),
393            popup_focus: Some(self.focus()),
394            popup_right: Some(Style::default().fg(self.s.bluegreen[0])),
395            popup_disabled: Some(Style::default().fg(self.s.gray[0])),
396            popup_highlight: Some(Style::default().underlined()),
397            popup: PopupStyle::default(),
398            ..Default::default()
399        }
400    }
401
402    /// Complete ButtonStyle
403    pub fn button_style(&self) -> ButtonStyle {
404        ButtonStyle {
405            style: self.button_base(),
406            focus: Some(self.focus()),
407            armed: Some(self.select()),
408            armed_delay: Some(Duration::from_millis(50)),
409            ..Default::default()
410        }
411    }
412
413    /// Complete TableStyle
414    pub fn table_style(&self) -> TableStyle {
415        TableStyle {
416            style: self.container_base(),
417            select_row: Some(self.select()),
418            show_row_focus: true,
419            focus_style: Some(self.focus()),
420            border_style: Some(self.container_border()),
421            scroll: Some(self.scroll_style()),
422            ..Default::default()
423        }
424    }
425
426    pub fn table_header(&self) -> Style {
427        self.style(self.s.blue[2])
428    }
429
430    pub fn table_footer(&self) -> Style {
431        self.style(self.s.blue[2])
432    }
433
434    /// Complete ListStyle
435    pub fn list_style(&self) -> ListStyle {
436        ListStyle {
437            style: self.container_base(),
438            select: Some(self.select()),
439            focus: Some(self.focus()),
440            scroll: Some(self.scroll_style()),
441            ..Default::default()
442        }
443    }
444
445    /// Scroll style
446    pub fn scroll_style(&self) -> ScrollStyle {
447        ScrollStyle {
448            thumb_style: Some(self.container_border()),
449            track_style: Some(self.container_border()),
450            min_style: Some(self.container_border()),
451            begin_style: Some(self.container_arrow()),
452            end_style: Some(self.container_arrow()),
453            ..Default::default()
454        }
455    }
456
457    /// Popup scroll style
458    pub fn popup_scroll_style(&self) -> ScrollStyle {
459        ScrollStyle {
460            thumb_style: Some(self.popup_border()),
461            track_style: Some(self.popup_border()),
462            min_style: Some(self.popup_border()),
463            begin_style: Some(self.popup_arrow()),
464            end_style: Some(self.popup_arrow()),
465            ..Default::default()
466        }
467    }
468
469    /// Dialog scroll style
470    pub fn dialog_scroll_style(&self) -> ScrollStyle {
471        ScrollStyle {
472            thumb_style: Some(self.dialog_border()),
473            track_style: Some(self.dialog_border()),
474            min_style: Some(self.dialog_border()),
475            begin_style: Some(self.dialog_arrow()),
476            end_style: Some(self.dialog_arrow()),
477            ..Default::default()
478        }
479    }
480
481    /// Split style
482    pub fn split_style(&self) -> SplitStyle {
483        SplitStyle {
484            style: self.container_border(),
485            arrow_style: Some(self.container_arrow()),
486            drag_style: Some(self.focus()),
487            ..Default::default()
488        }
489    }
490
491    /// View style
492    pub fn view_style(&self) -> ViewStyle {
493        ViewStyle {
494            scroll: Some(self.scroll_style()),
495            ..Default::default()
496        }
497    }
498
499    /// Tabbed style
500    pub fn tabbed_style(&self) -> TabbedStyle {
501        TabbedStyle {
502            style: self.container_border(),
503            tab: Some(self.gray(1)),
504            select: Some(self.gray(3)),
505            focus: Some(self.focus()),
506            ..Default::default()
507        }
508    }
509
510    /// Complete StatusLineStyle for a StatusLine with 3 indicator fields.
511    /// This is what I need for the
512    /// [minimal](https://github.com/thscharler/rat-salsa/blob/master/examples/minimal.rs)
513    /// example, which shows timings for Render/Event/Action.
514    pub fn statusline_style(&self) -> Vec<Style> {
515        vec![
516            self.status_base(),
517            Style::default()
518                .fg(self.s.text_color(self.s.white[0]))
519                .bg(self.s.blue[3]),
520            Style::default()
521                .fg(self.s.text_color(self.s.white[0]))
522                .bg(self.s.blue[2]),
523            Style::default()
524                .fg(self.s.text_color(self.s.white[0]))
525                .bg(self.s.blue[1]),
526        ]
527    }
528
529    /// FileDialog style.
530    pub fn file_dialog_style(&self) -> FileDialogStyle {
531        FileDialogStyle {
532            style: self.dialog_base(),
533            list: Some(self.list_style()),
534            roots: Some(ListStyle {
535                style: self.dialog_base(),
536                ..self.list_style()
537            }),
538            text: Some(self.text_style()),
539            button: Some(self.button_style()),
540            block: Some(Block::bordered()),
541            ..Default::default()
542        }
543    }
544
545    /// Complete MsgDialogStyle.
546    pub fn msg_dialog_style(&self) -> MsgDialogStyle {
547        MsgDialogStyle {
548            style: self.dialog_base(),
549            button: Some(self.button_style()),
550            ..Default::default()
551        }
552    }
553
554    /// Clipper style.
555    pub fn clipper_style(&self) -> ClipperStyle {
556        ClipperStyle {
557            style: self.container_base(),
558            scroll: Some(self.scroll_style()),
559            ..Default::default()
560        }
561    }
562}