1use alloc::{string::String, vec::Vec};
45
46use azul_core::{
47 callbacks::{CoreCallback, CoreCallbackData, Update},
48 dom::{
49 Dom, DomVec, EventFilter, FocusEventFilter, HoverEventFilter, IdOrClass, IdOrClass::Class,
50 IdOrClassVec, TabIndex,
51 },
52 refany::{OptionRefAny, RefAny},
53 window::VirtualKeyCode,
54};
55use azul_css::dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec};
56use azul_css::{
57 props::{
58 basic::{color::ColorU, font::{StyleFontFamily, StyleFontFamilyVec}, StyleFontSize},
59 layout::{LayoutDisplay, LayoutPosition, LayoutFlexGrow, LayoutMinWidth, LayoutFlexDirection, LayoutAlignItems, LayoutPaddingTop, LayoutPaddingBottom, LayoutPaddingLeft, LayoutPaddingRight, LayoutTop, LayoutLeft},
60 property::{CssProperty, *},
61 style::{StyleBackgroundContent, StyleBackgroundContentVec, StyleCursor, LayoutBorderTopWidth, LayoutBorderBottomWidth, LayoutBorderLeftWidth, LayoutBorderRightWidth, StyleBorderTopStyle, BorderStyle, StyleBorderBottomStyle, StyleBorderLeftStyle, StyleBorderRightStyle, StyleBorderTopColor, StyleBorderBottomColor, StyleBorderLeftColor, StyleBorderRightColor, StyleBorderTopLeftRadius, StyleBorderTopRightRadius, StyleBorderBottomLeftRadius, StyleBorderBottomRightRadius, StyleTextColor, StyleTextAlign, StyleUserSelect},
62 },
63 impl_option_inner, AzString, StringVec,
64};
65
66use crate::callbacks::{Callback, CallbackInfo};
67
68static COMBOBOX_WRAPPER_CLASS: &[IdOrClass] =
69 &[Class(AzString::from_const_str("__azul-native-combobox"))];
70static COMBOBOX_INPUT_CLASS: &[IdOrClass] = &[Class(AzString::from_const_str(
71 "__azul-native-combobox-input",
72))];
73static COMBOBOX_TEXT_CLASS: &[IdOrClass] =
74 &[Class(AzString::from_const_str("__azul-native-combobox-text"))];
75static COMBOBOX_ARROW_CLASS: &[IdOrClass] = &[Class(AzString::from_const_str(
76 "__azul-native-combobox-arrow",
77))];
78static COMBOBOX_LIST_CLASS: &[IdOrClass] =
79 &[Class(AzString::from_const_str("__azul-native-combobox-list"))];
80static COMBOBOX_OPTION_CLASS: &[IdOrClass] = &[Class(AzString::from_const_str(
81 "__azul-native-combobox-option",
82))];
83
84const SYSTEM_UI_STR: AzString = AzString::from_const_str("system:ui");
85const SYSTEM_UI_FAMILIES: &[StyleFontFamily] = &[StyleFontFamily::System(SYSTEM_UI_STR)];
86const SYSTEM_UI_FAMILY: StyleFontFamilyVec =
87 StyleFontFamilyVec::from_const_slice(SYSTEM_UI_FAMILIES);
88
89const LIST_OFFSET_Y: isize = 28;
93const MIN_WIDTH: isize = 160;
95const RADIUS: isize = 4;
96const ARROW_FONT_SIZE_PX: isize = 18;
97
98const WHITE: ColorU = ColorU { r: 255, g: 255, b: 255, a: 255 };
100const BORDER_COLOR: ColorU = ColorU { r: 172, g: 172, b: 172, a: 255 }; const BORDER_FOCUS: ColorU = ColorU { r: 66, g: 134, b: 244, a: 255 }; const TEXT_COLOR: ColorU = ColorU { r: 51, g: 51, b: 51, a: 255 }; const OPTION_HOVER_BG: ColorU = ColorU { r: 234, g: 244, b: 252, a: 255 }; const WHITE_BG_ITEMS: &[StyleBackgroundContent] = &[StyleBackgroundContent::Color(WHITE)];
106const WHITE_BG_VEC: StyleBackgroundContentVec =
107 StyleBackgroundContentVec::from_const_slice(WHITE_BG_ITEMS);
108const OPTION_HOVER_BG_ITEMS: &[StyleBackgroundContent] =
109 &[StyleBackgroundContent::Color(OPTION_HOVER_BG)];
110const OPTION_HOVER_BG_VEC: StyleBackgroundContentVec =
111 StyleBackgroundContentVec::from_const_slice(OPTION_HOVER_BG_ITEMS);
112
113pub type ComboBoxOnSelectCallbackType = extern "C" fn(RefAny, CallbackInfo, ComboBoxState) -> Update;
116impl_widget_callback!(
117 ComboBoxOnSelect,
118 OptionComboBoxOnSelect,
119 ComboBoxOnSelectCallback,
120 ComboBoxOnSelectCallbackType
121);
122
123azul_core::impl_managed_callback! {
124 wrapper: ComboBoxOnSelectCallback,
125 info_ty: CallbackInfo,
126 return_ty: Update,
127 default_ret: Update::DoNothing,
128 invoker_static: COMBOBOX_ON_SELECT_INVOKER,
129 invoker_ty: AzComboBoxOnSelectCallbackInvoker,
130 thunk_fn: az_combobox_on_select_callback_thunk,
131 setter_fn: AzApp_setComboBoxOnSelectCallbackInvoker,
132 from_handle_fn: AzComboBoxOnSelectCallback_createFromHostHandle,
133 extra_args: [ state: ComboBoxState ],
134}
135
136#[derive(Debug, Clone, PartialEq, Eq)]
139#[repr(C)]
140pub struct ComboBox {
141 pub combo_state: ComboBoxStateWrapper,
144 pub placeholder: AzString,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq)]
149#[repr(C)]
150pub struct ComboBoxStateWrapper {
151 pub inner: ComboBoxState,
153 pub items: StringVec,
155 pub on_select: OptionComboBoxOnSelect,
157}
158
159impl Default for ComboBoxStateWrapper {
160 fn default() -> Self {
161 Self {
162 inner: ComboBoxState::default(),
163 items: StringVec::from_const_slice(&[]),
164 on_select: None.into(),
165 }
166 }
167}
168
169#[derive(Debug, Clone, PartialEq, Eq)]
172#[repr(C)]
173pub struct ComboBoxState {
174 pub open: bool,
176 pub selected: usize,
178 pub text: AzString,
180}
181
182impl Default for ComboBoxState {
183 fn default() -> Self {
184 Self {
185 open: false,
186 selected: 0,
187 text: AzString::from_const_str(""),
188 }
189 }
190}
191
192static COMBOBOX_WRAPPER_STYLE: &[CssPropertyWithConditions] = &[
197 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::InlineBlock)),
198 CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Relative)),
199 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
200 CssPropertyWithConditions::simple(CssProperty::const_min_width(LayoutMinWidth::const_px(
201 MIN_WIDTH,
202 ))),
203 CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(13))),
204 CssPropertyWithConditions::simple(CssProperty::const_font_family(SYSTEM_UI_FAMILY)),
205];
206
207static COMBOBOX_INPUT_STYLE: &[CssPropertyWithConditions] = &[
209 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Flex)),
210 CssPropertyWithConditions::simple(CssProperty::const_flex_direction(LayoutFlexDirection::Row)),
211 CssPropertyWithConditions::simple(CssProperty::const_align_items(LayoutAlignItems::Center)),
212 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
213 CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Text)),
214 CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(3))),
216 CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
217 LayoutPaddingBottom::const_px(3),
218 )),
219 CssPropertyWithConditions::simple(CssProperty::const_padding_left(LayoutPaddingLeft::const_px(
220 4,
221 ))),
222 CssPropertyWithConditions::simple(CssProperty::const_padding_right(
223 LayoutPaddingRight::const_px(4),
224 )),
225 CssPropertyWithConditions::simple(CssProperty::const_border_top_width(
227 LayoutBorderTopWidth::const_px(1),
228 )),
229 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
230 LayoutBorderBottomWidth::const_px(1),
231 )),
232 CssPropertyWithConditions::simple(CssProperty::const_border_left_width(
233 LayoutBorderLeftWidth::const_px(1),
234 )),
235 CssPropertyWithConditions::simple(CssProperty::const_border_right_width(
236 LayoutBorderRightWidth::const_px(1),
237 )),
238 CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle {
239 inner: BorderStyle::Solid,
240 })),
241 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
242 StyleBorderBottomStyle {
243 inner: BorderStyle::Solid,
244 },
245 )),
246 CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle {
247 inner: BorderStyle::Solid,
248 })),
249 CssPropertyWithConditions::simple(CssProperty::const_border_right_style(
250 StyleBorderRightStyle {
251 inner: BorderStyle::Solid,
252 },
253 )),
254 CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
255 inner: BORDER_COLOR,
256 })),
257 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
258 StyleBorderBottomColor {
259 inner: BORDER_COLOR,
260 },
261 )),
262 CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor {
263 inner: BORDER_COLOR,
264 })),
265 CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
266 StyleBorderRightColor {
267 inner: BORDER_COLOR,
268 },
269 )),
270 CssPropertyWithConditions::simple(CssProperty::const_border_top_left_radius(
272 StyleBorderTopLeftRadius::const_px(RADIUS),
273 )),
274 CssPropertyWithConditions::simple(CssProperty::const_border_top_right_radius(
275 StyleBorderTopRightRadius::const_px(RADIUS),
276 )),
277 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_left_radius(
278 StyleBorderBottomLeftRadius::const_px(RADIUS),
279 )),
280 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_right_radius(
281 StyleBorderBottomRightRadius::const_px(RADIUS),
282 )),
283 CssPropertyWithConditions::simple(CssProperty::const_background_content(WHITE_BG_VEC)),
284 CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
285 inner: TEXT_COLOR,
286 })),
287 CssPropertyWithConditions::on_focus(CssProperty::const_border_top_color(StyleBorderTopColor {
289 inner: BORDER_FOCUS,
290 })),
291 CssPropertyWithConditions::on_focus(CssProperty::const_border_bottom_color(
292 StyleBorderBottomColor {
293 inner: BORDER_FOCUS,
294 },
295 )),
296 CssPropertyWithConditions::on_focus(CssProperty::const_border_left_color(StyleBorderLeftColor {
297 inner: BORDER_FOCUS,
298 })),
299 CssPropertyWithConditions::on_focus(CssProperty::const_border_right_color(
300 StyleBorderRightColor {
301 inner: BORDER_FOCUS,
302 },
303 )),
304];
305
306static COMBOBOX_TEXT_STYLE: &[CssPropertyWithConditions] = &[
308 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(1))),
309 CssPropertyWithConditions::simple(CssProperty::const_text_align(StyleTextAlign::Left)),
310 CssPropertyWithConditions::simple(CssProperty::const_padding_right(
311 LayoutPaddingRight::const_px(4),
312 )),
313];
314
315static COMBOBOX_ARROW_STYLE: &[CssPropertyWithConditions] = &[
317 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
318 CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(
319 ARROW_FONT_SIZE_PX,
320 ))),
321 CssPropertyWithConditions::simple(CssProperty::user_select(StyleUserSelect::None)),
322];
323
324fn build_list_style(open: bool) -> CssPropertyWithConditionsVec {
329 let display = if open {
330 LayoutDisplay::Block
331 } else {
332 LayoutDisplay::None
333 };
334 CssPropertyWithConditionsVec::from_vec(alloc::vec![
335 CssPropertyWithConditions::simple(CssProperty::const_display(display)),
336 CssPropertyWithConditions::simple(CssProperty::const_position(LayoutPosition::Absolute)),
337 CssPropertyWithConditions::simple(CssProperty::const_top(LayoutTop::const_px(LIST_OFFSET_Y))),
338 CssPropertyWithConditions::simple(CssProperty::const_left(LayoutLeft::const_px(0))),
339 CssPropertyWithConditions::simple(CssProperty::const_min_width(LayoutMinWidth::const_px(
340 MIN_WIDTH,
341 ))),
342 CssPropertyWithConditions::simple(CssProperty::const_border_top_width(
344 LayoutBorderTopWidth::const_px(1),
345 )),
346 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
347 LayoutBorderBottomWidth::const_px(1),
348 )),
349 CssPropertyWithConditions::simple(CssProperty::const_border_left_width(
350 LayoutBorderLeftWidth::const_px(1),
351 )),
352 CssPropertyWithConditions::simple(CssProperty::const_border_right_width(
353 LayoutBorderRightWidth::const_px(1),
354 )),
355 CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle {
356 inner: BorderStyle::Solid,
357 })),
358 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
359 StyleBorderBottomStyle {
360 inner: BorderStyle::Solid,
361 },
362 )),
363 CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle {
364 inner: BorderStyle::Solid,
365 })),
366 CssPropertyWithConditions::simple(CssProperty::const_border_right_style(
367 StyleBorderRightStyle {
368 inner: BorderStyle::Solid,
369 },
370 )),
371 CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
372 inner: BORDER_COLOR,
373 })),
374 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
375 StyleBorderBottomColor {
376 inner: BORDER_COLOR,
377 },
378 )),
379 CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor {
380 inner: BORDER_COLOR,
381 })),
382 CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
383 StyleBorderRightColor {
384 inner: BORDER_COLOR,
385 },
386 )),
387 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_left_radius(
389 StyleBorderBottomLeftRadius::const_px(RADIUS),
390 )),
391 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_right_radius(
392 StyleBorderBottomRightRadius::const_px(RADIUS),
393 )),
394 CssPropertyWithConditions::simple(CssProperty::const_background_content(WHITE_BG_VEC)),
395 ])
396}
397
398static COMBOBOX_OPTION_STYLE: &[CssPropertyWithConditions] = &[
400 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Block)),
401 CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(6))),
402 CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
403 LayoutPaddingBottom::const_px(6),
404 )),
405 CssPropertyWithConditions::simple(CssProperty::const_padding_left(LayoutPaddingLeft::const_px(
406 10,
407 ))),
408 CssPropertyWithConditions::simple(CssProperty::const_padding_right(
409 LayoutPaddingRight::const_px(10),
410 )),
411 CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)),
412 CssPropertyWithConditions::simple(CssProperty::user_select(StyleUserSelect::None)),
413 CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
414 inner: TEXT_COLOR,
415 })),
416 CssPropertyWithConditions::on_hover(CssProperty::const_background_content(OPTION_HOVER_BG_VEC)),
417];
418
419impl ComboBox {
420 #[must_use] pub fn new(items: StringVec) -> Self {
422 Self {
423 combo_state: ComboBoxStateWrapper {
424 inner: ComboBoxState::default(),
425 items,
426 on_select: None.into(),
427 },
428 placeholder: AzString::from_const_str(""),
429 }
430 }
431
432 #[must_use] pub fn create() -> Self {
434 Self::new(StringVec::from_const_slice(&[]))
435 }
436
437 #[inline]
439 pub const fn set_selected(&mut self, selected: usize) {
440 self.combo_state.inner.selected = selected;
441 }
442
443 #[inline]
445 #[must_use] pub const fn with_selected(mut self, selected: usize) -> Self {
446 self.set_selected(selected);
447 self
448 }
449
450 #[inline]
452 pub fn set_text(&mut self, text: AzString) {
453 self.combo_state.inner.text = text;
454 }
455
456 #[inline]
458 #[must_use] pub fn with_text(mut self, text: AzString) -> Self {
459 self.set_text(text);
460 self
461 }
462
463 #[inline]
465 pub fn set_placeholder(&mut self, placeholder: AzString) {
466 self.placeholder = placeholder;
467 }
468
469 #[inline]
471 #[must_use] pub fn with_placeholder(mut self, placeholder: AzString) -> Self {
472 self.set_placeholder(placeholder);
473 self
474 }
475
476 #[inline]
478 pub fn set_on_select<C: Into<ComboBoxOnSelectCallback>>(&mut self, data: RefAny, on_select: C) {
479 self.combo_state.on_select = Some(ComboBoxOnSelect {
480 callback: on_select.into(),
481 refany: data,
482 })
483 .into();
484 }
485
486 #[inline]
488 #[must_use] pub fn with_on_select<C: Into<ComboBoxOnSelectCallback>>(
489 mut self,
490 data: RefAny,
491 on_select: C,
492 ) -> Self {
493 self.set_on_select(data, on_select);
494 self
495 }
496
497 #[inline]
499 #[must_use] pub fn swap_with_default(&mut self) -> Self {
500 let mut s = Self::create();
501 core::mem::swap(&mut s, self);
502 s
503 }
504
505 #[must_use] pub fn dom(self) -> Dom {
508 let field_text = if self.combo_state.inner.text.as_str().is_empty() {
513 self.placeholder.clone()
514 } else {
515 self.combo_state.inner.text.clone()
516 };
517
518 let open = self.combo_state.inner.open;
519 let items = self.combo_state.items.clone();
520
521 let state_ref = RefAny::new(self.combo_state);
525
526 let text_node = Dom::create_text(field_text)
527 .with_ids_and_classes(IdOrClassVec::from_const_slice(COMBOBOX_TEXT_CLASS))
528 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(COMBOBOX_TEXT_STYLE));
529
530 let arrow = Dom::create_icon(AzString::from_const_str("arrow_drop_down"))
531 .with_ids_and_classes(IdOrClassVec::from_const_slice(COMBOBOX_ARROW_CLASS))
532 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(COMBOBOX_ARROW_STYLE));
533
534 let field = Dom::create_div()
538 .with_ids_and_classes(IdOrClassVec::from_const_slice(COMBOBOX_INPUT_CLASS))
539 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(COMBOBOX_INPUT_STYLE))
540 .with_tab_index(TabIndex::Auto)
541 .with_callbacks(
542 alloc::vec![
543 CoreCallbackData {
544 event: EventFilter::Hover(HoverEventFilter::MouseUp),
545 callback: CoreCallback {
546 cb: on_combobox_toggle as usize,
547 ctx: OptionRefAny::None,
548 },
549 refany: state_ref.clone(),
550 },
551 CoreCallbackData {
552 event: EventFilter::Focus(FocusEventFilter::TextInput),
553 callback: CoreCallback {
554 cb: on_combobox_text_input as usize,
555 ctx: OptionRefAny::None,
556 },
557 refany: state_ref.clone(),
558 },
559 CoreCallbackData {
560 event: EventFilter::Focus(FocusEventFilter::VirtualKeyDown),
561 callback: CoreCallback {
562 cb: on_combobox_key_down as usize,
563 ctx: OptionRefAny::None,
564 },
565 refany: state_ref.clone(),
566 },
567 ]
568 .into(),
569 )
570 .with_children(DomVec::from_vec(alloc::vec![text_node, arrow]));
571
572 let mut option_doms: Vec<Dom> = Vec::with_capacity(items.as_ref().len());
575 for option in items.as_ref() {
576 option_doms.push(
577 Dom::create_text(option.clone())
578 .with_ids_and_classes(IdOrClassVec::from_const_slice(COMBOBOX_OPTION_CLASS))
579 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(
580 COMBOBOX_OPTION_STYLE,
581 ))
582 .with_tab_index(TabIndex::Auto)
583 .with_callbacks(
584 alloc::vec![CoreCallbackData {
585 event: EventFilter::Hover(HoverEventFilter::MouseUp),
586 callback: CoreCallback {
587 cb: on_combobox_option_click as usize,
588 ctx: OptionRefAny::None,
589 },
590 refany: state_ref.clone(),
591 }]
592 .into(),
593 ),
594 );
595 }
596
597 let list = Dom::create_div()
598 .with_ids_and_classes(IdOrClassVec::from_const_slice(COMBOBOX_LIST_CLASS))
599 .with_css_props(build_list_style(open))
600 .with_children(DomVec::from_vec(option_doms));
601
602 Dom::create_div()
603 .with_ids_and_classes(IdOrClassVec::from_const_slice(COMBOBOX_WRAPPER_CLASS))
604 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(COMBOBOX_WRAPPER_STYLE))
605 .with_children(DomVec::from_vec(alloc::vec![field, list]))
607 }
608}
609
610impl Default for ComboBox {
611 fn default() -> Self {
612 Self::create()
613 }
614}
615
616extern "C" fn on_combobox_toggle(mut data: RefAny, mut info: CallbackInfo) -> Update {
619 let field = info.get_hit_node();
620 let Some(list) = info.get_next_sibling(field) else {
621 return Update::DoNothing;
622 };
623
624 let now_open = {
625 let Some(mut combo) = data.downcast_mut::<ComboBoxStateWrapper>() else {
626 return Update::DoNothing;
627 };
628 combo.inner.open = !combo.inner.open;
629 combo.inner.open
630 };
631
632 let display = if now_open {
635 LayoutDisplay::Block
636 } else {
637 LayoutDisplay::None
638 };
639 info.set_css_property(list, CssProperty::const_display(display));
640
641 Update::DoNothing
642}
643
644extern "C" fn on_combobox_text_input(data: RefAny, info: CallbackInfo) -> Update {
648 on_combobox_text_input_inner(data, info).unwrap_or(Update::DoNothing)
649}
650
651fn on_combobox_text_input_inner(mut data: RefAny, mut info: CallbackInfo) -> Option<Update> {
652 let field = info.get_hit_node();
653 let text_node = info.get_first_child(field)?;
654
655 let changeset = info.get_text_changeset()?;
656 let inserted_text = changeset.inserted_text.as_str().to_string();
657 if inserted_text.is_empty() {
658 return None;
659 }
660
661 let new_text = {
662 let mut combo = data.downcast_mut::<ComboBoxStateWrapper>()?;
663 let mut s: String = combo.inner.text.as_str().into();
664 s.push_str(&inserted_text);
665 combo.inner.text = s.clone().into();
666 s
667 };
668
669 info.change_node_text(text_node, new_text.into());
670 Some(Update::DoNothing)
671}
672
673extern "C" fn on_combobox_key_down(data: RefAny, info: CallbackInfo) -> Update {
675 on_combobox_key_down_inner(data, info).unwrap_or(Update::DoNothing)
676}
677
678fn on_combobox_key_down_inner(mut data: RefAny, mut info: CallbackInfo) -> Option<Update> {
679 let field = info.get_hit_node();
680 let text_node = info.get_first_child(field)?;
681
682 let keyboard_state = info.get_current_keyboard_state();
683 let c = keyboard_state.current_virtual_keycode.into_option()?;
684 if c != VirtualKeyCode::Back {
685 return None;
686 }
687
688 let new_text = {
689 let mut combo = data.downcast_mut::<ComboBoxStateWrapper>()?;
690 let mut s: String = combo.inner.text.as_str().into();
691 s.pop();
692 combo.inner.text = s.clone().into();
693 s
694 };
695
696 info.change_node_text(text_node, new_text.into());
697 Some(Update::DoNothing)
698}
699
700extern "C" fn on_combobox_option_click(mut data: RefAny, mut info: CallbackInfo) -> Update {
706 let option = info.get_hit_node();
707
708 let mut index = 0usize;
710 let mut cursor = option;
711 while let Some(prev) = info.get_previous_sibling(cursor) {
712 index += 1;
713 cursor = prev;
714 }
715
716 let Some(list) = info.get_parent(option) else {
717 return Update::DoNothing;
718 };
719 let Some(wrapper) = info.get_parent(list) else {
720 return Update::DoNothing;
721 };
722 let Some(field) = info.get_first_child(wrapper) else {
723 return Update::DoNothing;
724 };
725 let Some(text_node) = info.get_first_child(field) else {
726 return Update::DoNothing;
727 };
728
729 let (label, inner, result) = {
730 let Some(mut combo) = data.downcast_mut::<ComboBoxStateWrapper>() else {
731 return Update::DoNothing;
732 };
733 let Some(label) = combo.items.as_ref().get(index).cloned() else {
734 return Update::DoNothing;
735 };
736 combo.inner.selected = index;
737 combo.inner.text = label.clone();
738 combo.inner.open = false;
739 let inner = combo.inner.clone();
740 let combo = &mut *combo;
741 let result = match combo.on_select.as_mut() {
742 Some(ComboBoxOnSelect { callback, refany }) => {
743 (callback.cb)(refany.clone(), info, inner.clone())
744 }
745 None => Update::DoNothing,
746 };
747 (label, inner, result)
748 };
749 drop(inner);
750
751 info.change_node_text(text_node, label);
753 info.set_css_property(list, CssProperty::const_display(LayoutDisplay::None));
754
755 result
756}
757
758impl From<ComboBox> for Dom {
759 fn from(c: ComboBox) -> Self {
760 c.dom()
761 }
762}