use azul_core::{
callbacks::{CoreCallbackData, Update},
dom::{Dom, IdOrClass, IdOrClass::Class, IdOrClassVec, TabIndex},
refany::RefAny,
};
use azul_css::dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec};
use azul_css::{
props::{
basic::{color::ColorU, *},
layout::{LayoutDisplay, LayoutPosition, LayoutFlexGrow, LayoutTop, LayoutLeft, LayoutMinWidth, LayoutPaddingTop, LayoutPaddingBottom, LayoutPaddingLeft, LayoutPaddingRight},
property::{CssProperty, *},
style::{StyleCursor, StyleBackgroundContentVec, StyleBackgroundContent, LayoutBorderTopWidth, LayoutBorderBottomWidth, LayoutBorderLeftWidth, LayoutBorderRightWidth, StyleBorderTopStyle, BorderStyle, StyleBorderBottomStyle, StyleBorderLeftStyle, StyleBorderRightStyle, StyleBorderTopColor, StyleBorderBottomColor, StyleBorderLeftColor, StyleBorderRightColor, StyleBorderTopLeftRadius, StyleBorderTopRightRadius, StyleBorderBottomLeftRadius, StyleBorderBottomRightRadius},
},
impl_option_inner, AzString,
};
use crate::callbacks::{Callback, CallbackInfo};
static POPOVER_WRAPPER_CLASS: &[IdOrClass] =
&[Class(AzString::from_const_str("__azul-native-popover"))];
static POPOVER_TRIGGER_CLASS: &[IdOrClass] = &[Class(AzString::from_const_str(
"__azul-native-popover-trigger",
))];
static POPOVER_CONTENT_CLASS: &[IdOrClass] = &[Class(AzString::from_const_str(
"__azul-native-popover-content",
))];
const CONTENT_OFFSET_Y: isize = 32;
const CONTENT_MIN_WIDTH: isize = 160;
const CONTENT_RADIUS: isize = 6;
const CONTENT_BG_COLOR: ColorU = ColorU { r: 255, g: 255, b: 255, a: 255 };
const CONTENT_BORDER_COLOR: ColorU = ColorU { r: 204, g: 204, b: 204, a: 255 };
pub type PopoverOnToggleCallbackType = extern "C" fn(RefAny, CallbackInfo, PopoverState) -> Update;
impl_widget_callback!(
PopoverOnToggle,
OptionPopoverOnToggle,
PopoverOnToggleCallback,
PopoverOnToggleCallbackType
);
azul_core::impl_managed_callback! {
wrapper: PopoverOnToggleCallback,
info_ty: CallbackInfo,
return_ty: Update,
default_ret: Update::DoNothing,
invoker_static: POPOVER_ON_TOGGLE_INVOKER,
invoker_ty: AzPopoverOnToggleCallbackInvoker,
thunk_fn: az_popover_on_toggle_callback_thunk,
setter_fn: AzApp_setPopoverOnToggleCallbackInvoker,
from_handle_fn: AzPopoverOnToggleCallback_createFromHostHandle,
extra_args: [ state: PopoverState ],
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[repr(C)]
pub struct Popover {
pub popover_state: PopoverStateWrapper,
pub anchor: Dom,
pub content: Dom,
pub wrapper_style: CssPropertyWithConditionsVec,
pub content_style: CssPropertyWithConditionsVec,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
#[repr(C)]
pub struct PopoverStateWrapper {
pub inner: PopoverState,
pub on_toggle: OptionPopoverOnToggle,
}
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
#[repr(C)]
pub struct PopoverState {
pub open: bool,
}
static POPOVER_WRAPPER_STYLE: &[CssPropertyWithConditions] = &[
CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::InlineBlock)),
CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Relative)),
CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
];
static POPOVER_TRIGGER_STYLE: &[CssPropertyWithConditions] = &[
CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::InlineBlock)),
CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)),
];
fn build_content_style(open: bool) -> CssPropertyWithConditionsVec {
let display = if open {
LayoutDisplay::Block
} else {
LayoutDisplay::None
};
let bg_vec = StyleBackgroundContentVec::from_vec(alloc::vec![StyleBackgroundContent::Color(
CONTENT_BG_COLOR
)]);
CssPropertyWithConditionsVec::from_vec(alloc::vec![
CssPropertyWithConditions::simple(CssProperty::const_display(display)),
CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Absolute)),
CssPropertyWithConditions::simple(CssProperty::const_top(LayoutTop::const_px(
CONTENT_OFFSET_Y,
))),
CssPropertyWithConditions::simple(CssProperty::const_left(LayoutLeft::const_px(0))),
CssPropertyWithConditions::simple(CssProperty::const_min_width(LayoutMinWidth::const_px(
CONTENT_MIN_WIDTH,
))),
CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(
8,
))),
CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
LayoutPaddingBottom::const_px(8),
)),
CssPropertyWithConditions::simple(CssProperty::const_padding_left(
LayoutPaddingLeft::const_px(8),
)),
CssPropertyWithConditions::simple(CssProperty::const_padding_right(
LayoutPaddingRight::const_px(8),
)),
CssPropertyWithConditions::simple(CssProperty::const_border_top_width(
LayoutBorderTopWidth::const_px(1),
)),
CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
LayoutBorderBottomWidth::const_px(1),
)),
CssPropertyWithConditions::simple(CssProperty::const_border_left_width(
LayoutBorderLeftWidth::const_px(1),
)),
CssPropertyWithConditions::simple(CssProperty::const_border_right_width(
LayoutBorderRightWidth::const_px(1),
)),
CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle {
inner: BorderStyle::Solid,
})),
CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
StyleBorderBottomStyle {
inner: BorderStyle::Solid,
},
)),
CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle {
inner: BorderStyle::Solid,
})),
CssPropertyWithConditions::simple(CssProperty::const_border_right_style(
StyleBorderRightStyle {
inner: BorderStyle::Solid,
},
)),
CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
inner: CONTENT_BORDER_COLOR,
})),
CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
StyleBorderBottomColor {
inner: CONTENT_BORDER_COLOR,
},
)),
CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor {
inner: CONTENT_BORDER_COLOR,
})),
CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
StyleBorderRightColor {
inner: CONTENT_BORDER_COLOR,
},
)),
CssPropertyWithConditions::simple(CssProperty::const_border_top_left_radius(
StyleBorderTopLeftRadius::const_px(CONTENT_RADIUS),
)),
CssPropertyWithConditions::simple(CssProperty::const_border_top_right_radius(
StyleBorderTopRightRadius::const_px(CONTENT_RADIUS),
)),
CssPropertyWithConditions::simple(CssProperty::const_border_bottom_left_radius(
StyleBorderBottomLeftRadius::const_px(CONTENT_RADIUS),
)),
CssPropertyWithConditions::simple(CssProperty::const_border_bottom_right_radius(
StyleBorderBottomRightRadius::const_px(CONTENT_RADIUS),
)),
CssPropertyWithConditions::simple(CssProperty::const_background_content(bg_vec)),
])
}
impl Popover {
#[must_use] pub fn new(anchor: Dom, content: Dom) -> Self {
Self {
popover_state: PopoverStateWrapper::default(),
anchor,
content,
wrapper_style: CssPropertyWithConditionsVec::from_const_slice(POPOVER_WRAPPER_STYLE),
content_style: build_content_style(false),
}
}
#[inline]
pub fn set_open(&mut self, open: bool) {
self.popover_state.inner.open = open;
self.content_style = build_content_style(open);
}
#[inline]
#[must_use] pub fn with_open(mut self, open: bool) -> Self {
self.set_open(open);
self
}
#[inline]
pub fn set_on_toggle<C: Into<PopoverOnToggleCallback>>(&mut self, data: RefAny, on_toggle: C) {
self.popover_state.on_toggle = Some(PopoverOnToggle {
callback: on_toggle.into(),
refany: data,
})
.into();
}
#[inline]
#[must_use] pub fn with_on_toggle<C: Into<PopoverOnToggleCallback>>(
mut self,
data: RefAny,
on_toggle: C,
) -> Self {
self.set_on_toggle(data, on_toggle);
self
}
#[inline]
#[must_use] pub fn swap_with_default(&mut self) -> Self {
let mut s = Self::new(Dom::default(), Dom::default());
core::mem::swap(&mut s, self);
s
}
#[must_use] pub fn dom(self) -> Dom {
use azul_core::{callbacks::CoreCallback, dom::{EventFilter, HoverEventFilter}, refany::OptionRefAny};
let trigger = Dom::create_div()
.with_ids_and_classes(IdOrClassVec::from_const_slice(POPOVER_TRIGGER_CLASS))
.with_css_props(CssPropertyWithConditionsVec::from_const_slice(POPOVER_TRIGGER_STYLE))
.with_tab_index(TabIndex::Auto)
.with_callbacks(
vec![CoreCallbackData {
event: EventFilter::Hover(HoverEventFilter::MouseUp),
callback: CoreCallback {
cb: on_popover_toggle as usize,
ctx: OptionRefAny::None,
},
refany: RefAny::new(self.popover_state),
}]
.into(),
)
.with_children(vec![self.anchor].into());
let content = Dom::create_div()
.with_ids_and_classes(IdOrClassVec::from_const_slice(POPOVER_CONTENT_CLASS))
.with_css_props(self.content_style)
.with_children(vec![self.content].into());
Dom::create_div()
.with_ids_and_classes(IdOrClassVec::from_const_slice(POPOVER_WRAPPER_CLASS))
.with_css_props(self.wrapper_style)
.with_children(vec![trigger, content].into())
}
}
impl Default for Popover {
fn default() -> Self {
Self::new(Dom::default(), Dom::default())
}
}
extern "C" fn on_popover_toggle(mut data: RefAny, mut info: CallbackInfo) -> Update {
let trigger = info.get_hit_node();
let Some(content) = info.get_next_sibling(trigger) else {
return Update::DoNothing;
};
let (now_open, result) = {
let Some(mut pop) = data.downcast_mut::<PopoverStateWrapper>() else {
return Update::DoNothing;
};
pop.inner.open = !pop.inner.open;
let now_open = pop.inner.open;
let inner = pop.inner;
let pop = &mut *pop;
let result = match pop.on_toggle.as_mut() {
Some(PopoverOnToggle { callback, refany }) => (callback.cb)(refany.clone(), info, inner),
None => Update::DoNothing,
};
(now_open, result)
};
let display = if now_open {
LayoutDisplay::Block
} else {
LayoutDisplay::None
};
info.set_css_property(content, CssProperty::const_display(display));
result
}
impl From<Popover> for Dom {
fn from(p: Popover) -> Self {
p.dom()
}
}