1use crate::tokens::ThemeColor;
2use egui::{Color32, CornerRadius, Stroke};
3
4#[derive(Clone, Copy, Debug)]
9pub struct ThemeMetrics {
10 pub radius: f32,
11 pub radius_sm: f32,
12 pub radius_lg: f32,
13 pub border_width: f32,
14 pub focus_ring_width: f32,
15 pub button_height_sm: f32,
16 pub button_height_md: f32,
17 pub button_height_lg: f32,
18 pub button_padding_x_sm: f32,
19 pub button_padding_x_md: f32,
20 pub button_padding_x_lg: f32,
21 pub input_height: f32,
22 pub input_height_sm: f32,
23 pub input_height_lg: f32,
24 pub input_padding_x: f32,
25 pub switch_width: f32,
26 pub switch_height: f32,
27 pub switch_thumb_padding: f32,
28 pub checkbox_size: f32,
29 pub slider_thumb_radius: f32,
30 pub slider_track_height: f32,
31 pub font_size_xs: f32,
32 pub font_size_sm: f32,
33 pub font_size_md: f32,
34 pub font_size_lg: f32,
35}
36
37impl Default for ThemeMetrics {
38 fn default() -> Self {
39 Self {
40 radius: 6.0,
41 radius_sm: 4.0,
42 radius_lg: 8.0,
43 border_width: 1.0,
44 focus_ring_width: 2.0,
45 button_height_sm: 28.0,
46 button_height_md: 34.0,
47 button_height_lg: 40.0,
48 button_padding_x_sm: 10.0,
49 button_padding_x_md: 14.0,
50 button_padding_x_lg: 18.0,
51 input_height: 34.0,
52 input_height_sm: 28.0,
53 input_height_lg: 40.0,
54 input_padding_x: 10.0,
55 switch_width: 36.0,
56 switch_height: 20.0,
57 switch_thumb_padding: 2.0,
58 checkbox_size: 16.0,
59 slider_thumb_radius: 9.0,
60 slider_track_height: 6.0,
61 font_size_xs: 11.0,
62 font_size_sm: 13.0,
63 font_size_md: 14.0,
64 font_size_lg: 16.0,
65 }
66 }
67}
68
69#[derive(Clone, Copy, Debug)]
71pub struct Theme {
72 pub mode: ThemeMode,
73 pub colors: ThemeColor,
74 pub metrics: ThemeMetrics,
75}
76
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub enum ThemeMode {
79 Light,
80 Dark,
81}
82
83impl Theme {
84 pub const fn light() -> Self {
85 Self {
86 mode: ThemeMode::Light,
87 colors: ThemeColor::light(),
88 metrics: ThemeMetrics {
89 radius: 6.0,
90 radius_sm: 4.0,
91 radius_lg: 8.0,
92 border_width: 1.0,
93 focus_ring_width: 2.0,
94 button_height_sm: 28.0,
95 button_height_md: 34.0,
96 button_height_lg: 40.0,
97 button_padding_x_sm: 10.0,
98 button_padding_x_md: 14.0,
99 button_padding_x_lg: 18.0,
100 input_height: 34.0,
101 input_height_sm: 28.0,
102 input_height_lg: 40.0,
103 input_padding_x: 10.0,
104 switch_width: 36.0,
105 switch_height: 20.0,
106 switch_thumb_padding: 2.0,
107 checkbox_size: 16.0,
108 slider_thumb_radius: 9.0,
109 slider_track_height: 6.0,
110 font_size_xs: 11.0,
111 font_size_sm: 13.0,
112 font_size_md: 14.0,
113 font_size_lg: 16.0,
114 },
115 }
116 }
117
118 pub const fn dark() -> Self {
119 Self {
120 mode: ThemeMode::Dark,
121 colors: ThemeColor::dark(),
122 metrics: Self::light().metrics,
123 }
124 }
125
126 pub fn corner(&self) -> CornerRadius {
127 CornerRadius::same(self.metrics.radius as u8)
128 }
129
130 pub fn corner_sm(&self) -> CornerRadius {
131 CornerRadius::same(self.metrics.radius_sm as u8)
132 }
133
134 pub fn corner_lg(&self) -> CornerRadius {
135 CornerRadius::same(self.metrics.radius_lg as u8)
136 }
137
138 pub fn border_stroke(&self) -> Stroke {
139 Stroke::new(self.metrics.border_width, self.colors.border)
140 }
141
142 pub fn focus_ring(&self) -> Stroke {
143 Stroke::new(self.metrics.focus_ring_width, self.colors.ring)
144 }
145
146 pub fn input_border_stroke(&self) -> Stroke {
147 Stroke::new(self.metrics.border_width, self.colors.input_border)
148 }
149
150 pub fn apply_to_style(&self, style: &mut egui::Style) {
157 let c = &self.colors;
158 let visuals = &mut style.visuals;
159
160 visuals.dark_mode = matches!(self.mode, ThemeMode::Dark);
161 visuals.override_text_color = Some(c.foreground);
162 visuals.window_fill = c.popover_background;
163 visuals.panel_fill = c.background;
164 visuals.faint_bg_color = c.muted_background;
165 visuals.extreme_bg_color = c.background;
166 visuals.code_bg_color = c.muted_background;
167 visuals.window_stroke = self.border_stroke();
168 visuals.selection.bg_fill = c.selection_background;
169 visuals.selection.stroke = Stroke::new(1.0, c.primary_foreground);
170 visuals.hyperlink_color = c.link_foreground;
171
172 let radius = self.corner();
174 visuals.widgets.noninteractive.bg_fill = c.background;
175 visuals.widgets.noninteractive.weak_bg_fill = c.muted_background;
176 visuals.widgets.noninteractive.bg_stroke = self.border_stroke();
177 visuals.widgets.noninteractive.fg_stroke = Stroke::new(1.0, c.foreground);
178 visuals.widgets.noninteractive.corner_radius = radius;
179
180 visuals.widgets.inactive.bg_fill = c.secondary_background;
181 visuals.widgets.inactive.weak_bg_fill = c.secondary_background;
182 visuals.widgets.inactive.bg_stroke = Stroke::NONE;
183 visuals.widgets.inactive.fg_stroke = Stroke::new(1.0, c.secondary_foreground);
184 visuals.widgets.inactive.corner_radius = radius;
185
186 visuals.widgets.hovered.bg_fill = c.secondary_hover_background;
187 visuals.widgets.hovered.weak_bg_fill = c.secondary_hover_background;
188 visuals.widgets.hovered.bg_stroke = Stroke::new(1.0, c.border);
189 visuals.widgets.hovered.fg_stroke = Stroke::new(1.0, c.secondary_foreground);
190 visuals.widgets.hovered.corner_radius = radius;
191
192 visuals.widgets.active.bg_fill = c.secondary_active_background;
193 visuals.widgets.active.weak_bg_fill = c.secondary_active_background;
194 visuals.widgets.active.bg_stroke = Stroke::new(1.0, c.border);
195 visuals.widgets.active.fg_stroke = Stroke::new(1.0, c.secondary_foreground);
196 visuals.widgets.active.corner_radius = radius;
197
198 visuals.widgets.open.bg_fill = c.secondary_background;
199 visuals.widgets.open.weak_bg_fill = c.secondary_background;
200 visuals.widgets.open.bg_stroke = self.border_stroke();
201 visuals.widgets.open.fg_stroke = Stroke::new(1.0, c.secondary_foreground);
202 visuals.widgets.open.corner_radius = radius;
203
204 for (_text_style, font_id) in style.text_styles.iter_mut() {
206 font_id.size = font_id.size.max(self.metrics.font_size_sm);
208 }
209 style.spacing.button_padding = egui::vec2(self.metrics.button_padding_x_md, 6.0);
210 style.spacing.item_spacing = egui::vec2(8.0, 6.0);
211 }
212
213 pub fn install(self, ctx: &egui::Context) {
216 ctx.all_styles_mut(|s| self.apply_to_style(s));
217 ctx.data_mut(|d| d.insert_temp(egui::Id::new(THEME_KEY), self));
218 }
219
220 pub fn get(ctx: &egui::Context) -> Self {
223 ctx.data(|d| d.get_temp::<Theme>(egui::Id::new(THEME_KEY)))
224 .unwrap_or_else(Self::light)
225 }
226}
227
228const THEME_KEY: &str = "egui-components-theme/theme";
229
230impl Default for Theme {
231 fn default() -> Self {
232 Self::light()
233 }
234}
235
236pub fn mix(a: Color32, b: Color32, t: f32) -> Color32 {
238 let t = t.clamp(0.0, 1.0);
239 let lerp = |x: u8, y: u8| (x as f32 * (1.0 - t) + y as f32 * t) as u8;
240 Color32::from_rgba_unmultiplied(
241 lerp(a.r(), b.r()),
242 lerp(a.g(), b.g()),
243 lerp(a.b(), b.b()),
244 lerp(a.a(), b.a()),
245 )
246}