freya_components/
checkbox.rs

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