1use azul_core::{
19 callbacks::{CoreCallbackData, Update},
20 dom::{Dom, IdOrClass, IdOrClass::Class, IdOrClassVec, TabIndex},
21 refany::RefAny,
22};
23use azul_css::dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec};
24use azul_css::{
25 props::{
26 basic::{color::ColorU, font::{StyleFontFamily, StyleFontFamilyVec}, StyleFontSize},
27 layout::{LayoutDisplay, LayoutFlexDirection, LayoutAlignItems, LayoutAlignSelf, LayoutFlexGrow, LayoutPaddingTop, LayoutPaddingBottom, LayoutPaddingLeft, LayoutPaddingRight, LayoutMarginLeft},
28 property::{CssProperty, *},
29 style::{StyleBackgroundContentVec, StyleBackgroundContent, LayoutBorderTopWidth, LayoutBorderBottomWidth, LayoutBorderLeftWidth, LayoutBorderRightWidth, StyleBorderTopStyle, BorderStyle, StyleBorderBottomStyle, StyleBorderLeftStyle, StyleBorderRightStyle, StyleBorderTopColor, StyleBorderBottomColor, StyleBorderLeftColor, StyleBorderRightColor, StyleBorderTopLeftRadius, StyleBorderTopRightRadius, StyleBorderBottomLeftRadius, StyleBorderBottomRightRadius, StyleTextColor, StyleTextAlign, StyleCursor, StyleUserSelect},
30 },
31 impl_option_inner, AzString,
32};
33
34use crate::callbacks::{Callback, CallbackInfo};
35
36static ALERT_CONTAINER_CLASS: &[IdOrClass] =
37 &[Class(AzString::from_const_str("__azul-native-alert"))];
38static ALERT_MESSAGE_CLASS: &[IdOrClass] =
39 &[Class(AzString::from_const_str("__azul-native-alert-message"))];
40static ALERT_CLOSE_CLASS: &[IdOrClass] =
41 &[Class(AzString::from_const_str("__azul-native-alert-close"))];
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 AlertOnDismissCallbackType = extern "C" fn(RefAny, CallbackInfo, AlertState) -> Update;
50impl_widget_callback!(
51 AlertOnDismiss,
52 OptionAlertOnDismiss,
53 AlertOnDismissCallback,
54 AlertOnDismissCallbackType
55);
56
57azul_core::impl_managed_callback! {
58 wrapper: AlertOnDismissCallback,
59 info_ty: CallbackInfo,
60 return_ty: Update,
61 default_ret: Update::DoNothing,
62 invoker_static: ALERT_ON_DISMISS_INVOKER,
63 invoker_ty: AzAlertOnDismissCallbackInvoker,
64 thunk_fn: az_alert_on_dismiss_callback_thunk,
65 setter_fn: AzApp_setAlertOnDismissCallbackInvoker,
66 from_handle_fn: AzAlertOnDismissCallback_createFromHostHandle,
67 extra_args: [ state: AlertState ],
68}
69
70#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
72#[repr(C)]
73pub enum AlertKind {
74 #[default]
76 Info,
77 Success,
79 Warning,
81 Danger,
83}
84
85impl AlertKind {
86 #[allow(clippy::trivially_copy_pass_by_ref)] const fn colors(&self) -> (ColorU, ColorU, ColorU) {
89 match self {
90 Self::Info => (
91 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 }, ),
95 Self::Success => (
96 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 }, ),
100 Self::Warning => (
101 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 }, ),
105 Self::Danger => (
106 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 }, ),
110 }
111 }
112
113 #[must_use] pub const fn class_name(&self) -> &'static str {
115 match self {
116 Self::Info => "__azul-alert-info",
117 Self::Success => "__azul-alert-success",
118 Self::Warning => "__azul-alert-warning",
119 Self::Danger => "__azul-alert-danger",
120 }
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
126#[repr(C)]
127pub struct Alert {
128 pub alert_state: AlertStateWrapper,
130 pub message: AzString,
132 pub kind: AlertKind,
134 pub dismissible: bool,
136 pub container_style: CssPropertyWithConditionsVec,
138}
139
140#[derive(Debug, Default, Clone, PartialEq, Eq)]
141#[repr(C)]
142pub struct AlertStateWrapper {
143 pub inner: AlertState,
145 pub on_dismiss: OptionAlertOnDismiss,
147}
148
149#[derive(Debug, Copy, Clone, PartialEq, Eq)]
151#[repr(C)]
152pub struct AlertState {
153 pub visible: bool,
155}
156
157impl Default for AlertState {
158 fn default() -> Self {
159 Self { visible: true }
160 }
161}
162
163fn build_alert_style(kind: AlertKind) -> CssPropertyWithConditionsVec {
167 let (bg, border, text) = kind.colors();
168 let bg_vec =
169 StyleBackgroundContentVec::from_vec(alloc::vec![StyleBackgroundContent::Color(bg)]);
170 CssPropertyWithConditionsVec::from_vec(alloc::vec![
171 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Flex)),
172 CssPropertyWithConditions::simple(CssProperty::const_flex_direction(
173 LayoutFlexDirection::Row,
174 )),
175 CssPropertyWithConditions::simple(CssProperty::const_align_items(LayoutAlignItems::Start)),
176 CssPropertyWithConditions::simple(CssProperty::align_self(LayoutAlignSelf::Stretch)),
178 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(
179 0,
180 ))),
181 CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(
183 12,
184 ))),
185 CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
186 LayoutPaddingBottom::const_px(12),
187 )),
188 CssPropertyWithConditions::simple(CssProperty::const_padding_left(
189 LayoutPaddingLeft::const_px(12),
190 )),
191 CssPropertyWithConditions::simple(CssProperty::const_padding_right(
192 LayoutPaddingRight::const_px(12),
193 )),
194 CssPropertyWithConditions::simple(CssProperty::const_border_top_width(
196 LayoutBorderTopWidth::const_px(1),
197 )),
198 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
199 LayoutBorderBottomWidth::const_px(1),
200 )),
201 CssPropertyWithConditions::simple(CssProperty::const_border_left_width(
202 LayoutBorderLeftWidth::const_px(1),
203 )),
204 CssPropertyWithConditions::simple(CssProperty::const_border_right_width(
205 LayoutBorderRightWidth::const_px(1),
206 )),
207 CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle {
208 inner: BorderStyle::Solid,
209 })),
210 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
211 StyleBorderBottomStyle {
212 inner: BorderStyle::Solid,
213 },
214 )),
215 CssPropertyWithConditions::simple(CssProperty::const_border_left_style(
216 StyleBorderLeftStyle {
217 inner: BorderStyle::Solid,
218 },
219 )),
220 CssPropertyWithConditions::simple(CssProperty::const_border_right_style(
221 StyleBorderRightStyle {
222 inner: BorderStyle::Solid,
223 },
224 )),
225 CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
226 inner: border,
227 })),
228 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
229 StyleBorderBottomColor { inner: border },
230 )),
231 CssPropertyWithConditions::simple(CssProperty::const_border_left_color(
232 StyleBorderLeftColor { inner: border },
233 )),
234 CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
235 StyleBorderRightColor { inner: border },
236 )),
237 CssPropertyWithConditions::simple(CssProperty::const_border_top_left_radius(
239 StyleBorderTopLeftRadius::const_px(6),
240 )),
241 CssPropertyWithConditions::simple(CssProperty::const_border_top_right_radius(
242 StyleBorderTopRightRadius::const_px(6),
243 )),
244 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_left_radius(
245 StyleBorderBottomLeftRadius::const_px(6),
246 )),
247 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_right_radius(
248 StyleBorderBottomRightRadius::const_px(6),
249 )),
250 CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(14))),
251 CssPropertyWithConditions::simple(CssProperty::const_font_family(SYSTEM_UI_FAMILY)),
252 CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
254 inner: text,
255 })),
256 CssPropertyWithConditions::simple(CssProperty::const_background_content(bg_vec)),
257 ])
258}
259
260static ALERT_MESSAGE_STYLE: &[CssPropertyWithConditions] = &[
262 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(1))),
263 CssPropertyWithConditions::simple(CssProperty::const_text_align(StyleTextAlign::Left)),
264];
265
266static ALERT_CLOSE_STYLE: &[CssPropertyWithConditions] = &[
268 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
269 CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(18))),
270 CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)),
271 CssPropertyWithConditions::simple(CssProperty::user_select(StyleUserSelect::None)),
272 CssPropertyWithConditions::simple(CssProperty::const_margin_left(LayoutMarginLeft::const_px(
273 12,
274 ))),
275];
276
277impl Alert {
278 #[inline]
280 #[must_use] pub fn create(message: AzString) -> Self {
281 Self::with_kind(message, AlertKind::Info)
282 }
283
284 #[inline]
286 #[must_use] pub fn with_kind(message: AzString, kind: AlertKind) -> Self {
287 Self {
288 alert_state: AlertStateWrapper::default(),
289 message,
290 kind,
291 dismissible: false,
292 container_style: build_alert_style(kind),
293 }
294 }
295
296 #[inline]
298 pub fn set_kind(&mut self, kind: AlertKind) {
299 self.kind = kind;
300 self.container_style = build_alert_style(kind);
301 }
302
303 #[inline]
305 #[must_use] pub fn with_alert_kind(mut self, kind: AlertKind) -> Self {
306 self.set_kind(kind);
307 self
308 }
309
310 #[inline]
312 pub const fn set_dismissible(&mut self, dismissible: bool) {
313 self.dismissible = dismissible;
314 }
315
316 #[inline]
318 #[must_use] pub const fn with_dismissible(mut self, dismissible: bool) -> Self {
319 self.set_dismissible(dismissible);
320 self
321 }
322
323 #[inline]
326 pub fn set_on_dismiss<C: Into<AlertOnDismissCallback>>(&mut self, data: RefAny, on_dismiss: C) {
327 self.dismissible = true;
328 self.alert_state.on_dismiss = Some(AlertOnDismiss {
329 callback: on_dismiss.into(),
330 refany: data,
331 })
332 .into();
333 }
334
335 #[inline]
337 #[must_use] pub fn with_on_dismiss<C: Into<AlertOnDismissCallback>>(
338 mut self,
339 data: RefAny,
340 on_dismiss: C,
341 ) -> Self {
342 self.set_on_dismiss(data, on_dismiss);
343 self
344 }
345
346 #[inline]
348 #[must_use] pub fn swap_with_default(&mut self) -> Self {
349 let mut s = Self::create(AzString::from_const_str(""));
350 core::mem::swap(&mut s, self);
351 s
352 }
353
354 #[inline]
356 #[must_use] pub fn dom(self) -> Dom {
357 use azul_core::{
358 callbacks::CoreCallback,
359 dom::{EventFilter, HoverEventFilter},
360 refany::OptionRefAny,
361 };
362
363 let message = Dom::create_text(self.message)
364 .with_ids_and_classes(IdOrClassVec::from_const_slice(ALERT_MESSAGE_CLASS))
365 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(ALERT_MESSAGE_STYLE));
366
367 let mut children = alloc::vec![message];
368
369 if self.dismissible {
370 let close = Dom::create_text(AzString::from_const_str("\u{00D7}"))
371 .with_ids_and_classes(IdOrClassVec::from_const_slice(ALERT_CLOSE_CLASS))
372 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(ALERT_CLOSE_STYLE))
373 .with_tab_index(TabIndex::Auto)
374 .with_callbacks(
375 alloc::vec![CoreCallbackData {
376 event: EventFilter::Hover(HoverEventFilter::MouseUp),
377 callback: CoreCallback {
378 cb: default_on_alert_dismiss as usize,
379 ctx: OptionRefAny::None,
380 },
381 refany: RefAny::new(self.alert_state),
382 }]
383 .into(),
384 );
385 children.push(close);
386 }
387
388 Dom::create_div()
389 .with_ids_and_classes(IdOrClassVec::from_const_slice(ALERT_CONTAINER_CLASS))
390 .with_css_props(self.container_style)
391 .with_children(children.into())
392 }
393}
394
395impl Default for Alert {
396 fn default() -> Self {
397 Self::create(AzString::from_const_str(""))
398 }
399}
400
401extern "C" fn default_on_alert_dismiss(mut data: RefAny, mut info: CallbackInfo) -> Update {
406 let close_node = info.get_hit_node();
407 let Some(container) = info.get_parent(close_node) else {
408 return Update::DoNothing;
409 };
410
411 let result = {
412 let Some(mut alert) = data.downcast_mut::<AlertStateWrapper>() else {
413 return Update::DoNothing;
414 };
415 alert.inner.visible = false;
416 let inner = alert.inner;
417 let alert = &mut *alert;
418 match alert.on_dismiss.as_mut() {
419 Some(AlertOnDismiss { callback, refany }) => (callback.cb)(refany.clone(), info, inner),
420 None => Update::DoNothing,
421 }
422 };
423
424 info.set_css_property(container, CssProperty::const_display(LayoutDisplay::None));
429
430 result
431}
432
433impl From<Alert> for Dom {
434 fn from(a: Alert) -> Self {
435 a.dom()
436 }
437}