rat_theme2/
dark_theme.rs

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