1use azul_core::{
8 callbacks::{CoreCallback, CoreCallbackData, Update},
9 dom::{
10 Dom, DomVec, EventFilter, FocusEventFilter, IdOrClass, IdOrClass::Class, IdOrClassVec,
11 TabIndex,
12 },
13 menu::{Menu, MenuItem, MenuPopupPosition, StringMenuItem},
14 refany::RefAny,
15 window::ContextMenuMouseButton,
16};
17use azul_css::{
18 dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec},
19 props::{
20 basic::{
21 color::{ColorU, ColorOrSystem},
22 font::{StyleFontFamily, StyleFontFamilyVec},
23 *,
24 },
25 layout::*,
26 property::CssProperty,
27 style::*,
28 },
29 *,
30};
31
32use crate::callbacks::{Callback, CallbackInfo};
33
34pub type DropDownOnChoiceChangeCallbackType = extern "C" fn(RefAny, CallbackInfo, usize) -> Update;
40impl_widget_callback!(
41 DropDownOnChoiceChange,
42 OptionDropDownOnChoiceChange,
43 DropDownOnChoiceChangeCallback,
44 DropDownOnChoiceChangeCallbackType
45);
46
47azul_core::impl_managed_callback! {
48 wrapper: DropDownOnChoiceChangeCallback,
49 info_ty: CallbackInfo,
50 return_ty: Update,
51 default_ret: Update::DoNothing,
52 invoker_static: DROP_DOWN_ON_CHOICE_CHANGE_INVOKER,
53 invoker_ty: AzDropDownOnChoiceChangeCallbackInvoker,
54 thunk_fn: az_drop_down_on_choice_change_callback_thunk,
55 setter_fn: AzApp_setDropDownOnChoiceChangeCallbackInvoker,
56 from_handle_fn: AzDropDownOnChoiceChangeCallback_createFromHostHandle,
57 extra_args: [ choice_index: usize ],
58}
59
60const SYSTEM_UI_STR: AzString = AzString::from_const_str("system:ui");
63const SYSTEM_UI_FAMILIES: &[StyleFontFamily] = &[StyleFontFamily::System(SYSTEM_UI_STR)];
64const SYSTEM_UI_FAMILY: StyleFontFamilyVec =
65 StyleFontFamilyVec::from_const_slice(SYSTEM_UI_FAMILIES);
66
67const BORDER_NORMAL: ColorU = ColorU { r: 172, g: 172, b: 172, a: 255 };
70const BORDER_HOVER: ColorU = ColorU { r: 126, g: 180, b: 234, a: 255 };
71const BORDER_FOCUS: ColorU = ColorU { r: 86, g: 157, b: 229, a: 255 };
72
73const BG_GRADIENT_TOP: ColorU = ColorU { r: 245, g: 245, b: 245, a: 255 };
74const BG_GRADIENT_BOTTOM: ColorU = ColorU { r: 235, g: 235, b: 235, a: 255 };
75const BG_HOVER_TOP: ColorU = ColorU { r: 234, g: 244, b: 252, a: 255 };
76const BG_HOVER_BOTTOM: ColorU = ColorU { r: 218, g: 236, b: 252, a: 255 };
77const BG_ACTIVE_TOP: ColorU = ColorU { r: 218, g: 236, b: 252, a: 255 };
78const BG_ACTIVE_BOTTOM: ColorU = ColorU { r: 202, g: 226, b: 248, a: 255 };
79
80const NORMAL_BG_ITEMS: &[StyleBackgroundContent] =
81 &[StyleBackgroundContent::LinearGradient(LinearGradient {
82 direction: Direction::FromTo(DirectionCorners {
83 dir_from: DirectionCorner::Top,
84 dir_to: DirectionCorner::Bottom,
85 }),
86 extend_mode: ExtendMode::Clamp,
87 stops: NormalizedLinearColorStopVec::from_const_slice(&[
88 NormalizedLinearColorStop {
89 offset: PercentageValue::const_new(0),
90 color: ColorOrSystem::color(BG_GRADIENT_TOP),
91 },
92 NormalizedLinearColorStop {
93 offset: PercentageValue::const_new(100),
94 color: ColorOrSystem::color(BG_GRADIENT_BOTTOM),
95 },
96 ]),
97 })];
98
99const HOVER_BG_ITEMS: &[StyleBackgroundContent] =
100 &[StyleBackgroundContent::LinearGradient(LinearGradient {
101 direction: Direction::FromTo(DirectionCorners {
102 dir_from: DirectionCorner::Top,
103 dir_to: DirectionCorner::Bottom,
104 }),
105 extend_mode: ExtendMode::Clamp,
106 stops: NormalizedLinearColorStopVec::from_const_slice(&[
107 NormalizedLinearColorStop {
108 offset: PercentageValue::const_new(0),
109 color: ColorOrSystem::color(BG_HOVER_TOP),
110 },
111 NormalizedLinearColorStop {
112 offset: PercentageValue::const_new(100),
113 color: ColorOrSystem::color(BG_HOVER_BOTTOM),
114 },
115 ]),
116 })];
117
118const ACTIVE_BG_ITEMS: &[StyleBackgroundContent] =
119 &[StyleBackgroundContent::LinearGradient(LinearGradient {
120 direction: Direction::FromTo(DirectionCorners {
121 dir_from: DirectionCorner::Top,
122 dir_to: DirectionCorner::Bottom,
123 }),
124 extend_mode: ExtendMode::Clamp,
125 stops: NormalizedLinearColorStopVec::from_const_slice(&[
126 NormalizedLinearColorStop {
127 offset: PercentageValue::const_new(0),
128 color: ColorOrSystem::color(BG_ACTIVE_TOP),
129 },
130 NormalizedLinearColorStop {
131 offset: PercentageValue::const_new(100),
132 color: ColorOrSystem::color(BG_ACTIVE_BOTTOM),
133 },
134 ]),
135 })];
136
137static DROPDOWN_WRAPPER_STYLE: &[CssPropertyWithConditions] = &[
140 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::InlineFlex)),
142 CssPropertyWithConditions::simple(CssProperty::const_flex_direction(LayoutFlexDirection::Row)),
143 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
144 CssPropertyWithConditions::simple(CssProperty::const_align_items(LayoutAlignItems::Center)),
145 CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)),
146 CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(13))),
148 CssPropertyWithConditions::simple(CssProperty::const_font_family(SYSTEM_UI_FAMILY)),
149 CssPropertyWithConditions::simple(CssProperty::const_padding_left(LayoutPaddingLeft::const_px(4))),
151 CssPropertyWithConditions::simple(CssProperty::const_padding_right(LayoutPaddingRight::const_px(4))),
152 CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(2))),
153 CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(LayoutPaddingBottom::const_px(2))),
154 CssPropertyWithConditions::simple(CssProperty::const_border_top_width(LayoutBorderTopWidth::const_px(1))),
156 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(LayoutBorderBottomWidth::const_px(1))),
157 CssPropertyWithConditions::simple(CssProperty::const_border_left_width(LayoutBorderLeftWidth::const_px(1))),
158 CssPropertyWithConditions::simple(CssProperty::const_border_right_width(LayoutBorderRightWidth::const_px(1))),
159 CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle { inner: BorderStyle::Solid })),
160 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(StyleBorderBottomStyle { inner: BorderStyle::Solid })),
161 CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle { inner: BorderStyle::Solid })),
162 CssPropertyWithConditions::simple(CssProperty::const_border_right_style(StyleBorderRightStyle { inner: BorderStyle::Solid })),
163 CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor { inner: BORDER_NORMAL })),
164 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(StyleBorderBottomColor { inner: BORDER_NORMAL })),
165 CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor { inner: BORDER_NORMAL })),
166 CssPropertyWithConditions::simple(CssProperty::const_border_right_color(StyleBorderRightColor { inner: BORDER_NORMAL })),
167 CssPropertyWithConditions::simple(CssProperty::const_background_content(
169 StyleBackgroundContentVec::from_const_slice(NORMAL_BG_ITEMS),
170 )),
171 CssPropertyWithConditions::on_hover(CssProperty::const_border_top_color(StyleBorderTopColor { inner: BORDER_HOVER })),
173 CssPropertyWithConditions::on_hover(CssProperty::const_border_bottom_color(StyleBorderBottomColor { inner: BORDER_HOVER })),
174 CssPropertyWithConditions::on_hover(CssProperty::const_border_left_color(StyleBorderLeftColor { inner: BORDER_HOVER })),
175 CssPropertyWithConditions::on_hover(CssProperty::const_border_right_color(StyleBorderRightColor { inner: BORDER_HOVER })),
176 CssPropertyWithConditions::on_hover(CssProperty::const_background_content(
177 StyleBackgroundContentVec::from_const_slice(HOVER_BG_ITEMS),
178 )),
179 CssPropertyWithConditions::on_active(CssProperty::const_background_content(
181 StyleBackgroundContentVec::from_const_slice(ACTIVE_BG_ITEMS),
182 )),
183 CssPropertyWithConditions::on_focus(CssProperty::const_border_top_color(StyleBorderTopColor { inner: BORDER_FOCUS })),
185 CssPropertyWithConditions::on_focus(CssProperty::const_border_bottom_color(StyleBorderBottomColor { inner: BORDER_FOCUS })),
186 CssPropertyWithConditions::on_focus(CssProperty::const_border_left_color(StyleBorderLeftColor { inner: BORDER_FOCUS })),
187 CssPropertyWithConditions::on_focus(CssProperty::const_border_right_color(StyleBorderRightColor { inner: BORDER_FOCUS })),
188];
189
190static DROPDOWN_LABEL_STYLE: &[CssPropertyWithConditions] = &[
193 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(1))),
194 CssPropertyWithConditions::simple(CssProperty::const_padding_right(LayoutPaddingRight::const_px(8))),
195];
196
197static DROPDOWN_ARROW_ICON_STYLE: &[CssPropertyWithConditions] = &[
200 CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(18))),
201 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
202];
203
204#[derive(Debug, Clone, PartialEq)]
211#[repr(C)]
212pub struct DropDown {
213 pub choices: StringVec,
215 pub selected: usize,
217 pub on_choice_change: OptionDropDownOnChoiceChange,
219}
220
221impl Default for DropDown {
222 fn default() -> Self {
223 Self {
224 choices: StringVec::from_const_slice(&[]),
225 selected: 0,
226 on_choice_change: None.into(),
227 }
228 }
229}
230
231impl DropDown {
232 pub fn new(choices: StringVec) -> Self {
234 Self {
235 choices,
236 selected: 0,
237 on_choice_change: None.into(),
238 }
239 }
240
241 pub fn set_on_choice_change<C: Into<DropDownOnChoiceChangeCallback>>(&mut self, data: RefAny, callback: C) {
243 self.on_choice_change = Some(DropDownOnChoiceChange {
244 callback: callback.into(),
245 refany: data,
246 }).into();
247 }
248
249 pub fn with_on_choice_change<C: Into<DropDownOnChoiceChangeCallback>>(mut self, data: RefAny, callback: C) -> Self {
251 self.set_on_choice_change(data, callback);
252 self
253 }
254
255 pub fn swap_with_default(&mut self) -> Self {
257 let mut m = DropDown::default();
258 core::mem::swap(&mut m, self);
259 m
260 }
261
262 pub fn dom(self) -> Dom {
264 let selected_text = self.choices
265 .as_slice()
266 .get(self.selected)
267 .cloned()
268 .unwrap_or_else(|| AzString::from_const_str(""));
269
270 let refany = RefAny::new(self);
271
272 const DROPDOWN_CLASS: &[IdOrClass] =
273 &[Class(AzString::from_const_str("__azul-native-dropdown"))];
274
275 let wrapper = Dom::create_div()
277 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(DROPDOWN_WRAPPER_STYLE))
278 .with_ids_and_classes(IdOrClassVec::from_const_slice(DROPDOWN_CLASS))
279 .with_tab_index(TabIndex::Auto)
280 .with_callbacks(
281 vec![CoreCallbackData {
282 event: EventFilter::Focus(FocusEventFilter::FocusReceived),
283 refany: refany.clone(),
284 callback: CoreCallback {
285 cb: on_dropdown_click as usize,
286 ctx: azul_core::refany::OptionRefAny::None,
287 },
288 }]
289 .into(),
290 )
291 .with_children(DomVec::from_vec(vec![
292 Dom::create_p()
294 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(DROPDOWN_LABEL_STYLE))
295 .with_children(DomVec::from_vec(vec![
296 Dom::create_text(selected_text),
297 ])),
298 Dom::create_icon(AzString::from_const_str("arrow_drop_down"))
300 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(DROPDOWN_ARROW_ICON_STYLE)),
301 ]));
302
303 wrapper
304 }
305}
306
307struct ChoiceCallbackData {
312 choice_id: usize,
313 on_choice_change: OptionDropDownOnChoiceChange,
314}
315
316extern "C" fn on_dropdown_click(mut refany: RefAny, mut info: CallbackInfo) -> Update {
321 let refany = match refany.downcast_ref::<DropDown>() {
322 Some(s) => s,
323 None => return Update::DoNothing,
324 };
325
326 let menu_items: Vec<MenuItem> = refany
327 .choices
328 .iter()
329 .enumerate()
330 .map(|(idx, choice)| {
331 MenuItem::String(StringMenuItem::create(choice.clone()).with_callback(
332 RefAny::new(ChoiceCallbackData {
333 choice_id: idx,
334 on_choice_change: refany.on_choice_change.clone(),
335 }),
336 on_choice_selected as usize,
337 ))
338 })
339 .collect();
340
341 let menu = Menu {
342 items: menu_items.into(),
343 position: MenuPopupPosition::BottomOfHitRect,
344 context_mouse_btn: ContextMenuMouseButton::Right,
345 };
346
347 info.open_menu_for_hit_node(menu);
348 Update::DoNothing
349}
350
351extern "C" fn on_choice_selected(mut refany: RefAny, info: CallbackInfo) -> Update {
352 let mut refany = match refany.downcast_mut::<ChoiceCallbackData>() {
353 Some(s) => s,
354 None => return Update::DoNothing,
355 };
356
357 let choice_id = refany.choice_id;
358
359 match refany.on_choice_change.as_mut() {
360 Some(DropDownOnChoiceChange { refany, callback }) => {
361 (callback.cb)(refany.clone(), info.clone(), choice_id)
362 }
363 None => Update::DoNothing,
364 }
365}
366
367impl From<DropDown> for Dom {
368 fn from(b: DropDown) -> Dom {
369 b.dom()
370 }
371}