1use crate::color::native_color;
4use crate::color::{composite_color, layered_style};
5use clankerdiff_core::DiffTone;
6use clankerdiff_theme::FontStyle;
7use clankerdiff_theme::{
8 ButtonVariant, ControlState, ModalSize, NoticeTone, ReviewTheme, Rgba, SelectionState,
9 SemanticStyle, UiPalette,
10};
11use ratatui::style::{Color, Modifier, Style};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct RatatuiUiTheme {
16 palette: UiPalette,
17 pub canvas: Color,
18 pub surface: Color,
19 pub surface_hover: Color,
20 pub surface_selected: Color,
21 pub text: Color,
22 pub text_muted: Color,
23 pub border: Color,
24 pub accent: Color,
25 pub accent_foreground: Color,
26 pub positive: Color,
27 pub destructive: Color,
28}
29
30impl RatatuiUiTheme {
31 #[must_use]
32 pub fn control_style(self, variant: ButtonVariant, state: ControlState) -> Style {
33 semantic_style(
34 self.palette.control_style(variant, state),
35 self.palette.canvas,
36 )
37 }
38
39 #[must_use]
40 pub fn selection_style(self, state: SelectionState) -> Style {
41 semantic_style(self.palette.selection_style(state), self.palette.canvas)
42 }
43
44 #[must_use]
45 pub fn notice_style(self, tone: NoticeTone) -> Style {
46 semantic_style(self.palette.notice_style(tone), self.palette.canvas)
47 }
48
49 #[must_use]
50 pub const fn modal_size(size: ModalSize) -> (u16, u16) {
51 match size {
52 ModalSize::Compact => (40, 12),
53 ModalSize::Medium => (58, 18),
54 ModalSize::Wide => (72, 22),
55 }
56 }
57}
58
59impl From<&UiPalette> for RatatuiUiTheme {
60 fn from(palette: &UiPalette) -> Self {
61 let color = |value| composite_color(value, palette.canvas);
62 Self {
63 palette: *palette,
64 canvas: color(palette.canvas),
65 surface: color(palette.surface),
66 surface_hover: color(palette.surface_hover),
67 surface_selected: color(palette.surface_selected),
68 text: color(palette.text),
69 text_muted: color(palette.text_muted),
70 border: color(palette.border),
71 accent: color(palette.accent),
72 accent_foreground: color(palette.accent_foreground),
73 positive: color(palette.positive),
74 destructive: color(palette.destructive),
75 }
76 }
77}
78
79fn semantic_style(style: SemanticStyle, canvas: Rgba) -> Style {
80 let mut native = match style.background {
81 Some(background) => layered_style(style.foreground, background, canvas),
82 None => Style::new().fg(composite_color(style.foreground, canvas)),
83 };
84 if style.emphasized {
85 native = native.add_modifier(Modifier::BOLD);
86 }
87 native
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct RatatuiTheme {
94 pub ui: RatatuiUiTheme,
96 pub gutter: Color,
98 pub addition: Color,
100 pub deletion: Color,
102 pub addition_background: Color,
104 pub deletion_background: Color,
106}
107
108impl RatatuiTheme {
109 #[must_use]
110 pub const fn tone(&self, tone: DiffTone) -> (Color, Color) {
111 match tone {
112 DiffTone::Added => (self.addition, self.addition_background),
113 DiffTone::Removed => (self.deletion, self.deletion_background),
114 DiffTone::Context | DiffTone::Meta => (self.ui.text, self.ui.canvas),
115 }
116 }
117}
118
119impl From<&ReviewTheme> for RatatuiTheme {
120 fn from(theme: &ReviewTheme) -> Self {
121 let palette = &theme.diff;
122 let color = |value| composite_color(value, palette.background);
123 Self {
124 ui: RatatuiUiTheme::from(&UiPalette::from(palette)),
125 gutter: color(palette.gutter),
126 addition: color(palette.addition),
127 deletion: color(palette.deletion),
128 addition_background: color(palette.addition_background),
129 deletion_background: color(palette.deletion_background),
130 }
131 }
132}
133
134pub(crate) fn syntax_style(foreground: Rgba, font: FontStyle, background: Color) -> Style {
135 let mut modifiers = Modifier::empty();
136 modifiers.set(Modifier::BOLD, font.bold);
137 modifiers.set(Modifier::ITALIC, font.italic);
138 modifiers.set(Modifier::UNDERLINED, font.underline);
139 Style::new()
140 .fg(native_color(foreground, background))
141 .bg(background)
142 .add_modifier(modifiers)
143}