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