use azul_core::{
callbacks::{CoreCallbackData, Update},
dom::{Dom, IdOrClass, IdOrClass::Class, IdOrClassVec, TabIndex},
refany::RefAny,
};
use azul_css::dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec};
use azul_css::{
props::{
basic::{color::ColorU, *},
layout::{LayoutDisplay, LayoutFlexDirection, LayoutAlignItems, LayoutAlignSelf, LayoutFlexGrow, LayoutWidth, LayoutHeight, LayoutPaddingLeft, LayoutPaddingRight, LayoutPaddingTop, LayoutPaddingBottom, LayoutMarginLeft},
property::{CssProperty, *},
style::{StyleBackgroundContent, StyleBackgroundContentVec, StyleBorderTopLeftRadius, StyleBorderTopRightRadius, StyleBorderBottomLeftRadius, StyleBorderBottomRightRadius, StyleCursor},
},
impl_option_inner, AzString,
};
use crate::callbacks::{Callback, CallbackInfo};
static SWITCH_TRACK_CLASS: &[IdOrClass] =
&[Class(AzString::from_const_str("__azul-native-switch"))];
static SWITCH_KNOB_CLASS: &[IdOrClass] =
&[Class(AzString::from_const_str("__azul-native-switch-knob"))];
pub type SwitchOnToggleCallbackType = extern "C" fn(RefAny, CallbackInfo, SwitchState) -> Update;
impl_widget_callback!(
SwitchOnToggle,
OptionSwitchOnToggle,
SwitchOnToggleCallback,
SwitchOnToggleCallbackType
);
azul_core::impl_managed_callback! {
wrapper: SwitchOnToggleCallback,
info_ty: CallbackInfo,
return_ty: Update,
default_ret: Update::DoNothing,
invoker_static: SWITCH_ON_TOGGLE_INVOKER,
invoker_ty: AzSwitchOnToggleCallbackInvoker,
thunk_fn: az_switch_on_toggle_callback_thunk,
setter_fn: AzApp_setSwitchOnToggleCallbackInvoker,
from_handle_fn: AzSwitchOnToggleCallback_createFromHostHandle,
extra_args: [ state: SwitchState ],
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(C)]
pub struct Switch {
pub switch_state: SwitchStateWrapper,
pub track_style: CssPropertyWithConditionsVec,
pub knob_style: CssPropertyWithConditionsVec,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[repr(C)]
pub struct SwitchStateWrapper {
pub inner: SwitchState,
pub on_toggle: OptionSwitchOnToggle,
}
#[derive(Copy, Debug, Default, Clone, PartialEq, Eq)]
#[repr(C)]
pub struct SwitchState {
pub checked: bool,
}
const TRACK_WIDTH: isize = 36;
const TRACK_HEIGHT: isize = 20;
const TRACK_PADDING: isize = 2;
const TRACK_RADIUS: isize = 10;
const KNOB_SIZE: isize = 16;
const KNOB_RADIUS: isize = 8;
const KNOB_TRAVEL: isize = TRACK_WIDTH - (2 * TRACK_PADDING) - KNOB_SIZE;
const TRACK_OFF_COLOR: ColorU = ColorU {
r: 204,
g: 204,
b: 204,
a: 255,
}; const TRACK_ON_COLOR: ColorU = ColorU {
r: 76,
g: 217,
b: 100,
a: 255,
}; const KNOB_COLOR: ColorU = ColorU {
r: 255,
g: 255,
b: 255,
a: 255,
};
const TRACK_OFF_BG_ITEMS: &[StyleBackgroundContent] =
&[StyleBackgroundContent::Color(TRACK_OFF_COLOR)];
const TRACK_OFF_BG: StyleBackgroundContentVec =
StyleBackgroundContentVec::from_const_slice(TRACK_OFF_BG_ITEMS);
const TRACK_ON_BG_ITEMS: &[StyleBackgroundContent] =
&[StyleBackgroundContent::Color(TRACK_ON_COLOR)];
const TRACK_ON_BG: StyleBackgroundContentVec =
StyleBackgroundContentVec::from_const_slice(TRACK_ON_BG_ITEMS);
const KNOB_BG_ITEMS: &[StyleBackgroundContent] = &[StyleBackgroundContent::Color(KNOB_COLOR)];
const KNOB_BG: StyleBackgroundContentVec =
StyleBackgroundContentVec::from_const_slice(KNOB_BG_ITEMS);
fn build_track_style(checked: bool) -> CssPropertyWithConditionsVec {
let bg = if checked { TRACK_ON_BG } else { TRACK_OFF_BG };
CssPropertyWithConditionsVec::from_vec(alloc::vec![
CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Flex)),
CssPropertyWithConditions::simple(CssProperty::const_flex_direction(
LayoutFlexDirection::Row,
)),
CssPropertyWithConditions::simple(CssProperty::const_align_items(LayoutAlignItems::Center)),
CssPropertyWithConditions::simple(CssProperty::align_self(LayoutAlignSelf::Center)),
CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(
0,
))),
CssPropertyWithConditions::simple(CssProperty::const_width(LayoutWidth::const_px(
TRACK_WIDTH,
))),
CssPropertyWithConditions::simple(CssProperty::const_height(LayoutHeight::const_px(
TRACK_HEIGHT,
))),
CssPropertyWithConditions::simple(CssProperty::const_padding_left(
LayoutPaddingLeft::const_px(TRACK_PADDING),
)),
CssPropertyWithConditions::simple(CssProperty::const_padding_right(
LayoutPaddingRight::const_px(TRACK_PADDING),
)),
CssPropertyWithConditions::simple(CssProperty::const_padding_top(
LayoutPaddingTop::const_px(TRACK_PADDING),
)),
CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
LayoutPaddingBottom::const_px(TRACK_PADDING),
)),
CssPropertyWithConditions::simple(CssProperty::const_border_top_left_radius(
StyleBorderTopLeftRadius::const_px(TRACK_RADIUS),
)),
CssPropertyWithConditions::simple(CssProperty::const_border_top_right_radius(
StyleBorderTopRightRadius::const_px(TRACK_RADIUS),
)),
CssPropertyWithConditions::simple(CssProperty::const_border_bottom_left_radius(
StyleBorderBottomLeftRadius::const_px(TRACK_RADIUS),
)),
CssPropertyWithConditions::simple(CssProperty::const_border_bottom_right_radius(
StyleBorderBottomRightRadius::const_px(TRACK_RADIUS),
)),
CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)),
CssPropertyWithConditions::simple(CssProperty::const_background_content(bg)),
])
}
fn build_knob_style(checked: bool) -> CssPropertyWithConditionsVec {
let margin = if checked { KNOB_TRAVEL } else { 0 };
CssPropertyWithConditionsVec::from_vec(alloc::vec![
CssPropertyWithConditions::simple(CssProperty::const_width(LayoutWidth::const_px(
KNOB_SIZE,
))),
CssPropertyWithConditions::simple(CssProperty::const_height(LayoutHeight::const_px(
KNOB_SIZE,
))),
CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(
0,
))),
CssPropertyWithConditions::simple(CssProperty::const_border_top_left_radius(
StyleBorderTopLeftRadius::const_px(KNOB_RADIUS),
)),
CssPropertyWithConditions::simple(CssProperty::const_border_top_right_radius(
StyleBorderTopRightRadius::const_px(KNOB_RADIUS),
)),
CssPropertyWithConditions::simple(CssProperty::const_border_bottom_left_radius(
StyleBorderBottomLeftRadius::const_px(KNOB_RADIUS),
)),
CssPropertyWithConditions::simple(CssProperty::const_border_bottom_right_radius(
StyleBorderBottomRightRadius::const_px(KNOB_RADIUS),
)),
CssPropertyWithConditions::simple(CssProperty::const_background_content(KNOB_BG)),
CssPropertyWithConditions::simple(CssProperty::const_margin_left(
LayoutMarginLeft::const_px(margin),
)),
])
}
impl Switch {
#[must_use] pub fn create(checked: bool) -> Self {
Self {
switch_state: SwitchStateWrapper {
inner: SwitchState { checked },
..Default::default()
},
track_style: build_track_style(checked),
knob_style: build_knob_style(checked),
}
}
#[inline]
#[must_use] pub fn swap_with_default(&mut self) -> Self {
let mut s = Self::create(false);
core::mem::swap(&mut s, self);
s
}
#[inline]
pub fn set_on_toggle<C: Into<SwitchOnToggleCallback>>(&mut self, data: RefAny, on_toggle: C) {
self.switch_state.on_toggle = Some(SwitchOnToggle {
callback: on_toggle.into(),
refany: data,
})
.into();
}
#[inline]
#[must_use] pub fn with_on_toggle<C: Into<SwitchOnToggleCallback>>(
mut self,
data: RefAny,
on_toggle: C,
) -> Self {
self.set_on_toggle(data, on_toggle);
self
}
#[inline]
#[must_use] pub fn dom(self) -> Dom {
use azul_core::{
callbacks::{CoreCallback, CoreCallbackData},
dom::{Dom, EventFilter, HoverEventFilter},
};
Dom::create_div()
.with_ids_and_classes(IdOrClassVec::from(SWITCH_TRACK_CLASS))
.with_css_props(self.track_style)
.with_callbacks(
vec![CoreCallbackData {
event: EventFilter::Hover(HoverEventFilter::MouseUp),
callback: CoreCallback {
cb: input::default_on_switch_clicked as usize,
ctx: azul_core::refany::OptionRefAny::None,
},
refany: RefAny::new(self.switch_state),
}]
.into(),
)
.with_tab_index(TabIndex::Auto)
.with_children(
vec![Dom::create_div()
.with_ids_and_classes(IdOrClassVec::from(SWITCH_KNOB_CLASS))
.with_css_props(self.knob_style)]
.into(),
)
}
}
impl Default for Switch {
fn default() -> Self {
Self::create(false)
}
}
mod input {
use azul_core::{callbacks::Update, refany::RefAny};
use azul_css::props::{layout::LayoutMarginLeft, property::CssProperty};
use super::{SwitchOnToggle, SwitchStateWrapper, KNOB_TRAVEL, TRACK_OFF_BG, TRACK_ON_BG};
use crate::callbacks::CallbackInfo;
pub(super) extern "C" fn default_on_switch_clicked(
mut switch: RefAny,
mut info: CallbackInfo,
) -> Update {
let Some(mut switch) = switch.downcast_mut::<SwitchStateWrapper>() else {
return Update::DoNothing;
};
let track_id = info.get_hit_node();
let Some(knob_id) = info.get_first_child(track_id) else {
return Update::DoNothing;
};
switch.inner.checked = !switch.inner.checked;
let result = {
let switch = &mut *switch;
let on_toggle = &mut switch.on_toggle;
let inner = switch.inner;
match on_toggle.as_mut() {
Some(SwitchOnToggle {
callback,
refany: data,
}) => (callback.cb)(data.clone(), info, inner),
None => Update::DoNothing,
}
};
if switch.inner.checked {
info.set_css_property(track_id, CssProperty::const_background_content(TRACK_ON_BG));
info.set_css_property(
knob_id,
CssProperty::const_margin_left(LayoutMarginLeft::const_px(KNOB_TRAVEL)),
);
} else {
info.set_css_property(track_id, CssProperty::const_background_content(TRACK_OFF_BG));
info.set_css_property(
knob_id,
CssProperty::const_margin_left(LayoutMarginLeft::const_px(0)),
);
}
result
}
}
impl From<Switch> for Dom {
fn from(s: Switch) -> Self {
s.dom()
}
}