rat_theme4/
dark_theme.rs

1use crate::palette::Palette;
2use crate::{Category, SalsaTheme};
3use crate::{StyleName, WidgetStyle};
4use rat_widget::button::ButtonStyle;
5use rat_widget::calendar::CalendarStyle;
6use rat_widget::checkbox::CheckboxStyle;
7use rat_widget::choice::ChoiceStyle;
8use rat_widget::clipper::ClipperStyle;
9use rat_widget::color_input::ColorInputStyle;
10use rat_widget::combobox::ComboboxStyle;
11use rat_widget::dialog_frame::DialogFrameStyle;
12use rat_widget::file_dialog::FileDialogStyle;
13use rat_widget::form::FormStyle;
14use rat_widget::line_number::LineNumberStyle;
15use rat_widget::list::ListStyle;
16use rat_widget::menu::MenuStyle;
17use rat_widget::msgdialog::MsgDialogStyle;
18use rat_widget::paragraph::ParagraphStyle;
19use rat_widget::radio::{RadioLayout, RadioStyle};
20use rat_widget::scrolled::ScrollStyle;
21use rat_widget::shadow::{ShadowDirection, ShadowStyle};
22use rat_widget::slider::SliderStyle;
23use rat_widget::splitter::SplitStyle;
24use rat_widget::statusline::StatusLineStyle;
25use rat_widget::tabbed::TabbedStyle;
26use rat_widget::table::TableStyle;
27use rat_widget::text::TextStyle;
28use rat_widget::view::ViewStyle;
29use ratatui::layout::Alignment;
30use ratatui::style::{Style, Stylize};
31use ratatui::widgets::{Block, Borders};
32use std::time::Duration;
33
34/// A dark theme.
35pub fn dark_theme(name: &str, p: Palette) -> SalsaTheme {
36    let mut th = SalsaTheme::new(name, Category::Dark, p);
37
38    th.define(Style::INPUT, th.p.high_contrast(p.gray[3]));
39    th.define(Style::FOCUS, th.p.high_contrast(p.primary[2]));
40    th.define(Style::SELECT, th.p.high_contrast(p.secondary[1]));
41    th.define(Style::TEXT_FOCUS, th.p.high_contrast(p.primary[1]));
42    th.define(Style::TEXT_SELECT, th.p.high_contrast(p.secondary[1]));
43    th.define(Style::BUTTON_BASE, th.p.gray(2));
44
45    th.define(Style::CONTAINER_BASE, th.p.normal_contrast(p.black[1]));
46    th.define(
47        Style::CONTAINER_BORDER,
48        th.p.normal_contrast_color(p.black[1], &p.gray),
49    );
50    th.define(
51        Style::CONTAINER_ARROWS,
52        th.p.normal_contrast_color(p.black[1], &p.gray),
53    );
54
55    th.define(Style::POPUP_BASE, th.p.high_contrast(p.white[0]));
56    th.define(Style::POPUP_BORDER, th.p.normal_contrast(p.white[0]));
57    th.define(Style::POPUP_ARROW, th.p.normal_contrast(p.white[0]));
58
59    th.define(Style::DIALOG_BASE, th.p.high_contrast(p.gray[1]));
60    th.define(Style::DIALOG_BORDER, th.p.normal_contrast(p.gray[1]));
61    th.define(Style::DIALOG_ARROW, th.p.normal_contrast(p.gray[1]));
62
63    th.define(Style::STATUS_BASE, th.p.normal_contrast(p.black[2]));
64
65    th.define_fn(WidgetStyle::BUTTON, button);
66    th.define_fn(WidgetStyle::CHECKBOX, checkbox);
67    th.define_fn(WidgetStyle::CHOICE, choice);
68    th.define_fn(WidgetStyle::CLIPPER, clipper);
69    th.define_fn(WidgetStyle::COMBOBOX, combobox);
70    th.define_fn(WidgetStyle::COLOR_INPUT, color_input);
71    th.define_fn(WidgetStyle::DIALOG_FRAME, dialog_frame);
72    th.define_fn(WidgetStyle::FILE_DIALOG, file_dialog);
73    th.define_fn(WidgetStyle::FORM, form);
74    th.define_fn(WidgetStyle::LINE_NR, line_nr);
75    th.define_fn(WidgetStyle::LIST, list);
76    th.define_fn(WidgetStyle::MENU, menu);
77    th.define_fn(WidgetStyle::MONTH, month);
78    th.define_fn(WidgetStyle::MSG_DIALOG, msg_dialog);
79    th.define_fn(WidgetStyle::PARAGRAPH, paragraph);
80    th.define_fn(WidgetStyle::RADIO, radio);
81    th.define_fn(WidgetStyle::SCROLL, scroll);
82    th.define_fn(WidgetStyle::SCROLL_DIALOG, dialog_scroll);
83    th.define_fn(WidgetStyle::SCROLL_POPUP, popup_scroll);
84    th.define_fn(WidgetStyle::SHADOW, shadow);
85    th.define_fn(WidgetStyle::SLIDER, slider);
86    th.define_fn(WidgetStyle::SPLIT, split);
87    th.define_fn(WidgetStyle::STATUSLINE, statusline);
88    th.define_fn(WidgetStyle::TABBED, tabbed);
89    th.define_fn(WidgetStyle::TABLE, table);
90    th.define_fn(WidgetStyle::TEXT, text);
91    th.define_fn(WidgetStyle::TEXTAREA, textarea);
92    th.define_fn(WidgetStyle::TEXTVIEW, textview);
93    th.define_fn(WidgetStyle::VIEW, view);
94
95    th
96}
97
98fn button(th: &SalsaTheme) -> ButtonStyle {
99    ButtonStyle {
100        style: th.style(Style::BUTTON_BASE),
101        focus: Some(th.style(Style::FOCUS)),
102        armed: Some(th.style(Style::SELECT)),
103        hover: Some(th.style(Style::SELECT)),
104        armed_delay: Some(Duration::from_millis(50)),
105        ..Default::default()
106    }
107}
108
109fn checkbox(th: &SalsaTheme) -> CheckboxStyle {
110    CheckboxStyle {
111        style: th.style(Style::INPUT),
112        focus: Some(th.style(Style::TEXT_FOCUS)),
113        ..Default::default()
114    }
115}
116
117fn combobox(th: &SalsaTheme) -> ComboboxStyle {
118    ComboboxStyle {
119        choice: choice(th),
120        text: text(th),
121        ..Default::default()
122    }
123}
124
125fn choice(th: &SalsaTheme) -> ChoiceStyle {
126    ChoiceStyle {
127        style: th.style(Style::INPUT),
128        select: Some(th.style(Style::TEXT_SELECT)),
129        focus: Some(th.style(Style::TEXT_FOCUS)),
130        popup_style: Some(th.style(Style::POPUP_BASE)),
131        popup_border: Some(th.style(Style::POPUP_BORDER)),
132        popup_scroll: Some(popup_scroll(th)),
133        popup_block: Some(
134            Block::bordered()
135                .borders(Borders::LEFT)
136                .border_style(th.style::<Style>(Style::POPUP_BORDER)),
137        ),
138        ..Default::default()
139    }
140}
141
142fn clipper(th: &SalsaTheme) -> ClipperStyle {
143    ClipperStyle {
144        style: th.style(Style::CONTAINER_BASE),
145        scroll: Some(scroll(th)),
146        ..Default::default()
147    }
148}
149
150fn dialog_frame(th: &SalsaTheme) -> DialogFrameStyle {
151    DialogFrameStyle {
152        style: th.style(Style::DIALOG_BASE),
153        block: Some(Block::bordered().style(th.style::<Style>(Style::DIALOG_BORDER))),
154        button_style: Some(button(th)),
155        ..DialogFrameStyle::default()
156    }
157}
158
159fn file_dialog(th: &SalsaTheme) -> FileDialogStyle {
160    FileDialogStyle {
161        style: th.style(Style::DIALOG_BASE),
162        list: Some(list(th)),
163        roots: Some(ListStyle {
164            style: th.style(Style::DIALOG_BASE),
165            ..list(th)
166        }),
167        text: Some(text(th)),
168        button: Some(button(th)),
169        block: Some(Block::bordered()),
170        ..Default::default()
171    }
172}
173
174fn form(th: &SalsaTheme) -> FormStyle {
175    FormStyle {
176        style: th.style(Style::CONTAINER_BASE),
177        navigation: Some(th.style(Style::CONTAINER_ARROWS)),
178        block: Some(
179            Block::bordered()
180                .borders(Borders::TOP | Borders::BOTTOM)
181                .border_style(th.style::<Style>(Style::CONTAINER_BORDER)),
182        ),
183        ..Default::default()
184    }
185}
186
187fn line_nr(th: &SalsaTheme) -> LineNumberStyle {
188    LineNumberStyle {
189        style: th.style(Style::CONTAINER_BASE),
190        cursor: Some(th.style(Style::TEXT_SELECT)),
191        ..LineNumberStyle::default()
192    }
193}
194
195fn list(th: &SalsaTheme) -> ListStyle {
196    ListStyle {
197        style: th.style(Style::CONTAINER_BASE),
198        select: Some(th.style(Style::SELECT)),
199        focus: Some(th.style(Style::FOCUS)),
200        scroll: Some(scroll(th)),
201        ..Default::default()
202    }
203}
204
205fn menu(th: &SalsaTheme) -> MenuStyle {
206    MenuStyle {
207        style: th.style(Style::STATUS_BASE),
208        title: Some(th.p.yellow(2)),
209        focus: Some(th.style(Style::FOCUS)),
210        right: Some(th.p.fg_bluegreen(0)),
211        disabled: Some(th.p.fg_gray(0)),
212        highlight: Some(Style::default().underlined()),
213        block: Some(Block::bordered()),
214        popup: Default::default(),
215        popup_border: Some(th.style(Style::STATUS_BASE)),
216        popup_style: Some(th.style(Style::STATUS_BASE)),
217        ..Default::default()
218    }
219}
220
221fn month(th: &SalsaTheme) -> CalendarStyle {
222    CalendarStyle {
223        style: th.p.normal_contrast(th.p.black[2]),
224        title: None,
225        weeknum: Some(th.p.fg_limegreen(2)),
226        weekday: Some(th.p.fg_limegreen(2)),
227        day: None,
228        select: Some(th.style(Style::SELECT)),
229        focus: Some(th.style(Style::FOCUS)),
230        ..CalendarStyle::default()
231    }
232}
233
234fn msg_dialog(th: &SalsaTheme) -> MsgDialogStyle {
235    MsgDialogStyle {
236        style: th.style(Style::DIALOG_BASE),
237        button: Some(button(th)),
238        ..Default::default()
239    }
240}
241
242fn paragraph(th: &SalsaTheme) -> ParagraphStyle {
243    ParagraphStyle {
244        style: th.style(Style::CONTAINER_BASE),
245        focus: Some(th.style(Style::FOCUS)),
246        scroll: Some(scroll(th)),
247        ..Default::default()
248    }
249}
250
251fn radio(th: &SalsaTheme) -> RadioStyle {
252    RadioStyle {
253        layout: Some(RadioLayout::Stacked),
254        style: th.style(Style::INPUT),
255        focus: Some(th.style(Style::TEXT_FOCUS)),
256        ..Default::default()
257    }
258}
259
260/// Scroll style
261fn scroll(th: &SalsaTheme) -> ScrollStyle {
262    ScrollStyle {
263        thumb_style: Some(th.style(Style::CONTAINER_BORDER)),
264        track_style: Some(th.style(Style::CONTAINER_BORDER)),
265        min_style: Some(th.style(Style::CONTAINER_BORDER)),
266        begin_style: Some(th.style(Style::CONTAINER_ARROWS)),
267        end_style: Some(th.style(Style::CONTAINER_ARROWS)),
268        ..Default::default()
269    }
270}
271
272fn popup_scroll(th: &SalsaTheme) -> ScrollStyle {
273    ScrollStyle {
274        thumb_style: Some(th.style(Style::POPUP_BORDER)),
275        track_style: Some(th.style(Style::POPUP_BORDER)),
276        min_style: Some(th.style(Style::POPUP_BORDER)),
277        begin_style: Some(th.style(Style::POPUP_ARROW)),
278        end_style: Some(th.style(Style::POPUP_ARROW)),
279        ..Default::default()
280    }
281}
282
283fn dialog_scroll(th: &SalsaTheme) -> ScrollStyle {
284    ScrollStyle {
285        thumb_style: Some(th.style(Style::DIALOG_BORDER)),
286        track_style: Some(th.style(Style::DIALOG_BORDER)),
287        min_style: Some(th.style(Style::DIALOG_BORDER)),
288        begin_style: Some(th.style(Style::POPUP_ARROW)),
289        end_style: Some(th.style(Style::POPUP_ARROW)),
290        ..Default::default()
291    }
292}
293
294fn shadow(th: &SalsaTheme) -> ShadowStyle {
295    ShadowStyle {
296        style: th.p.normal_contrast(th.p.black[0]),
297        dir: ShadowDirection::BottomRight,
298        ..ShadowStyle::default()
299    }
300}
301
302fn slider(th: &SalsaTheme) -> SliderStyle {
303    SliderStyle {
304        style: th.style(Style::INPUT),
305        bounds: Some(th.p.gray(2)),
306        knob: Some(th.style(Style::TEXT_SELECT)),
307        focus: Some(th.style(Style::TEXT_FOCUS)),
308        text_align: Some(Alignment::Center),
309        ..Default::default()
310    }
311}
312
313fn split(th: &SalsaTheme) -> SplitStyle {
314    SplitStyle {
315        style: th.style(Style::CONTAINER_BORDER),
316        arrow_style: Some(th.style(Style::CONTAINER_ARROWS)),
317        drag_style: Some(th.style(Style::FOCUS)),
318        ..Default::default()
319    }
320}
321
322fn statusline(th: &SalsaTheme) -> StatusLineStyle {
323    StatusLineStyle {
324        styles: vec![
325            th.style(Style::STATUS_BASE),
326            th.p.normal_contrast(th.p.blue[3]),
327            th.p.normal_contrast(th.p.blue[2]),
328            th.p.normal_contrast(th.p.blue[1]),
329        ],
330        ..Default::default()
331    }
332}
333
334fn tabbed(th: &SalsaTheme) -> TabbedStyle {
335    TabbedStyle {
336        style: th.style(Style::CONTAINER_BASE),
337        tab: Some(th.p.gray(2)),
338        select: Some(th.p.secondary(0)),
339        focus: Some(th.style(Style::FOCUS)),
340        ..Default::default()
341    }
342}
343
344fn table(th: &SalsaTheme) -> TableStyle {
345    TableStyle {
346        style: th.style(Style::CONTAINER_BASE),
347        select_row: Some(th.style(Style::SELECT)),
348        show_row_focus: true,
349        focus_style: Some(th.style(Style::FOCUS)),
350        border_style: Some(th.style(Style::CONTAINER_BORDER)),
351        scroll: Some(scroll(th)),
352        header: Some(th.p.blue(2)),
353        footer: Some(th.p.blue(2)),
354        ..Default::default()
355    }
356}
357
358fn color_input(th: &SalsaTheme) -> ColorInputStyle {
359    ColorInputStyle {
360        text: TextStyle {
361            style: th.style(Style::INPUT),
362            focus: Some(th.style(Style::TEXT_FOCUS)),
363            select: Some(th.style(Style::TEXT_SELECT)),
364            invalid: Some(th.p.fg_red(3)),
365            ..TextStyle::default()
366        },
367        ..Default::default()
368    }
369}
370
371fn text(th: &SalsaTheme) -> TextStyle {
372    TextStyle {
373        style: th.style(Style::INPUT),
374        focus: Some(th.style(Style::TEXT_FOCUS)),
375        select: Some(th.style(Style::TEXT_SELECT)),
376        invalid: Some(th.p.fg_red(3)),
377        ..TextStyle::default()
378    }
379}
380
381fn textarea(th: &SalsaTheme) -> TextStyle {
382    TextStyle {
383        style: th.style(Style::INPUT),
384        focus: Some(th.style(Style::TEXT_FOCUS)),
385        select: Some(th.style(Style::TEXT_SELECT)),
386        scroll: Some(scroll(th)),
387        border_style: Some(th.style(Style::CONTAINER_BORDER)),
388        ..TextStyle::default()
389    }
390}
391
392fn textview(th: &SalsaTheme) -> TextStyle {
393    TextStyle {
394        style: th.style(Style::CONTAINER_BASE),
395        focus: Some(th.style(Style::CONTAINER_BASE)),
396        select: Some(th.style(Style::TEXT_SELECT)),
397        scroll: Some(scroll(th)),
398        border_style: Some(th.style(Style::CONTAINER_BORDER)),
399        ..TextStyle::default()
400    }
401}
402
403fn view(th: &SalsaTheme) -> ViewStyle {
404    ViewStyle {
405        scroll: Some(scroll(th)),
406        ..Default::default()
407    }
408}