use azul_core::{
callbacks::{CoreCallback, CoreCallbackData, Update},
dom::{
Dom, DomVec, EventFilter, FocusEventFilter, IdOrClass, IdOrClass::Class, IdOrClassVec,
TabIndex,
},
menu::{Menu, MenuItem, MenuPopupPosition, StringMenuItem},
refany::RefAny,
window::ContextMenuMouseButton,
};
use azul_css::{
dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec},
props::{
basic::{
color::{ColorU, ColorOrSystem},
font::{StyleFontFamily, StyleFontFamilyVec},
*,
},
layout::*,
property::CssProperty,
style::*,
},
*,
};
use crate::callbacks::{Callback, CallbackInfo};
pub type DropDownOnChoiceChangeCallbackType = extern "C" fn(RefAny, CallbackInfo, usize) -> Update;
impl_widget_callback!(
DropDownOnChoiceChange,
OptionDropDownOnChoiceChange,
DropDownOnChoiceChangeCallback,
DropDownOnChoiceChangeCallbackType
);
azul_core::impl_managed_callback! {
wrapper: DropDownOnChoiceChangeCallback,
info_ty: CallbackInfo,
return_ty: Update,
default_ret: Update::DoNothing,
invoker_static: DROP_DOWN_ON_CHOICE_CHANGE_INVOKER,
invoker_ty: AzDropDownOnChoiceChangeCallbackInvoker,
thunk_fn: az_drop_down_on_choice_change_callback_thunk,
setter_fn: AzApp_setDropDownOnChoiceChangeCallbackInvoker,
from_handle_fn: AzDropDownOnChoiceChangeCallback_createFromHostHandle,
extra_args: [ choice_index: usize ],
}
const SYSTEM_UI_STR: AzString = AzString::from_const_str("system:ui");
const SYSTEM_UI_FAMILIES: &[StyleFontFamily] = &[StyleFontFamily::System(SYSTEM_UI_STR)];
const SYSTEM_UI_FAMILY: StyleFontFamilyVec =
StyleFontFamilyVec::from_const_slice(SYSTEM_UI_FAMILIES);
const BORDER_NORMAL: ColorU = ColorU { r: 172, g: 172, b: 172, a: 255 };
const BORDER_HOVER: ColorU = ColorU { r: 126, g: 180, b: 234, a: 255 };
const BORDER_FOCUS: ColorU = ColorU { r: 86, g: 157, b: 229, a: 255 };
const BG_GRADIENT_TOP: ColorU = ColorU { r: 245, g: 245, b: 245, a: 255 };
const BG_GRADIENT_BOTTOM: ColorU = ColorU { r: 235, g: 235, b: 235, a: 255 };
const BG_HOVER_TOP: ColorU = ColorU { r: 234, g: 244, b: 252, a: 255 };
const BG_HOVER_BOTTOM: ColorU = ColorU { r: 218, g: 236, b: 252, a: 255 };
const BG_ACTIVE_TOP: ColorU = ColorU { r: 218, g: 236, b: 252, a: 255 };
const BG_ACTIVE_BOTTOM: ColorU = ColorU { r: 202, g: 226, b: 248, a: 255 };
const NORMAL_BG_ITEMS: &[StyleBackgroundContent] =
&[StyleBackgroundContent::LinearGradient(LinearGradient {
direction: Direction::FromTo(DirectionCorners {
dir_from: DirectionCorner::Top,
dir_to: DirectionCorner::Bottom,
}),
extend_mode: ExtendMode::Clamp,
stops: NormalizedLinearColorStopVec::from_const_slice(&[
NormalizedLinearColorStop {
offset: PercentageValue::const_new(0),
color: ColorOrSystem::color(BG_GRADIENT_TOP),
},
NormalizedLinearColorStop {
offset: PercentageValue::const_new(100),
color: ColorOrSystem::color(BG_GRADIENT_BOTTOM),
},
]),
})];
const HOVER_BG_ITEMS: &[StyleBackgroundContent] =
&[StyleBackgroundContent::LinearGradient(LinearGradient {
direction: Direction::FromTo(DirectionCorners {
dir_from: DirectionCorner::Top,
dir_to: DirectionCorner::Bottom,
}),
extend_mode: ExtendMode::Clamp,
stops: NormalizedLinearColorStopVec::from_const_slice(&[
NormalizedLinearColorStop {
offset: PercentageValue::const_new(0),
color: ColorOrSystem::color(BG_HOVER_TOP),
},
NormalizedLinearColorStop {
offset: PercentageValue::const_new(100),
color: ColorOrSystem::color(BG_HOVER_BOTTOM),
},
]),
})];
const ACTIVE_BG_ITEMS: &[StyleBackgroundContent] =
&[StyleBackgroundContent::LinearGradient(LinearGradient {
direction: Direction::FromTo(DirectionCorners {
dir_from: DirectionCorner::Top,
dir_to: DirectionCorner::Bottom,
}),
extend_mode: ExtendMode::Clamp,
stops: NormalizedLinearColorStopVec::from_const_slice(&[
NormalizedLinearColorStop {
offset: PercentageValue::const_new(0),
color: ColorOrSystem::color(BG_ACTIVE_TOP),
},
NormalizedLinearColorStop {
offset: PercentageValue::const_new(100),
color: ColorOrSystem::color(BG_ACTIVE_BOTTOM),
},
]),
})];
static DROPDOWN_WRAPPER_STYLE: &[CssPropertyWithConditions] = &[
CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::InlineFlex)),
CssPropertyWithConditions::simple(CssProperty::const_flex_direction(LayoutFlexDirection::Row)),
CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
CssPropertyWithConditions::simple(CssProperty::const_align_items(LayoutAlignItems::Center)),
CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)),
CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(13))),
CssPropertyWithConditions::simple(CssProperty::const_font_family(SYSTEM_UI_FAMILY)),
CssPropertyWithConditions::simple(CssProperty::const_padding_left(LayoutPaddingLeft::const_px(4))),
CssPropertyWithConditions::simple(CssProperty::const_padding_right(LayoutPaddingRight::const_px(4))),
CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(2))),
CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(LayoutPaddingBottom::const_px(2))),
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: BORDER_NORMAL })),
CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(StyleBorderBottomColor { inner: BORDER_NORMAL })),
CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor { inner: BORDER_NORMAL })),
CssPropertyWithConditions::simple(CssProperty::const_border_right_color(StyleBorderRightColor { inner: BORDER_NORMAL })),
CssPropertyWithConditions::simple(CssProperty::const_background_content(
StyleBackgroundContentVec::from_const_slice(NORMAL_BG_ITEMS),
)),
CssPropertyWithConditions::on_hover(CssProperty::const_border_top_color(StyleBorderTopColor { inner: BORDER_HOVER })),
CssPropertyWithConditions::on_hover(CssProperty::const_border_bottom_color(StyleBorderBottomColor { inner: BORDER_HOVER })),
CssPropertyWithConditions::on_hover(CssProperty::const_border_left_color(StyleBorderLeftColor { inner: BORDER_HOVER })),
CssPropertyWithConditions::on_hover(CssProperty::const_border_right_color(StyleBorderRightColor { inner: BORDER_HOVER })),
CssPropertyWithConditions::on_hover(CssProperty::const_background_content(
StyleBackgroundContentVec::from_const_slice(HOVER_BG_ITEMS),
)),
CssPropertyWithConditions::on_active(CssProperty::const_background_content(
StyleBackgroundContentVec::from_const_slice(ACTIVE_BG_ITEMS),
)),
CssPropertyWithConditions::on_focus(CssProperty::const_border_top_color(StyleBorderTopColor { inner: BORDER_FOCUS })),
CssPropertyWithConditions::on_focus(CssProperty::const_border_bottom_color(StyleBorderBottomColor { inner: BORDER_FOCUS })),
CssPropertyWithConditions::on_focus(CssProperty::const_border_left_color(StyleBorderLeftColor { inner: BORDER_FOCUS })),
CssPropertyWithConditions::on_focus(CssProperty::const_border_right_color(StyleBorderRightColor { inner: BORDER_FOCUS })),
];
static DROPDOWN_LABEL_STYLE: &[CssPropertyWithConditions] = &[
CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(1))),
CssPropertyWithConditions::simple(CssProperty::const_padding_right(LayoutPaddingRight::const_px(8))),
];
static DROPDOWN_ARROW_ICON_STYLE: &[CssPropertyWithConditions] = &[
CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(18))),
CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
];
#[derive(Debug, Clone, PartialEq)]
#[repr(C)]
pub struct DropDown {
pub choices: StringVec,
pub selected: usize,
pub on_choice_change: OptionDropDownOnChoiceChange,
}
impl Default for DropDown {
fn default() -> Self {
Self {
choices: StringVec::from_const_slice(&[]),
selected: 0,
on_choice_change: None.into(),
}
}
}
impl DropDown {
pub fn new(choices: StringVec) -> Self {
Self {
choices,
selected: 0,
on_choice_change: None.into(),
}
}
pub fn set_on_choice_change<C: Into<DropDownOnChoiceChangeCallback>>(&mut self, data: RefAny, callback: C) {
self.on_choice_change = Some(DropDownOnChoiceChange {
callback: callback.into(),
refany: data,
}).into();
}
pub fn with_on_choice_change<C: Into<DropDownOnChoiceChangeCallback>>(mut self, data: RefAny, callback: C) -> Self {
self.set_on_choice_change(data, callback);
self
}
pub fn swap_with_default(&mut self) -> Self {
let mut m = DropDown::default();
core::mem::swap(&mut m, self);
m
}
pub fn dom(self) -> Dom {
let selected_text = self.choices
.as_slice()
.get(self.selected)
.cloned()
.unwrap_or_else(|| AzString::from_const_str(""));
let refany = RefAny::new(self);
const DROPDOWN_CLASS: &[IdOrClass] =
&[Class(AzString::from_const_str("__azul-native-dropdown"))];
let wrapper = Dom::create_div()
.with_css_props(CssPropertyWithConditionsVec::from_const_slice(DROPDOWN_WRAPPER_STYLE))
.with_ids_and_classes(IdOrClassVec::from_const_slice(DROPDOWN_CLASS))
.with_tab_index(TabIndex::Auto)
.with_callbacks(
vec![CoreCallbackData {
event: EventFilter::Focus(FocusEventFilter::FocusReceived),
refany: refany.clone(),
callback: CoreCallback {
cb: on_dropdown_click as usize,
ctx: azul_core::refany::OptionRefAny::None,
},
}]
.into(),
)
.with_children(DomVec::from_vec(vec![
Dom::create_p()
.with_css_props(CssPropertyWithConditionsVec::from_const_slice(DROPDOWN_LABEL_STYLE))
.with_children(DomVec::from_vec(vec![
Dom::create_text(selected_text),
])),
Dom::create_icon(AzString::from_const_str("arrow_drop_down"))
.with_css_props(CssPropertyWithConditionsVec::from_const_slice(DROPDOWN_ARROW_ICON_STYLE)),
]));
wrapper
}
}
struct ChoiceCallbackData {
choice_id: usize,
on_choice_change: OptionDropDownOnChoiceChange,
}
extern "C" fn on_dropdown_click(mut refany: RefAny, mut info: CallbackInfo) -> Update {
let refany = match refany.downcast_ref::<DropDown>() {
Some(s) => s,
None => return Update::DoNothing,
};
let menu_items: Vec<MenuItem> = refany
.choices
.iter()
.enumerate()
.map(|(idx, choice)| {
MenuItem::String(StringMenuItem::create(choice.clone()).with_callback(
RefAny::new(ChoiceCallbackData {
choice_id: idx,
on_choice_change: refany.on_choice_change.clone(),
}),
on_choice_selected as usize,
))
})
.collect();
let menu = Menu {
items: menu_items.into(),
position: MenuPopupPosition::BottomOfHitRect,
context_mouse_btn: ContextMenuMouseButton::Right,
};
info.open_menu_for_hit_node(menu);
Update::DoNothing
}
extern "C" fn on_choice_selected(mut refany: RefAny, info: CallbackInfo) -> Update {
let mut refany = match refany.downcast_mut::<ChoiceCallbackData>() {
Some(s) => s,
None => return Update::DoNothing,
};
let choice_id = refany.choice_id;
match refany.on_choice_change.as_mut() {
Some(DropDownOnChoiceChange { refany, callback }) => {
(callback.cb)(refany.clone(), info.clone(), choice_id)
}
None => Update::DoNothing,
}
}
impl From<DropDown> for Dom {
fn from(b: DropDown) -> Dom {
b.dom()
}
}