1use azul_core::{
10 callbacks::{CoreCallbackData, Update},
11 dom::{Dom, IdOrClass, IdOrClass::Class, IdOrClassVec, TabIndex},
12 refany::RefAny,
13};
14use azul_css::dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec};
15use azul_css::{
16 props::{
17 basic::{color::ColorU, *},
18 layout::{LayoutDisplay, LayoutFlexDirection, LayoutAlignItems, LayoutAlignSelf, LayoutFlexGrow, LayoutWidth, LayoutHeight, LayoutPaddingLeft, LayoutPaddingRight, LayoutPaddingTop, LayoutPaddingBottom, LayoutMarginLeft},
19 property::{CssProperty, *},
20 style::{StyleBackgroundContent, StyleBackgroundContentVec, StyleBorderTopLeftRadius, StyleBorderTopRightRadius, StyleBorderBottomLeftRadius, StyleBorderBottomRightRadius, StyleCursor},
21 },
22 impl_option_inner, AzString,
23};
24
25use crate::callbacks::{Callback, CallbackInfo};
26
27static SWITCH_TRACK_CLASS: &[IdOrClass] =
28 &[Class(AzString::from_const_str("__azul-native-switch"))];
29static SWITCH_KNOB_CLASS: &[IdOrClass] =
30 &[Class(AzString::from_const_str("__azul-native-switch-knob"))];
31
32pub type SwitchOnToggleCallbackType = extern "C" fn(RefAny, CallbackInfo, SwitchState) -> Update;
34impl_widget_callback!(
35 SwitchOnToggle,
36 OptionSwitchOnToggle,
37 SwitchOnToggleCallback,
38 SwitchOnToggleCallbackType
39);
40
41azul_core::impl_managed_callback! {
42 wrapper: SwitchOnToggleCallback,
43 info_ty: CallbackInfo,
44 return_ty: Update,
45 default_ret: Update::DoNothing,
46 invoker_static: SWITCH_ON_TOGGLE_INVOKER,
47 invoker_ty: AzSwitchOnToggleCallbackInvoker,
48 thunk_fn: az_switch_on_toggle_callback_thunk,
49 setter_fn: AzApp_setSwitchOnToggleCallbackInvoker,
50 from_handle_fn: AzSwitchOnToggleCallback_createFromHostHandle,
51 extra_args: [ state: SwitchState ],
52}
53
54#[derive(Debug, Clone, PartialEq, Eq)]
56#[repr(C)]
57pub struct Switch {
58 pub switch_state: SwitchStateWrapper,
59 pub track_style: CssPropertyWithConditionsVec,
61 pub knob_style: CssPropertyWithConditionsVec,
63}
64
65#[derive(Debug, Default, Clone, PartialEq, Eq)]
66#[repr(C)]
67pub struct SwitchStateWrapper {
68 pub inner: SwitchState,
70 pub on_toggle: OptionSwitchOnToggle,
72}
73
74#[derive(Copy, Debug, Default, Clone, PartialEq, Eq)]
76#[repr(C)]
77pub struct SwitchState {
78 pub checked: bool,
80}
81
82const TRACK_WIDTH: isize = 36;
84const TRACK_HEIGHT: isize = 20;
85const TRACK_PADDING: isize = 2;
86const TRACK_RADIUS: isize = 10;
87const KNOB_SIZE: isize = 16;
88const KNOB_RADIUS: isize = 8;
89const KNOB_TRAVEL: isize = TRACK_WIDTH - (2 * TRACK_PADDING) - KNOB_SIZE;
91
92const TRACK_OFF_COLOR: ColorU = ColorU {
94 r: 204,
95 g: 204,
96 b: 204,
97 a: 255,
98}; const TRACK_ON_COLOR: ColorU = ColorU {
100 r: 76,
101 g: 217,
102 b: 100,
103 a: 255,
104}; const KNOB_COLOR: ColorU = ColorU {
106 r: 255,
107 g: 255,
108 b: 255,
109 a: 255,
110}; const TRACK_OFF_BG_ITEMS: &[StyleBackgroundContent] =
113 &[StyleBackgroundContent::Color(TRACK_OFF_COLOR)];
114const TRACK_OFF_BG: StyleBackgroundContentVec =
115 StyleBackgroundContentVec::from_const_slice(TRACK_OFF_BG_ITEMS);
116const TRACK_ON_BG_ITEMS: &[StyleBackgroundContent] =
117 &[StyleBackgroundContent::Color(TRACK_ON_COLOR)];
118const TRACK_ON_BG: StyleBackgroundContentVec =
119 StyleBackgroundContentVec::from_const_slice(TRACK_ON_BG_ITEMS);
120const KNOB_BG_ITEMS: &[StyleBackgroundContent] = &[StyleBackgroundContent::Color(KNOB_COLOR)];
121const KNOB_BG: StyleBackgroundContentVec =
122 StyleBackgroundContentVec::from_const_slice(KNOB_BG_ITEMS);
123
124fn build_track_style(checked: bool) -> CssPropertyWithConditionsVec {
128 let bg = if checked { TRACK_ON_BG } else { TRACK_OFF_BG };
129 CssPropertyWithConditionsVec::from_vec(alloc::vec![
130 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Flex)),
131 CssPropertyWithConditions::simple(CssProperty::const_flex_direction(
132 LayoutFlexDirection::Row,
133 )),
134 CssPropertyWithConditions::simple(CssProperty::const_align_items(LayoutAlignItems::Center)),
135 CssPropertyWithConditions::simple(CssProperty::align_self(LayoutAlignSelf::Center)),
136 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(
137 0,
138 ))),
139 CssPropertyWithConditions::simple(CssProperty::const_width(LayoutWidth::const_px(
140 TRACK_WIDTH,
141 ))),
142 CssPropertyWithConditions::simple(CssProperty::const_height(LayoutHeight::const_px(
143 TRACK_HEIGHT,
144 ))),
145 CssPropertyWithConditions::simple(CssProperty::const_padding_left(
146 LayoutPaddingLeft::const_px(TRACK_PADDING),
147 )),
148 CssPropertyWithConditions::simple(CssProperty::const_padding_right(
149 LayoutPaddingRight::const_px(TRACK_PADDING),
150 )),
151 CssPropertyWithConditions::simple(CssProperty::const_padding_top(
152 LayoutPaddingTop::const_px(TRACK_PADDING),
153 )),
154 CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
155 LayoutPaddingBottom::const_px(TRACK_PADDING),
156 )),
157 CssPropertyWithConditions::simple(CssProperty::const_border_top_left_radius(
158 StyleBorderTopLeftRadius::const_px(TRACK_RADIUS),
159 )),
160 CssPropertyWithConditions::simple(CssProperty::const_border_top_right_radius(
161 StyleBorderTopRightRadius::const_px(TRACK_RADIUS),
162 )),
163 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_left_radius(
164 StyleBorderBottomLeftRadius::const_px(TRACK_RADIUS),
165 )),
166 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_right_radius(
167 StyleBorderBottomRightRadius::const_px(TRACK_RADIUS),
168 )),
169 CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)),
170 CssPropertyWithConditions::simple(CssProperty::const_background_content(bg)),
171 ])
172}
173
174fn build_knob_style(checked: bool) -> CssPropertyWithConditionsVec {
177 let margin = if checked { KNOB_TRAVEL } else { 0 };
178 CssPropertyWithConditionsVec::from_vec(alloc::vec![
179 CssPropertyWithConditions::simple(CssProperty::const_width(LayoutWidth::const_px(
180 KNOB_SIZE,
181 ))),
182 CssPropertyWithConditions::simple(CssProperty::const_height(LayoutHeight::const_px(
183 KNOB_SIZE,
184 ))),
185 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(
186 0,
187 ))),
188 CssPropertyWithConditions::simple(CssProperty::const_border_top_left_radius(
189 StyleBorderTopLeftRadius::const_px(KNOB_RADIUS),
190 )),
191 CssPropertyWithConditions::simple(CssProperty::const_border_top_right_radius(
192 StyleBorderTopRightRadius::const_px(KNOB_RADIUS),
193 )),
194 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_left_radius(
195 StyleBorderBottomLeftRadius::const_px(KNOB_RADIUS),
196 )),
197 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_right_radius(
198 StyleBorderBottomRightRadius::const_px(KNOB_RADIUS),
199 )),
200 CssPropertyWithConditions::simple(CssProperty::const_background_content(KNOB_BG)),
201 CssPropertyWithConditions::simple(CssProperty::const_margin_left(
202 LayoutMarginLeft::const_px(margin),
203 )),
204 ])
205}
206
207impl Switch {
208 #[must_use] pub fn create(checked: bool) -> Self {
210 Self {
211 switch_state: SwitchStateWrapper {
212 inner: SwitchState { checked },
213 ..Default::default()
214 },
215 track_style: build_track_style(checked),
216 knob_style: build_knob_style(checked),
217 }
218 }
219
220 #[inline]
221 #[must_use] pub fn swap_with_default(&mut self) -> Self {
222 let mut s = Self::create(false);
223 core::mem::swap(&mut s, self);
224 s
225 }
226
227 #[inline]
228 pub fn set_on_toggle<C: Into<SwitchOnToggleCallback>>(&mut self, data: RefAny, on_toggle: C) {
229 self.switch_state.on_toggle = Some(SwitchOnToggle {
230 callback: on_toggle.into(),
231 refany: data,
232 })
233 .into();
234 }
235
236 #[inline]
237 #[must_use] pub fn with_on_toggle<C: Into<SwitchOnToggleCallback>>(
238 mut self,
239 data: RefAny,
240 on_toggle: C,
241 ) -> Self {
242 self.set_on_toggle(data, on_toggle);
243 self
244 }
245
246 #[inline]
247 #[must_use] pub fn dom(self) -> Dom {
248 use azul_core::{
249 callbacks::{CoreCallback, CoreCallbackData},
250 dom::{Dom, EventFilter, HoverEventFilter},
251 };
252
253 Dom::create_div()
254 .with_ids_and_classes(IdOrClassVec::from(SWITCH_TRACK_CLASS))
255 .with_css_props(self.track_style)
256 .with_callbacks(
257 vec![CoreCallbackData {
258 event: EventFilter::Hover(HoverEventFilter::MouseUp),
259 callback: CoreCallback {
260 cb: input::default_on_switch_clicked as usize,
261 ctx: azul_core::refany::OptionRefAny::None,
262 },
263 refany: RefAny::new(self.switch_state),
264 }]
265 .into(),
266 )
267 .with_tab_index(TabIndex::Auto)
268 .with_children(
269 vec![Dom::create_div()
270 .with_ids_and_classes(IdOrClassVec::from(SWITCH_KNOB_CLASS))
271 .with_css_props(self.knob_style)]
272 .into(),
273 )
274 }
275}
276
277impl Default for Switch {
278 fn default() -> Self {
279 Self::create(false)
280 }
281}
282
283mod input {
285
286 use azul_core::{callbacks::Update, refany::RefAny};
287 use azul_css::props::{layout::LayoutMarginLeft, property::CssProperty};
288
289 use super::{SwitchOnToggle, SwitchStateWrapper, KNOB_TRAVEL, TRACK_OFF_BG, TRACK_ON_BG};
290 use crate::callbacks::CallbackInfo;
291
292 pub(super) extern "C" fn default_on_switch_clicked(
293 mut switch: RefAny,
294 mut info: CallbackInfo,
295 ) -> Update {
296 let Some(mut switch) = switch.downcast_mut::<SwitchStateWrapper>() else {
297 return Update::DoNothing;
298 };
299
300 let track_id = info.get_hit_node();
301 let Some(knob_id) = info.get_first_child(track_id) else {
302 return Update::DoNothing;
303 };
304
305 switch.inner.checked = !switch.inner.checked;
306
307 let result = {
308 let switch = &mut *switch;
310 let on_toggle = &mut switch.on_toggle;
311 let inner = switch.inner;
312
313 match on_toggle.as_mut() {
314 Some(SwitchOnToggle {
315 callback,
316 refany: data,
317 }) => (callback.cb)(data.clone(), info, inner),
318 None => Update::DoNothing,
319 }
320 };
321
322 if switch.inner.checked {
324 info.set_css_property(track_id, CssProperty::const_background_content(TRACK_ON_BG));
325 info.set_css_property(
326 knob_id,
327 CssProperty::const_margin_left(LayoutMarginLeft::const_px(KNOB_TRAVEL)),
328 );
329 } else {
330 info.set_css_property(track_id, CssProperty::const_background_content(TRACK_OFF_BG));
331 info.set_css_property(
332 knob_id,
333 CssProperty::const_margin_left(LayoutMarginLeft::const_px(0)),
334 );
335 }
336
337 result
338 }
339}
340
341impl From<Switch> for Dom {
342 fn from(s: Switch) -> Self {
343 s.dom()
344 }
345}