1use azul_core::{
34 callbacks::{CoreCallbackData, Update},
35 dom::{Dom, IdOrClass, IdOrClass::Class, IdOrClassVec, TabIndex},
36 refany::RefAny,
37};
38use azul_css::dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec};
39use azul_css::{
40 props::{
41 basic::{color::ColorU, font::{StyleFontFamily, StyleFontFamilyVec}, StyleFontSize},
42 layout::{LayoutDisplay, LayoutFlexDirection, LayoutAlignItems, LayoutFlexGrow, LayoutPosition, LayoutInsetBottom, LayoutRight, LayoutMaxWidth, LayoutPaddingTop, LayoutPaddingBottom, LayoutPaddingLeft, LayoutPaddingRight, LayoutMarginLeft},
43 property::{CssProperty, *},
44 style::{StyleBackgroundContentVec, StyleBackgroundContent, LayoutBorderTopWidth, LayoutBorderBottomWidth, LayoutBorderLeftWidth, LayoutBorderRightWidth, StyleBorderTopStyle, BorderStyle, StyleBorderBottomStyle, StyleBorderLeftStyle, StyleBorderRightStyle, StyleBorderTopColor, StyleBorderBottomColor, StyleBorderLeftColor, StyleBorderRightColor, StyleBorderTopLeftRadius, StyleBorderTopRightRadius, StyleBorderBottomLeftRadius, StyleBorderBottomRightRadius, StyleTextColor, StyleTextAlign, StyleCursor, StyleUserSelect},
45 },
46 impl_option_inner, AzString,
47};
48
49use crate::callbacks::{Callback, CallbackInfo};
50
51static TOAST_CONTAINER_CLASS: &[IdOrClass] =
52 &[Class(AzString::from_const_str("__azul-native-toast"))];
53static TOAST_MESSAGE_CLASS: &[IdOrClass] =
54 &[Class(AzString::from_const_str("__azul-native-toast-message"))];
55static TOAST_CLOSE_CLASS: &[IdOrClass] =
56 &[Class(AzString::from_const_str("__azul-native-toast-close"))];
57
58const SYSTEM_UI_STR: AzString = AzString::from_const_str("system:ui");
59const SYSTEM_UI_FAMILIES: &[StyleFontFamily] = &[StyleFontFamily::System(SYSTEM_UI_STR)];
60const SYSTEM_UI_FAMILY: StyleFontFamilyVec =
61 StyleFontFamilyVec::from_const_slice(SYSTEM_UI_FAMILIES);
62
63const TOAST_INSET: isize = 24;
65const TOAST_MAX_WIDTH: isize = 360;
67
68pub type ToastOnDismissCallbackType = extern "C" fn(RefAny, CallbackInfo, ToastState) -> Update;
70impl_widget_callback!(
71 ToastOnDismiss,
72 OptionToastOnDismiss,
73 ToastOnDismissCallback,
74 ToastOnDismissCallbackType
75);
76
77azul_core::impl_managed_callback! {
78 wrapper: ToastOnDismissCallback,
79 info_ty: CallbackInfo,
80 return_ty: Update,
81 default_ret: Update::DoNothing,
82 invoker_static: TOAST_ON_DISMISS_INVOKER,
83 invoker_ty: AzToastOnDismissCallbackInvoker,
84 thunk_fn: az_toast_on_dismiss_callback_thunk,
85 setter_fn: AzApp_setToastOnDismissCallbackInvoker,
86 from_handle_fn: AzToastOnDismissCallback_createFromHostHandle,
87 extra_args: [ state: ToastState ],
88}
89
90#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
93#[repr(C)]
94pub enum ToastKind {
95 #[default]
97 Info,
98 Success,
100 Warning,
102 Danger,
104}
105
106impl ToastKind {
107 #[allow(clippy::trivially_copy_pass_by_ref)] const fn colors(&self) -> (ColorU, ColorU, ColorU) {
110 match self {
111 Self::Info => (
112 ColorU { r: 207, g: 244, b: 252, a: 255 }, ColorU { r: 182, g: 239, b: 251, a: 255 }, ColorU { r: 5, g: 81, b: 96, a: 255 }, ),
116 Self::Success => (
117 ColorU { r: 209, g: 231, b: 221, a: 255 }, ColorU { r: 186, g: 219, b: 204, a: 255 }, ColorU { r: 15, g: 81, b: 50, a: 255 }, ),
121 Self::Warning => (
122 ColorU { r: 255, g: 243, b: 205, a: 255 }, ColorU { r: 255, g: 236, b: 181, a: 255 }, ColorU { r: 102, g: 77, b: 3, a: 255 }, ),
126 Self::Danger => (
127 ColorU { r: 248, g: 215, b: 218, a: 255 }, ColorU { r: 245, g: 194, b: 199, a: 255 }, ColorU { r: 132, g: 32, b: 41, a: 255 }, ),
131 }
132 }
133
134 #[must_use] pub const fn class_name(&self) -> &'static str {
136 match self {
137 Self::Info => "__azul-toast-info",
138 Self::Success => "__azul-toast-success",
139 Self::Warning => "__azul-toast-warning",
140 Self::Danger => "__azul-toast-danger",
141 }
142 }
143}
144
145#[derive(Debug, Clone, PartialEq, Eq)]
147#[repr(C)]
148pub struct Toast {
149 pub toast_state: ToastStateWrapper,
151 pub message: AzString,
153 pub kind: ToastKind,
155 pub dismissible: bool,
158 pub container_style: CssPropertyWithConditionsVec,
160}
161
162#[derive(Debug, Default, Clone, PartialEq, Eq)]
163#[repr(C)]
164pub struct ToastStateWrapper {
165 pub inner: ToastState,
167 pub on_dismiss: OptionToastOnDismiss,
169}
170
171#[derive(Debug, Copy, Clone, PartialEq, Eq)]
173#[repr(C)]
174pub struct ToastState {
175 pub visible: bool,
177}
178
179impl Default for ToastState {
180 fn default() -> Self {
181 Self { visible: true }
182 }
183}
184
185fn build_toast_style(kind: ToastKind) -> CssPropertyWithConditionsVec {
190 let (bg, border, text) = kind.colors();
191 let bg_vec =
192 StyleBackgroundContentVec::from_vec(alloc::vec![StyleBackgroundContent::Color(bg)]);
193 CssPropertyWithConditionsVec::from_vec(alloc::vec![
194 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Flex)),
195 CssPropertyWithConditions::simple(CssProperty::const_flex_direction(
196 LayoutFlexDirection::Row,
197 )),
198 CssPropertyWithConditions::simple(CssProperty::const_align_items(LayoutAlignItems::Start)),
199 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(
200 0,
201 ))),
202 CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Absolute)),
204 CssPropertyWithConditions::simple(CssProperty::const_bottom(LayoutInsetBottom::const_px(
205 TOAST_INSET,
206 ))),
207 CssPropertyWithConditions::simple(CssProperty::const_right(LayoutRight::const_px(
208 TOAST_INSET,
209 ))),
210 CssPropertyWithConditions::simple(CssProperty::const_max_width(LayoutMaxWidth::const_px(
212 TOAST_MAX_WIDTH,
213 ))),
214 CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(
216 12,
217 ))),
218 CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
219 LayoutPaddingBottom::const_px(12),
220 )),
221 CssPropertyWithConditions::simple(CssProperty::const_padding_left(
222 LayoutPaddingLeft::const_px(12),
223 )),
224 CssPropertyWithConditions::simple(CssProperty::const_padding_right(
225 LayoutPaddingRight::const_px(12),
226 )),
227 CssPropertyWithConditions::simple(CssProperty::const_border_top_width(
229 LayoutBorderTopWidth::const_px(1),
230 )),
231 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
232 LayoutBorderBottomWidth::const_px(1),
233 )),
234 CssPropertyWithConditions::simple(CssProperty::const_border_left_width(
235 LayoutBorderLeftWidth::const_px(1),
236 )),
237 CssPropertyWithConditions::simple(CssProperty::const_border_right_width(
238 LayoutBorderRightWidth::const_px(1),
239 )),
240 CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle {
241 inner: BorderStyle::Solid,
242 })),
243 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
244 StyleBorderBottomStyle {
245 inner: BorderStyle::Solid,
246 },
247 )),
248 CssPropertyWithConditions::simple(CssProperty::const_border_left_style(
249 StyleBorderLeftStyle {
250 inner: BorderStyle::Solid,
251 },
252 )),
253 CssPropertyWithConditions::simple(CssProperty::const_border_right_style(
254 StyleBorderRightStyle {
255 inner: BorderStyle::Solid,
256 },
257 )),
258 CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
259 inner: border,
260 })),
261 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
262 StyleBorderBottomColor { inner: border },
263 )),
264 CssPropertyWithConditions::simple(CssProperty::const_border_left_color(
265 StyleBorderLeftColor { inner: border },
266 )),
267 CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
268 StyleBorderRightColor { inner: border },
269 )),
270 CssPropertyWithConditions::simple(CssProperty::const_border_top_left_radius(
272 StyleBorderTopLeftRadius::const_px(6),
273 )),
274 CssPropertyWithConditions::simple(CssProperty::const_border_top_right_radius(
275 StyleBorderTopRightRadius::const_px(6),
276 )),
277 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_left_radius(
278 StyleBorderBottomLeftRadius::const_px(6),
279 )),
280 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_right_radius(
281 StyleBorderBottomRightRadius::const_px(6),
282 )),
283 CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(14))),
284 CssPropertyWithConditions::simple(CssProperty::const_font_family(SYSTEM_UI_FAMILY)),
285 CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
287 inner: text,
288 })),
289 CssPropertyWithConditions::simple(CssProperty::const_background_content(bg_vec)),
290 ])
291}
292
293static TOAST_MESSAGE_STYLE: &[CssPropertyWithConditions] = &[
295 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(1))),
296 CssPropertyWithConditions::simple(CssProperty::const_text_align(StyleTextAlign::Left)),
297];
298
299static TOAST_CLOSE_STYLE: &[CssPropertyWithConditions] = &[
301 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
302 CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(18))),
303 CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)),
304 CssPropertyWithConditions::simple(CssProperty::user_select(StyleUserSelect::None)),
305 CssPropertyWithConditions::simple(CssProperty::const_margin_left(LayoutMarginLeft::const_px(
306 12,
307 ))),
308];
309
310impl Toast {
311 #[inline]
314 #[must_use] pub fn create(message: AzString) -> Self {
315 Self::with_kind(message, ToastKind::Info)
316 }
317
318 #[inline]
320 #[must_use] pub fn with_kind(message: AzString, kind: ToastKind) -> Self {
321 Self {
322 toast_state: ToastStateWrapper::default(),
323 message,
324 kind,
325 dismissible: true,
326 container_style: build_toast_style(kind),
327 }
328 }
329
330 #[inline]
332 pub fn set_kind(&mut self, kind: ToastKind) {
333 self.kind = kind;
334 self.container_style = build_toast_style(kind);
335 }
336
337 #[inline]
339 #[must_use] pub fn with_toast_kind(mut self, kind: ToastKind) -> Self {
340 self.set_kind(kind);
341 self
342 }
343
344 #[inline]
346 pub const fn set_dismissible(&mut self, dismissible: bool) {
347 self.dismissible = dismissible;
348 }
349
350 #[inline]
352 #[must_use] pub const fn with_dismissible(mut self, dismissible: bool) -> Self {
353 self.set_dismissible(dismissible);
354 self
355 }
356
357 #[inline]
360 pub fn set_on_dismiss<C: Into<ToastOnDismissCallback>>(&mut self, data: RefAny, on_dismiss: C) {
361 self.dismissible = true;
362 self.toast_state.on_dismiss = Some(ToastOnDismiss {
363 callback: on_dismiss.into(),
364 refany: data,
365 })
366 .into();
367 }
368
369 #[inline]
371 #[must_use] pub fn with_on_dismiss<C: Into<ToastOnDismissCallback>>(
372 mut self,
373 data: RefAny,
374 on_dismiss: C,
375 ) -> Self {
376 self.set_on_dismiss(data, on_dismiss);
377 self
378 }
379
380 #[inline]
382 #[must_use] pub fn swap_with_default(&mut self) -> Self {
383 let mut s = Self::create(AzString::from_const_str(""));
384 core::mem::swap(&mut s, self);
385 s
386 }
387
388 #[inline]
390 #[must_use] pub fn dom(self) -> Dom {
391 use azul_core::{
392 callbacks::CoreCallback,
393 dom::{EventFilter, HoverEventFilter},
394 refany::OptionRefAny,
395 };
396
397 let message = Dom::create_text(self.message)
398 .with_ids_and_classes(IdOrClassVec::from_const_slice(TOAST_MESSAGE_CLASS))
399 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(TOAST_MESSAGE_STYLE));
400
401 let mut children = alloc::vec![message];
402
403 if self.dismissible {
404 let close = Dom::create_text(AzString::from_const_str("\u{00D7}"))
405 .with_ids_and_classes(IdOrClassVec::from_const_slice(TOAST_CLOSE_CLASS))
406 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(TOAST_CLOSE_STYLE))
407 .with_tab_index(TabIndex::Auto)
408 .with_callbacks(
409 alloc::vec![CoreCallbackData {
410 event: EventFilter::Hover(HoverEventFilter::MouseUp),
411 callback: CoreCallback {
412 cb: default_on_toast_dismiss as usize,
413 ctx: OptionRefAny::None,
414 },
415 refany: RefAny::new(self.toast_state),
416 }]
417 .into(),
418 );
419 children.push(close);
420 }
421
422 Dom::create_div()
423 .with_ids_and_classes(IdOrClassVec::from_const_slice(TOAST_CONTAINER_CLASS))
424 .with_css_props(self.container_style)
425 .with_children(children.into())
426 }
427}
428
429impl Default for Toast {
430 fn default() -> Self {
431 Self::create(AzString::from_const_str(""))
432 }
433}
434
435extern "C" fn default_on_toast_dismiss(mut data: RefAny, mut info: CallbackInfo) -> Update {
440 let close_node = info.get_hit_node();
441 let Some(container) = info.get_parent(close_node) else {
442 return Update::DoNothing;
443 };
444
445 let result = {
446 let Some(mut toast) = data.downcast_mut::<ToastStateWrapper>() else {
447 return Update::DoNothing;
448 };
449 toast.inner.visible = false;
450 let inner = toast.inner;
451 let toast = &mut *toast;
452 match toast.on_dismiss.as_mut() {
453 Some(ToastOnDismiss { callback, refany }) => (callback.cb)(refany.clone(), info, inner),
454 None => Update::DoNothing,
455 }
456 };
457
458 info.set_css_property(container, CssProperty::const_display(LayoutDisplay::None));
464
465 result
466}
467
468impl From<Toast> for Dom {
469 fn from(t: Toast) -> Self {
470 t.dom()
471 }
472}