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