1use 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#[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 fn style(&self, bg: Color) -> Style {
134 self.p.style(bg, Contrast::Normal)
135 }
136
137 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 fn name(&self) -> &str {
154 &self.name
155 }
156
157 fn palette(&self) -> &Palette {
159 &self.p
160 }
161
162 fn white(&self, n: usize) -> Style {
165 self.p.white(n, Contrast::Normal)
166 }
167
168 fn black(&self, n: usize) -> Style {
171 self.p.black(n, Contrast::Normal)
172 }
173
174 fn gray(&self, n: usize) -> Style {
177 self.p.gray(n, Contrast::Normal)
178 }
179
180 fn red(&self, n: usize) -> Style {
183 self.p.red(n, Contrast::Normal)
184 }
185
186 fn orange(&self, n: usize) -> Style {
189 self.p.orange(n, Contrast::Normal)
190 }
191
192 fn yellow(&self, n: usize) -> Style {
195 self.p.yellow(n, Contrast::Normal)
196 }
197
198 fn limegreen(&self, n: usize) -> Style {
201 self.p.limegreen(n, Contrast::Normal)
202 }
203
204 fn green(&self, n: usize) -> Style {
207 self.p.green(n, Contrast::Normal)
208 }
209
210 fn bluegreen(&self, n: usize) -> Style {
213 self.p.bluegreen(n, Contrast::Normal)
214 }
215
216 fn cyan(&self, n: usize) -> Style {
219 self.p.cyan(n, Contrast::Normal)
220 }
221
222 fn blue(&self, n: usize) -> Style {
225 self.p.blue(n, Contrast::Normal)
226 }
227
228 fn deepblue(&self, n: usize) -> Style {
231 self.p.deepblue(n, Contrast::Normal)
232 }
233
234 fn purple(&self, n: usize) -> Style {
237 self.p.purple(n, Contrast::Normal)
238 }
239
240 fn magenta(&self, n: usize) -> Style {
243 self.p.magenta(n, Contrast::Normal)
244 }
245
246 fn redpink(&self, n: usize) -> Style {
249 self.p.redpink(n, Contrast::Normal)
250 }
251
252 fn primary(&self, n: usize) -> Style {
255 self.p.primary(n, Contrast::Normal)
256 }
257
258 fn secondary(&self, n: usize) -> Style {
261 self.p.secondary(n, Contrast::Normal)
262 }
263
264 fn focus(&self) -> Style {
266 self.high_style(self.p.primary[2])
267 }
268
269 fn select(&self) -> Style {
271 self.high_style(self.p.secondary[1])
272 }
273
274 fn text_input(&self) -> Style {
276 self.high_style(self.p.gray[3])
277 }
278
279 fn text_focus(&self) -> Style {
281 self.high_style(self.p.primary[1])
282 }
283
284 fn text_select(&self) -> Style {
286 self.high_style(self.p.secondary[1])
287 }
288
289 fn container_base(&self) -> Style {
291 self.style(self.p.black[1])
292 }
293
294 fn container_border(&self) -> Style {
296 self.container_base().fg(self.p.gray[0])
297 }
298
299 fn container_arrow(&self) -> Style {
301 self.container_base().fg(self.p.gray[0])
302 }
303
304 fn popup_base(&self) -> Style {
306 self.style(self.p.white[0])
307 }
308
309 fn popup_border(&self) -> Style {
311 self.popup_base().fg(self.p.gray[0])
312 }
313
314 fn popup_arrow(&self) -> Style {
316 self.popup_base().fg(self.p.gray[0])
317 }
318
319 fn dialog_base(&self) -> Style {
321 self.style(self.p.gray[1])
322 }
323
324 fn dialog_border(&self) -> Style {
326 self.dialog_base().fg(self.p.white[0])
327 }
328
329 fn dialog_arrow(&self) -> Style {
331 self.dialog_base().fg(self.p.white[0])
332 }
333
334 fn status_base(&self) -> Style {
336 self.style(self.p.black[2])
337 }
338
339 fn button_base(&self) -> Style {
341 self.style(self.p.gray[2])
342 }
343
344 fn button_armed(&self) -> Style {
346 self.style(self.p.secondary[0])
347 }
348
349 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 fn view_style(&self) -> ViewStyle {
571 ViewStyle {
572 scroll: Some(self.scroll_style()),
573 ..Default::default()
574 }
575 }
576
577 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 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 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 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 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 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}