Skip to main content

azul_layout/widgets/
check_box.rs

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