rat_theme3/
dark_theme.rs

1//!
2//! Implements a dark theme.
3//!
4
5use crate::{Contrast, Palette, SalsaTheme};
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::form::FormStyle;
13use rat_widget::line_number::LineNumberStyle;
14use rat_widget::list::ListStyle;
15use rat_widget::menu::MenuStyle;
16use rat_widget::msgdialog::MsgDialogStyle;
17use rat_widget::paragraph::ParagraphStyle;
18use rat_widget::radio::{RadioLayout, RadioStyle};
19use rat_widget::scrolled::ScrollStyle;
20use rat_widget::shadow::{ShadowDirection, ShadowStyle};
21use rat_widget::slider::SliderStyle;
22use rat_widget::splitter::SplitStyle;
23use rat_widget::statusline::StatusLineStyle;
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};
32#[cfg(feature = "serde")]
33use serde::de::{Error, MapAccess, SeqAccess, Visitor};
34#[cfg(feature = "serde")]
35use serde::ser::SerializeStruct;
36#[cfg(feature = "serde")]
37use serde::{Deserialize, Deserializer, Serialize, Serializer};
38#[cfg(feature = "serde")]
39use std::fmt::Formatter;
40use std::time::Duration;
41
42/// One sample theme which prefers dark colors from the color-palette
43/// and generates styles for widgets.
44///
45/// The widget set fits for the widgets provided by
46/// [rat-widget](https://www.docs.rs/rat-widget), for other needs
47/// take it as an idea for your own implementation.
48///
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct DarkTheme {
51    name: Box<str>,
52    p: Palette,
53}
54
55#[cfg(feature = "serde")]
56impl Serialize for DarkTheme {
57    fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
58    where
59        S: Serializer,
60    {
61        let mut theme = ser.serialize_struct("DarkTheme", 2)?;
62        theme.serialize_field("name", &self.name)?;
63        theme.serialize_field("p", &self.p)?;
64        theme.end()
65    }
66}
67
68#[cfg(feature = "serde")]
69struct DarkThemeVisitor;
70
71#[cfg(feature = "serde")]
72impl<'de> Visitor<'de> for DarkThemeVisitor {
73    type Value = DarkTheme;
74
75    fn expecting(&self, f: &mut Formatter) -> std::fmt::Result {
76        write!(f, "struct DarkTheme")
77    }
78
79    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
80    where
81        A: SeqAccess<'de>,
82    {
83        let name = seq
84            .next_element::<Box<str>>()?
85            .ok_or(A::Error::invalid_length(0, &"DarkTheme.name"))?;
86        let p = seq
87            .next_element::<Palette>()?
88            .ok_or(A::Error::invalid_length(0, &"DarkTheme.p"))?;
89        Ok(DarkTheme { name, p })
90    }
91
92    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
93    where
94        A: MapAccess<'de>,
95    {
96        let mut name = None;
97        let mut p = None;
98
99        while let Some(key) = map.next_key::<&str>()? {
100            match key {
101                "name" => name = Some(map.next_value::<Box<str>>()?),
102                "p" => p = Some(map.next_value::<Palette>()?),
103                _ => {}
104            }
105        }
106        let name = name.ok_or(A::Error::missing_field("name"))?;
107        let p = p.ok_or(A::Error::missing_field("p"))?;
108
109        Ok(DarkTheme { name, p })
110    }
111}
112
113#[cfg(feature = "serde")]
114impl<'de> Deserialize<'de> for DarkTheme {
115    fn deserialize<D>(des: D) -> Result<Self, D::Error>
116    where
117        D: Deserializer<'de>,
118    {
119        const FIELDS: &'static [&'static str] = &["name", "p"];
120        des.deserialize_struct("DarkTheme", FIELDS, DarkThemeVisitor)
121    }
122}
123
124impl DarkTheme {
125    pub fn new(name: &str, s: Palette) -> Self {
126        Self {
127            name: Box::from(name),
128            p: s,
129        }
130    }
131
132    /// Create a style from a background color
133    fn style(&self, bg: Color) -> Style {
134        self.p.style(bg, Contrast::Normal)
135    }
136
137    /// Create a style from a background color
138    fn high_style(&self, bg: Color) -> Style {
139        self.p.style(bg, Contrast::High)
140    }
141
142    fn table_header(&self) -> Style {
143        self.style(self.p.blue[2])
144    }
145
146    fn table_footer(&self) -> Style {
147        self.style(self.p.blue[2])
148    }
149}
150
151impl SalsaTheme for DarkTheme {
152    /// Some display name.
153    fn name(&self) -> &str {
154        &self.name
155    }
156
157    /// The underlying palette.
158    fn palette(&self) -> &Palette {
159        &self.p
160    }
161
162    /// Create a style from the given white shade.
163    /// n is `0..8`
164    fn white(&self, n: usize) -> Style {
165        self.p.white(n, Contrast::Normal)
166    }
167
168    /// Create a style from the given black shade.
169    /// n is `0..8`
170    fn black(&self, n: usize) -> Style {
171        self.p.black(n, Contrast::Normal)
172    }
173
174    /// Create a style from the given gray shade.
175    /// n is `0..8`
176    fn gray(&self, n: usize) -> Style {
177        self.p.gray(n, Contrast::Normal)
178    }
179
180    /// Create a style from the given red shade.
181    /// n is `0..8`
182    fn red(&self, n: usize) -> Style {
183        self.p.red(n, Contrast::Normal)
184    }
185
186    /// Create a style from the given orange shade.
187    /// n is `0..8`
188    fn orange(&self, n: usize) -> Style {
189        self.p.orange(n, Contrast::Normal)
190    }
191
192    /// Create a style from the given yellow shade.
193    /// n is `0..8`
194    fn yellow(&self, n: usize) -> Style {
195        self.p.yellow(n, Contrast::Normal)
196    }
197
198    /// Create a style from the given limegreen shade.
199    /// n is `0..8`
200    fn limegreen(&self, n: usize) -> Style {
201        self.p.limegreen(n, Contrast::Normal)
202    }
203
204    /// Create a style from the given green shade.
205    /// n is `0..8`
206    fn green(&self, n: usize) -> Style {
207        self.p.green(n, Contrast::Normal)
208    }
209
210    /// Create a style from the given bluegreen shade.
211    /// n is `0..8`
212    fn bluegreen(&self, n: usize) -> Style {
213        self.p.bluegreen(n, Contrast::Normal)
214    }
215
216    /// Create a style from the given cyan shade.
217    /// n is `0..8`
218    fn cyan(&self, n: usize) -> Style {
219        self.p.cyan(n, Contrast::Normal)
220    }
221
222    /// Create a style from the given blue shade.
223    /// n is `0..8`
224    fn blue(&self, n: usize) -> Style {
225        self.p.blue(n, Contrast::Normal)
226    }
227
228    /// Create a style from the given deepblue shade.
229    /// n is `0..8`
230    fn deepblue(&self, n: usize) -> Style {
231        self.p.deepblue(n, Contrast::Normal)
232    }
233
234    /// Create a style from the given purple shade.
235    /// n is `0..8`
236    fn purple(&self, n: usize) -> Style {
237        self.p.purple(n, Contrast::Normal)
238    }
239
240    /// Create a style from the given magenta shade.
241    /// n is `0..8`
242    fn magenta(&self, n: usize) -> Style {
243        self.p.magenta(n, Contrast::Normal)
244    }
245
246    /// Create a style from the given redpink shade.
247    /// n is `0..8`
248    fn redpink(&self, n: usize) -> Style {
249        self.p.redpink(n, Contrast::Normal)
250    }
251
252    /// Create a style from the given primary shade.
253    /// n is `0..8`
254    fn primary(&self, n: usize) -> Style {
255        self.p.primary(n, Contrast::Normal)
256    }
257
258    /// Create a style from the given secondary shade.
259    /// n is `0..8`
260    fn secondary(&self, n: usize) -> Style {
261        self.p.secondary(n, Contrast::Normal)
262    }
263
264    /// Focus style
265    fn focus(&self) -> Style {
266        self.high_style(self.p.primary[2])
267    }
268
269    /// Selection style
270    fn select(&self) -> Style {
271        self.high_style(self.p.secondary[1])
272    }
273
274    /// Text field style.
275    fn text_input(&self) -> Style {
276        self.high_style(self.p.gray[3])
277    }
278
279    /// Focused text field style.
280    fn text_focus(&self) -> Style {
281        self.high_style(self.p.primary[1])
282    }
283
284    /// Text selection style.
285    fn text_select(&self) -> Style {
286        self.high_style(self.p.secondary[1])
287    }
288
289    /// Container base
290    fn container_base(&self) -> Style {
291        self.style(self.p.black[1])
292    }
293
294    /// Container border
295    fn container_border(&self) -> Style {
296        self.container_base().fg(self.p.gray[0])
297    }
298
299    /// Container arrows
300    fn container_arrow(&self) -> Style {
301        self.container_base().fg(self.p.gray[0])
302    }
303
304    /// Background for popups.
305    fn popup_base(&self) -> Style {
306        self.style(self.p.white[0])
307    }
308
309    /// Dialog arrows
310    fn popup_border(&self) -> Style {
311        self.popup_base().fg(self.p.gray[0])
312    }
313
314    /// Dialog arrows
315    fn popup_arrow(&self) -> Style {
316        self.popup_base().fg(self.p.gray[0])
317    }
318
319    /// Background for dialogs.
320    fn dialog_base(&self) -> Style {
321        self.style(self.p.gray[1])
322    }
323
324    /// Dialog arrows
325    fn dialog_border(&self) -> Style {
326        self.dialog_base().fg(self.p.white[0])
327    }
328
329    /// Dialog arrows
330    fn dialog_arrow(&self) -> Style {
331        self.dialog_base().fg(self.p.white[0])
332    }
333
334    /// Style for the status line.
335    fn status_base(&self) -> Style {
336        self.style(self.p.black[2])
337    }
338
339    /// Base style for buttons.
340    fn button_base(&self) -> Style {
341        self.style(self.p.gray[2])
342    }
343
344    /// Armed style for buttons.
345    fn button_armed(&self) -> Style {
346        self.style(self.p.secondary[0])
347    }
348
349    /// Complete MonthStyle.
350    fn month_style(&self) -> CalendarStyle {
351        CalendarStyle {
352            style: self.style(self.p.black[2]),
353            title: None,
354            weeknum: Some(Style::new().fg(self.p.limegreen[2])),
355            weekday: Some(Style::new().fg(self.p.limegreen[2])),
356            day: None,
357            select: Some(self.select()),
358            focus: Some(self.focus()),
359            ..CalendarStyle::default()
360        }
361    }
362
363    /// Style for shadows.
364    fn shadow_style(&self) -> ShadowStyle {
365        ShadowStyle {
366            style: Style::new().bg(self.p.black[0]),
367            dir: ShadowDirection::BottomRight,
368            ..ShadowStyle::default()
369        }
370    }
371
372    /// Style for LineNumbers.
373    fn line_nr_style(&self) -> LineNumberStyle {
374        LineNumberStyle {
375            style: self.container_base().fg(self.p.gray[1]),
376            cursor: Some(self.text_select()),
377            ..LineNumberStyle::default()
378        }
379    }
380
381    /// Complete TextAreaStyle
382    fn textarea_style(&self) -> TextStyle {
383        TextStyle {
384            style: self.text_input(),
385            focus: Some(self.focus()),
386            select: Some(self.text_select()),
387            scroll: Some(self.scroll_style()),
388            border_style: Some(self.container_border()),
389            ..TextStyle::default()
390        }
391    }
392
393    /// Complete TextInputStyle
394    fn text_style(&self) -> TextStyle {
395        TextStyle {
396            style: self.text_input(),
397            focus: Some(self.text_focus()),
398            select: Some(self.text_select()),
399            invalid: Some(Style::default().bg(self.p.red[3])),
400            ..TextStyle::default()
401        }
402    }
403
404    /// Text-label style.
405    fn label_style(&self) -> Style {
406        self.container_base()
407    }
408
409    fn paragraph_style(&self) -> ParagraphStyle {
410        ParagraphStyle {
411            style: self.container_base(),
412            focus: Some(self.focus()),
413            scroll: Some(self.scroll_style()),
414            ..Default::default()
415        }
416    }
417
418    fn choice_style(&self) -> ChoiceStyle {
419        ChoiceStyle {
420            style: self.text_input(),
421            select: Some(self.text_select()),
422            focus: Some(self.text_focus()),
423            popup_style: Some(self.popup_base()),
424            popup_border: Some(self.popup_border()),
425            popup_scroll: Some(self.popup_scroll_style()),
426            popup_block: Some(
427                Block::bordered()
428                    .borders(Borders::LEFT)
429                    .border_style(self.popup_border()),
430            ),
431            ..Default::default()
432        }
433    }
434
435    fn radio_style(&self) -> RadioStyle {
436        RadioStyle {
437            layout: Some(RadioLayout::Stacked),
438            style: self.text_input(),
439            focus: Some(self.text_focus()),
440            ..Default::default()
441        }
442    }
443
444    /// Complete CheckboxStyle
445    fn checkbox_style(&self) -> CheckboxStyle {
446        CheckboxStyle {
447            style: self.text_input(),
448            focus: Some(self.text_focus()),
449            ..Default::default()
450        }
451    }
452
453    /// Slider Style
454    fn slider_style(&self) -> SliderStyle {
455        SliderStyle {
456            style: self.text_input(),
457            bounds: Some(self.gray(2)),
458            knob: Some(self.select()),
459            focus: Some(self.focus()),
460            text_align: Some(Alignment::Center),
461            ..Default::default()
462        }
463    }
464
465    /// Complete MenuStyle
466    fn menu_style(&self) -> MenuStyle {
467        MenuStyle {
468            style: self.status_base(),
469            title: Some(self.style(self.p.yellow[2])),
470            focus: Some(self.focus()),
471            right: Some(Style::default().fg(self.p.bluegreen[0])),
472            disabled: Some(Style::default().fg(self.p.gray[0])),
473            highlight: Some(Style::default().underlined()),
474            popup_style: Some(self.status_base()),
475            popup_block: Some(Block::bordered()),
476            popup_focus: Some(self.focus()),
477            popup_right: Some(Style::default().fg(self.p.bluegreen[0])),
478            popup_disabled: Some(Style::default().fg(self.p.gray[0])),
479            popup_highlight: Some(Style::default().underlined()),
480            popup: Default::default(),
481            ..Default::default()
482        }
483    }
484
485    /// Complete ButtonStyle
486    fn button_style(&self) -> ButtonStyle {
487        ButtonStyle {
488            style: self.button_base(),
489            focus: Some(self.focus()),
490            armed: Some(self.select()),
491            hover: Some(self.select()),
492            armed_delay: Some(Duration::from_millis(50)),
493            ..Default::default()
494        }
495    }
496
497    /// Complete TableStyle
498    fn table_style(&self) -> TableStyle {
499        TableStyle {
500            style: self.container_base(),
501            select_row: Some(self.select()),
502            show_row_focus: true,
503            focus_style: Some(self.focus()),
504            border_style: Some(self.container_border()),
505            scroll: Some(self.scroll_style()),
506            header: Some(self.table_header()),
507            footer: Some(self.table_footer()),
508            ..Default::default()
509        }
510    }
511
512    /// Complete ListStyle
513    fn list_style(&self) -> ListStyle {
514        ListStyle {
515            style: self.container_base(),
516            select: Some(self.select()),
517            focus: Some(self.focus()),
518            scroll: Some(self.scroll_style()),
519            ..Default::default()
520        }
521    }
522
523    /// Scroll style
524    fn scroll_style(&self) -> ScrollStyle {
525        ScrollStyle {
526            thumb_style: Some(self.container_border()),
527            track_style: Some(self.container_border()),
528            min_style: Some(self.container_border()),
529            begin_style: Some(self.container_arrow()),
530            end_style: Some(self.container_arrow()),
531            ..Default::default()
532        }
533    }
534
535    /// Popup scroll style
536    fn popup_scroll_style(&self) -> ScrollStyle {
537        ScrollStyle {
538            thumb_style: Some(self.popup_border()),
539            track_style: Some(self.popup_border()),
540            min_style: Some(self.popup_border()),
541            begin_style: Some(self.popup_arrow()),
542            end_style: Some(self.popup_arrow()),
543            ..Default::default()
544        }
545    }
546
547    /// Dialog scroll style
548    fn dialog_scroll_style(&self) -> ScrollStyle {
549        ScrollStyle {
550            thumb_style: Some(self.dialog_border()),
551            track_style: Some(self.dialog_border()),
552            min_style: Some(self.dialog_border()),
553            begin_style: Some(self.dialog_arrow()),
554            end_style: Some(self.dialog_arrow()),
555            ..Default::default()
556        }
557    }
558
559    /// Split style
560    fn split_style(&self) -> SplitStyle {
561        SplitStyle {
562            style: self.container_border(),
563            arrow_style: Some(self.container_arrow()),
564            drag_style: Some(self.focus()),
565            ..Default::default()
566        }
567    }
568
569    /// View style
570    fn view_style(&self) -> ViewStyle {
571        ViewStyle {
572            scroll: Some(self.scroll_style()),
573            ..Default::default()
574        }
575    }
576
577    /// Tabbed style
578    fn tabbed_style(&self) -> TabbedStyle {
579        let style = self.high_style(self.p.black[1]);
580        TabbedStyle {
581            style,
582            tab: Some(self.gray(1)),
583            select: Some(self.style(self.p.primary[4])),
584            focus: Some(self.focus()),
585            ..Default::default()
586        }
587    }
588
589    /// Complete StatusLineStyle for a StatusLine with 3 indicator fields.
590    fn statusline_style(&self) -> Vec<Style> {
591        vec![
592            self.status_base(),
593            self.p.normal_contrast(self.p.white[0]).bg(self.p.blue[3]),
594            self.p.normal_contrast(self.p.white[0]).bg(self.p.blue[2]),
595            self.p.normal_contrast(self.p.white[0]).bg(self.p.blue[1]),
596        ]
597    }
598
599    /// StatusLineStyle for a StatusLine with 3 indicator fields.
600    fn statusline_style_ext(&self) -> StatusLineStyle {
601        StatusLineStyle {
602            styles: vec![
603                self.status_base(),
604                self.p.normal_contrast(self.p.white[0]).bg(self.p.blue[3]),
605                self.p.normal_contrast(self.p.white[0]).bg(self.p.blue[2]),
606                self.p.normal_contrast(self.p.white[0]).bg(self.p.blue[1]),
607            ],
608            ..Default::default()
609        }
610    }
611
612    /// FileDialog style.
613    fn file_dialog_style(&self) -> FileDialogStyle {
614        FileDialogStyle {
615            style: self.dialog_base(),
616            list: Some(self.list_style()),
617            roots: Some(ListStyle {
618                style: self.dialog_base(),
619                ..self.list_style()
620            }),
621            text: Some(self.text_style()),
622            button: Some(self.button_style()),
623            block: Some(Block::bordered()),
624            ..Default::default()
625        }
626    }
627
628    /// Complete MsgDialogStyle.
629    fn msg_dialog_style(&self) -> MsgDialogStyle {
630        MsgDialogStyle {
631            style: self.dialog_base(),
632            button: Some(self.button_style()),
633            ..Default::default()
634        }
635    }
636
637    fn form_style(&self) -> FormStyle {
638        FormStyle {
639            style: self.container_base(),
640            navigation: Some(self.container_arrow()),
641            block: Some(
642                Block::bordered()
643                    .borders(Borders::TOP | Borders::BOTTOM)
644                    .border_style(self.container_border()),
645            ),
646            ..Default::default()
647        }
648    }
649
650    /// Clipper style.
651    fn clipper_style(&self) -> ClipperStyle {
652        ClipperStyle {
653            style: self.container_base(),
654            scroll: Some(self.scroll_style()),
655            ..Default::default()
656        }
657    }
658
659    fn textview_style(&self) -> TextStyle {
660        TextStyle {
661            style: self.container_base(),
662            focus: Some(self.container_base()),
663            select: Some(self.text_select()),
664            scroll: Some(self.scroll_style()),
665            border_style: Some(self.container_border()),
666            ..TextStyle::default()
667        }
668    }
669}