Skip to main content

mottes_floem_components/
classes.rs

1//! # Classes
2//!
3//! Classes that you can apply to floem components. Make sure you use `construct_theme`
4//! or the classes won't be usable!
5//!
6//! # Example
7//!
8//! ```no_run
9//! # use mottes_floem_components::classes::{construct_theme, Button};
10//! # use mottes_floem_components::theme::Theme;
11//! use floem::views::{button, Decorators};
12//!
13//! button("Button").class(Button).style(|_| construct_theme(Theme::dark()));
14//! ```
15
16use floem::{
17    peniko::Color,
18    style::{CursorStyle, Style, StyleValue},
19    style_class,
20    views::{PlaceholderTextClass, RadioButtonClass, RadioButtonDotClass},
21};
22
23use crate::{theme::Theme, BORDER_RADIUS};
24
25style_class!(pub Button);
26style_class!(pub Label);
27style_class!(pub TextInput);
28style_class!(pub ToggleButton);
29style_class!(pub RadioButton);
30style_class!(pub BadgeOne);
31style_class!(pub BadgeTwo);
32style_class!(pub BadgeThree);
33style_class!(pub BadgeFour);
34style_class!(pub BadgeFive);
35
36fn parse_color(color: &str) -> Color {
37    #[allow(clippy::expect_fun_call)]
38    Color::parse(color).expect(format!("failed to parse {color}").as_str())
39}
40
41/// Make classes available to a component using a given color theme.
42pub fn construct_theme(theme: Theme) -> Style {
43    Style::new()
44        .class(Button, button(theme))
45        .class(Label, label(theme))
46        .class(TextInput, text_input(theme))
47        .class(ToggleButton, toggle_button(theme))
48        .class(RadioButton, radio_button(theme))
49        .class(
50            BadgeOne,
51            badge(parse_color(theme.base00), parse_color(theme.base0a)),
52        )
53        .class(
54            BadgeTwo,
55            badge(parse_color(theme.base00), parse_color(theme.base0b)),
56        )
57        .class(
58            BadgeThree,
59            badge(parse_color(theme.base00), parse_color(theme.base0c)),
60        )
61        .class(
62            BadgeFour,
63            badge(parse_color(theme.base00), parse_color(theme.base0d)),
64        )
65        .class(BadgeFive, |s| {
66            let badge_style =
67                badge(parse_color(theme.base03), parse_color(theme.base00))(s.clone());
68            s.apply(badge_style)
69                .border(0.5)
70                .border_color(parse_color(theme.base03))
71        })
72}
73
74fn button(theme: Theme) -> impl FnOnce(Style) -> Style {
75    |s| {
76        s.border_radius(BORDER_RADIUS)
77            .border(1.0)
78            .border_color(parse_color(theme.base03))
79            .background(parse_color(theme.base00))
80            .color(parse_color(theme.base05))
81            .hover(|s| {
82                s.background(parse_color(theme.base01))
83                    .color(parse_color(theme.base05))
84            })
85            .active(|s| {
86                s.background(parse_color(theme.base0a))
87                    .color(parse_color(theme.base00))
88            })
89            .focus(|s| {
90                s.background(parse_color(theme.base01))
91                    .color(parse_color(theme.base05))
92                    .hover(|s| s.background(parse_color(theme.base00)))
93                    .border_color(parse_color(theme.base03))
94            })
95            .disabled(|s| s.background(parse_color(theme.base01).multiply_alpha(0.6)))
96    }
97}
98
99fn label(theme: Theme) -> impl FnOnce(Style) -> Style {
100    |s| s.color(parse_color(theme.base05))
101}
102
103fn text_input(theme: Theme) -> impl FnOnce(Style) -> Style {
104    |s| {
105        s.color(parse_color(theme.base05))
106            .border_radius(BORDER_RADIUS)
107            .border_color(parse_color(theme.base03))
108            .background(parse_color(theme.base00))
109            .hover(|s| s.background(parse_color(theme.base00)))
110            .focus(|s| {
111                s.hover(|s| s.background(parse_color(theme.base00)))
112                    .border_color(parse_color(theme.base0a))
113            })
114            .cursor_color(StyleValue::Val(parse_color(theme.base05).into()))
115            .cursor(CursorStyle::Text)
116            .disabled(|s| {
117                s.color(parse_color(theme.base05).multiply_alpha(0.6))
118                    .background(parse_color(theme.base03).multiply_alpha(0.6))
119            })
120            .class(PlaceholderTextClass, |s| s.color(parse_color(theme.base03)))
121    }
122}
123
124fn toggle_button(theme: Theme) -> impl FnOnce(Style) -> Style {
125    |s| {
126        {
127            s.border_radius(BORDER_RADIUS)
128                .background(parse_color(theme.base00))
129                .color(parse_color(theme.base05))
130                .border_color(parse_color(theme.base03))
131                .hover(|s| s.background(parse_color(theme.base00)))
132                .focus(|s| {
133                    s.hover(|s| s.background(parse_color(theme.base00)))
134                        .border_color(parse_color(theme.base0a))
135                })
136                .disabled(|s| {
137                    s.color(parse_color(theme.base05).multiply_alpha(0.6))
138                        .background(parse_color(theme.base03).multiply_alpha(0.6))
139                })
140                .active(|s| s.background(parse_color(theme.base02)))
141            // TODO: find a way to change the background color if toggled
142        }
143    }
144}
145
146fn radio_button(theme: Theme) -> impl FnOnce(Style) -> Style {
147    |s| {
148        s.class(RadioButtonClass, |s| {
149            s.background(parse_color(theme.base00))
150                .border_color(parse_color(theme.base03))
151                .hover(|s| s.background(parse_color(theme.base00)))
152                .focus(|s| {
153                    s.hover(|s| s.background(parse_color(theme.base00)))
154                        .border_color(parse_color(theme.base0a))
155                })
156                .disabled(|s| {
157                    s.color(parse_color(theme.base05).multiply_alpha(0.6))
158                        .background(parse_color(theme.base03).multiply_alpha(0.6))
159                })
160                .active(|s| s.background(parse_color(theme.base02)))
161        })
162        .class(RadioButtonDotClass, |s| {
163            s.background(parse_color(theme.base0a))
164        })
165    }
166}
167
168fn badge(text_color: Color, badge_color: Color) -> impl FnOnce(Style) -> Style {
169    move |s| {
170        s.border_radius(BORDER_RADIUS)
171            .color(text_color)
172            .background(badge_color)
173            .height(17)
174            .width(75)
175            .padding_top(2)
176            .padding_left(5)
177    }
178}