freya_components/
checkbox.rs

1use freya_core::prelude::*;
2use torin::prelude::*;
3
4use crate::{
5    get_theme,
6    icons::tick::TickIcon,
7    theming::component_themes::{
8        CheckboxTheme,
9        CheckboxThemePartial,
10    },
11};
12
13/// Checkbox component.
14///
15/// # Example
16///
17/// ```rust
18/// # use std::collections::HashSet;
19/// # use freya::prelude::*;
20/// fn app() -> impl IntoElement {
21///     let mut checked = use_state(|| false);
22///
23///     rect()
24///         .spacing(8.)
25///         .child(
26///             Tile::new()
27///                 .on_select(move |_| checked.toggle())
28///                 .child(Checkbox::new().selected(checked()))
29///                 .leading("Click to check"),
30///         )
31///         .child(
32///             Tile::new()
33///                 .on_select(move |_| checked.toggle())
34///                 .child(Checkbox::new().selected(!checked()))
35///                 .child("Click to check"),
36///         )
37/// }
38///
39/// # use freya_testing::prelude::*;
40/// # launch_doc(|| {
41/// #   rect()
42///         .spacing(8.).center().expanded().child(app())
43/// # }, (250., 250.).into(), "./images/gallery_checkbox.png");
44/// ```
45///
46/// # Preview
47/// ![Checkbox Preview][checkbox]
48#[cfg_attr(feature = "docs",
49    doc = embed_doc_image::embed_image!("checkbox", "images/gallery_checkbox.png")
50)]
51#[derive(Clone, PartialEq)]
52pub struct Checkbox {
53    pub(crate) theme: Option<CheckboxThemePartial>,
54    selected: bool,
55    key: DiffKey,
56    size: f32,
57}
58
59impl Default for Checkbox {
60    fn default() -> Self {
61        Self::new()
62    }
63}
64
65impl Checkbox {
66    pub fn new() -> Self {
67        Self {
68            selected: false,
69            theme: None,
70            key: DiffKey::None,
71            size: 20.,
72        }
73    }
74
75    pub fn selected(mut self, selected: bool) -> Self {
76        self.selected = selected;
77        self
78    }
79
80    pub fn theme(mut self, theme: CheckboxThemePartial) -> Self {
81        self.theme = Some(theme);
82        self
83    }
84
85    pub fn key(mut self, key: impl Into<DiffKey>) -> Self {
86        self.key = key.into();
87        self
88    }
89
90    pub fn size(mut self, size: impl Into<f32>) -> Self {
91        self.size = size.into();
92        self
93    }
94}
95
96impl Render for Checkbox {
97    fn render(&self) -> impl IntoElement {
98        let focus = use_focus();
99        let focus_status = use_focus_status(focus);
100        let CheckboxTheme {
101            border_fill,
102            unselected_fill,
103            selected_fill,
104            selected_icon_fill,
105        } = get_theme!(&self.theme, checkbox);
106
107        let (background, fill) = if self.selected {
108            (selected_fill, selected_fill)
109        } else {
110            (Color::TRANSPARENT, unselected_fill)
111        };
112
113        let border = Border::new()
114            .fill(fill)
115            .width(2.)
116            .alignment(BorderAlignment::Inner);
117
118        let focused_border = (focus_status() == FocusStatus::Keyboard).then(|| {
119            Border::new()
120                .fill(border_fill)
121                .width((self.size * 0.15).ceil())
122                .alignment(BorderAlignment::Outer)
123        });
124
125        rect()
126            .a11y_id(focus.a11y_id())
127            .a11y_focusable(Focusable::Enabled)
128            .width(Size::px(self.size))
129            .height(Size::px(self.size))
130            .padding(Gaps::new_all(4.0))
131            .main_align(Alignment::center())
132            .cross_align(Alignment::center())
133            .corner_radius(CornerRadius::new_all(self.size * 0.24))
134            .border(border)
135            .border(focused_border)
136            .background(background)
137            .on_key_down({
138                move |e: Event<KeyboardEventData>| {
139                    if !Focus::is_pressed(&e) {
140                        e.stop_propagation();
141                    }
142                }
143            })
144            .maybe_child(self.selected.then(|| {
145                TickIcon::new()
146                    .width(Size::px(self.size * 0.7))
147                    .height(Size::px(self.size * 0.7))
148                    .fill(selected_icon_fill)
149            }))
150    }
151
152    fn render_key(&self) -> DiffKey {
153        self.key.clone().or(self.default_key())
154    }
155}