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