1use super::{Status, StyleFn, colors};
6use iced_core::{Background, Color, Theme};
7
8#[derive(Clone, Copy, Debug)]
10pub struct Style {
11 pub background: Background,
13
14 pub border_radius: f32,
16
17 pub border_width: f32,
19
20 pub border_color: Color,
22
23 pub head_background: Background,
25
26 pub head_text_color: Color,
28
29 pub body_background: Background,
31
32 pub body_text_color: Color,
34
35 pub foot_background: Background,
37
38 pub foot_text_color: Color,
40
41 pub close_color: Color,
43}
44
45pub trait Catalog {
47 type Class<'a>;
49
50 fn default<'a>() -> Self::Class<'a>;
52
53 fn style(&self, class: &Self::Class<'_>, status: Status) -> Style;
55}
56
57impl Default for Style {
58 fn default() -> Self {
59 Self {
60 background: Color::WHITE.into(),
61 border_radius: 10.0,
62 border_width: 1.0,
63 border_color: [0.87, 0.87, 0.87].into(),
64 head_background: Background::Color([0.87, 0.87, 0.87].into()),
65 head_text_color: Color::BLACK,
66 body_background: Color::TRANSPARENT.into(),
67 body_text_color: Color::BLACK,
68 foot_background: Color::TRANSPARENT.into(),
69 foot_text_color: Color::BLACK,
70 close_color: Color::BLACK,
71 }
72 }
73}
74
75impl Catalog for Theme {
76 type Class<'a> = StyleFn<'a, Self, Style>;
77
78 fn default<'a>() -> Self::Class<'a> {
79 Box::new(primary)
80 }
81
82 fn style(&self, class: &Self::Class<'_>, status: Status) -> Style {
83 class(self, status)
84 }
85}
86
87#[must_use]
89pub fn primary(theme: &Theme, _status: Status) -> Style {
90 backing_with_text(theme, colors::PRIMARY, colors::WHITE)
91}
92
93#[must_use]
95pub fn secondary(theme: &Theme, _status: Status) -> Style {
96 backing_with_text(theme, colors::SECONDARY, colors::WHITE)
97}
98
99#[must_use]
101pub fn success(theme: &Theme, _status: Status) -> Style {
102 backing_with_text(theme, colors::SUCCESS, colors::WHITE)
103}
104
105#[must_use]
107pub fn danger(theme: &Theme, _status: Status) -> Style {
108 backing_with_text(theme, colors::DANGER, colors::WHITE)
109}
110
111#[must_use]
113pub fn warning(theme: &Theme, _status: Status) -> Style {
114 backing_only(theme, colors::WARNING)
115}
116
117#[must_use]
119pub fn info(theme: &Theme, _status: Status) -> Style {
120 backing_only(theme, colors::INFO)
121}
122
123#[must_use]
125pub fn light(theme: &Theme, _status: Status) -> Style {
126 backing_only(theme, colors::LIGHT)
127}
128
129#[must_use]
131pub fn dark(theme: &Theme, _status: Status) -> Style {
132 backing_with_text(theme, colors::DARK, colors::WHITE)
133}
134
135#[must_use]
137pub fn white(theme: &Theme, _status: Status) -> Style {
138 backing_only(theme, colors::WHITE)
139}
140
141fn backing_with_text(theme: &Theme, color: Color, text_color: Color) -> Style {
142 let palette = theme.extended_palette();
143 let foreground = theme.palette();
144
145 Style {
146 border_color: color,
147 head_background: color.into(),
148 head_text_color: text_color,
149 close_color: text_color,
150 background: palette.background.base.color.into(),
151 body_text_color: foreground.text,
152 foot_text_color: foreground.text,
153 ..Style::default()
154 }
155}
156
157fn backing_only(theme: &Theme, color: Color) -> Style {
158 let palette = theme.extended_palette();
159 let foreground = theme.palette();
160
161 Style {
162 border_color: color,
163 head_background: color.into(),
164 background: palette.background.base.color.into(),
165 body_text_color: foreground.text,
166 foot_text_color: foreground.text,
167 ..Style::default()
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use iced_core::{Background, Color, Theme};
175
176 #[test]
177 fn style_default() {
178 let style = Style::default();
179 assert_eq!(style.background, Background::Color(Color::WHITE));
180 assert_eq!(style.border_radius, 10.0);
181 assert_eq!(style.border_width, 1.0);
182 assert!(matches!(style.head_background, Background::Color(_)));
183 assert_eq!(style.head_text_color, Color::BLACK);
184 assert_eq!(style.body_background, Background::Color(Color::TRANSPARENT));
185 assert_eq!(style.body_text_color, Color::BLACK);
186 assert_eq!(style.foot_background, Background::Color(Color::TRANSPARENT));
187 assert_eq!(style.foot_text_color, Color::BLACK);
188 assert_eq!(style.close_color, Color::BLACK);
189 }
190
191 #[test]
192 fn backing_with_text_creates_style() {
193 let theme = Theme::TokyoNight;
194 let color = Color::from_rgb(1.0, 0.0, 0.0);
195 let text = Color::WHITE;
196 let style = backing_with_text(&theme, color, text);
197
198 assert_eq!(style.border_color, color);
199 assert_eq!(style.head_background, Background::Color(color));
200 assert_eq!(style.head_text_color, text);
201 assert_eq!(style.close_color, text);
202 assert!(matches!(style.background, Background::Color(_)));
203 assert!(matches!(style.body_background, Background::Color(_)));
204 assert!(matches!(style.foot_background, Background::Color(_)));
205 }
206
207 #[test]
208 fn backing_only_creates_style() {
209 let theme = Theme::TokyoNight;
210 let color = Color::from_rgb(0.0, 1.0, 0.0);
211 let style = backing_only(&theme, color);
212
213 assert_eq!(style.border_color, color);
214 assert_eq!(style.head_background, Background::Color(color));
215 assert!(matches!(style.background, Background::Color(_)));
216 assert!(matches!(style.body_background, Background::Color(_)));
217 assert!(matches!(style.foot_background, Background::Color(_)));
218 }
219
220 #[test]
221 fn primary_theme() {
222 let theme = Theme::TokyoNight;
223 let style = primary(&theme, Status::Active);
224
225 assert!(matches!(style.background, Background::Color(_)));
226 assert!(matches!(style.head_background, Background::Color(_)));
227 assert_eq!(style.head_text_color, colors::WHITE);
228 assert_eq!(style.close_color, colors::WHITE);
229 }
230
231 #[test]
232 fn secondary_theme() {
233 let theme = Theme::TokyoNight;
234 let style = secondary(&theme, Status::Active);
235
236 assert!(matches!(style.background, Background::Color(_)));
237 assert!(matches!(style.head_background, Background::Color(_)));
238 assert_eq!(style.head_text_color, colors::WHITE);
239 assert_eq!(style.close_color, colors::WHITE);
240 }
241
242 #[test]
243 fn success_theme() {
244 let theme = Theme::TokyoNight;
245 let style = success(&theme, Status::Active);
246
247 assert!(matches!(style.background, Background::Color(_)));
248 assert!(matches!(style.head_background, Background::Color(_)));
249 assert_eq!(style.head_text_color, colors::WHITE);
250 assert_eq!(style.close_color, colors::WHITE);
251 }
252
253 #[test]
254 fn danger_theme() {
255 let theme = Theme::TokyoNight;
256 let style = danger(&theme, Status::Active);
257
258 assert!(matches!(style.background, Background::Color(_)));
259 assert!(matches!(style.head_background, Background::Color(_)));
260 assert_eq!(style.head_text_color, colors::WHITE);
261 assert_eq!(style.close_color, colors::WHITE);
262 }
263
264 #[test]
265 fn warning_theme() {
266 let theme = Theme::TokyoNight;
267 let style = warning(&theme, Status::Active);
268
269 assert!(matches!(style.background, Background::Color(_)));
270 assert!(matches!(style.head_background, Background::Color(_)));
271 assert_eq!(style.border_color, colors::WARNING);
272 }
273
274 #[test]
275 fn info_theme() {
276 let theme = Theme::TokyoNight;
277 let style = info(&theme, Status::Active);
278
279 assert!(matches!(style.background, Background::Color(_)));
280 assert!(matches!(style.head_background, Background::Color(_)));
281 assert_eq!(style.border_color, colors::INFO);
282 }
283
284 #[test]
285 fn light_theme() {
286 let theme = Theme::TokyoNight;
287 let style = light(&theme, Status::Active);
288
289 assert!(matches!(style.background, Background::Color(_)));
290 assert!(matches!(style.head_background, Background::Color(_)));
291 assert_eq!(style.border_color, colors::LIGHT);
292 }
293
294 #[test]
295 fn dark_theme() {
296 let theme = Theme::TokyoNight;
297 let style = dark(&theme, Status::Active);
298
299 assert!(matches!(style.background, Background::Color(_)));
300 assert!(matches!(style.head_background, Background::Color(_)));
301 assert_eq!(style.head_text_color, colors::WHITE);
302 assert_eq!(style.close_color, colors::WHITE);
303 assert_eq!(style.border_color, colors::DARK);
304 }
305
306 #[test]
307 fn white_theme() {
308 let theme = Theme::TokyoNight;
309 let style = white(&theme, Status::Active);
310
311 assert!(matches!(style.background, Background::Color(_)));
312 assert!(matches!(style.head_background, Background::Color(_)));
313 assert_eq!(style.border_color, colors::WHITE);
314 }
315
316 #[test]
317 fn catalog_default_class() {
318 let _class = <Theme as Catalog>::default();
319 }
320
321 #[test]
322 fn catalog_style() {
323 let theme = Theme::TokyoNight;
324 let class = <Theme as Catalog>::default();
325 let style = theme.style(&class, Status::Active);
326
327 assert!(matches!(style.background, Background::Color(_)));
328 assert!(matches!(style.head_background, Background::Color(_)));
329 }
330
331 #[test]
332 fn primary_theme_with_hovered_status() {
333 let theme = Theme::TokyoNight;
334 let style = primary(&theme, Status::Hovered);
335
336 assert!(matches!(style.background, Background::Color(_)));
337 assert!(matches!(style.head_background, Background::Color(_)));
338 }
339
340 #[test]
341 fn primary_theme_with_disabled_status() {
342 let theme = Theme::TokyoNight;
343 let style = primary(&theme, Status::Disabled);
344
345 assert!(matches!(style.background, Background::Color(_)));
346 assert!(matches!(style.head_background, Background::Color(_)));
347 }
348}