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