1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use std::fmt::Display;

use floem::{
    event::EventListener,
    peniko::Color,
    reactive::{create_signal, ReadSignal},
    style::AlignItems,
    view::View,
    views::{container, h_stack, label, svg, Decorators},
};

use crate::{
    accents::{BorderColorVariant, PrimaryFillColorVariant},
    theme::Theme,
};

impl Theme {
    fn checkbox_symbol(self, read_signal: ReadSignal<bool>) -> impl View {
        const CHECKED_SVG: &str = r#"
			<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
				<g transform="matrix(0.925671,0,0,0.925671,2.36266,1.94611)">
					<path d="M5.19,11.83L0.18,7.44L1.82,5.56L4.81,8.17L10,1.25L12,2.75L5.19,11.83Z" style="fill:white;fill-rule:nonzero;"/>
				</g>
			</svg>
		"#;
        let svg_str = move || if read_signal.get() { CHECKED_SVG } else { "" }.to_string();
        svg(svg_str)
    }

    /// Instantiates a checkbox widget controlling a boolean with a text label next to it.
    pub fn labeled_checkbox<S: Display + 'static>(
        self,
        read_signal: ReadSignal<bool>,
        label_render_func: impl Fn() -> S + 'static,
    ) -> impl View {
        let (is_hovering, set_is_hovering) = create_signal(false);
        let (is_focused, set_is_focused) = create_signal(false);

        container(
            h_stack((
                self.checkbox_symbol(read_signal).style(move |s| {
                    let accent_color = self.accent_color.get();

                    let is_selected = read_signal.get();
                    let unhovered_bg_color = match is_selected {
                        true => {
                            accent_color.primary_fill_color(PrimaryFillColorVariant::DefaultColored)
                        }
                        false => accent_color
                            .primary_fill_color(PrimaryFillColorVariant::DefaultGrayscale),
                    };
                    let unhovered_border_color = match is_selected {
                        true => accent_color.border_color(BorderColorVariant::DefaultColored),
                        false => accent_color.border_color(BorderColorVariant::DefaultGrayscale),
                    };
                    let hovered_bg_color = match is_selected {
                        true => accent_color.primary_fill_color(PrimaryFillColorVariant::Hovered),
                        false => Color::BLACK.with_alpha_factor(0.1),
                    };
                    let hovered_border_color = match is_selected {
                        true => accent_color.border_color(BorderColorVariant::HoveredColored),
                        false => accent_color.border_color(BorderColorVariant::HoveredGrayscale),
                    };

                    s.background(unhovered_bg_color)
                        .padding(12.0)
                        .border(1.0)
                        .border_color(unhovered_border_color)
                        .border_radius(5.0)
                        .disabled(|s| {
                            s.background(
                                self.accent_color
                                    .get()
                                    .primary_fill_color(PrimaryFillColorVariant::Disabled),
                            )
                            .border_color(
                                self.accent_color
                                    .get()
                                    .border_color(BorderColorVariant::Disabled),
                            )
                        })
                        .apply_if(is_hovering.get(), |s| {
                            s.background(hovered_bg_color)
                                .border_color(hovered_border_color)
                        })
                        .apply_if(is_focused.get(), |s| match is_selected {
                            true => s.border_color(
                                self.accent_color
                                    .get()
                                    .border_color(BorderColorVariant::FocusedColored),
                            ),
                            false => s.border_color(
                                self.accent_color
                                    .get()
                                    .border_color(BorderColorVariant::FocusedGrayscale),
                            ),
                        })
                }),
                label(label_render_func).style(move |s| {
                    s.disabled(|s| s.color(self.accent_color.get().disabled_text_color()))
                }),
            ))
            .keyboard_navigatable()
            .style(|s| {
                s.align_items(AlignItems::Center)
                    .border_radius(5.0)
                    .focus_visible(|s| {
                        s.outline(2.0)
                            .outline_color(Color::WHITE.with_alpha_factor(0.5))
                    })
                    .gap(10.0, 0.0)
            })
            .on_event_stop(EventListener::PointerEnter, move |_| {
                set_is_hovering.set(true);
            })
            .on_event_stop(EventListener::PointerLeave, move |_| {
                set_is_hovering.set(false);
            })
            .on_event_stop(EventListener::FocusGained, move |_| {
                set_is_focused.set(true);
            })
            .on_event_stop(EventListener::FocusLost, move |_| {
                set_is_focused.set(false);
            }),
        )
    }
}