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;
13#[allow(deprecated)]
14use rat_widget::pager::PagerStyle;
15use rat_widget::paragraph::ParagraphStyle;
16use rat_widget::popup::PopupStyle;
17use rat_widget::radio::{RadioLayout, RadioStyle};
18use rat_widget::scrolled::ScrollStyle;
19use rat_widget::shadow::{ShadowDirection, ShadowStyle};
20use rat_widget::slider::SliderStyle;
21use rat_widget::splitter::SplitStyle;
22use rat_widget::statusline::StatusLineStyle;
23use rat_widget::tabbed::TabbedStyle;
24use rat_widget::table::TableStyle;
25use rat_widget::text::TextStyle;
26use rat_widget::view::ViewStyle;
27use ratatui::layout::Alignment;
28use ratatui::prelude::Style;
29use ratatui::style::{Color, Stylize};
30use ratatui::widgets::{Block, Borders};
31use std::borrow::Cow;
32use std::time::Duration;
33
34#[derive(Debug, Clone)]
36pub struct ShellTheme {
37 p: Palette,
38 name: Box<str>,
39}
40
41impl ShellTheme {
42 pub fn new(name: &str, p: Palette) -> Self {
43 Self {
44 p,
45 name: Box::from(name),
46 }
47 }
48
49 fn fg_style(&self, color: Color) -> Style {
51 Style::new().fg(color)
52 }
53}
54
55impl SalsaTheme for ShellTheme {
56 fn name(&self) -> &str {
57 &self.name
58 }
59
60 fn palette(&self) -> &Palette {
61 &self.p
62 }
63
64 fn white(&self, n: usize) -> Style {
67 self.p.white(n, Contrast::Normal)
68 }
69
70 fn black(&self, n: usize) -> Style {
73 self.p.black(n, Contrast::Normal)
74 }
75
76 fn gray(&self, n: usize) -> Style {
79 self.p.gray(n, Contrast::Normal)
80 }
81
82 fn red(&self, n: usize) -> Style {
85 self.p.red(n, Contrast::Normal)
86 }
87
88 fn orange(&self, n: usize) -> Style {
91 self.p.orange(n, Contrast::Normal)
92 }
93
94 fn yellow(&self, n: usize) -> Style {
97 self.p.yellow(n, Contrast::Normal)
98 }
99
100 fn limegreen(&self, n: usize) -> Style {
103 self.p.limegreen(n, Contrast::Normal)
104 }
105
106 fn green(&self, n: usize) -> Style {
109 self.p.green(n, Contrast::Normal)
110 }
111
112 fn bluegreen(&self, n: usize) -> Style {
115 self.p.bluegreen(n, Contrast::Normal)
116 }
117
118 fn cyan(&self, n: usize) -> Style {
121 self.p.cyan(n, Contrast::Normal)
122 }
123
124 fn blue(&self, n: usize) -> Style {
127 self.p.blue(n, Contrast::Normal)
128 }
129
130 fn deepblue(&self, n: usize) -> Style {
133 self.p.deepblue(n, Contrast::Normal)
134 }
135
136 fn purple(&self, n: usize) -> Style {
139 self.p.purple(n, Contrast::Normal)
140 }
141
142 fn magenta(&self, n: usize) -> Style {
145 self.p.magenta(n, Contrast::Normal)
146 }
147
148 fn redpink(&self, n: usize) -> Style {
151 self.p.redpink(n, Contrast::Normal)
152 }
153
154 fn primary(&self, n: usize) -> Style {
157 self.p.primary(n, Contrast::Normal)
158 }
159
160 fn secondary(&self, n: usize) -> Style {
163 self.p.secondary(n, Contrast::Normal)
164 }
165
166 fn focus(&self) -> Style {
167 self.p.high_contrast(self.p.primary[Palette::BRIGHT_2])
168 }
169
170 fn select(&self) -> Style {
171 self.p.high_contrast(self.p.secondary[Palette::BRIGHT_2])
172 }
173
174 fn text_input(&self) -> Style {
175 self.p.normal_contrast(self.p.gray[Palette::BRIGHT_0])
176 }
177
178 fn text_focus(&self) -> Style {
179 self.p.normal_contrast(self.p.gray[Palette::BRIGHT_3])
180 }
181
182 fn text_select(&self) -> Style {
183 self.p.normal_contrast(self.p.secondary[Palette::BRIGHT_0])
184 }
185
186 fn container_base(&self) -> Style {
188 Default::default()
189 }
190
191 fn container_border(&self) -> Style {
193 Default::default()
194 }
195
196 fn container_arrow(&self) -> Style {
198 Default::default()
199 }
200
201 fn popup_base(&self) -> Style {
203 self.p
204 .style(self.p.gray[Palette::BRIGHT_0], Contrast::Normal)
205 }
206
207 fn popup_border(&self) -> Style {
209 self.popup_base().fg(self.p.gray[Palette::BRIGHT_0])
210 }
211
212 fn popup_arrow(&self) -> Style {
214 self.popup_base().fg(self.p.gray[Palette::BRIGHT_0])
215 }
216
217 fn dialog_base(&self) -> Style {
219 self.p
220 .style(self.p.gray[Palette::BRIGHT_1], Contrast::Normal)
221 }
222
223 fn dialog_border(&self) -> Style {
225 self.dialog_base().fg(self.p.white[Palette::BRIGHT_0])
226 }
227
228 fn dialog_arrow(&self) -> Style {
230 self.dialog_base().fg(self.p.white[Palette::BRIGHT_0])
231 }
232
233 fn status_base(&self) -> Style {
235 Default::default()
236 }
237
238 fn button_base(&self) -> Style {
240 self.p
241 .style(self.p.gray[Palette::BRIGHT_2], Contrast::Normal)
242 }
243
244 fn button_armed(&self) -> Style {
246 self.p
247 .style(self.p.secondary[Palette::BRIGHT_0], Contrast::Normal)
248 }
249
250 fn month_style(&self) -> CalendarStyle {
252 CalendarStyle {
253 style: Default::default(),
254 title: None,
255 weeknum: Some(Style::new().fg(self.p.limegreen[Palette::BRIGHT_0])),
256 weekday: Some(Style::new().fg(self.p.limegreen[Palette::BRIGHT_0])),
257 day: None,
258 select: Some(self.select()),
259 focus: Some(self.focus()),
260 ..CalendarStyle::default()
261 }
262 }
263
264 fn shadow_style(&self) -> ShadowStyle {
266 ShadowStyle {
267 style: Style::new().bg(self.p.black[Palette::DARK_0]),
268 dir: ShadowDirection::BottomRight,
269 ..ShadowStyle::default()
270 }
271 }
272
273 fn line_nr_style(&self) -> LineNumberStyle {
275 LineNumberStyle {
276 style: self.container_base(),
277 cursor: Some(self.text_select()),
278 ..LineNumberStyle::default()
279 }
280 }
281
282 fn textarea_style(&self) -> TextStyle {
284 TextStyle {
285 style: self.text_input(),
286 select: Some(self.text_select()),
287 scroll: Some(self.scroll_style()),
288 border_style: Some(self.container_border()),
289 ..TextStyle::default()
290 }
291 }
292
293 fn text_style(&self) -> TextStyle {
295 TextStyle {
296 style: self.text_input(),
297 focus: Some(self.text_focus()),
298 select: Some(self.text_select()),
299 invalid: Some(self.fg_style(self.p.red[Palette::BRIGHT_3])),
300 ..TextStyle::default()
301 }
302 }
303
304 fn label_style(&self) -> Style {
306 self.container_base()
307 }
308
309 fn paragraph_style(&self) -> ParagraphStyle {
310 ParagraphStyle {
311 style: self.container_base(),
312 focus: Some(self.focus()),
313 scroll: Some(self.scroll_style()),
314 ..Default::default()
315 }
316 }
317
318 #[allow(deprecated)]
319 fn choice_style(&self) -> ChoiceStyle {
320 ChoiceStyle {
321 style: self.text_input(),
322 select: Some(self.text_select()),
323 focus: Some(self.text_focus()),
324 popup: PopupStyle::default(),
325 popup_style: Some(self.popup_base()),
326 popup_border: Some(self.popup_border()),
327 popup_scroll: Some(self.popup_scroll_style()),
328 popup_block: Some(
329 Block::bordered()
330 .borders(Borders::LEFT)
331 .border_style(self.popup_border()),
332 ),
333 ..Default::default()
334 }
335 }
336
337 fn radio_style(&self) -> RadioStyle {
338 RadioStyle {
339 layout: Some(RadioLayout::Stacked),
340 style: self.text_input(),
341 focus: Some(self.text_focus()),
342 ..Default::default()
343 }
344 }
345
346 fn checkbox_style(&self) -> CheckboxStyle {
348 CheckboxStyle {
349 style: self.text_input(),
350 focus: Some(self.text_focus()),
351 ..Default::default()
352 }
353 }
354
355 fn slider_style(&self) -> SliderStyle {
357 SliderStyle {
358 style: self.text_input(),
359 bounds: Some(self.gray(2)),
360 knob: Some(self.select()),
361 focus: Some(self.focus()),
362 text_align: Some(Alignment::Center),
363 ..Default::default()
364 }
365 }
366
367 fn menu_style(&self) -> MenuStyle {
369 MenuStyle {
370 style: self.status_base(),
371 title: Some(self.fg_style(self.p.yellow[Palette::BRIGHT_2])),
372 focus: Some(self.focus()),
373 right: Some(self.fg_style(self.p.green[Palette::BRIGHT_3])),
374 disabled: Some(self.fg_style(self.p.gray[Palette::BRIGHT_2])),
375 highlight: Some(Style::default().underlined()),
376 block: Some(Block::bordered().style(self.popup_border())),
377 ..Default::default()
378 }
379 }
380
381 fn button_style(&self) -> ButtonStyle {
383 ButtonStyle {
384 style: self.button_base(),
385 focus: Some(self.focus()),
386 armed: Some(self.select()),
387 armed_delay: Some(Duration::from_millis(50)),
388 ..Default::default()
389 }
390 }
391
392 fn table_style(&self) -> TableStyle {
394 TableStyle {
395 style: self.container_base(),
396 select_row: Some(self.select()),
397 show_row_focus: true,
398 focus_style: Some(self.focus()),
399 border_style: Some(self.container_border()),
400 scroll: Some(self.scroll_style()),
401 header: Some(self.fg_style(self.p.green[Palette::BRIGHT_2])),
402 footer: Some(self.fg_style(self.p.green[Palette::BRIGHT_2])),
403 ..Default::default()
404 }
405 }
406
407 fn list_style(&self) -> ListStyle {
409 ListStyle {
410 style: self.container_base(),
411 select: Some(self.select()),
412 focus: Some(self.focus()),
413 scroll: Some(self.scroll_style()),
414 ..Default::default()
415 }
416 }
417
418 fn scroll_style(&self) -> ScrollStyle {
420 ScrollStyle {
421 thumb_style: Some(self.container_border()),
422 track_style: Some(self.container_border()),
423 min_style: Some(self.container_border()),
424 begin_style: Some(self.container_arrow()),
425 end_style: Some(self.container_arrow()),
426 ..Default::default()
427 }
428 }
429
430 fn popup_scroll_style(&self) -> ScrollStyle {
432 ScrollStyle {
433 thumb_style: Some(self.popup_border()),
434 track_style: Some(self.popup_border()),
435 min_style: Some(self.popup_border()),
436 begin_style: Some(self.popup_arrow()),
437 end_style: Some(self.popup_arrow()),
438 ..Default::default()
439 }
440 }
441
442 fn dialog_scroll_style(&self) -> ScrollStyle {
444 ScrollStyle {
445 thumb_style: Some(self.dialog_border()),
446 track_style: Some(self.dialog_border()),
447 min_style: Some(self.dialog_border()),
448 begin_style: Some(self.dialog_arrow()),
449 end_style: Some(self.dialog_arrow()),
450 ..Default::default()
451 }
452 }
453
454 fn split_style(&self) -> SplitStyle {
456 SplitStyle {
457 style: self.container_border(),
458 arrow_style: Some(self.container_arrow()),
459 drag_style: Some(self.focus()),
460 ..Default::default()
461 }
462 }
463
464 fn view_style(&self) -> ViewStyle {
466 ViewStyle {
467 scroll: Some(self.scroll_style()),
468 ..Default::default()
469 }
470 }
471
472 fn tabbed_style(&self) -> TabbedStyle {
474 TabbedStyle {
475 style: self.container_base(),
476 tab: Some(self.button_base()),
477 select: Some(self.button_armed()),
478 focus: Some(self.focus()),
479 ..Default::default()
480 }
481 }
482
483 fn statusline_style(&self) -> Vec<Style> {
485 vec![
486 self.status_base(),
487 self.fg_style(self.p.blue[Palette::BRIGHT_2]),
488 self.fg_style(self.p.blue[Palette::BRIGHT_2]),
489 self.fg_style(self.p.blue[Palette::BRIGHT_2]),
490 ]
491 }
492
493 fn statusline_style_ext(&self) -> StatusLineStyle {
495 StatusLineStyle {
496 sep: Some(Cow::Borrowed("|")),
497 styles: vec![
498 self.status_base(),
499 self.status_base().fg(self.p.blue[Palette::BRIGHT_2]),
500 self.status_base().fg(self.p.blue[Palette::BRIGHT_2]),
501 self.status_base().fg(self.p.blue[Palette::BRIGHT_2]),
502 ],
503 ..Default::default()
504 }
505 }
506
507 fn file_dialog_style(&self) -> FileDialogStyle {
509 FileDialogStyle {
510 style: self.dialog_base(),
511 list: Some(self.list_style()),
512 roots: Some(ListStyle {
513 style: self.dialog_base(),
514 ..self.list_style()
515 }),
516 text: Some(self.text_style()),
517 button: Some(self.button_style()),
518 block: Some(Block::bordered()),
519 ..Default::default()
520 }
521 }
522
523 fn msg_dialog_style(&self) -> MsgDialogStyle {
525 MsgDialogStyle {
526 style: self.dialog_base(),
527 button: Some(self.button_style()),
528 ..Default::default()
529 }
530 }
531
532 #[allow(deprecated)]
534 fn pager_style(&self) -> PagerStyle {
535 PagerStyle {
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 form_style(&self) -> FormStyle {
549 FormStyle {
550 style: self.container_base(),
551 navigation: Some(self.container_arrow()),
552 block: Some(
553 Block::default()
554 .borders(Borders::TOP)
555 .border_style(self.container_border()),
556 ),
557 ..Default::default()
558 }
559 }
560
561 fn clipper_style(&self) -> ClipperStyle {
563 ClipperStyle {
564 style: self.container_base(),
565 scroll: Some(self.scroll_style()),
566 ..Default::default()
567 }
568 }
569
570 fn textview_style(&self) -> TextStyle {
571 TextStyle {
572 style: self.container_base(),
573 select: Some(self.text_select()),
574 scroll: Some(self.scroll_style()),
575 border_style: Some(self.container_border()),
576 ..TextStyle::default()
577 }
578 }
579}