Skip to main content

iced_aw/style/
card.rs

1//! Displays a [`Card`](crate::widget::Card).
2//!
3//! *This API requires the following crate features to be activated: card*
4
5use super::{Status, StyleFn, colors};
6use iced_core::{Background, Color, Theme};
7
8/// The appearance of a [`Card`](crate::widget::card::Card).
9#[derive(Clone, Copy, Debug)]
10pub struct Style {
11    /// The background of the [`Card`](crate::widget::card::Card).
12    pub background: Background,
13
14    /// The border radius of the [`Card`](crate::widget::card::Card).
15    pub border_radius: f32,
16
17    /// The border width of the [`Card`](crate::widget::card::Card).
18    pub border_width: f32,
19
20    /// The border color of the [`Card`](crate::widget::card::Card).
21    pub border_color: Color,
22
23    /// The background of the head of the [`Card`](crate::widget::card::Card).
24    pub head_background: Background,
25
26    /// The text color of the head of the [`Card`](crate::widget::card::Card).
27    pub head_text_color: Color,
28
29    /// The background of the body of the [`Card`](crate::widget::card::Card).
30    pub body_background: Background,
31
32    /// The text color of the body of the [`Card`](crate::widget::card::Card).
33    pub body_text_color: Color,
34
35    /// The background of the foot of the [`Card`](crate::widget::card::Card).
36    pub foot_background: Background,
37
38    /// The text color of the foot of the [`Card`](crate::widget::card::Card).
39    pub foot_text_color: Color,
40
41    /// The color of the close icon of the [`Card`](crate::widget::card::Card).
42    pub close_color: Color,
43}
44
45/// The appearance of a [`Card`](crate::widget::card::Card).
46pub trait Catalog {
47    ///Style for the trait to use.
48    type Class<'a>;
49
50    /// The default class produced by the [`Catalog`].
51    fn default<'a>() -> Self::Class<'a>;
52
53    /// The [`Style`] of a class with the given status.
54    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/// The primary theme of a [`Card`](crate::widget::card::Card).
88#[must_use]
89pub fn primary(theme: &Theme, _status: Status) -> Style {
90    backing_with_text(theme, colors::PRIMARY, colors::WHITE)
91}
92
93/// The secondary theme of a [`Card`](crate::widget::card::Card).
94#[must_use]
95pub fn secondary(theme: &Theme, _status: Status) -> Style {
96    backing_with_text(theme, colors::SECONDARY, colors::WHITE)
97}
98
99/// The success theme of a [`Card`](crate::widget::card::Card).
100#[must_use]
101pub fn success(theme: &Theme, _status: Status) -> Style {
102    backing_with_text(theme, colors::SUCCESS, colors::WHITE)
103}
104
105/// The danger theme of a [`Card`](crate::widget::card::Card).
106#[must_use]
107pub fn danger(theme: &Theme, _status: Status) -> Style {
108    backing_with_text(theme, colors::DANGER, colors::WHITE)
109}
110
111/// The warning theme of a [`Card`](crate::widget::card::Card).
112#[must_use]
113pub fn warning(theme: &Theme, _status: Status) -> Style {
114    backing_only(theme, colors::WARNING)
115}
116
117/// The info theme of a [`Card`](crate::widget::card::Card).
118#[must_use]
119pub fn info(theme: &Theme, _status: Status) -> Style {
120    backing_only(theme, colors::INFO)
121}
122
123/// The light theme of a [`Card`](crate::widget::card::Card).
124#[must_use]
125pub fn light(theme: &Theme, _status: Status) -> Style {
126    backing_only(theme, colors::LIGHT)
127}
128
129/// The dark theme of a [`Card`](crate::widget::card::Card).
130#[must_use]
131pub fn dark(theme: &Theme, _status: Status) -> Style {
132    backing_with_text(theme, colors::DARK, colors::WHITE)
133}
134
135/// The white theme of a [`Card`](crate::widget::card::Card).
136#[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}