maycoon_widgets/
checkbox.rs

1use maycoon_core::app::context::AppContext;
2use maycoon_core::app::info::AppInfo;
3use maycoon_core::app::update::Update;
4use maycoon_core::layout;
5use maycoon_core::layout::{Dimension, LayoutNode, LayoutStyle, LengthPercentageAuto, StyleNode};
6use maycoon_core::signal::MaybeSignal;
7use maycoon_core::vgi::kurbo::{Rect, RoundedRect, RoundedRectRadii, Stroke};
8use maycoon_core::vgi::{Brush, Scene};
9use maycoon_core::widget::{Widget, WidgetLayoutExt};
10use maycoon_core::window::{ElementState, MouseButton};
11use maycoon_theme::id::WidgetId;
12use maycoon_theme::theme::Theme;
13use nalgebra::Vector2;
14
15/// A checkbox widget. Changes state when it's clicked.
16///
17/// See the [checkbox](https://github.com/maycoon-ui/maycoon/blob/master/examples/checkbox/src/main.rs) example for how to use it in practice.
18///
19/// ### Theming
20/// Styling the checkbox require following properties:
21/// - `color_unchecked` -  The color of the checkbox, when it's not checked (inner value is false).
22/// - `color_checked` - The color of the checkbox, when it's checked (inner value is true).
23pub struct Checkbox {
24    layout_style: MaybeSignal<LayoutStyle>,
25    value: MaybeSignal<bool>,
26    on_change: MaybeSignal<Update>,
27}
28
29impl Checkbox {
30    /// Create a new checkbox with the given value.
31    ///
32    /// The value should be a signal, so it's mutable.
33    pub fn new(value: impl Into<MaybeSignal<bool>>) -> Self {
34        Self {
35            layout_style: LayoutStyle {
36                size: Vector2::<Dimension>::new(Dimension::length(20.0), Dimension::length(20.0)),
37                margin: layout::Rect::<LengthPercentageAuto> {
38                    left: LengthPercentageAuto::length(0.5),
39                    right: LengthPercentageAuto::length(0.5),
40                    top: LengthPercentageAuto::length(0.5),
41                    bottom: LengthPercentageAuto::length(0.5),
42                },
43                ..Default::default()
44            }
45            .into(),
46            value: value.into(),
47            on_change: Update::empty().into(),
48        }
49    }
50
51    /// Sets the value of the checkbox and returns itself.
52    pub fn with_value(mut self, value: impl Into<MaybeSignal<bool>>) -> Self {
53        self.value = value.into();
54        self
55    }
56
57    /// Sets the update value to apply on changes.
58    pub fn with_on_change(mut self, on_change: impl Into<MaybeSignal<Update>>) -> Self {
59        self.on_change = on_change.into();
60        self
61    }
62}
63
64impl WidgetLayoutExt for Checkbox {
65    fn set_layout_style(&mut self, layout_style: impl Into<MaybeSignal<LayoutStyle>>) {
66        self.layout_style = layout_style.into();
67    }
68}
69
70impl Widget for Checkbox {
71    fn render(
72        &mut self,
73        scene: &mut dyn Scene,
74        theme: &mut dyn Theme,
75        layout_node: &LayoutNode,
76        _: &AppInfo,
77        _: AppContext,
78    ) {
79        let checked = *self.value.get();
80
81        let color = if let Some(style) = theme.of(self.widget_id()) {
82            if checked {
83                style.get_color("color_checked").unwrap()
84            } else {
85                style.get_color("color_unchecked").unwrap()
86            }
87        } else if checked {
88            theme.defaults().interactive().active()
89        } else {
90            theme.defaults().interactive().inactive()
91        };
92
93        scene.draw_rounded_rect(
94            &Brush::Solid(color),
95            None,
96            Some(&Stroke::new(3.0)),
97            &RoundedRect::from_rect(
98                Rect::new(
99                    layout_node.layout.location.x as f64,
100                    layout_node.layout.location.y as f64,
101                    (layout_node.layout.location.x + layout_node.layout.size.width) as f64,
102                    (layout_node.layout.location.y + layout_node.layout.size.height) as f64,
103                ),
104                RoundedRectRadii::from_single_radius(5.0),
105            ),
106        );
107
108        if checked {
109            scene.draw_rounded_rect(
110                &Brush::Solid(color),
111                None,
112                None,
113                &RoundedRect::from_rect(
114                    Rect::new(
115                        layout_node.layout.location.x as f64 + 5.0,
116                        layout_node.layout.location.y as f64 + 5.0,
117                        (layout_node.layout.location.x + layout_node.layout.size.width) as f64
118                            - 5.0,
119                        (layout_node.layout.location.y + layout_node.layout.size.height) as f64
120                            - 5.0,
121                    ),
122                    RoundedRectRadii::from_single_radius(2.5),
123                ),
124            );
125        }
126    }
127
128    fn layout_style(&self) -> StyleNode {
129        StyleNode {
130            style: self.layout_style.get().clone(),
131            children: Vec::new(),
132        }
133    }
134
135    fn update(&mut self, layout: &LayoutNode, _: AppContext, info: &AppInfo) -> Update {
136        let mut update = Update::empty();
137
138        if let Some(cursor) = &info.cursor_pos
139            && (cursor.x as f32 >= layout.layout.location.x
140                && cursor.x as f32 <= layout.layout.location.x + layout.layout.size.width
141                && cursor.y as f32 >= layout.layout.location.y
142                && cursor.y as f32 <= layout.layout.location.y + layout.layout.size.height)
143        {
144            for (_, btn, el) in &info.buttons {
145                if btn == &MouseButton::Left && *el == ElementState::Released {
146                    update |= *self.on_change.get();
147                    update |= Update::DRAW;
148
149                    if let Some(sig) = self.value.as_signal() {
150                        let checked = *sig.get();
151                        sig.set(!checked);
152                    }
153                }
154            }
155        }
156
157        update
158    }
159
160    fn widget_id(&self) -> WidgetId {
161        WidgetId::new("maycoon-widgets", "Checkbox")
162    }
163}