1use azul_core::{
20 callbacks::{CoreCallbackData, Update},
21 dom::{Dom, IdOrClass, IdOrClass::Class, IdOrClassVec, TabIndex},
22 refany::RefAny,
23};
24use azul_css::dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec};
25use azul_css::{
26 props::{
27 basic::{color::ColorU, font::{StyleFontFamily, StyleFontFamilyVec}, StyleFontSize},
28 layout::{LayoutDisplay, LayoutFlexDirection, LayoutAlignItems, LayoutAlignSelf, LayoutFlexGrow, LayoutPaddingTop, LayoutPaddingBottom, LayoutPaddingLeft, LayoutPaddingRight, LayoutMarginLeft},
29 property::{CssProperty, *},
30 style::{StyleBackgroundContentVec, StyleBackgroundContent, StyleBorderTopLeftRadius, StyleBorderTopRightRadius, StyleBorderBottomLeftRadius, StyleBorderBottomRightRadius, StyleTextColor, StyleTextAlign, StyleUserSelect, StyleCursor},
31 },
32 impl_option_inner, AzString,
33};
34
35use crate::callbacks::{Callback, CallbackInfo};
36
37static CHIP_CONTAINER_CLASS: &[IdOrClass] = &[Class(AzString::from_const_str("__azul-native-chip"))];
38static CHIP_LABEL_CLASS: &[IdOrClass] =
39 &[Class(AzString::from_const_str("__azul-native-chip-label"))];
40static CHIP_REMOVE_CLASS: &[IdOrClass] =
41 &[Class(AzString::from_const_str("__azul-native-chip-remove"))];
42
43const SYSTEM_UI_STR: AzString = AzString::from_const_str("system:ui");
44const SYSTEM_UI_FAMILIES: &[StyleFontFamily] = &[StyleFontFamily::System(SYSTEM_UI_STR)];
45const SYSTEM_UI_FAMILY: StyleFontFamilyVec =
46 StyleFontFamilyVec::from_const_slice(SYSTEM_UI_FAMILIES);
47
48pub type ChipOnRemoveCallbackType = extern "C" fn(RefAny, CallbackInfo, ChipState) -> Update;
50impl_widget_callback!(
51 ChipOnRemove,
52 OptionChipOnRemove,
53 ChipOnRemoveCallback,
54 ChipOnRemoveCallbackType
55);
56
57azul_core::impl_managed_callback! {
58 wrapper: ChipOnRemoveCallback,
59 info_ty: CallbackInfo,
60 return_ty: Update,
61 default_ret: Update::DoNothing,
62 invoker_static: CHIP_ON_REMOVE_INVOKER,
63 invoker_ty: AzChipOnRemoveCallbackInvoker,
64 thunk_fn: az_chip_on_remove_callback_thunk,
65 setter_fn: AzApp_setChipOnRemoveCallbackInvoker,
66 from_handle_fn: AzChipOnRemoveCallback_createFromHostHandle,
67 extra_args: [ state: ChipState ],
68}
69
70pub type ChipOnClickCallbackType = extern "C" fn(RefAny, CallbackInfo, ChipState) -> Update;
72impl_widget_callback!(
73 ChipOnClick,
74 OptionChipOnClick,
75 ChipOnClickCallback,
76 ChipOnClickCallbackType
77);
78
79azul_core::impl_managed_callback! {
80 wrapper: ChipOnClickCallback,
81 info_ty: CallbackInfo,
82 return_ty: Update,
83 default_ret: Update::DoNothing,
84 invoker_static: CHIP_ON_CLICK_INVOKER,
85 invoker_ty: AzChipOnClickCallbackInvoker,
86 thunk_fn: az_chip_on_click_callback_thunk,
87 setter_fn: AzApp_setChipOnClickCallbackInvoker,
88 from_handle_fn: AzChipOnClickCallback_createFromHostHandle,
89 extra_args: [ state: ChipState ],
90}
91
92#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
94#[repr(C)]
95pub enum ChipKind {
96 #[default]
98 Default,
99 Primary,
101 Success,
103 Danger,
105 Warning,
107 Info,
109}
110
111impl ChipKind {
112 #[allow(clippy::trivially_copy_pass_by_ref)] const fn colors(&self) -> (ColorU, ColorU) {
115 const WHITE: ColorU = ColorU { r: 255, g: 255, b: 255, a: 255 };
116 const DARK: ColorU = ColorU { r: 33, g: 37, b: 41, a: 255 };
117 match self {
118 Self::Default => (ColorU { r: 233, g: 236, b: 239, a: 255 }, DARK),
121 Self::Primary => (ColorU { r: 13, g: 110, b: 253, a: 255 }, WHITE),
122 Self::Success => (ColorU { r: 25, g: 135, b: 84, a: 255 }, WHITE),
123 Self::Danger => (ColorU { r: 220, g: 53, b: 69, a: 255 }, WHITE),
124 Self::Warning => (ColorU { r: 255, g: 193, b: 7, a: 255 }, DARK),
125 Self::Info => (ColorU { r: 13, g: 202, b: 240, a: 255 }, DARK),
126 }
127 }
128
129 #[must_use] pub const fn class_name(&self) -> &'static str {
131 match self {
132 Self::Default => "__azul-chip-default",
133 Self::Primary => "__azul-chip-primary",
134 Self::Success => "__azul-chip-success",
135 Self::Danger => "__azul-chip-danger",
136 Self::Warning => "__azul-chip-warning",
137 Self::Info => "__azul-chip-info",
138 }
139 }
140}
141
142#[derive(Debug, Clone, PartialEq, Eq)]
144#[repr(C)]
145pub struct Chip {
146 pub chip_state: ChipStateWrapper,
148 pub label: AzString,
150 pub kind: ChipKind,
152 pub removable: bool,
154 pub container_style: CssPropertyWithConditionsVec,
156}
157
158#[derive(Debug, Default, Clone, PartialEq, Eq)]
159#[repr(C)]
160pub struct ChipStateWrapper {
161 pub inner: ChipState,
163 pub on_remove: OptionChipOnRemove,
165 pub on_click: OptionChipOnClick,
167}
168
169#[derive(Debug, Copy, Clone, PartialEq, Eq)]
171#[repr(C)]
172pub struct ChipState {
173 pub visible: bool,
175}
176
177impl Default for ChipState {
178 fn default() -> Self {
179 Self { visible: true }
180 }
181}
182
183fn build_chip_style(kind: ChipKind) -> CssPropertyWithConditionsVec {
187 let (bg, text) = kind.colors();
188 let bg_vec =
189 StyleBackgroundContentVec::from_vec(alloc::vec![StyleBackgroundContent::Color(bg)]);
190 CssPropertyWithConditionsVec::from_vec(alloc::vec![
191 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Flex)),
192 CssPropertyWithConditions::simple(CssProperty::const_flex_direction(
193 LayoutFlexDirection::Row,
194 )),
195 CssPropertyWithConditions::simple(CssProperty::const_align_items(LayoutAlignItems::Center)),
196 CssPropertyWithConditions::simple(CssProperty::align_self(LayoutAlignSelf::Start)),
198 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(
199 0,
200 ))),
201 CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(
203 4,
204 ))),
205 CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
206 LayoutPaddingBottom::const_px(4),
207 )),
208 CssPropertyWithConditions::simple(CssProperty::const_padding_left(
209 LayoutPaddingLeft::const_px(10),
210 )),
211 CssPropertyWithConditions::simple(CssProperty::const_padding_right(
212 LayoutPaddingRight::const_px(10),
213 )),
214 CssPropertyWithConditions::simple(CssProperty::const_border_top_left_radius(
216 StyleBorderTopLeftRadius::const_px(12),
217 )),
218 CssPropertyWithConditions::simple(CssProperty::const_border_top_right_radius(
219 StyleBorderTopRightRadius::const_px(12),
220 )),
221 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_left_radius(
222 StyleBorderBottomLeftRadius::const_px(12),
223 )),
224 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_right_radius(
225 StyleBorderBottomRightRadius::const_px(12),
226 )),
227 CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(13))),
228 CssPropertyWithConditions::simple(CssProperty::const_font_family(SYSTEM_UI_FAMILY)),
229 CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
231 inner: text,
232 })),
233 CssPropertyWithConditions::simple(CssProperty::const_background_content(bg_vec)),
234 ])
235}
236
237static CHIP_LABEL_STYLE: &[CssPropertyWithConditions] = &[
239 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
240 CssPropertyWithConditions::simple(CssProperty::const_text_align(StyleTextAlign::Left)),
241 CssPropertyWithConditions::simple(CssProperty::user_select(StyleUserSelect::None)),
242];
243
244static CHIP_REMOVE_STYLE: &[CssPropertyWithConditions] = &[
246 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
247 CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(14))),
248 CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)),
249 CssPropertyWithConditions::simple(CssProperty::user_select(StyleUserSelect::None)),
250 CssPropertyWithConditions::simple(CssProperty::const_margin_left(LayoutMarginLeft::const_px(
251 6,
252 ))),
253];
254
255impl Chip {
256 #[inline]
258 #[must_use] pub fn create(label: AzString) -> Self {
259 Self::with_kind(label, ChipKind::Default)
260 }
261
262 #[inline]
264 #[must_use] pub fn with_kind(label: AzString, kind: ChipKind) -> Self {
265 Self {
266 chip_state: ChipStateWrapper::default(),
267 label,
268 kind,
269 removable: false,
270 container_style: build_chip_style(kind),
271 }
272 }
273
274 #[inline]
276 pub fn set_kind(&mut self, kind: ChipKind) {
277 self.kind = kind;
278 self.container_style = build_chip_style(kind);
279 }
280
281 #[inline]
283 #[must_use] pub fn with_chip_kind(mut self, kind: ChipKind) -> Self {
284 self.set_kind(kind);
285 self
286 }
287
288 #[inline]
290 pub const fn set_removable(&mut self, removable: bool) {
291 self.removable = removable;
292 }
293
294 #[inline]
296 #[must_use] pub const fn with_removable(mut self, removable: bool) -> Self {
297 self.set_removable(removable);
298 self
299 }
300
301 #[inline]
303 pub fn set_on_remove<C: Into<ChipOnRemoveCallback>>(&mut self, data: RefAny, on_remove: C) {
304 self.removable = true;
305 self.chip_state.on_remove = Some(ChipOnRemove {
306 callback: on_remove.into(),
307 refany: data,
308 })
309 .into();
310 }
311
312 #[inline]
314 #[must_use] pub fn with_on_remove<C: Into<ChipOnRemoveCallback>>(
315 mut self,
316 data: RefAny,
317 on_remove: C,
318 ) -> Self {
319 self.set_on_remove(data, on_remove);
320 self
321 }
322
323 #[inline]
325 pub fn set_on_click<C: Into<ChipOnClickCallback>>(&mut self, data: RefAny, on_click: C) {
326 self.chip_state.on_click = Some(ChipOnClick {
327 callback: on_click.into(),
328 refany: data,
329 })
330 .into();
331 }
332
333 #[inline]
335 #[must_use] pub fn with_on_click<C: Into<ChipOnClickCallback>>(
336 mut self,
337 data: RefAny,
338 on_click: C,
339 ) -> Self {
340 self.set_on_click(data, on_click);
341 self
342 }
343
344 #[inline]
346 #[must_use] pub fn swap_with_default(&mut self) -> Self {
347 let mut s = Self::create(AzString::from_const_str(""));
348 core::mem::swap(&mut s, self);
349 s
350 }
351
352 #[inline]
354 #[must_use] pub fn dom(self) -> Dom {
355 use azul_core::{
356 callbacks::CoreCallback,
357 dom::{EventFilter, HoverEventFilter},
358 refany::OptionRefAny,
359 };
360
361 let has_on_click = matches!(self.chip_state.on_click, OptionChipOnClick::Some(_));
362
363 let state_ref = RefAny::new(self.chip_state);
366
367 let mut label = Dom::create_text(self.label)
368 .with_ids_and_classes(IdOrClassVec::from_const_slice(CHIP_LABEL_CLASS))
369 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(CHIP_LABEL_STYLE));
370
371 if has_on_click {
378 label = label.with_tab_index(TabIndex::Auto).with_callbacks(
379 alloc::vec![CoreCallbackData {
380 event: EventFilter::Hover(HoverEventFilter::MouseUp),
381 callback: CoreCallback {
382 cb: default_on_chip_click as usize,
383 ctx: OptionRefAny::None,
384 },
385 refany: state_ref.clone(),
386 }]
387 .into(),
388 );
389 }
390
391 let mut children = alloc::vec![label];
392
393 if self.removable {
394 let remove = Dom::create_text(AzString::from_const_str("\u{00D7}"))
395 .with_ids_and_classes(IdOrClassVec::from_const_slice(CHIP_REMOVE_CLASS))
396 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(CHIP_REMOVE_STYLE))
397 .with_tab_index(TabIndex::Auto)
398 .with_callbacks(
399 alloc::vec![CoreCallbackData {
400 event: EventFilter::Hover(HoverEventFilter::MouseUp),
401 callback: CoreCallback {
402 cb: default_on_chip_remove as usize,
403 ctx: OptionRefAny::None,
404 },
405 refany: state_ref,
406 }]
407 .into(),
408 );
409 children.push(remove);
410 }
411
412 Dom::create_div()
413 .with_ids_and_classes(IdOrClassVec::from_const_slice(CHIP_CONTAINER_CLASS))
414 .with_css_props(self.container_style)
415 .with_children(children.into())
416 }
417}
418
419impl Default for Chip {
420 fn default() -> Self {
421 Self::create(AzString::from_const_str(""))
422 }
423}
424
425extern "C" fn default_on_chip_remove(mut data: RefAny, mut info: CallbackInfo) -> Update {
430 let remove_node = info.get_hit_node();
431 let Some(container) = info.get_parent(remove_node) else {
432 return Update::DoNothing;
433 };
434
435 let result = {
436 let Some(mut chip) = data.downcast_mut::<ChipStateWrapper>() else {
437 return Update::DoNothing;
438 };
439 chip.inner.visible = false;
440 let inner = chip.inner;
441 let chip = &mut *chip;
442 match chip.on_remove.as_mut() {
443 Some(ChipOnRemove { callback, refany }) => (callback.cb)(refany.clone(), info, inner),
444 None => Update::DoNothing,
445 }
446 };
447
448 info.set_css_property(container, CssProperty::const_display(LayoutDisplay::None));
453
454 result
455}
456
457extern "C" fn default_on_chip_click(mut data: RefAny, info: CallbackInfo) -> Update {
460 let Some(mut chip) = data.downcast_mut::<ChipStateWrapper>() else {
461 return Update::DoNothing;
462 };
463 let inner = chip.inner;
464 let chip = &mut *chip;
465 match chip.on_click.as_mut() {
466 Some(ChipOnClick { callback, refany }) => (callback.cb)(refany.clone(), info, inner),
467 None => Update::DoNothing,
468 }
469}
470
471impl From<Chip> for Dom {
472 fn from(c: Chip) -> Self {
473 c.dom()
474 }
475}