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