1use azul_core::{
37 callbacks::{CoreCallback, CoreCallbackData, Update},
38 dom::{Dom, DomVec, IdOrClass, IdOrClass::Class, IdOrClassVec, TabIndex},
39 refany::RefAny,
40};
41use azul_css::dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec};
42use azul_css::{
43 props::{
44 basic::{color::ColorU, font::{StyleFontFamily, StyleFontFamilyVec}, PixelValue, StyleFontSize},
45 layout::{LayoutDisplay, LayoutPosition, LayoutTop, LayoutLeft, LayoutWidth, LayoutHeight, LayoutFlexDirection, LayoutJustifyContent, LayoutAlignItems, LayoutFlexGrow, LayoutMinWidth, LayoutMaxWidth, LayoutPaddingTop, LayoutPaddingBottom, LayoutPaddingLeft, LayoutPaddingRight, LayoutRight},
46 property::{CssProperty, *},
47 style::{StyleBackgroundContentVec, StyleBackgroundContent, LayoutBorderTopWidth, LayoutBorderBottomWidth, LayoutBorderLeftWidth, LayoutBorderRightWidth, StyleBorderTopStyle, BorderStyle, StyleBorderBottomStyle, StyleBorderLeftStyle, StyleBorderRightStyle, StyleBorderTopColor, StyleBorderBottomColor, StyleBorderLeftColor, StyleBorderRightColor, StyleBorderTopLeftRadius, StyleBorderTopRightRadius, StyleBorderBottomLeftRadius, StyleBorderBottomRightRadius, StyleTextColor, StyleTextAlign, StyleUserSelect, StyleCursor},
48 },
49 impl_option_inner, AzString,
50};
51
52use crate::callbacks::{Callback, CallbackInfo};
53
54static MODAL_BACKDROP_CLASS: &[IdOrClass] =
55 &[Class(AzString::from_const_str("__azul-native-modal"))];
56static MODAL_PANEL_CLASS: &[IdOrClass] =
57 &[Class(AzString::from_const_str("__azul-native-modal-panel"))];
58static MODAL_TITLE_CLASS: &[IdOrClass] =
59 &[Class(AzString::from_const_str("__azul-native-modal-title"))];
60static MODAL_CLOSE_CLASS: &[IdOrClass] =
61 &[Class(AzString::from_const_str("__azul-native-modal-close"))];
62static MODAL_CONTENT_CLASS: &[IdOrClass] =
63 &[Class(AzString::from_const_str("__azul-native-modal-content"))];
64
65const SYSTEM_UI_STR: AzString = AzString::from_const_str("system:ui");
66const SYSTEM_UI_FAMILIES: &[StyleFontFamily] = &[StyleFontFamily::System(SYSTEM_UI_STR)];
67const SYSTEM_UI_FAMILY: StyleFontFamilyVec =
68 StyleFontFamilyVec::from_const_slice(SYSTEM_UI_FAMILIES);
69
70const PANEL_MIN_WIDTH: isize = 280;
72const PANEL_MAX_WIDTH: isize = 520;
73const PANEL_RADIUS: isize = 8;
74
75const BACKDROP_COLOR: ColorU = ColorU { r: 0, g: 0, b: 0, a: 128 };
78const PANEL_BG_COLOR: ColorU = ColorU { r: 255, g: 255, b: 255, a: 255 };
79const PANEL_BORDER_COLOR: ColorU = ColorU { r: 204, g: 204, b: 204, a: 255 }; const TITLE_COLOR: ColorU = ColorU { r: 33, g: 37, b: 41, a: 255 }; const CLOSE_COLOR: ColorU = ColorU { r: 108, g: 117, b: 125, a: 255 }; pub type ModalOnCloseCallbackType = extern "C" fn(RefAny, CallbackInfo, ModalState) -> Update;
86impl_widget_callback!(
87 ModalOnClose,
88 OptionModalOnClose,
89 ModalOnCloseCallback,
90 ModalOnCloseCallbackType
91);
92
93azul_core::impl_managed_callback! {
94 wrapper: ModalOnCloseCallback,
95 info_ty: CallbackInfo,
96 return_ty: Update,
97 default_ret: Update::DoNothing,
98 invoker_static: MODAL_ON_CLOSE_INVOKER,
99 invoker_ty: AzModalOnCloseCallbackInvoker,
100 thunk_fn: az_modal_on_close_callback_thunk,
101 setter_fn: AzApp_setModalOnCloseCallbackInvoker,
102 from_handle_fn: AzModalOnCloseCallback_createFromHostHandle,
103 extra_args: [ state: ModalState ],
104}
105
106#[derive(Debug, Clone, PartialEq, Eq)]
109#[repr(C)]
110pub struct Modal {
111 pub modal_state: ModalStateWrapper,
113 pub title: AzString,
115 pub content: Dom,
117 pub show_close_button: bool,
119 pub backdrop_style: CssPropertyWithConditionsVec,
121}
122
123#[derive(Debug, Default, Clone, PartialEq, Eq)]
124#[repr(C)]
125pub struct ModalStateWrapper {
126 pub inner: ModalState,
128 pub on_close: OptionModalOnClose,
130}
131
132#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
134#[repr(C)]
135pub struct ModalState {
136 pub open: bool,
138}
139
140fn build_backdrop_style(open: bool) -> CssPropertyWithConditionsVec {
144 let display = if open {
145 LayoutDisplay::Flex
146 } else {
147 LayoutDisplay::None
148 };
149 let bg_vec = StyleBackgroundContentVec::from_vec(alloc::vec![StyleBackgroundContent::Color(
150 BACKDROP_COLOR
151 )]);
152 CssPropertyWithConditionsVec::from_vec(alloc::vec![
153 CssPropertyWithConditions::simple(CssProperty::const_display(display)),
154 CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Absolute)),
155 CssPropertyWithConditions::simple(CssProperty::const_top(LayoutTop::const_px(0))),
156 CssPropertyWithConditions::simple(CssProperty::const_left(LayoutLeft::const_px(0))),
157 CssPropertyWithConditions::simple(CssProperty::const_width(LayoutWidth::Px(
160 PixelValue::const_percent(100),
161 ))),
162 CssPropertyWithConditions::simple(CssProperty::const_height(LayoutHeight::Px(
163 PixelValue::const_percent(100),
164 ))),
165 CssPropertyWithConditions::simple(CssProperty::const_flex_direction(
167 LayoutFlexDirection::Row,
168 )),
169 CssPropertyWithConditions::simple(CssProperty::const_justify_content(
170 LayoutJustifyContent::Center,
171 )),
172 CssPropertyWithConditions::simple(CssProperty::const_align_items(LayoutAlignItems::Center)),
173 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
174 CssPropertyWithConditions::simple(CssProperty::const_background_content(bg_vec)),
175 ])
176}
177
178static MODAL_PANEL_STYLE: &[CssPropertyWithConditions] = &[
183 CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Relative)),
184 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Flex)),
185 CssPropertyWithConditions::simple(CssProperty::const_flex_direction(LayoutFlexDirection::Column)),
186 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
187 CssPropertyWithConditions::simple(CssProperty::const_min_width(LayoutMinWidth::const_px(
188 PANEL_MIN_WIDTH,
189 ))),
190 CssPropertyWithConditions::simple(CssProperty::const_max_width(LayoutMaxWidth::const_px(
191 PANEL_MAX_WIDTH,
192 ))),
193 CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(20))),
195 CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
196 LayoutPaddingBottom::const_px(20),
197 )),
198 CssPropertyWithConditions::simple(CssProperty::const_padding_left(LayoutPaddingLeft::const_px(
199 20,
200 ))),
201 CssPropertyWithConditions::simple(CssProperty::const_padding_right(
202 LayoutPaddingRight::const_px(20),
203 )),
204 CssPropertyWithConditions::simple(CssProperty::const_border_top_width(
206 LayoutBorderTopWidth::const_px(1),
207 )),
208 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
209 LayoutBorderBottomWidth::const_px(1),
210 )),
211 CssPropertyWithConditions::simple(CssProperty::const_border_left_width(
212 LayoutBorderLeftWidth::const_px(1),
213 )),
214 CssPropertyWithConditions::simple(CssProperty::const_border_right_width(
215 LayoutBorderRightWidth::const_px(1),
216 )),
217 CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle {
218 inner: BorderStyle::Solid,
219 })),
220 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
221 StyleBorderBottomStyle {
222 inner: BorderStyle::Solid,
223 },
224 )),
225 CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle {
226 inner: BorderStyle::Solid,
227 })),
228 CssPropertyWithConditions::simple(CssProperty::const_border_right_style(
229 StyleBorderRightStyle {
230 inner: BorderStyle::Solid,
231 },
232 )),
233 CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
234 inner: PANEL_BORDER_COLOR,
235 })),
236 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
237 StyleBorderBottomColor {
238 inner: PANEL_BORDER_COLOR,
239 },
240 )),
241 CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor {
242 inner: PANEL_BORDER_COLOR,
243 })),
244 CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
245 StyleBorderRightColor {
246 inner: PANEL_BORDER_COLOR,
247 },
248 )),
249 CssPropertyWithConditions::simple(CssProperty::const_border_top_left_radius(
251 StyleBorderTopLeftRadius::const_px(PANEL_RADIUS),
252 )),
253 CssPropertyWithConditions::simple(CssProperty::const_border_top_right_radius(
254 StyleBorderTopRightRadius::const_px(PANEL_RADIUS),
255 )),
256 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_left_radius(
257 StyleBorderBottomLeftRadius::const_px(PANEL_RADIUS),
258 )),
259 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_right_radius(
260 StyleBorderBottomRightRadius::const_px(PANEL_RADIUS),
261 )),
262 CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(14))),
263 CssPropertyWithConditions::simple(CssProperty::const_font_family(SYSTEM_UI_FAMILY)),
264 CssPropertyWithConditions::simple(CssProperty::const_background_content(
265 StyleBackgroundContentVec::from_const_slice(&[StyleBackgroundContent::Color(
266 PANEL_BG_COLOR,
267 )]),
268 )),
269];
270
271static MODAL_TITLE_STYLE: &[CssPropertyWithConditions] = &[
274 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
275 CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(18))),
276 CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
277 inner: TITLE_COLOR,
278 })),
279 CssPropertyWithConditions::simple(CssProperty::const_text_align(StyleTextAlign::Left)),
280 CssPropertyWithConditions::simple(CssProperty::const_padding_right(
281 LayoutPaddingRight::const_px(24),
282 )),
283 CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
284 LayoutPaddingBottom::const_px(12),
285 )),
286 CssPropertyWithConditions::simple(CssProperty::user_select(StyleUserSelect::None)),
287];
288
289static MODAL_CLOSE_STYLE: &[CssPropertyWithConditions] = &[
292 CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Absolute)),
293 CssPropertyWithConditions::simple(CssProperty::const_top(LayoutTop::const_px(8))),
294 CssPropertyWithConditions::simple(CssProperty::const_right(LayoutRight::const_px(12))),
295 CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(22))),
296 CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
297 inner: CLOSE_COLOR,
298 })),
299 CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)),
300 CssPropertyWithConditions::simple(CssProperty::user_select(StyleUserSelect::None)),
301];
302
303static MODAL_CONTENT_STYLE: &[CssPropertyWithConditions] = &[
305 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Block)),
306 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(1))),
307];
308
309impl Modal {
310 #[must_use] pub fn create(content: Dom) -> Self {
313 Self {
314 modal_state: ModalStateWrapper::default(),
315 title: AzString::from_const_str(""),
316 content,
317 show_close_button: true,
318 backdrop_style: build_backdrop_style(false),
319 }
320 }
321
322 #[inline]
324 pub fn set_title(&mut self, title: AzString) {
325 self.title = title;
326 }
327
328 #[inline]
330 #[must_use] pub fn with_title(mut self, title: AzString) -> Self {
331 self.set_title(title);
332 self
333 }
334
335 #[inline]
337 pub fn set_content(&mut self, content: Dom) {
338 self.content = content;
339 }
340
341 #[inline]
343 #[must_use] pub fn with_content(mut self, content: Dom) -> Self {
344 self.set_content(content);
345 self
346 }
347
348 #[inline]
350 pub fn set_open(&mut self, open: bool) {
351 self.modal_state.inner.open = open;
352 self.backdrop_style = build_backdrop_style(open);
353 }
354
355 #[inline]
357 #[must_use] pub fn with_open(mut self, open: bool) -> Self {
358 self.set_open(open);
359 self
360 }
361
362 #[inline]
364 pub const fn set_close_button(&mut self, show: bool) {
365 self.show_close_button = show;
366 }
367
368 #[inline]
370 #[must_use] pub const fn with_close_button(mut self, show: bool) -> Self {
371 self.set_close_button(show);
372 self
373 }
374
375 #[inline]
377 pub fn set_on_close<C: Into<ModalOnCloseCallback>>(&mut self, data: RefAny, on_close: C) {
378 self.modal_state.on_close = Some(ModalOnClose {
379 callback: on_close.into(),
380 refany: data,
381 })
382 .into();
383 }
384
385 #[inline]
387 #[must_use] pub fn with_on_close<C: Into<ModalOnCloseCallback>>(
388 mut self,
389 data: RefAny,
390 on_close: C,
391 ) -> Self {
392 self.set_on_close(data, on_close);
393 self
394 }
395
396 #[inline]
398 #[must_use] pub fn swap_with_default(&mut self) -> Self {
399 let mut s = Self::create(Dom::default());
400 core::mem::swap(&mut s, self);
401 s
402 }
403
404 #[must_use] pub fn dom(self) -> Dom {
407 let mut panel_children = Vec::new();
411
412 if self.show_close_button {
413 let close = Dom::create_text(AzString::from_const_str("\u{00D7}"))
414 .with_ids_and_classes(IdOrClassVec::from_const_slice(MODAL_CLOSE_CLASS))
415 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(MODAL_CLOSE_STYLE))
416 .with_tab_index(TabIndex::Auto)
417 .with_callbacks(
418 alloc::vec![CoreCallbackData {
419 event: azul_core::dom::EventFilter::Hover(
420 azul_core::dom::HoverEventFilter::MouseUp,
421 ),
422 callback: CoreCallback {
423 cb: on_modal_close as usize,
424 ctx: azul_core::refany::OptionRefAny::None,
425 },
426 refany: RefAny::new(self.modal_state),
427 }]
428 .into(),
429 );
430 panel_children.push(close);
431 }
432
433 if !self.title.as_str().is_empty() {
434 let title = Dom::create_text(self.title)
435 .with_ids_and_classes(IdOrClassVec::from_const_slice(MODAL_TITLE_CLASS))
436 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(MODAL_TITLE_STYLE));
437 panel_children.push(title);
438 }
439
440 let content = Dom::create_div()
441 .with_ids_and_classes(IdOrClassVec::from_const_slice(MODAL_CONTENT_CLASS))
442 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(MODAL_CONTENT_STYLE))
443 .with_children(DomVec::from_vec(alloc::vec![self.content]));
444 panel_children.push(content);
445
446 let panel = Dom::create_div()
447 .with_ids_and_classes(IdOrClassVec::from_const_slice(MODAL_PANEL_CLASS))
448 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(MODAL_PANEL_STYLE))
449 .with_children(DomVec::from_vec(panel_children));
450
451 Dom::create_div()
452 .with_ids_and_classes(IdOrClassVec::from_const_slice(MODAL_BACKDROP_CLASS))
453 .with_css_props(self.backdrop_style)
454 .with_children(DomVec::from_vec(alloc::vec![panel]))
455 }
456}
457
458impl Default for Modal {
459 fn default() -> Self {
460 Self::create(Dom::default())
461 }
462}
463
464extern "C" fn on_modal_close(mut data: RefAny, mut info: CallbackInfo) -> Update {
470 let close_node = info.get_hit_node();
471 let Some(panel) = info.get_parent(close_node) else {
472 return Update::DoNothing;
473 };
474 let Some(backdrop) = info.get_parent(panel) else {
475 return Update::DoNothing;
476 };
477
478 let result = {
479 let Some(mut modal) = data.downcast_mut::<ModalStateWrapper>() else {
480 return Update::DoNothing;
481 };
482 modal.inner.open = false;
483 let inner = modal.inner;
484 let modal = &mut *modal;
485 match modal.on_close.as_mut() {
486 Some(ModalOnClose { callback, refany }) => (callback.cb)(refany.clone(), info, inner),
487 None => Update::DoNothing,
488 }
489 };
490
491 info.set_css_property(backdrop, CssProperty::const_display(LayoutDisplay::None));
495
496 result
497}
498
499impl From<Modal> for Dom {
500 fn from(m: Modal) -> Self {
501 m.dom()
502 }
503}