Skip to main content

azul_layout/widgets/
check_box.rs

1use azul_core::{
2    callbacks::{CoreCallbackData, Update},
3    dom::{Dom, IdOrClass, IdOrClass::Class, IdOrClassVec, TabIndex},
4    refany::RefAny,
5};
6use azul_css::dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec};
7use azul_css::{
8    props::{
9        basic::{color::ColorU, *},
10        layout::*,
11        property::{CssProperty, *},
12        style::*,
13    },
14    *,
15};
16
17use crate::callbacks::{Callback, CallbackInfo};
18
19static CHECKBOX_CONTAINER_CLASS: &[IdOrClass] = &[Class(AzString::from_const_str(
20    "__azul-native-checkbox-container",
21))];
22static CHECKBOX_CONTENT_CLASS: &[IdOrClass] = &[Class(AzString::from_const_str(
23    "__azul-native-checkbox-content",
24))];
25
26pub type CheckBoxOnToggleCallbackType =
27    extern "C" fn(RefAny, CallbackInfo, CheckBoxState) -> Update;
28impl_widget_callback!(
29    CheckBoxOnToggle,
30    OptionCheckBoxOnToggle,
31    CheckBoxOnToggleCallback,
32    CheckBoxOnToggleCallbackType
33);
34
35#[derive(Debug, Clone, PartialEq)]
36#[repr(C)]
37pub struct CheckBox {
38    pub check_box_state: CheckBoxStateWrapper,
39    /// Style for the checkbox container
40    pub container_style: CssPropertyWithConditionsVec,
41    /// Style for the checkbox content
42    pub content_style: CssPropertyWithConditionsVec,
43}
44
45#[derive(Debug, Default, Clone, PartialEq)]
46#[repr(C)]
47pub struct CheckBoxStateWrapper {
48    /// Content (image or text) of this CheckBox, centered by default
49    pub inner: CheckBoxState,
50    /// Optional: Function to call when the CheckBox is toggled
51    pub on_toggle: OptionCheckBoxOnToggle,
52}
53
54#[derive(Debug, Default, Clone, PartialEq)]
55#[repr(C)]
56pub struct CheckBoxState {
57    pub checked: bool,
58}
59
60const BACKGROUND_COLOR: ColorU = ColorU {
61    r: 255,
62    g: 255,
63    b: 255,
64    a: 255,
65}; // white
66const BACKGROUND_THEME_LIGHT: &[StyleBackgroundContent] =
67    &[StyleBackgroundContent::Color(BACKGROUND_COLOR)];
68const BACKGROUND_COLOR_LIGHT: StyleBackgroundContentVec =
69    StyleBackgroundContentVec::from_const_slice(BACKGROUND_THEME_LIGHT);
70const COLOR_9B9B9B: ColorU = ColorU {
71    r: 155,
72    g: 155,
73    b: 155,
74    a: 255,
75}; // #9b9b9b
76
77const FILL_COLOR: ColorU = ColorU {
78    r: 155,
79    g: 155,
80    b: 155,
81    a: 255,
82}; // #9b9b9b
83const FILL_THEME: &[StyleBackgroundContent] = &[StyleBackgroundContent::Color(FILL_COLOR)];
84const FILL_COLOR_BACKGROUND: StyleBackgroundContentVec =
85    StyleBackgroundContentVec::from_const_slice(FILL_THEME);
86
87static DEFAULT_CHECKBOX_CONTAINER_STYLE: &[CssPropertyWithConditions] = &[
88    CssPropertyWithConditions::simple(CssProperty::const_background_content(
89        BACKGROUND_COLOR_LIGHT,
90    )),
91    CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Block)),
92    CssPropertyWithConditions::simple(CssProperty::const_width(LayoutWidth::const_px(14))),
93    CssPropertyWithConditions::simple(CssProperty::const_height(LayoutHeight::const_px(14))),
94    // padding: 2px
95    CssPropertyWithConditions::simple(CssProperty::const_padding_left(
96        LayoutPaddingLeft::const_px(2),
97    )),
98    CssPropertyWithConditions::simple(CssProperty::const_padding_right(
99        LayoutPaddingRight::const_px(2),
100    )),
101    CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(
102        2,
103    ))),
104    CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
105        LayoutPaddingBottom::const_px(2),
106    )),
107    // border: 1px solid #484c52;
108    CssPropertyWithConditions::simple(CssProperty::const_border_top_width(
109        LayoutBorderTopWidth::const_px(1),
110    )),
111    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
112        LayoutBorderBottomWidth::const_px(1),
113    )),
114    CssPropertyWithConditions::simple(CssProperty::const_border_left_width(
115        LayoutBorderLeftWidth::const_px(1),
116    )),
117    CssPropertyWithConditions::simple(CssProperty::const_border_right_width(
118        LayoutBorderRightWidth::const_px(1),
119    )),
120    CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle {
121        inner: BorderStyle::Inset,
122    })),
123    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
124        StyleBorderBottomStyle {
125            inner: BorderStyle::Inset,
126        },
127    )),
128    CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle {
129        inner: BorderStyle::Inset,
130    })),
131    CssPropertyWithConditions::simple(CssProperty::const_border_right_style(
132        StyleBorderRightStyle {
133            inner: BorderStyle::Inset,
134        },
135    )),
136    CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
137        inner: COLOR_9B9B9B,
138    })),
139    CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
140        StyleBorderBottomColor {
141            inner: COLOR_9B9B9B,
142        },
143    )),
144    CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor {
145        inner: COLOR_9B9B9B,
146    })),
147    CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
148        StyleBorderRightColor {
149            inner: COLOR_9B9B9B,
150        },
151    )),
152    CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)),
153];
154
155static DEFAULT_CHECKBOX_CONTENT_STYLE_CHECKED: &[CssPropertyWithConditions] = &[
156    CssPropertyWithConditions::simple(CssProperty::const_width(LayoutWidth::const_px(8))),
157    CssPropertyWithConditions::simple(CssProperty::const_height(LayoutHeight::const_px(8))),
158    CssPropertyWithConditions::simple(CssProperty::const_background_content(FILL_COLOR_BACKGROUND)),
159    CssPropertyWithConditions::simple(CssProperty::const_opacity(StyleOpacity::const_new(100))),
160    // padding: 2px
161];
162
163static DEFAULT_CHECKBOX_CONTENT_STYLE_UNCHECKED: &[CssPropertyWithConditions] = &[
164    CssPropertyWithConditions::simple(CssProperty::const_width(LayoutWidth::const_px(8))),
165    CssPropertyWithConditions::simple(CssProperty::const_height(LayoutHeight::const_px(8))),
166    CssPropertyWithConditions::simple(CssProperty::const_background_content(FILL_COLOR_BACKGROUND)),
167    CssPropertyWithConditions::simple(CssProperty::const_opacity(StyleOpacity::const_new(0))),
168    // padding: 2px
169];
170
171impl CheckBox {
172    pub fn create(checked: bool) -> Self {
173        Self {
174            check_box_state: CheckBoxStateWrapper {
175                inner: CheckBoxState { checked },
176                ..Default::default()
177            },
178            container_style: CssPropertyWithConditionsVec::from_const_slice(
179                DEFAULT_CHECKBOX_CONTAINER_STYLE,
180            ),
181            content_style: if checked {
182                CssPropertyWithConditionsVec::from_const_slice(
183                    DEFAULT_CHECKBOX_CONTENT_STYLE_CHECKED,
184                )
185            } else {
186                CssPropertyWithConditionsVec::from_const_slice(
187                    DEFAULT_CHECKBOX_CONTENT_STYLE_UNCHECKED,
188                )
189            },
190        }
191    }
192
193    #[inline]
194    pub fn swap_with_default(&mut self) -> Self {
195        let mut s = Self::create(false);
196        core::mem::swap(&mut s, self);
197        s
198    }
199
200    #[inline]
201    pub fn set_on_toggle<C: Into<CheckBoxOnToggleCallback>>(&mut self, data: RefAny, on_toggle: C) {
202        self.check_box_state.on_toggle = Some(CheckBoxOnToggle {
203            callback: on_toggle.into(),
204            refany: data,
205        })
206        .into();
207    }
208
209    #[inline]
210    pub fn with_on_toggle<C: Into<CheckBoxOnToggleCallback>>(
211        mut self,
212        data: RefAny,
213        on_toggle: C,
214    ) -> Self {
215        self.set_on_toggle(data, on_toggle);
216        self
217    }
218
219    #[inline]
220    pub fn dom(self) -> Dom {
221        use azul_core::{
222            callbacks::{CoreCallback, CoreCallbackData},
223            dom::{Dom, EventFilter, HoverEventFilter},
224        };
225
226        Dom::create_div()
227            .with_ids_and_classes(IdOrClassVec::from(CHECKBOX_CONTAINER_CLASS))
228            .with_css_props(self.container_style)
229            .with_callbacks(
230                vec![CoreCallbackData {
231                    event: EventFilter::Hover(HoverEventFilter::MouseUp),
232                    callback: CoreCallback {
233                        cb: self::input::default_on_checkbox_clicked as usize,
234                        ctx: azul_core::refany::OptionRefAny::None,
235                    },
236                    refany: RefAny::new(self.check_box_state),
237                }]
238                .into(),
239            )
240            .with_tab_index(TabIndex::Auto)
241            .with_children(
242                vec![Dom::create_div()
243                    .with_ids_and_classes(IdOrClassVec::from(CHECKBOX_CONTENT_CLASS))
244                    .with_css_props(self.content_style)]
245                .into(),
246            )
247    }
248}
249
250// handle input events for the checkbox
251mod input {
252
253    use azul_core::{callbacks::Update, refany::RefAny};
254    use azul_css::props::{property::CssProperty, style::effects::StyleOpacity};
255
256    use super::{CheckBoxOnToggle, CheckBoxStateWrapper};
257    use crate::callbacks::CallbackInfo;
258
259    pub(super) extern "C" fn default_on_checkbox_clicked(
260        mut check_box: RefAny,
261        mut info: CallbackInfo,
262    ) -> Update {
263        let mut check_box = match check_box.downcast_mut::<CheckBoxStateWrapper>() {
264            Some(s) => s,
265            None => return Update::DoNothing,
266        };
267
268        let checkbox_content_id = match info.get_first_child(info.get_hit_node()) {
269            Some(s) => s,
270            None => return Update::DoNothing,
271        };
272
273        check_box.inner.checked = !check_box.inner.checked;
274
275        let result = {
276            // rustc doesn't understand the borrowing lifetime here
277            let check_box = &mut *check_box;
278            let ontoggle = &mut check_box.on_toggle;
279            let inner = check_box.inner.clone();
280
281            match ontoggle.as_mut() {
282                Some(CheckBoxOnToggle {
283                    callback,
284                    refany: data,
285                }) => (callback.cb)(data.clone(), info.clone(), inner),
286                None => Update::DoNothing,
287            }
288        };
289
290        if check_box.inner.checked {
291            info.set_css_property(
292                checkbox_content_id,
293                CssProperty::const_opacity(StyleOpacity::const_new(100)),
294            );
295        } else {
296            info.set_css_property(
297                checkbox_content_id,
298                CssProperty::const_opacity(StyleOpacity::const_new(0)),
299            );
300        }
301
302        result
303    }
304}
305
306impl From<CheckBox> for Dom {
307    fn from(b: CheckBox) -> Dom {
308        b.dom()
309    }
310}