1use azul_core::{
30 callbacks::{CoreCallbackData, Update},
31 dom::{Dom, IdOrClass, IdOrClass::Class, IdOrClassVec, TabIndex},
32 refany::RefAny,
33};
34use azul_css::dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec};
35use azul_css::{
36 props::{
37 basic::{color::ColorU, *},
38 layout::{LayoutDisplay, LayoutPosition, LayoutFlexGrow, LayoutTop, LayoutLeft, LayoutMinWidth, LayoutPaddingTop, LayoutPaddingBottom, LayoutPaddingLeft, LayoutPaddingRight},
39 property::{CssProperty, *},
40 style::{StyleCursor, StyleBackgroundContentVec, StyleBackgroundContent, LayoutBorderTopWidth, LayoutBorderBottomWidth, LayoutBorderLeftWidth, LayoutBorderRightWidth, StyleBorderTopStyle, BorderStyle, StyleBorderBottomStyle, StyleBorderLeftStyle, StyleBorderRightStyle, StyleBorderTopColor, StyleBorderBottomColor, StyleBorderLeftColor, StyleBorderRightColor, StyleBorderTopLeftRadius, StyleBorderTopRightRadius, StyleBorderBottomLeftRadius, StyleBorderBottomRightRadius},
41 },
42 impl_option_inner, AzString,
43};
44
45use crate::callbacks::{Callback, CallbackInfo};
46
47static POPOVER_WRAPPER_CLASS: &[IdOrClass] =
48 &[Class(AzString::from_const_str("__azul-native-popover"))];
49static POPOVER_TRIGGER_CLASS: &[IdOrClass] = &[Class(AzString::from_const_str(
50 "__azul-native-popover-trigger",
51))];
52static POPOVER_CONTENT_CLASS: &[IdOrClass] = &[Class(AzString::from_const_str(
53 "__azul-native-popover-content",
54))];
55
56const CONTENT_OFFSET_Y: isize = 32;
60const CONTENT_MIN_WIDTH: isize = 160;
62const CONTENT_RADIUS: isize = 6;
63
64const CONTENT_BG_COLOR: ColorU = ColorU { r: 255, g: 255, b: 255, a: 255 };
67const CONTENT_BORDER_COLOR: ColorU = ColorU { r: 204, g: 204, b: 204, a: 255 };
69
70pub type PopoverOnToggleCallbackType = extern "C" fn(RefAny, CallbackInfo, PopoverState) -> Update;
73impl_widget_callback!(
74 PopoverOnToggle,
75 OptionPopoverOnToggle,
76 PopoverOnToggleCallback,
77 PopoverOnToggleCallbackType
78);
79
80azul_core::impl_managed_callback! {
81 wrapper: PopoverOnToggleCallback,
82 info_ty: CallbackInfo,
83 return_ty: Update,
84 default_ret: Update::DoNothing,
85 invoker_static: POPOVER_ON_TOGGLE_INVOKER,
86 invoker_ty: AzPopoverOnToggleCallbackInvoker,
87 thunk_fn: az_popover_on_toggle_callback_thunk,
88 setter_fn: AzApp_setPopoverOnToggleCallbackInvoker,
89 from_handle_fn: AzPopoverOnToggleCallback_createFromHostHandle,
90 extra_args: [ state: PopoverState ],
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
95#[repr(C)]
96pub struct Popover {
97 pub popover_state: PopoverStateWrapper,
99 pub anchor: Dom,
101 pub content: Dom,
103 pub wrapper_style: CssPropertyWithConditionsVec,
105 pub content_style: CssPropertyWithConditionsVec,
107}
108
109#[derive(Debug, Default, Clone, PartialEq, Eq)]
110#[repr(C)]
111pub struct PopoverStateWrapper {
112 pub inner: PopoverState,
114 pub on_toggle: OptionPopoverOnToggle,
116}
117
118#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
120#[repr(C)]
121pub struct PopoverState {
122 pub open: bool,
124}
125
126static POPOVER_WRAPPER_STYLE: &[CssPropertyWithConditions] = &[
129 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::InlineBlock)),
130 CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Relative)),
131 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
132];
133
134static POPOVER_TRIGGER_STYLE: &[CssPropertyWithConditions] = &[
136 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::InlineBlock)),
137 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
138 CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)),
139];
140
141fn build_content_style(open: bool) -> CssPropertyWithConditionsVec {
146 let display = if open {
147 LayoutDisplay::Block
148 } else {
149 LayoutDisplay::None
150 };
151 let bg_vec = StyleBackgroundContentVec::from_vec(alloc::vec![StyleBackgroundContent::Color(
152 CONTENT_BG_COLOR
153 )]);
154 CssPropertyWithConditionsVec::from_vec(alloc::vec![
155 CssPropertyWithConditions::simple(CssProperty::const_display(display)),
156 CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Absolute)),
157 CssPropertyWithConditions::simple(CssProperty::const_top(LayoutTop::const_px(
158 CONTENT_OFFSET_Y,
159 ))),
160 CssPropertyWithConditions::simple(CssProperty::const_left(LayoutLeft::const_px(0))),
161 CssPropertyWithConditions::simple(CssProperty::const_min_width(LayoutMinWidth::const_px(
162 CONTENT_MIN_WIDTH,
163 ))),
164 CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(
166 8,
167 ))),
168 CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
169 LayoutPaddingBottom::const_px(8),
170 )),
171 CssPropertyWithConditions::simple(CssProperty::const_padding_left(
172 LayoutPaddingLeft::const_px(8),
173 )),
174 CssPropertyWithConditions::simple(CssProperty::const_padding_right(
175 LayoutPaddingRight::const_px(8),
176 )),
177 CssPropertyWithConditions::simple(CssProperty::const_border_top_width(
179 LayoutBorderTopWidth::const_px(1),
180 )),
181 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
182 LayoutBorderBottomWidth::const_px(1),
183 )),
184 CssPropertyWithConditions::simple(CssProperty::const_border_left_width(
185 LayoutBorderLeftWidth::const_px(1),
186 )),
187 CssPropertyWithConditions::simple(CssProperty::const_border_right_width(
188 LayoutBorderRightWidth::const_px(1),
189 )),
190 CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle {
191 inner: BorderStyle::Solid,
192 })),
193 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
194 StyleBorderBottomStyle {
195 inner: BorderStyle::Solid,
196 },
197 )),
198 CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle {
199 inner: BorderStyle::Solid,
200 })),
201 CssPropertyWithConditions::simple(CssProperty::const_border_right_style(
202 StyleBorderRightStyle {
203 inner: BorderStyle::Solid,
204 },
205 )),
206 CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
207 inner: CONTENT_BORDER_COLOR,
208 })),
209 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
210 StyleBorderBottomColor {
211 inner: CONTENT_BORDER_COLOR,
212 },
213 )),
214 CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor {
215 inner: CONTENT_BORDER_COLOR,
216 })),
217 CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
218 StyleBorderRightColor {
219 inner: CONTENT_BORDER_COLOR,
220 },
221 )),
222 CssPropertyWithConditions::simple(CssProperty::const_border_top_left_radius(
224 StyleBorderTopLeftRadius::const_px(CONTENT_RADIUS),
225 )),
226 CssPropertyWithConditions::simple(CssProperty::const_border_top_right_radius(
227 StyleBorderTopRightRadius::const_px(CONTENT_RADIUS),
228 )),
229 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_left_radius(
230 StyleBorderBottomLeftRadius::const_px(CONTENT_RADIUS),
231 )),
232 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_right_radius(
233 StyleBorderBottomRightRadius::const_px(CONTENT_RADIUS),
234 )),
235 CssPropertyWithConditions::simple(CssProperty::const_background_content(bg_vec)),
236 ])
237}
238
239impl Popover {
240 #[must_use] pub fn new(anchor: Dom, content: Dom) -> Self {
243 Self {
244 popover_state: PopoverStateWrapper::default(),
245 anchor,
246 content,
247 wrapper_style: CssPropertyWithConditionsVec::from_const_slice(POPOVER_WRAPPER_STYLE),
248 content_style: build_content_style(false),
249 }
250 }
251
252 #[inline]
254 pub fn set_open(&mut self, open: bool) {
255 self.popover_state.inner.open = open;
256 self.content_style = build_content_style(open);
257 }
258
259 #[inline]
261 #[must_use] pub fn with_open(mut self, open: bool) -> Self {
262 self.set_open(open);
263 self
264 }
265
266 #[inline]
268 pub fn set_on_toggle<C: Into<PopoverOnToggleCallback>>(&mut self, data: RefAny, on_toggle: C) {
269 self.popover_state.on_toggle = Some(PopoverOnToggle {
270 callback: on_toggle.into(),
271 refany: data,
272 })
273 .into();
274 }
275
276 #[inline]
278 #[must_use] pub fn with_on_toggle<C: Into<PopoverOnToggleCallback>>(
279 mut self,
280 data: RefAny,
281 on_toggle: C,
282 ) -> Self {
283 self.set_on_toggle(data, on_toggle);
284 self
285 }
286
287 #[inline]
289 #[must_use] pub fn swap_with_default(&mut self) -> Self {
290 let mut s = Self::new(Dom::default(), Dom::default());
291 core::mem::swap(&mut s, self);
292 s
293 }
294
295 #[must_use] pub fn dom(self) -> Dom {
298 use azul_core::{callbacks::CoreCallback, dom::{EventFilter, HoverEventFilter}, refany::OptionRefAny};
299
300 let trigger = Dom::create_div()
306 .with_ids_and_classes(IdOrClassVec::from_const_slice(POPOVER_TRIGGER_CLASS))
307 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(POPOVER_TRIGGER_STYLE))
308 .with_tab_index(TabIndex::Auto)
309 .with_callbacks(
310 vec![CoreCallbackData {
311 event: EventFilter::Hover(HoverEventFilter::MouseUp),
312 callback: CoreCallback {
313 cb: on_popover_toggle as usize,
314 ctx: OptionRefAny::None,
315 },
316 refany: RefAny::new(self.popover_state),
317 }]
318 .into(),
319 )
320 .with_children(vec![self.anchor].into());
321
322 let content = Dom::create_div()
323 .with_ids_and_classes(IdOrClassVec::from_const_slice(POPOVER_CONTENT_CLASS))
324 .with_css_props(self.content_style)
325 .with_children(vec![self.content].into());
326
327 Dom::create_div()
328 .with_ids_and_classes(IdOrClassVec::from_const_slice(POPOVER_WRAPPER_CLASS))
329 .with_css_props(self.wrapper_style)
330 .with_children(vec![trigger, content].into())
332 }
333}
334
335impl Default for Popover {
336 fn default() -> Self {
337 Self::new(Dom::default(), Dom::default())
338 }
339}
340
341extern "C" fn on_popover_toggle(mut data: RefAny, mut info: CallbackInfo) -> Update {
346 let trigger = info.get_hit_node();
347 let Some(content) = info.get_next_sibling(trigger) else {
348 return Update::DoNothing;
349 };
350
351 let (now_open, result) = {
352 let Some(mut pop) = data.downcast_mut::<PopoverStateWrapper>() else {
353 return Update::DoNothing;
354 };
355 pop.inner.open = !pop.inner.open;
356 let now_open = pop.inner.open;
357 let inner = pop.inner;
358 let pop = &mut *pop;
359 let result = match pop.on_toggle.as_mut() {
360 Some(PopoverOnToggle { callback, refany }) => (callback.cb)(refany.clone(), info, inner),
361 None => Update::DoNothing,
362 };
363 (now_open, result)
364 };
365
366 let display = if now_open {
370 LayoutDisplay::Block
371 } else {
372 LayoutDisplay::None
373 };
374 info.set_css_property(content, CssProperty::const_display(display));
375
376 result
377}
378
379impl From<Popover> for Dom {
380 fn from(p: Popover) -> Self {
381 p.dom()
382 }
383}