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, font::{StyleFontFamily, StyleFontFamilyVec}, StyleFontSize},
layout::{LayoutDisplay, LayoutFlexDirection, LayoutAlignItems, LayoutAlignSelf, LayoutFlexGrow, LayoutPaddingTop, LayoutPaddingBottom, LayoutPaddingLeft, LayoutPaddingRight, LayoutMarginLeft},
property::{CssProperty, *},
style::{StyleBackgroundContentVec, StyleBackgroundContent, StyleBorderTopLeftRadius, StyleBorderTopRightRadius, StyleBorderBottomLeftRadius, StyleBorderBottomRightRadius, StyleTextColor, StyleTextAlign, StyleUserSelect, StyleCursor},
},
impl_option_inner, AzString,
};
use crate::callbacks::{Callback, CallbackInfo};
static CHIP_CONTAINER_CLASS: &[IdOrClass] = &[Class(AzString::from_const_str("__azul-native-chip"))];
static CHIP_LABEL_CLASS: &[IdOrClass] =
&[Class(AzString::from_const_str("__azul-native-chip-label"))];
static CHIP_REMOVE_CLASS: &[IdOrClass] =
&[Class(AzString::from_const_str("__azul-native-chip-remove"))];
const SYSTEM_UI_STR: AzString = AzString::from_const_str("system:ui");
const SYSTEM_UI_FAMILIES: &[StyleFontFamily] = &[StyleFontFamily::System(SYSTEM_UI_STR)];
const SYSTEM_UI_FAMILY: StyleFontFamilyVec =
StyleFontFamilyVec::from_const_slice(SYSTEM_UI_FAMILIES);
pub type ChipOnRemoveCallbackType = extern "C" fn(RefAny, CallbackInfo, ChipState) -> Update;
impl_widget_callback!(
ChipOnRemove,
OptionChipOnRemove,
ChipOnRemoveCallback,
ChipOnRemoveCallbackType
);
azul_core::impl_managed_callback! {
wrapper: ChipOnRemoveCallback,
info_ty: CallbackInfo,
return_ty: Update,
default_ret: Update::DoNothing,
invoker_static: CHIP_ON_REMOVE_INVOKER,
invoker_ty: AzChipOnRemoveCallbackInvoker,
thunk_fn: az_chip_on_remove_callback_thunk,
setter_fn: AzApp_setChipOnRemoveCallbackInvoker,
from_handle_fn: AzChipOnRemoveCallback_createFromHostHandle,
extra_args: [ state: ChipState ],
}
pub type ChipOnClickCallbackType = extern "C" fn(RefAny, CallbackInfo, ChipState) -> Update;
impl_widget_callback!(
ChipOnClick,
OptionChipOnClick,
ChipOnClickCallback,
ChipOnClickCallbackType
);
azul_core::impl_managed_callback! {
wrapper: ChipOnClickCallback,
info_ty: CallbackInfo,
return_ty: Update,
default_ret: Update::DoNothing,
invoker_static: CHIP_ON_CLICK_INVOKER,
invoker_ty: AzChipOnClickCallbackInvoker,
thunk_fn: az_chip_on_click_callback_thunk,
setter_fn: AzApp_setChipOnClickCallbackInvoker,
from_handle_fn: AzChipOnClickCallback_createFromHostHandle,
extra_args: [ state: ChipState ],
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
#[repr(C)]
pub enum ChipKind {
#[default]
Default,
Primary,
Success,
Danger,
Warning,
Info,
}
impl ChipKind {
#[allow(clippy::trivially_copy_pass_by_ref)] const fn colors(&self) -> (ColorU, ColorU) {
const WHITE: ColorU = ColorU { r: 255, g: 255, b: 255, a: 255 };
const DARK: ColorU = ColorU { r: 33, g: 37, b: 41, a: 255 };
match self {
Self::Default => (ColorU { r: 233, g: 236, b: 239, a: 255 }, DARK),
Self::Primary => (ColorU { r: 13, g: 110, b: 253, a: 255 }, WHITE),
Self::Success => (ColorU { r: 25, g: 135, b: 84, a: 255 }, WHITE),
Self::Danger => (ColorU { r: 220, g: 53, b: 69, a: 255 }, WHITE),
Self::Warning => (ColorU { r: 255, g: 193, b: 7, a: 255 }, DARK),
Self::Info => (ColorU { r: 13, g: 202, b: 240, a: 255 }, DARK),
}
}
#[must_use] pub const fn class_name(&self) -> &'static str {
match self {
Self::Default => "__azul-chip-default",
Self::Primary => "__azul-chip-primary",
Self::Success => "__azul-chip-success",
Self::Danger => "__azul-chip-danger",
Self::Warning => "__azul-chip-warning",
Self::Info => "__azul-chip-info",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(C)]
pub struct Chip {
pub chip_state: ChipStateWrapper,
pub label: AzString,
pub kind: ChipKind,
pub removable: bool,
pub container_style: CssPropertyWithConditionsVec,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[repr(C)]
pub struct ChipStateWrapper {
pub inner: ChipState,
pub on_remove: OptionChipOnRemove,
pub on_click: OptionChipOnClick,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(C)]
pub struct ChipState {
pub visible: bool,
}
impl Default for ChipState {
fn default() -> Self {
Self { visible: true }
}
}
fn build_chip_style(kind: ChipKind) -> CssPropertyWithConditionsVec {
let (bg, text) = kind.colors();
let bg_vec =
StyleBackgroundContentVec::from_vec(alloc::vec![StyleBackgroundContent::Color(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::Start)),
CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(
0,
))),
CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(
4,
))),
CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
LayoutPaddingBottom::const_px(4),
)),
CssPropertyWithConditions::simple(CssProperty::const_padding_left(
LayoutPaddingLeft::const_px(10),
)),
CssPropertyWithConditions::simple(CssProperty::const_padding_right(
LayoutPaddingRight::const_px(10),
)),
CssPropertyWithConditions::simple(CssProperty::const_border_top_left_radius(
StyleBorderTopLeftRadius::const_px(12),
)),
CssPropertyWithConditions::simple(CssProperty::const_border_top_right_radius(
StyleBorderTopRightRadius::const_px(12),
)),
CssPropertyWithConditions::simple(CssProperty::const_border_bottom_left_radius(
StyleBorderBottomLeftRadius::const_px(12),
)),
CssPropertyWithConditions::simple(CssProperty::const_border_bottom_right_radius(
StyleBorderBottomRightRadius::const_px(12),
)),
CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(13))),
CssPropertyWithConditions::simple(CssProperty::const_font_family(SYSTEM_UI_FAMILY)),
CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
inner: text,
})),
CssPropertyWithConditions::simple(CssProperty::const_background_content(bg_vec)),
])
}
static CHIP_LABEL_STYLE: &[CssPropertyWithConditions] = &[
CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
CssPropertyWithConditions::simple(CssProperty::const_text_align(StyleTextAlign::Left)),
CssPropertyWithConditions::simple(CssProperty::user_select(StyleUserSelect::None)),
];
static CHIP_REMOVE_STYLE: &[CssPropertyWithConditions] = &[
CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(14))),
CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)),
CssPropertyWithConditions::simple(CssProperty::user_select(StyleUserSelect::None)),
CssPropertyWithConditions::simple(CssProperty::const_margin_left(LayoutMarginLeft::const_px(
6,
))),
];
impl Chip {
#[inline]
#[must_use] pub fn create(label: AzString) -> Self {
Self::with_kind(label, ChipKind::Default)
}
#[inline]
#[must_use] pub fn with_kind(label: AzString, kind: ChipKind) -> Self {
Self {
chip_state: ChipStateWrapper::default(),
label,
kind,
removable: false,
container_style: build_chip_style(kind),
}
}
#[inline]
pub fn set_kind(&mut self, kind: ChipKind) {
self.kind = kind;
self.container_style = build_chip_style(kind);
}
#[inline]
#[must_use] pub fn with_chip_kind(mut self, kind: ChipKind) -> Self {
self.set_kind(kind);
self
}
#[inline]
pub const fn set_removable(&mut self, removable: bool) {
self.removable = removable;
}
#[inline]
#[must_use] pub const fn with_removable(mut self, removable: bool) -> Self {
self.set_removable(removable);
self
}
#[inline]
pub fn set_on_remove<C: Into<ChipOnRemoveCallback>>(&mut self, data: RefAny, on_remove: C) {
self.removable = true;
self.chip_state.on_remove = Some(ChipOnRemove {
callback: on_remove.into(),
refany: data,
})
.into();
}
#[inline]
#[must_use] pub fn with_on_remove<C: Into<ChipOnRemoveCallback>>(
mut self,
data: RefAny,
on_remove: C,
) -> Self {
self.set_on_remove(data, on_remove);
self
}
#[inline]
pub fn set_on_click<C: Into<ChipOnClickCallback>>(&mut self, data: RefAny, on_click: C) {
self.chip_state.on_click = Some(ChipOnClick {
callback: on_click.into(),
refany: data,
})
.into();
}
#[inline]
#[must_use] pub fn with_on_click<C: Into<ChipOnClickCallback>>(
mut self,
data: RefAny,
on_click: C,
) -> Self {
self.set_on_click(data, on_click);
self
}
#[inline]
#[must_use] pub fn swap_with_default(&mut self) -> Self {
let mut s = Self::create(AzString::from_const_str(""));
core::mem::swap(&mut s, self);
s
}
#[inline]
#[must_use] pub fn dom(self) -> Dom {
use azul_core::{
callbacks::CoreCallback,
dom::{EventFilter, HoverEventFilter},
refany::OptionRefAny,
};
let has_on_click = matches!(self.chip_state.on_click, OptionChipOnClick::Some(_));
let state_ref = RefAny::new(self.chip_state);
let mut label = Dom::create_text(self.label)
.with_ids_and_classes(IdOrClassVec::from_const_slice(CHIP_LABEL_CLASS))
.with_css_props(CssPropertyWithConditionsVec::from_const_slice(CHIP_LABEL_STYLE));
if has_on_click {
label = label.with_tab_index(TabIndex::Auto).with_callbacks(
alloc::vec![CoreCallbackData {
event: EventFilter::Hover(HoverEventFilter::MouseUp),
callback: CoreCallback {
cb: default_on_chip_click as usize,
ctx: OptionRefAny::None,
},
refany: state_ref.clone(),
}]
.into(),
);
}
let mut children = alloc::vec![label];
if self.removable {
let remove = Dom::create_text(AzString::from_const_str("\u{00D7}"))
.with_ids_and_classes(IdOrClassVec::from_const_slice(CHIP_REMOVE_CLASS))
.with_css_props(CssPropertyWithConditionsVec::from_const_slice(CHIP_REMOVE_STYLE))
.with_tab_index(TabIndex::Auto)
.with_callbacks(
alloc::vec![CoreCallbackData {
event: EventFilter::Hover(HoverEventFilter::MouseUp),
callback: CoreCallback {
cb: default_on_chip_remove as usize,
ctx: OptionRefAny::None,
},
refany: state_ref,
}]
.into(),
);
children.push(remove);
}
Dom::create_div()
.with_ids_and_classes(IdOrClassVec::from_const_slice(CHIP_CONTAINER_CLASS))
.with_css_props(self.container_style)
.with_children(children.into())
}
}
impl Default for Chip {
fn default() -> Self {
Self::create(AzString::from_const_str(""))
}
}
extern "C" fn default_on_chip_remove(mut data: RefAny, mut info: CallbackInfo) -> Update {
let remove_node = info.get_hit_node();
let Some(container) = info.get_parent(remove_node) else {
return Update::DoNothing;
};
let result = {
let Some(mut chip) = data.downcast_mut::<ChipStateWrapper>() else {
return Update::DoNothing;
};
chip.inner.visible = false;
let inner = chip.inner;
let chip = &mut *chip;
match chip.on_remove.as_mut() {
Some(ChipOnRemove { callback, refany }) => (callback.cb)(refany.clone(), info, inner),
None => Update::DoNothing,
}
};
info.set_css_property(container, CssProperty::const_display(LayoutDisplay::None));
result
}
extern "C" fn default_on_chip_click(mut data: RefAny, info: CallbackInfo) -> Update {
let Some(mut chip) = data.downcast_mut::<ChipStateWrapper>() else {
return Update::DoNothing;
};
let inner = chip.inner;
let chip = &mut *chip;
match chip.on_click.as_mut() {
Some(ChipOnClick { callback, refany }) => (callback.cb)(refany.clone(), info, inner),
None => Update::DoNothing,
}
}
impl From<Chip> for Dom {
fn from(c: Chip) -> Self {
c.dom()
}
}