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