1
//! Checkbox widget with toggle callback support and default native-like styling.
2
//!
3
//! Key types: [`CheckBox`], [`CheckBoxState`], [`CheckBoxOnToggle`].
4

            
5
use azul_core::{
6
    callbacks::{CoreCallbackData, Update},
7
    dom::{Dom, IdOrClass, IdOrClass::Class, IdOrClassVec, TabIndex},
8
    refany::RefAny,
9
};
10
use azul_css::dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec};
11
use azul_css::{
12
    props::{
13
        basic::{color::ColorU, *},
14
        layout::*,
15
        property::{CssProperty, *},
16
        style::*,
17
    },
18
    *,
19
};
20

            
21
use crate::callbacks::{Callback, CallbackInfo};
22

            
23
static CHECKBOX_CONTAINER_CLASS: &[IdOrClass] = &[Class(AzString::from_const_str(
24
    "__azul-native-checkbox-container",
25
))];
26
static 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.
31
pub type CheckBoxOnToggleCallbackType =
32
    extern "C" fn(RefAny, CallbackInfo, CheckBoxState) -> Update;
33
impl_widget_callback!(
34
    CheckBoxOnToggle,
35
    OptionCheckBoxOnToggle,
36
    CheckBoxOnToggleCallback,
37
    CheckBoxOnToggleCallbackType
38
);
39

            
40
azul_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)]
56
pub 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)]
66
pub 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)]
76
pub struct CheckBoxState {
77
    pub checked: bool,
78
}
79

            
80
const BACKGROUND_COLOR: ColorU = ColorU {
81
    r: 255,
82
    g: 255,
83
    b: 255,
84
    a: 255,
85
}; // white
86
const BACKGROUND_THEME_LIGHT: &[StyleBackgroundContent] =
87
    &[StyleBackgroundContent::Color(BACKGROUND_COLOR)];
88
const BACKGROUND_COLOR_LIGHT: StyleBackgroundContentVec =
89
    StyleBackgroundContentVec::from_const_slice(BACKGROUND_THEME_LIGHT);
90
const COLOR_9B9B9B: ColorU = ColorU {
91
    r: 155,
92
    g: 155,
93
    b: 155,
94
    a: 255,
95
}; // #9b9b9b
96

            
97
const FILL_COLOR: ColorU = ColorU {
98
    r: 155,
99
    g: 155,
100
    b: 155,
101
    a: 255,
102
}; // #9b9b9b
103
const FILL_THEME: &[StyleBackgroundContent] = &[StyleBackgroundContent::Color(FILL_COLOR)];
104
const FILL_COLOR_BACKGROUND: StyleBackgroundContentVec =
105
    StyleBackgroundContentVec::from_const_slice(FILL_THEME);
106

            
107
static 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

            
175
static 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

            
182
static 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

            
189
impl 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
269
mod 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

            
324
impl From<CheckBox> for Dom {
325
    fn from(b: CheckBox) -> Dom {
326
        b.dom()
327
    }
328
}