1use crate::{Contrast, Palette, SalsaTheme};
2use rat_widget::button::ButtonStyle;
3use rat_widget::calendar::CalendarStyle;
4use rat_widget::checkbox::CheckboxStyle;
5use rat_widget::choice::ChoiceStyle;
6use rat_widget::clipper::ClipperStyle;
7use rat_widget::file_dialog::FileDialogStyle;
8use rat_widget::form::FormStyle;
9use rat_widget::line_number::LineNumberStyle;
10use rat_widget::list::ListStyle;
11use rat_widget::menu::MenuStyle;
12use rat_widget::msgdialog::MsgDialogStyle;
13use rat_widget::paragraph::ParagraphStyle;
14use rat_widget::popup::PopupStyle;
15use rat_widget::radio::{RadioLayout, RadioStyle};
16use rat_widget::scrolled::ScrollStyle;
17use rat_widget::shadow::{ShadowDirection, ShadowStyle};
18use rat_widget::slider::SliderStyle;
19use rat_widget::splitter::SplitStyle;
20use rat_widget::statusline::StatusLineStyle;
21use rat_widget::tabbed::TabbedStyle;
22use rat_widget::table::TableStyle;
23use rat_widget::text::TextStyle;
24use rat_widget::view::ViewStyle;
25use ratatui_core::layout::Alignment;
26use ratatui_core::style::{Color, Style};
27use ratatui_widgets::block::Block;
28use ratatui_widgets::borders::Borders;
29use std::borrow::Cow;
30use std::time::Duration;
31
32#[derive(Debug, Clone)]
34pub struct ShellTheme {
35 p: Palette,
36 name: Box<str>,
37}
38
39impl ShellTheme {
40 pub fn new(name: &str, p: Palette) -> Self {
41 Self {
42 p,
43 name: Box::from(name),
44 }
45 }
46
47 fn fg_style(&self, color: Color) -> Style {
49 Style::new().fg(color)
50 }
51}
52
53impl SalsaTheme for ShellTheme {
54 fn name(&self) -> &str {
55 &self.name
56 }
57
58 fn palette(&self) -> &Palette {
59 &self.p
60 }
61
62 fn white(&self, n: usize) -> Style {
65 self.p.white(n, Contrast::Normal)
66 }
67
68 fn black(&self, n: usize) -> Style {
71 self.p.black(n, Contrast::Normal)
72 }
73
74 fn gray(&self, n: usize) -> Style {
77 self.p.gray(n, Contrast::Normal)
78 }
79
80 fn red(&self, n: usize) -> Style {
83 self.p.red(n, Contrast::Normal)
84 }
85
86 fn orange(&self, n: usize) -> Style {
89 self.p.orange(n, Contrast::Normal)
90 }
91
92 fn yellow(&self, n: usize) -> Style {
95 self.p.yellow(n, Contrast::Normal)
96 }
97
98 fn limegreen(&self, n: usize) -> Style {
101 self.p.limegreen(n, Contrast::Normal)
102 }
103
104 fn green(&self, n: usize) -> Style {
107 self.p.green(n, Contrast::Normal)
108 }
109
110 fn bluegreen(&self, n: usize) -> Style {
113 self.p.bluegreen(n, Contrast::Normal)
114 }
115
116 fn cyan(&self, n: usize) -> Style {
119 self.p.cyan(n, Contrast::Normal)
120 }
121
122 fn blue(&self, n: usize) -> Style {
125 self.p.blue(n, Contrast::Normal)
126 }
127
128 fn deepblue(&self, n: usize) -> Style {
131 self.p.deepblue(n, Contrast::Normal)
132 }
133
134 fn purple(&self, n: usize) -> Style {
137 self.p.purple(n, Contrast::Normal)
138 }
139
140 fn magenta(&self, n: usize) -> Style {
143 self.p.magenta(n, Contrast::Normal)
144 }
145
146 fn redpink(&self, n: usize) -> Style {
149 self.p.redpink(n, Contrast::Normal)
150 }
151
152 fn primary(&self, n: usize) -> Style {
155 self.p.primary(n, Contrast::Normal)
156 }
157
158 fn secondary(&self, n: usize) -> Style {
161 self.p.secondary(n, Contrast::Normal)
162 }
163
164 fn focus(&self) -> Style {
165 self.p.high_contrast(self.p.primary[Palette::BRIGHT_2])
166 }
167
168 fn select(&self) -> Style {
169 self.p.high_contrast(self.p.secondary[Palette::BRIGHT_2])
170 }
171
172 fn text_input(&self) -> Style {
173 self.p.normal_contrast(self.p.gray[Palette::BRIGHT_0])
174 }
175
176 fn text_focus(&self) -> Style {
177 self.p.normal_contrast(self.p.gray[Palette::BRIGHT_3])
178 }
179
180 fn text_select(&self) -> Style {
181 self.p.normal_contrast(self.p.secondary[Palette::BRIGHT_0])
182 }
183
184 fn container_base(&self) -> Style {
186 Default::default()
187 }
188
189 fn container_border(&self) -> Style {
191 Default::default()
192 }
193
194 fn container_arrow(&self) -> Style {
196 Default::default()
197 }
198
199 fn popup_base(&self) -> Style {
201 self.p
202 .style(self.p.gray[Palette::BRIGHT_0], Contrast::Normal)
203 }
204
205 fn popup_border(&self) -> Style {
207 self.popup_base().fg(self.p.gray[Palette::BRIGHT_0])
208 }
209
210 fn popup_arrow(&self) -> Style {
212 self.popup_base().fg(self.p.gray[Palette::BRIGHT_0])
213 }
214
215 fn dialog_base(&self) -> Style {
217 self.p
218 .style(self.p.gray[Palette::BRIGHT_1], Contrast::Normal)
219 }
220
221 fn dialog_border(&self) -> Style {
223 self.dialog_base().fg(self.p.white[Palette::BRIGHT_0])
224 }
225
226 fn dialog_arrow(&self) -> Style {
228 self.dialog_base().fg(self.p.white[Palette::BRIGHT_0])
229 }
230
231 fn status_base(&self) -> Style {
233 Default::default()
234 }
235
236 fn button_base(&self) -> Style {
238 self.p
239 .style(self.p.gray[Palette::BRIGHT_2], Contrast::Normal)
240 }
241
242 fn button_armed(&self) -> Style {
244 self.p
245 .style(self.p.secondary[Palette::BRIGHT_0], Contrast::Normal)
246 }
247
248 fn month_style(&self) -> CalendarStyle {
250 CalendarStyle {
251 style: Default::default(),
252 title: None,
253 weeknum: Some(Style::new().fg(self.p.limegreen[Palette::BRIGHT_0])),
254 weekday: Some(Style::new().fg(self.p.limegreen[Palette::BRIGHT_0])),
255 day: None,
256 select: Some(self.select()),
257 focus: Some(self.focus()),
258 ..CalendarStyle::default()
259 }
260 }
261
262 fn shadow_style(&self) -> ShadowStyle {
264 ShadowStyle {
265 style: Style::new().bg(self.p.black[Palette::DARK_0]),
266 dir: ShadowDirection::BottomRight,
267 ..ShadowStyle::default()
268 }
269 }
270
271 fn line_nr_style(&self) -> LineNumberStyle {
273 LineNumberStyle {
274 style: self.container_base(),
275 cursor: Some(self.text_select()),
276 ..LineNumberStyle::default()
277 }
278 }
279
280 fn textarea_style(&self) -> TextStyle {
282 TextStyle {
283 style: self.text_input(),
284 select: Some(self.text_select()),
285 scroll: Some(self.scroll_style()),
286 border_style: Some(self.container_border()),
287 ..TextStyle::default()
288 }
289 }
290
291 fn text_style(&self) -> TextStyle {
293 TextStyle {
294 style: self.text_input(),
295 focus: Some(self.text_focus()),
296 select: Some(self.text_select()),
297 invalid: Some(self.fg_style(self.p.red[Palette::BRIGHT_3])),
298 ..TextStyle::default()
299 }
300 }
301
302 fn label_style(&self) -> Style {
304 self.container_base()
305 }
306
307 fn paragraph_style(&self) -> ParagraphStyle {
308 ParagraphStyle {
309 style: self.container_base(),
310 focus: Some(self.focus()),
311 scroll: Some(self.scroll_style()),
312 ..Default::default()
313 }
314 }
315
316 fn choice_style(&self) -> ChoiceStyle {
317 ChoiceStyle {
318 style: self.text_input(),
319 select: Some(self.text_select()),
320 focus: Some(self.text_focus()),
321 popup: PopupStyle::default(),
322 popup_style: Some(self.popup_base()),
323 popup_border: Some(self.popup_border()),
324 popup_scroll: Some(self.popup_scroll_style()),
325 popup_block: Some(
326 Block::bordered()
327 .borders(Borders::LEFT)
328 .border_style(self.popup_border()),
329 ),
330 ..Default::default()
331 }
332 }
333
334 fn radio_style(&self) -> RadioStyle {
335 RadioStyle {
336 layout: Some(RadioLayout::Stacked),
337 style: self.text_input(),
338 focus: Some(self.text_focus()),
339 ..Default::default()
340 }
341 }
342
343 fn checkbox_style(&self) -> CheckboxStyle {
345 CheckboxStyle {
346 style: self.text_input(),
347 focus: Some(self.text_focus()),
348 ..Default::default()
349 }
350 }
351
352 fn slider_style(&self) -> SliderStyle {
354 SliderStyle {
355 style: self.text_input(),
356 bounds: Some(self.gray(2)),
357 knob: Some(self.select()),
358 focus: Some(self.focus()),
359 text_align: Some(Alignment::Center),
360 ..Default::default()
361 }
362 }
363
364 fn menu_style(&self) -> MenuStyle {
366 MenuStyle {
367 style: self.status_base(),
368 title: Some(self.fg_style(self.p.yellow[Palette::BRIGHT_2])),
369 focus: Some(self.focus()),
370 right: Some(self.fg_style(self.p.green[Palette::BRIGHT_3])),
371 disabled: Some(self.fg_style(self.p.gray[Palette::BRIGHT_2])),
372 highlight: Some(Style::default().underlined()),
373 popup_block: Some(Block::bordered().style(self.popup_border())),
374 popup_focus: Some(self.focus()),
375 popup_right: Some(self.fg_style(self.p.green[Palette::BRIGHT_3])),
376 popup_disabled: Some(self.fg_style(self.p.gray[Palette::BRIGHT_2])),
377 popup_highlight: Some(Style::default().underlined()),
378 ..Default::default()
379 }
380 }
381
382 fn button_style(&self) -> ButtonStyle {
384 ButtonStyle {
385 style: self.button_base(),
386 focus: Some(self.focus()),
387 armed: Some(self.select()),
388 armed_delay: Some(Duration::from_millis(50)),
389 ..Default::default()
390 }
391 }
392
393 fn table_style(&self) -> TableStyle {
395 TableStyle {
396 style: self.container_base(),
397 select_row: Some(self.select()),
398 show_row_focus: true,
399 focus_style: Some(self.focus()),
400 border_style: Some(self.container_border()),
401 scroll: Some(self.scroll_style()),
402 header: Some(self.fg_style(self.p.green[Palette::BRIGHT_2])),
403 footer: Some(self.fg_style(self.p.green[Palette::BRIGHT_2])),
404 ..Default::default()
405 }
406 }
407
408 fn list_style(&self) -> ListStyle {
410 ListStyle {
411 style: self.container_base(),
412 select: Some(self.select()),
413 focus: Some(self.focus()),
414 scroll: Some(self.scroll_style()),
415 ..Default::default()
416 }
417 }
418
419 fn scroll_style(&self) -> ScrollStyle {
421 ScrollStyle {
422 thumb_style: Some(self.container_border()),
423 track_style: Some(self.container_border()),
424 min_style: Some(self.container_border()),
425 begin_style: Some(self.container_arrow()),
426 end_style: Some(self.container_arrow()),
427 ..Default::default()
428 }
429 }
430
431 fn popup_scroll_style(&self) -> ScrollStyle {
433 ScrollStyle {
434 thumb_style: Some(self.popup_border()),
435 track_style: Some(self.popup_border()),
436 min_style: Some(self.popup_border()),
437 begin_style: Some(self.popup_arrow()),
438 end_style: Some(self.popup_arrow()),
439 ..Default::default()
440 }
441 }
442
443 fn dialog_scroll_style(&self) -> ScrollStyle {
445 ScrollStyle {
446 thumb_style: Some(self.dialog_border()),
447 track_style: Some(self.dialog_border()),
448 min_style: Some(self.dialog_border()),
449 begin_style: Some(self.dialog_arrow()),
450 end_style: Some(self.dialog_arrow()),
451 ..Default::default()
452 }
453 }
454
455 fn split_style(&self) -> SplitStyle {
457 SplitStyle {
458 style: self.container_border(),
459 arrow_style: Some(self.container_arrow()),
460 drag_style: Some(self.focus()),
461 ..Default::default()
462 }
463 }
464
465 fn view_style(&self) -> ViewStyle {
467 ViewStyle {
468 scroll: Some(self.scroll_style()),
469 ..Default::default()
470 }
471 }
472
473 fn tabbed_style(&self) -> TabbedStyle {
475 TabbedStyle {
476 style: self.container_base(),
477 tab: Some(self.button_base()),
478 select: Some(self.button_armed()),
479 focus: Some(self.focus()),
480 ..Default::default()
481 }
482 }
483
484 fn statusline_style(&self) -> Vec<Style> {
486 vec![
487 self.status_base(),
488 self.fg_style(self.p.blue[Palette::BRIGHT_2]),
489 self.fg_style(self.p.blue[Palette::BRIGHT_2]),
490 self.fg_style(self.p.blue[Palette::BRIGHT_2]),
491 ]
492 }
493
494 fn statusline_style_ext(&self) -> StatusLineStyle {
496 StatusLineStyle {
497 sep: Some(Cow::Borrowed("|")),
498 styles: vec![
499 self.status_base(),
500 self.status_base().fg(self.p.blue[Palette::BRIGHT_2]),
501 self.status_base().fg(self.p.blue[Palette::BRIGHT_2]),
502 self.status_base().fg(self.p.blue[Palette::BRIGHT_2]),
503 ],
504 ..Default::default()
505 }
506 }
507
508 fn file_dialog_style(&self) -> FileDialogStyle {
510 FileDialogStyle {
511 style: self.dialog_base(),
512 list: Some(self.list_style()),
513 roots: Some(ListStyle {
514 style: self.dialog_base(),
515 ..self.list_style()
516 }),
517 text: Some(self.text_style()),
518 button: Some(self.button_style()),
519 block: Some(Block::bordered()),
520 ..Default::default()
521 }
522 }
523
524 fn msg_dialog_style(&self) -> MsgDialogStyle {
526 MsgDialogStyle {
527 style: self.dialog_base(),
528 button: Some(self.button_style()),
529 ..Default::default()
530 }
531 }
532
533 fn form_style(&self) -> FormStyle {
535 FormStyle {
536 style: self.container_base(),
537 navigation: Some(self.container_arrow()),
538 block: Some(
539 Block::default()
540 .borders(Borders::TOP)
541 .border_style(self.container_border()),
542 ),
543 ..Default::default()
544 }
545 }
546
547 fn clipper_style(&self) -> ClipperStyle {
549 ClipperStyle {
550 style: self.container_base(),
551 scroll: Some(self.scroll_style()),
552 ..Default::default()
553 }
554 }
555
556 fn textview_style(&self) -> TextStyle {
557 TextStyle {
558 style: self.container_base(),
559 focus: Some(self.container_base()),
560 select: Some(self.text_select()),
561 scroll: Some(self.scroll_style()),
562 border_style: Some(self.container_border()),
563 ..TextStyle::default()
564 }
565 }
566}