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