rat_widget/
combobox.rs

1use crate::_private::NonExhaustive;
2use crate::choice::{
3    Choice, ChoiceClose, ChoiceFocus, ChoicePopup, ChoiceSelect, ChoiceState, ChoiceStyle,
4    ChoiceWidget,
5};
6use crate::combobox::event::ComboboxOutcome;
7use crate::event::ChoiceOutcome;
8use crate::text::HasScreenCursor;
9use rat_event::util::{MouseFlags, item_at, mouse_trap};
10use rat_event::{ConsumedEvent, HandleEvent, MouseOnly, Popup, Regular, ct_event};
11use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
12use rat_popup::Placement;
13use rat_popup::event::PopupOutcome;
14use rat_reloc::RelocatableState;
15use rat_scrolled::event::ScrollOutcome;
16use rat_scrolled::{Scroll, ScrollAreaState};
17use rat_text::TextStyle;
18use rat_text::event::TextOutcome;
19use rat_text::text_input::{TextInput, TextInputState};
20use ratatui_core::buffer::Buffer;
21use ratatui_core::layout::{Alignment, Rect};
22use ratatui_core::style::Style;
23use ratatui_core::text::Line;
24use ratatui_core::widgets::StatefulWidget;
25use ratatui_crossterm::crossterm::event::Event;
26use ratatui_widgets::block::Block;
27use std::cmp::max;
28
29#[derive(Debug, Clone)]
30pub struct Combobox<'a> {
31    choice: Choice<'a, String>,
32    text: TextInput<'a>,
33}
34
35#[derive(Debug)]
36pub struct ComboboxWidget<'a> {
37    choice: ChoiceWidget<'a, String>,
38    text: TextInput<'a>,
39}
40
41#[derive(Debug)]
42pub struct ComboboxPopup<'a> {
43    choice: ChoicePopup<'a, String>,
44}
45
46#[derive(Debug, Clone)]
47pub struct ComboboxStyle {
48    pub choice: ChoiceStyle,
49    pub text: TextStyle,
50
51    pub non_exhaustive: NonExhaustive,
52}
53
54#[derive(Debug)]
55pub struct ComboboxState {
56    /// Total area.
57    /// __read only__. renewed with each render.
58    pub area: Rect,
59    /// Area inside the border.
60    pub inner: Rect,
61    /// Core
62    pub choice: ChoiceState<String>,
63    /// Text
64    pub text: TextInputState,
65
66    /// Focus flag.
67    /// __read+write__
68    pub focus: FocusFlag,
69    /// Mouse util.
70    pub mouse: MouseFlags,
71
72    pub non_exhaustive: NonExhaustive,
73}
74
75pub(crate) mod event {
76    use rat_event::{ConsumedEvent, Outcome};
77    use rat_popup::event::PopupOutcome;
78
79    /// Result value for event-handling.
80    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
81    pub enum ComboboxOutcome {
82        /// The given event was not handled at all.
83        Continue,
84        /// The event was handled, no repaint necessary.
85        Unchanged,
86        /// The event was handled, repaint necessary.
87        Changed,
88        /// An item has been selected.
89        Value,
90        /// Textinput has changed
91        TextChanged,
92    }
93
94    impl ConsumedEvent for ComboboxOutcome {
95        fn is_consumed(&self) -> bool {
96            *self != ComboboxOutcome::Continue
97        }
98    }
99
100    impl From<Outcome> for ComboboxOutcome {
101        fn from(value: Outcome) -> Self {
102            match value {
103                Outcome::Continue => ComboboxOutcome::Continue,
104                Outcome::Unchanged => ComboboxOutcome::Unchanged,
105                Outcome::Changed => ComboboxOutcome::Changed,
106            }
107        }
108    }
109
110    impl From<ComboboxOutcome> for Outcome {
111        fn from(value: ComboboxOutcome) -> Self {
112            match value {
113                ComboboxOutcome::Continue => Outcome::Continue,
114                ComboboxOutcome::Unchanged => Outcome::Unchanged,
115                ComboboxOutcome::Changed => Outcome::Changed,
116                ComboboxOutcome::Value => Outcome::Changed,
117                ComboboxOutcome::TextChanged => Outcome::Changed,
118            }
119        }
120    }
121
122    impl From<PopupOutcome> for ComboboxOutcome {
123        fn from(value: PopupOutcome) -> Self {
124            match value {
125                PopupOutcome::Continue => ComboboxOutcome::Continue,
126                PopupOutcome::Unchanged => ComboboxOutcome::Unchanged,
127                PopupOutcome::Changed => ComboboxOutcome::Changed,
128                PopupOutcome::Hide => ComboboxOutcome::Changed,
129            }
130        }
131    }
132}
133
134impl Default for ComboboxStyle {
135    fn default() -> Self {
136        Self {
137            choice: Default::default(),
138            text: Default::default(),
139            non_exhaustive: NonExhaustive,
140        }
141    }
142}
143
144impl Default for Combobox<'_> {
145    fn default() -> Self {
146        Self {
147            choice: Choice::default().skip_item_render(true),
148            text: Default::default(),
149        }
150    }
151}
152
153impl<'a> Combobox<'a> {
154    pub fn new() -> Self {
155        Self::default()
156    }
157
158    /// Button text.
159    #[inline]
160    pub fn items<V: Into<Line<'a>>>(
161        mut self,
162        items: impl IntoIterator<Item = (String, V)>,
163    ) -> Self {
164        self.choice = self.choice.items(items);
165        self
166    }
167
168    /// Add an item.
169    pub fn item(mut self, value: impl Into<String>, item: impl Into<Line<'a>>) -> Self {
170        self.choice = self.choice.item(value.into(), item);
171        self
172    }
173
174    /// Can return to default with user interaction.
175    pub fn default_value(mut self, default: String) -> Self {
176        self.choice = self.choice.default_value(default);
177        self
178    }
179
180    /// Combined styles.
181    pub fn styles(mut self, styles: ComboboxStyle) -> Self {
182        self.choice = self.choice.styles(styles.choice);
183        self.text = self.text.styles(styles.text);
184        self
185    }
186
187    /// Base style.
188    pub fn style(mut self, style: Style) -> Self {
189        self.choice = self.choice.style(style);
190        self.text = self.text.style(style);
191        self
192    }
193
194    /// Style for the down button.
195    pub fn button_style(mut self, style: Style) -> Self {
196        self.choice = self.choice.button_style(style);
197        self
198    }
199
200    /// Selection in the list.
201    pub fn select_style(mut self, style: Style) -> Self {
202        self.choice = self.choice.select_style(style);
203        self
204    }
205
206    /// Focused style.
207    pub fn focus_style(mut self, style: Style) -> Self {
208        self.choice = self.choice.focus_style(style);
209        self
210    }
211
212    /// Textstyle
213    pub fn text_style(mut self, style: TextStyle) -> Self {
214        self.text = self.text.styles(style);
215        self
216    }
217
218    /// Block for the main widget.
219    pub fn block(mut self, block: Block<'a>) -> Self {
220        self.choice = self.choice.block(block);
221        self
222    }
223
224    /// Alignment of the popup.
225    ///
226    /// __Default__
227    /// Default is Left.
228    pub fn popup_alignment(mut self, alignment: Alignment) -> Self {
229        self.choice = self.choice.popup_alignment(alignment);
230        self
231    }
232
233    /// Placement of the popup.
234    ///
235    /// __Default__
236    /// Default is BelowOrAbove.
237    pub fn popup_placement(mut self, placement: Placement) -> Self {
238        self.choice = self.choice.popup_placement(placement);
239        self
240    }
241
242    /// Outer boundary for the popup.
243    pub fn popup_boundary(mut self, boundary: Rect) -> Self {
244        self.choice = self.choice.popup_boundary(boundary);
245        self
246    }
247
248    /// Override the popup length.
249    ///
250    /// __Default__
251    /// Defaults to the number of items or 5.
252    pub fn popup_len(mut self, len: u16) -> Self {
253        self.choice = self.choice.popup_len(len);
254        self
255    }
256
257    /// Base style for the popup.
258    pub fn popup_style(mut self, style: Style) -> Self {
259        self.choice = self.choice.popup_style(style);
260        self
261    }
262
263    /// Block for the popup.
264    pub fn popup_block(mut self, block: Block<'a>) -> Self {
265        self.choice = self.choice.popup_block(block);
266        self
267    }
268
269    /// Scroll for the popup.
270    pub fn popup_scroll(mut self, scroll: Scroll<'a>) -> Self {
271        self.choice = self.choice.popup_scroll(scroll);
272        self
273    }
274
275    /// Adds an extra offset to the widget area.
276    ///
277    /// This can be used to
278    /// * place the widget under the mouse cursor.
279    /// * align the widget not by the outer bounds but by
280    ///   the text content.
281    pub fn popup_offset(mut self, offset: (i16, i16)) -> Self {
282        self.choice = self.choice.popup_offset(offset);
283        self
284    }
285
286    /// Sets only the x offset.
287    /// See [offset](Self::popup_offset)
288    pub fn popup_x_offset(mut self, offset: i16) -> Self {
289        self.choice = self.choice.popup_x_offset(offset);
290        self
291    }
292
293    /// Sets only the y offset.
294    /// See [offset](Self::popup_offset)
295    pub fn popup_y_offset(mut self, offset: i16) -> Self {
296        self.choice = self.choice.popup_y_offset(offset);
297        self
298    }
299
300    /// Sets the behaviour for selecting from the list.
301    pub fn behave_focus(mut self, focus: ChoiceFocus) -> Self {
302        self.choice = self.choice.behave_focus(focus);
303        self
304    }
305
306    /// Sets the behaviour for selecting from the list.
307    pub fn behave_select(mut self, select: ChoiceSelect) -> Self {
308        self.choice = self.choice.behave_select(select);
309        self
310    }
311
312    /// Sets the behaviour for closing the list.
313    pub fn behave_close(mut self, close: ChoiceClose) -> Self {
314        self.choice = self.choice.behave_close(close);
315        self
316    }
317
318    /// Inherent width.
319    pub fn width(&self) -> u16 {
320        self.choice.width()
321    }
322
323    /// Inherent height.
324    pub fn height(&self) -> u16 {
325        self.choice.height()
326    }
327
328    /// Choice itself doesn't render.
329    ///
330    /// This builds the widgets from the parameters set for Choice.
331    pub fn into_widgets(self) -> (ComboboxWidget<'a>, ComboboxPopup<'a>) {
332        let (choice, choice_popup) = self.choice.into_widgets();
333        (
334            ComboboxWidget {
335                choice,
336                text: self.text,
337            },
338            ComboboxPopup {
339                choice: choice_popup,
340            },
341        )
342    }
343}
344
345impl<'a> ComboboxWidget<'a> {
346    /// Inherent width.
347    pub fn width(&self) -> u16 {
348        self.choice.width()
349    }
350
351    /// Inherent height.
352    pub fn height(&self) -> u16 {
353        self.choice.height()
354    }
355}
356
357impl<'a> StatefulWidget for &ComboboxWidget<'a> {
358    type State = ComboboxState;
359
360    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
361        render_combobox(self, area, buf, state);
362    }
363}
364
365impl StatefulWidget for ComboboxWidget<'_> {
366    type State = ComboboxState;
367
368    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
369        render_combobox(&self, area, buf, state);
370    }
371}
372
373fn render_combobox(
374    widget: &ComboboxWidget<'_>,
375    area: Rect,
376    buf: &mut Buffer,
377    state: &mut ComboboxState,
378) {
379    state.area = area;
380    (&widget.choice).render(area, buf, &mut state.choice);
381    state.inner = state.choice.inner;
382    (&widget.text).render(state.choice.item_area, buf, &mut state.text);
383}
384
385impl ComboboxPopup<'_> {
386    /// Calculate the layout for the popup before rendering.
387    /// Area is the area of the ChoiceWidget not the ChoicePopup.
388    pub fn layout(&self, area: Rect, buf: &mut Buffer, state: &mut ComboboxState) -> Rect {
389        self.choice.layout(area, buf, &mut state.choice)
390    }
391}
392
393impl StatefulWidget for &ComboboxPopup<'_> {
394    type State = ComboboxState;
395
396    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
397        render_popup(self, area, buf, state);
398    }
399}
400
401impl StatefulWidget for ComboboxPopup<'_> {
402    type State = ComboboxState;
403
404    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
405        render_popup(&self, area, buf, state);
406    }
407}
408
409fn render_popup(
410    widget: &ComboboxPopup<'_>,
411    _area: Rect,
412    buf: &mut Buffer,
413    state: &mut ComboboxState,
414) {
415    (&widget.choice).render(Rect::default(), buf, &mut state.choice);
416}
417
418impl Clone for ComboboxState {
419    fn clone(&self) -> Self {
420        let mut text = self.text.clone();
421        let mut choice = self.choice.clone();
422        let focus = focus_cb(self.focus.new_instance(), text.focus, choice.focus);
423        text.focus = focus.clone();
424        choice.focus = focus.clone();
425
426        Self {
427            area: self.area,
428            inner: self.inner,
429            choice,
430            text,
431            focus,
432            mouse: Default::default(),
433            non_exhaustive: NonExhaustive,
434        }
435    }
436}
437
438impl Default for ComboboxState {
439    fn default() -> Self {
440        let mut text = TextInputState::default();
441        let mut choice = ChoiceState::default();
442        let focus = focus_cb(FocusFlag::default(), text.focus, choice.focus);
443        text.focus = focus.clone();
444        choice.focus = focus.clone();
445
446        Self {
447            area: Default::default(),
448            inner: Default::default(),
449            choice,
450            text,
451            focus,
452            mouse: Default::default(),
453            non_exhaustive: NonExhaustive,
454        }
455    }
456}
457
458fn focus_cb(flag: FocusFlag, choice: FocusFlag, text: FocusFlag) -> FocusFlag {
459    let choice_clone = choice.clone();
460    let text_clone = text.clone();
461    flag.on_lost(move || {
462        choice_clone.call_on_lost();
463        text_clone.call_on_lost();
464    });
465    let choice_clone = choice.clone();
466    let text_clone = text.clone();
467    flag.on_gained(move || {
468        choice_clone.call_on_gained();
469        text_clone.call_on_gained();
470    });
471    flag
472}
473
474impl HasScreenCursor for ComboboxState {
475    fn screen_cursor(&self) -> Option<(u16, u16)> {
476        self.text.screen_cursor()
477    }
478}
479
480impl HasFocus for ComboboxState {
481    fn build(&self, builder: &mut FocusBuilder) {
482        builder.widget_with_flags(self.focus(), self.area(), 0, self.navigable());
483        builder.widget_with_flags(self.focus(), self.choice.popup.area, 1, Navigation::Mouse);
484    }
485
486    fn focus(&self) -> FocusFlag {
487        self.focus.clone()
488    }
489
490    fn area(&self) -> Rect {
491        self.area
492    }
493}
494
495impl RelocatableState for ComboboxState {
496    fn relocate(&mut self, _shift: (i16, i16), _clip: Rect) {
497        // relocate after the popup is rendered.
498    }
499
500    fn relocate_popup(&mut self, shift: (i16, i16), clip: Rect) {
501        self.area.relocate(shift, clip);
502        self.inner.relocate(shift, clip);
503        self.choice.relocate(shift, clip);
504        self.text.relocate(shift, clip);
505        self.choice.relocate_popup(shift, clip);
506    }
507}
508
509impl ComboboxState {
510    pub fn new() -> Self {
511        Self::default()
512    }
513
514    pub fn named(name: &str) -> Self {
515        let mut text = TextInputState::default();
516        let mut choice = ChoiceState::default();
517        let focus = focus_cb(FocusFlag::new().with_name(name), text.focus, choice.focus);
518        text.focus = focus.clone();
519        choice.focus = focus.clone();
520
521        Self {
522            area: Default::default(),
523            inner: Default::default(),
524            choice,
525            text,
526            focus,
527            mouse: Default::default(),
528            non_exhaustive: NonExhaustive,
529        }
530    }
531
532    /// Popup is active?
533    pub fn is_popup_active(&self) -> bool {
534        self.choice.is_popup_active()
535    }
536
537    /// Flip the popup state.
538    pub fn flip_popup_active(&mut self) {
539        self.choice.flip_popup_active();
540    }
541
542    /// Show the popup.
543    pub fn set_popup_active(&mut self, active: bool) -> bool {
544        self.choice.set_popup_active(active)
545    }
546
547    /// Set a default-value other than T::default()
548    ///
549    /// The starting value will still be T::default()
550    /// after this. You must call clear() to use this
551    /// default.
552    ///
553    /// This default will be overridden by a default set
554    /// on the widget.
555    pub fn set_default_value(&mut self, default_value: Option<String>) {
556        self.choice.set_default_value(default_value);
557    }
558
559    /// A default value.
560    pub fn default_value(&self) -> &Option<String> {
561        self.choice.default_value()
562    }
563
564    /// Select the given value.
565    ///
566    /// If the value doesn't exist in the list or the list is
567    /// empty the value will still be set, but selected will be
568    /// None. The list will be empty before the first render, but
569    /// the first thing render will do is set the list of values.
570    /// This will adjust the selected index if possible.
571    /// It's still ok to set a value here that can not be represented.
572    /// As long as there is no user interaction, the same value
573    /// will be returned by value().
574    pub fn set_value(&mut self, value: impl Into<String>) -> bool {
575        let value = value.into();
576        self.text.set_value(value.clone());
577        self.choice.set_value(value)
578    }
579
580    /// Get the selected value.
581    pub fn value(&self) -> String {
582        self.text.value()
583    }
584
585    /// Select the default value or T::default.
586    pub fn clear(&mut self) -> bool {
587        self.text.clear() || self.choice.clear()
588    }
589
590    /// Select the value at index. This will set the value
591    /// to the given index in the value-list. If the index is
592    /// out of bounds or the value-list is empty it will
593    /// set selected to None and leave the value as is.
594    /// The list is empty before the first render so this
595    /// may not work as expected.
596    ///
597    /// The selected index is a best effort artefact, the main
598    /// thing is the value itself.
599    ///
600    /// Use of set_value() is preferred.
601    pub fn select(&mut self, select: usize) -> bool {
602        if self.choice.select(select) {
603            self.text.set_value(self.choice.value());
604            true
605        } else {
606            false
607        }
608    }
609
610    /// Returns the selected index or None if the
611    /// value is not in the list or the list is empty.
612    ///
613    /// You can still get the value set with set_value() though.
614    pub fn selected(&self) -> Option<usize> {
615        self.choice.selected()
616    }
617
618    /// Any items?
619    pub fn is_empty(&self) -> bool {
620        self.choice.is_empty()
621    }
622
623    /// Number of items.
624    pub fn len(&self) -> usize {
625        self.choice.len()
626    }
627
628    /// Scroll offset for the item list.
629    pub fn clear_offset(&mut self) {
630        self.choice.set_offset(0);
631    }
632
633    /// Scroll offset for the item list.
634    pub fn set_offset(&mut self, offset: usize) -> bool {
635        self.choice.set_offset(offset)
636    }
637
638    /// Scroll offset for the item list.
639    pub fn offset(&self) -> usize {
640        self.choice.offset()
641    }
642
643    /// Scroll offset for the item list.
644    pub fn max_offset(&self) -> usize {
645        self.choice.max_offset()
646    }
647
648    /// Page length for the item list.
649    pub fn page_len(&self) -> usize {
650        self.choice.page_len()
651    }
652
653    /// Scroll unit for the item list.
654    pub fn scroll_by(&self) -> usize {
655        self.choice.scroll_by()
656    }
657
658    /// Scroll the item list to the selected value.
659    pub fn scroll_to_selected(&mut self) -> bool {
660        self.choice.scroll_to_selected()
661    }
662}
663
664impl ComboboxState {
665    /// Select by first character.
666    pub fn select_by_char(&mut self, c: char) -> bool {
667        if self.choice.select_by_char(c) {
668            self.text.set_value(self.choice.value());
669            true
670        } else {
671            false
672        }
673    }
674
675    /// Select by first character. Reverse direction
676    pub fn reverse_select_by_char(&mut self, c: char) -> bool {
677        if self.choice.reverse_select_by_char(c) {
678            self.text.set_value(self.choice.value());
679            true
680        } else {
681            false
682        }
683    }
684
685    /// Select at position
686    pub fn move_to(&mut self, n: usize) -> ComboboxOutcome {
687        match self.choice.move_to(n) {
688            ChoiceOutcome::Continue => ComboboxOutcome::Continue,
689            ChoiceOutcome::Unchanged => ComboboxOutcome::Unchanged,
690            ChoiceOutcome::Changed => ComboboxOutcome::Changed,
691            ChoiceOutcome::Value => {
692                self.text.set_value(self.choice.value());
693                ComboboxOutcome::Value
694            }
695        }
696    }
697
698    /// Select next entry.
699    pub fn move_down(&mut self, n: usize) -> ComboboxOutcome {
700        match self.choice.move_down(n) {
701            ChoiceOutcome::Continue => ComboboxOutcome::Continue,
702            ChoiceOutcome::Unchanged => ComboboxOutcome::Unchanged,
703            ChoiceOutcome::Changed => ComboboxOutcome::Changed,
704            ChoiceOutcome::Value => {
705                self.text.set_value(self.choice.value());
706                ComboboxOutcome::Value
707            }
708        }
709    }
710
711    /// Select prev entry.
712    pub fn move_up(&mut self, n: usize) -> ComboboxOutcome {
713        match self.choice.move_up(n) {
714            ChoiceOutcome::Continue => ComboboxOutcome::Continue,
715            ChoiceOutcome::Unchanged => ComboboxOutcome::Unchanged,
716            ChoiceOutcome::Changed => ComboboxOutcome::Changed,
717            ChoiceOutcome::Value => {
718                self.text.set_value(self.choice.value());
719                ComboboxOutcome::Value
720            }
721        }
722    }
723}
724
725impl HandleEvent<Event, Popup, ComboboxOutcome> for ComboboxState {
726    fn handle(&mut self, event: &Event, _qualifier: Popup) -> ComboboxOutcome {
727        let r = if self.is_focused() {
728            match event {
729                ct_event!(keycode press Enter) => {
730                    self.flip_popup_active();
731                    ComboboxOutcome::Changed
732                }
733                ct_event!(keycode press Esc) => {
734                    if self.set_popup_active(false) {
735                        ComboboxOutcome::Changed
736                    } else {
737                        ComboboxOutcome::Continue
738                    }
739                }
740                ct_event!(keycode press Down) => self.move_down(1),
741                ct_event!(keycode press Up) => self.move_up(1),
742                ct_event!(keycode press PageUp) => self.move_up(self.page_len()),
743                ct_event!(keycode press PageDown) => self.move_down(self.page_len()),
744                ct_event!(keycode press ALT-Home) => self.move_to(0),
745                ct_event!(keycode press ALT-End) => self.move_to(self.len().saturating_sub(1)),
746                Event::Key(_) => match self.text.handle(event, Regular) {
747                    TextOutcome::Continue => ComboboxOutcome::Continue,
748                    TextOutcome::Unchanged => ComboboxOutcome::Unchanged,
749                    TextOutcome::Changed => ComboboxOutcome::Changed,
750                    TextOutcome::TextChanged => ComboboxOutcome::TextChanged,
751                },
752                _ => ComboboxOutcome::Continue,
753            }
754        } else {
755            ComboboxOutcome::Continue
756        };
757
758        if !r.is_consumed() {
759            self.handle(event, MouseOnly)
760        } else {
761            r
762        }
763    }
764}
765
766impl HandleEvent<Event, MouseOnly, ComboboxOutcome> for ComboboxState {
767    fn handle(&mut self, event: &Event, _qualifier: MouseOnly) -> ComboboxOutcome {
768        let r0 = handle_mouse(self, event);
769        let r1 = handle_select(self, event);
770        let r2 = handle_close(self, event);
771        let mut r = max(r0, max(r1, r2));
772
773        r = r.or_else(|| match self.text.handle(event, MouseOnly) {
774            TextOutcome::Continue => ComboboxOutcome::Continue,
775            TextOutcome::Unchanged => ComboboxOutcome::Unchanged,
776            TextOutcome::Changed => ComboboxOutcome::Changed,
777            TextOutcome::TextChanged => ComboboxOutcome::TextChanged,
778        });
779        r = r.or_else(|| mouse_trap(event, self.choice.popup.area).into());
780
781        r
782    }
783}
784
785fn handle_mouse(state: &mut ComboboxState, event: &Event) -> ComboboxOutcome {
786    match event {
787        ct_event!(mouse down Left for x,y)
788            if state.choice.button_area.contains((*x, *y).into()) =>
789        {
790            if !state.gained_focus() {
791                state.flip_popup_active();
792                ComboboxOutcome::Changed
793            } else {
794                // hide is down by self.popup.handle() as this click
795                // is outside the popup area!!
796                ComboboxOutcome::Continue
797            }
798        }
799        ct_event!(mouse down Left for x,y)
800        | ct_event!(mouse down Right for x,y)
801        | ct_event!(mouse down Middle for x,y)
802            if !state.choice.item_area.contains((*x, *y).into())
803                && !state.choice.button_area.contains((*x, *y).into()) =>
804        {
805            match state.choice.popup.handle(event, Popup) {
806                PopupOutcome::Hide => {
807                    state.set_popup_active(false);
808                    ComboboxOutcome::Changed
809                }
810                r => r.into(),
811            }
812        }
813        _ => ComboboxOutcome::Continue,
814    }
815}
816
817fn handle_select(state: &mut ComboboxState, event: &Event) -> ComboboxOutcome {
818    match state.choice.behave_select {
819        ChoiceSelect::MouseScroll => {
820            let mut sas = ScrollAreaState::new()
821                .area(state.choice.popup.area)
822                .v_scroll(&mut state.choice.popup_scroll);
823            let mut r = match sas.handle(event, MouseOnly) {
824                ScrollOutcome::Up(n) => state.move_up(n),
825                ScrollOutcome::Down(n) => state.move_down(n),
826                ScrollOutcome::VPos(n) => state.move_to(n),
827                _ => ComboboxOutcome::Continue,
828            };
829
830            r = r.or_else(|| match event {
831                ct_event!(mouse down Left for x,y)
832                    if state.choice.popup.area.contains((*x, *y).into()) =>
833                {
834                    if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
835                        state.move_to(state.offset() + n)
836                    } else {
837                        ComboboxOutcome::Unchanged
838                    }
839                }
840                ct_event!(mouse drag Left for x,y)
841                    if state.choice.popup.area.contains((*x, *y).into()) =>
842                {
843                    if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
844                        state.move_to(state.offset() + n)
845                    } else {
846                        ComboboxOutcome::Unchanged
847                    }
848                }
849                _ => ComboboxOutcome::Continue,
850            });
851            r
852        }
853        ChoiceSelect::MouseMove => {
854            // effect: move the content below the mouse and keep visible selection.
855            let mut r = if let Some(selected) = state.choice.core.selected() {
856                let rel_sel = selected.saturating_sub(state.offset());
857                let mut sas = ScrollAreaState::new()
858                    .area(state.choice.popup.area)
859                    .v_scroll(&mut state.choice.popup_scroll);
860                match sas.handle(event, MouseOnly) {
861                    ScrollOutcome::Up(n) => {
862                        state.choice.popup_scroll.scroll_up(n);
863                        if state.select(state.offset() + rel_sel) {
864                            ComboboxOutcome::Value
865                        } else {
866                            ComboboxOutcome::Unchanged
867                        }
868                    }
869                    ScrollOutcome::Down(n) => {
870                        state.choice.popup_scroll.scroll_down(n);
871                        if state.select(state.offset() + rel_sel) {
872                            ComboboxOutcome::Value
873                        } else {
874                            ComboboxOutcome::Unchanged
875                        }
876                    }
877                    ScrollOutcome::VPos(n) => {
878                        if state.choice.popup_scroll.set_offset(n) {
879                            ComboboxOutcome::Value
880                        } else {
881                            ComboboxOutcome::Unchanged
882                        }
883                    }
884                    _ => ComboboxOutcome::Continue,
885                }
886            } else {
887                ComboboxOutcome::Continue
888            };
889
890            r = r.or_else(|| match event {
891                ct_event!(mouse moved for x,y)
892                    if state.choice.popup.area.contains((*x, *y).into()) =>
893                {
894                    if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
895                        state.move_to(state.offset() + n)
896                    } else {
897                        ComboboxOutcome::Unchanged
898                    }
899                }
900                _ => ComboboxOutcome::Continue,
901            });
902            r
903        }
904        ChoiceSelect::MouseClick => {
905            // effect: move the content below the mouse and keep visible selection.
906            let mut sas = ScrollAreaState::new()
907                .area(state.choice.popup.area)
908                .v_scroll(&mut state.choice.popup_scroll);
909            let mut r = match sas.handle(event, MouseOnly) {
910                ScrollOutcome::Up(n) => {
911                    if state.choice.popup_scroll.scroll_up(n) {
912                        ComboboxOutcome::Changed
913                    } else {
914                        ComboboxOutcome::Unchanged
915                    }
916                }
917                ScrollOutcome::Down(n) => {
918                    if state.choice.popup_scroll.scroll_down(n) {
919                        ComboboxOutcome::Changed
920                    } else {
921                        ComboboxOutcome::Unchanged
922                    }
923                }
924                ScrollOutcome::VPos(n) => {
925                    if state.choice.popup_scroll.set_offset(n) {
926                        ComboboxOutcome::Changed
927                    } else {
928                        ComboboxOutcome::Unchanged
929                    }
930                }
931                _ => ComboboxOutcome::Continue,
932            };
933
934            r = r.or_else(|| match event {
935                ct_event!(mouse down Left for x,y)
936                    if state.choice.popup.area.contains((*x, *y).into()) =>
937                {
938                    if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
939                        state.move_to(state.offset() + n)
940                    } else {
941                        ComboboxOutcome::Unchanged
942                    }
943                }
944                ct_event!(mouse drag Left for x,y)
945                    if state.choice.popup.area.contains((*x, *y).into()) =>
946                {
947                    if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
948                        state.move_to(state.offset() + n)
949                    } else {
950                        ComboboxOutcome::Unchanged
951                    }
952                }
953                _ => ComboboxOutcome::Continue,
954            });
955            r
956        }
957    }
958}
959
960fn handle_close(state: &mut ComboboxState, event: &Event) -> ComboboxOutcome {
961    match state.choice.behave_close {
962        ChoiceClose::SingleClick => match event {
963            ct_event!(mouse down Left for x,y)
964                if state.choice.popup.area.contains((*x, *y).into()) =>
965            {
966                if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
967                    let r = state.move_to(state.offset() + n);
968                    let s = if state.set_popup_active(false) {
969                        ComboboxOutcome::Changed
970                    } else {
971                        ComboboxOutcome::Unchanged
972                    };
973                    max(r, s)
974                } else {
975                    ComboboxOutcome::Unchanged
976                }
977            }
978            _ => ComboboxOutcome::Continue,
979        },
980        ChoiceClose::DoubleClick => match event {
981            ct_event!(mouse any for m) if state.mouse.doubleclick(state.choice.popup.area, m) => {
982                if let Some(n) = item_at(&state.choice.item_areas, m.column, m.row) {
983                    let r = state.move_to(state.offset() + n);
984                    let s = if state.set_popup_active(false) {
985                        ComboboxOutcome::Changed
986                    } else {
987                        ComboboxOutcome::Unchanged
988                    };
989                    max(r, s)
990                } else {
991                    ComboboxOutcome::Unchanged
992                }
993            }
994            _ => ComboboxOutcome::Continue,
995        },
996    }
997}
998
999/// Handle events for the popup.
1000/// Call before other handlers to deal with intersections
1001/// with other widgets.
1002pub fn handle_events(state: &mut ComboboxState, focus: bool, event: &Event) -> ComboboxOutcome {
1003    state.focus.set(focus);
1004    HandleEvent::handle(state, event, Popup)
1005}
1006
1007/// Handle only mouse-events.
1008pub fn handle_mouse_events(state: &mut ComboboxState, event: &Event) -> ComboboxOutcome {
1009    HandleEvent::handle(state, event, MouseOnly)
1010}