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: String, item: impl Into<Line<'a>>) -> Self {
168        self.choice = self.choice.item(value, 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(area, 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        self.area.relocate(shift, clip);
496        self.inner.relocate(shift, clip);
497        self.choice.relocate(shift, clip);
498        self.text.relocate(shift, clip);
499    }
500
501    fn relocate_popup(&mut self, shift: (i16, i16), clip: Rect) {
502        self.choice.relocate_popup(shift, clip);
503    }
504}
505
506impl ComboboxState {
507    pub fn new() -> Self {
508        Self::default()
509    }
510
511    pub fn named(name: &str) -> Self {
512        let mut text = TextInputState::default();
513        let mut choice = ChoiceState::default();
514        let focus = focus_cb(FocusFlag::new().with_name(name), text.focus, choice.focus);
515        text.focus = focus.clone();
516        choice.focus = focus.clone();
517
518        Self {
519            area: Default::default(),
520            inner: Default::default(),
521            choice,
522            text,
523            focus,
524            mouse: Default::default(),
525            non_exhaustive: NonExhaustive,
526        }
527    }
528
529    /// Popup is active?
530    pub fn is_popup_active(&self) -> bool {
531        self.choice.is_popup_active()
532    }
533
534    /// Flip the popup state.
535    pub fn flip_popup_active(&mut self) {
536        self.choice.flip_popup_active();
537    }
538
539    /// Show the popup.
540    pub fn set_popup_active(&mut self, active: bool) -> bool {
541        self.choice.set_popup_active(active)
542    }
543
544    /// Set a default-value other than T::default()
545    ///
546    /// The starting value will still be T::default()
547    /// after this. You must call clear() to use this
548    /// default.
549    ///
550    /// This default will be overridden by a default set
551    /// on the widget.
552    pub fn set_default_value(&mut self, default_value: Option<String>) {
553        self.choice.set_default_value(default_value);
554    }
555
556    /// A default value.
557    pub fn default_value(&self) -> &Option<String> {
558        self.choice.default_value()
559    }
560
561    /// Select the given value.
562    ///
563    /// If the value doesn't exist in the list or the list is
564    /// empty the value will still be set, but selected will be
565    /// None. The list will be empty before the first render, but
566    /// the first thing render will do is set the list of values.
567    /// This will adjust the selected index if possible.
568    /// It's still ok to set a value here that can not be represented.
569    /// As long as there is no user interaction, the same value
570    /// will be returned by value().
571    pub fn set_value(&mut self, value: impl Into<String>) -> bool {
572        let value = value.into();
573        self.text.set_value(value.clone());
574        self.choice.set_value(value)
575    }
576
577    /// Get the selected value.
578    pub fn value(&self) -> String {
579        self.text.value()
580    }
581
582    /// Select the default value or T::default.
583    pub fn clear(&mut self) -> bool {
584        self.text.clear() || self.choice.clear()
585    }
586
587    /// Select the value at index. This will set the value
588    /// to the given index in the value-list. If the index is
589    /// out of bounds or the value-list is empty it will
590    /// set selected to None and leave the value as is.
591    /// The list is empty before the first render so this
592    /// may not work as expected.
593    ///
594    /// The selected index is a best effort artefact, the main
595    /// thing is the value itself.
596    ///
597    /// Use of set_value() is preferred.
598    pub fn select(&mut self, select: usize) -> bool {
599        if self.choice.select(select) {
600            self.text.set_value(self.choice.value());
601            true
602        } else {
603            false
604        }
605    }
606
607    /// Returns the selected index or None if the
608    /// value is not in the list or the list is empty.
609    ///
610    /// You can still get the value set with set_value() though.
611    pub fn selected(&self) -> Option<usize> {
612        self.choice.selected()
613    }
614
615    /// Any items?
616    pub fn is_empty(&self) -> bool {
617        self.choice.is_empty()
618    }
619
620    /// Number of items.
621    pub fn len(&self) -> usize {
622        self.choice.len()
623    }
624
625    /// Scroll offset for the item list.
626    pub fn clear_offset(&mut self) {
627        self.choice.set_offset(0);
628    }
629
630    /// Scroll offset for the item list.
631    pub fn set_offset(&mut self, offset: usize) -> bool {
632        self.choice.set_offset(offset)
633    }
634
635    /// Scroll offset for the item list.
636    pub fn offset(&self) -> usize {
637        self.choice.offset()
638    }
639
640    /// Scroll offset for the item list.
641    pub fn max_offset(&self) -> usize {
642        self.choice.max_offset()
643    }
644
645    /// Page length for the item list.
646    pub fn page_len(&self) -> usize {
647        self.choice.page_len()
648    }
649
650    /// Scroll unit for the item list.
651    pub fn scroll_by(&self) -> usize {
652        self.choice.scroll_by()
653    }
654
655    /// Scroll the item list to the selected value.
656    pub fn scroll_to_selected(&mut self) -> bool {
657        self.choice.scroll_to_selected()
658    }
659}
660
661impl ComboboxState {
662    /// Select by first character.
663    pub fn select_by_char(&mut self, c: char) -> bool {
664        if self.choice.select_by_char(c) {
665            self.text.set_value(self.choice.value());
666            true
667        } else {
668            false
669        }
670    }
671
672    /// Select by first character. Reverse direction
673    pub fn reverse_select_by_char(&mut self, c: char) -> bool {
674        if self.choice.reverse_select_by_char(c) {
675            self.text.set_value(self.choice.value());
676            true
677        } else {
678            false
679        }
680    }
681
682    /// Select at position
683    pub fn move_to(&mut self, n: usize) -> ComboboxOutcome {
684        match self.choice.move_to(n) {
685            ChoiceOutcome::Continue => ComboboxOutcome::Continue,
686            ChoiceOutcome::Unchanged => ComboboxOutcome::Unchanged,
687            ChoiceOutcome::Changed => ComboboxOutcome::Changed,
688            ChoiceOutcome::Value => {
689                self.text.set_value(self.choice.value());
690                ComboboxOutcome::Value
691            }
692        }
693    }
694
695    /// Select next entry.
696    pub fn move_down(&mut self, n: usize) -> ComboboxOutcome {
697        match self.choice.move_down(n) {
698            ChoiceOutcome::Continue => ComboboxOutcome::Continue,
699            ChoiceOutcome::Unchanged => ComboboxOutcome::Unchanged,
700            ChoiceOutcome::Changed => ComboboxOutcome::Changed,
701            ChoiceOutcome::Value => {
702                self.text.set_value(self.choice.value());
703                ComboboxOutcome::Value
704            }
705        }
706    }
707
708    /// Select prev entry.
709    pub fn move_up(&mut self, n: usize) -> ComboboxOutcome {
710        match self.choice.move_up(n) {
711            ChoiceOutcome::Continue => ComboboxOutcome::Continue,
712            ChoiceOutcome::Unchanged => ComboboxOutcome::Unchanged,
713            ChoiceOutcome::Changed => ComboboxOutcome::Changed,
714            ChoiceOutcome::Value => {
715                self.text.set_value(self.choice.value());
716                ComboboxOutcome::Value
717            }
718        }
719    }
720}
721
722impl HandleEvent<crossterm::event::Event, Popup, ComboboxOutcome> for ComboboxState {
723    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Popup) -> ComboboxOutcome {
724        let r = if self.is_focused() {
725            match event {
726                ct_event!(keycode press Enter) => {
727                    self.flip_popup_active();
728                    ComboboxOutcome::Changed
729                }
730                ct_event!(keycode press Esc) => {
731                    if self.set_popup_active(false) {
732                        ComboboxOutcome::Changed
733                    } else {
734                        ComboboxOutcome::Continue
735                    }
736                }
737                ct_event!(keycode press Down) => self.move_down(1),
738                ct_event!(keycode press Up) => self.move_up(1),
739                ct_event!(keycode press PageUp) => self.move_up(self.page_len()),
740                ct_event!(keycode press PageDown) => self.move_down(self.page_len()),
741                ct_event!(keycode press ALT-Home) => self.move_to(0),
742                ct_event!(keycode press ALT-End) => self.move_to(self.len().saturating_sub(1)),
743                crossterm::event::Event::Key(_) => match self.text.handle(event, Regular) {
744                    TextOutcome::Continue => ComboboxOutcome::Continue,
745                    TextOutcome::Unchanged => ComboboxOutcome::Unchanged,
746                    TextOutcome::Changed => ComboboxOutcome::Changed,
747                    TextOutcome::TextChanged => ComboboxOutcome::TextChanged,
748                },
749                _ => ComboboxOutcome::Continue,
750            }
751        } else {
752            ComboboxOutcome::Continue
753        };
754
755        if !r.is_consumed() {
756            self.handle(event, MouseOnly)
757        } else {
758            r
759        }
760    }
761}
762
763impl HandleEvent<crossterm::event::Event, MouseOnly, ComboboxOutcome> for ComboboxState {
764    fn handle(
765        &mut self,
766        event: &crossterm::event::Event,
767        _qualifier: MouseOnly,
768    ) -> ComboboxOutcome {
769        let r0 = handle_mouse(self, event);
770        let r1 = handle_select(self, event);
771        let r2 = handle_close(self, event);
772        let mut r = max(r0, max(r1, r2));
773
774        r = r.or_else(|| match self.text.handle(event, MouseOnly) {
775            TextOutcome::Continue => ComboboxOutcome::Continue,
776            TextOutcome::Unchanged => ComboboxOutcome::Unchanged,
777            TextOutcome::Changed => ComboboxOutcome::Changed,
778            TextOutcome::TextChanged => ComboboxOutcome::TextChanged,
779        });
780        r = r.or_else(|| mouse_trap(event, self.choice.popup.area).into());
781
782        r
783    }
784}
785
786fn handle_mouse(state: &mut ComboboxState, event: &crossterm::event::Event) -> ComboboxOutcome {
787    match event {
788        ct_event!(mouse down Left for x,y)
789            if state.choice.button_area.contains((*x, *y).into()) =>
790        {
791            if !state.gained_focus() {
792                state.flip_popup_active();
793                ComboboxOutcome::Changed
794            } else {
795                // hide is down by self.popup.handle() as this click
796                // is outside the popup area!!
797                ComboboxOutcome::Continue
798            }
799        }
800        ct_event!(mouse down Left for x,y)
801        | ct_event!(mouse down Right for x,y)
802        | ct_event!(mouse down Middle for x,y)
803            if !state.choice.item_area.contains((*x, *y).into())
804                && !state.choice.button_area.contains((*x, *y).into()) =>
805        {
806            match state.choice.popup.handle(event, Popup) {
807                PopupOutcome::Hide => {
808                    state.set_popup_active(false);
809                    ComboboxOutcome::Changed
810                }
811                r => r.into(),
812            }
813        }
814        _ => ComboboxOutcome::Continue,
815    }
816}
817
818fn handle_select(state: &mut ComboboxState, event: &crossterm::event::Event) -> ComboboxOutcome {
819    match state.choice.behave_select {
820        ChoiceSelect::MouseScroll => {
821            let mut sas = ScrollAreaState::new()
822                .area(state.choice.popup.area)
823                .v_scroll(&mut state.choice.popup_scroll);
824            let mut r = match sas.handle(event, MouseOnly) {
825                ScrollOutcome::Up(n) => state.move_up(n),
826                ScrollOutcome::Down(n) => state.move_down(n),
827                ScrollOutcome::VPos(n) => state.move_to(n),
828                _ => ComboboxOutcome::Continue,
829            };
830
831            r = r.or_else(|| match event {
832                ct_event!(mouse down Left for x,y)
833                    if state.choice.popup.area.contains((*x, *y).into()) =>
834                {
835                    if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
836                        state.move_to(state.offset() + n)
837                    } else {
838                        ComboboxOutcome::Unchanged
839                    }
840                }
841                ct_event!(mouse drag Left for x,y)
842                    if state.choice.popup.area.contains((*x, *y).into()) =>
843                {
844                    if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
845                        state.move_to(state.offset() + n)
846                    } else {
847                        ComboboxOutcome::Unchanged
848                    }
849                }
850                _ => ComboboxOutcome::Continue,
851            });
852            r
853        }
854        ChoiceSelect::MouseMove => {
855            // effect: move the content below the mouse and keep visible selection.
856            let mut r = if let Some(selected) = state.choice.core.selected() {
857                let rel_sel = selected.saturating_sub(state.offset());
858                let mut sas = ScrollAreaState::new()
859                    .area(state.choice.popup.area)
860                    .v_scroll(&mut state.choice.popup_scroll);
861                match sas.handle(event, MouseOnly) {
862                    ScrollOutcome::Up(n) => {
863                        state.choice.popup_scroll.scroll_up(n);
864                        if state.select(state.offset() + rel_sel) {
865                            ComboboxOutcome::Value
866                        } else {
867                            ComboboxOutcome::Unchanged
868                        }
869                    }
870                    ScrollOutcome::Down(n) => {
871                        state.choice.popup_scroll.scroll_down(n);
872                        if state.select(state.offset() + rel_sel) {
873                            ComboboxOutcome::Value
874                        } else {
875                            ComboboxOutcome::Unchanged
876                        }
877                    }
878                    ScrollOutcome::VPos(n) => {
879                        if state.choice.popup_scroll.set_offset(n) {
880                            ComboboxOutcome::Value
881                        } else {
882                            ComboboxOutcome::Unchanged
883                        }
884                    }
885                    _ => ComboboxOutcome::Continue,
886                }
887            } else {
888                ComboboxOutcome::Continue
889            };
890
891            r = r.or_else(|| match event {
892                ct_event!(mouse moved for x,y)
893                    if state.choice.popup.area.contains((*x, *y).into()) =>
894                {
895                    if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
896                        state.move_to(state.offset() + n)
897                    } else {
898                        ComboboxOutcome::Unchanged
899                    }
900                }
901                _ => ComboboxOutcome::Continue,
902            });
903            r
904        }
905        ChoiceSelect::MouseClick => {
906            // effect: move the content below the mouse and keep visible selection.
907            let mut sas = ScrollAreaState::new()
908                .area(state.choice.popup.area)
909                .v_scroll(&mut state.choice.popup_scroll);
910            let mut r = match sas.handle(event, MouseOnly) {
911                ScrollOutcome::Up(n) => {
912                    if state.choice.popup_scroll.scroll_up(n) {
913                        ComboboxOutcome::Changed
914                    } else {
915                        ComboboxOutcome::Unchanged
916                    }
917                }
918                ScrollOutcome::Down(n) => {
919                    if state.choice.popup_scroll.scroll_down(n) {
920                        ComboboxOutcome::Changed
921                    } else {
922                        ComboboxOutcome::Unchanged
923                    }
924                }
925                ScrollOutcome::VPos(n) => {
926                    if state.choice.popup_scroll.set_offset(n) {
927                        ComboboxOutcome::Changed
928                    } else {
929                        ComboboxOutcome::Unchanged
930                    }
931                }
932                _ => ComboboxOutcome::Continue,
933            };
934
935            r = r.or_else(|| match event {
936                ct_event!(mouse down Left for x,y)
937                    if state.choice.popup.area.contains((*x, *y).into()) =>
938                {
939                    if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
940                        state.move_to(state.offset() + n)
941                    } else {
942                        ComboboxOutcome::Unchanged
943                    }
944                }
945                ct_event!(mouse drag Left for x,y)
946                    if state.choice.popup.area.contains((*x, *y).into()) =>
947                {
948                    if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
949                        state.move_to(state.offset() + n)
950                    } else {
951                        ComboboxOutcome::Unchanged
952                    }
953                }
954                _ => ComboboxOutcome::Continue,
955            });
956            r
957        }
958    }
959}
960
961fn handle_close(state: &mut ComboboxState, event: &crossterm::event::Event) -> ComboboxOutcome {
962    match state.choice.behave_close {
963        ChoiceClose::SingleClick => match event {
964            ct_event!(mouse down Left for x,y)
965                if state.choice.popup.area.contains((*x, *y).into()) =>
966            {
967                if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
968                    let r = state.move_to(state.offset() + n);
969                    let s = if state.set_popup_active(false) {
970                        ComboboxOutcome::Changed
971                    } else {
972                        ComboboxOutcome::Unchanged
973                    };
974                    max(r, s)
975                } else {
976                    ComboboxOutcome::Unchanged
977                }
978            }
979            _ => ComboboxOutcome::Continue,
980        },
981        ChoiceClose::DoubleClick => match event {
982            ct_event!(mouse any for m) if state.mouse.doubleclick(state.choice.popup.area, m) => {
983                if let Some(n) = item_at(&state.choice.item_areas, m.column, m.row) {
984                    let r = state.move_to(state.offset() + n);
985                    let s = if state.set_popup_active(false) {
986                        ComboboxOutcome::Changed
987                    } else {
988                        ComboboxOutcome::Unchanged
989                    };
990                    max(r, s)
991                } else {
992                    ComboboxOutcome::Unchanged
993                }
994            }
995            _ => ComboboxOutcome::Continue,
996        },
997    }
998}
999
1000/// Handle events for the popup.
1001/// Call before other handlers to deal with intersections
1002/// with other widgets.
1003pub fn handle_events(
1004    state: &mut ComboboxState,
1005    focus: bool,
1006    event: &crossterm::event::Event,
1007) -> ComboboxOutcome {
1008    state.focus.set(focus);
1009    HandleEvent::handle(state, event, Popup)
1010}
1011
1012/// Handle only mouse-events.
1013pub fn handle_mouse_events(
1014    state: &mut ComboboxState,
1015    event: &crossterm::event::Event,
1016) -> ComboboxOutcome {
1017    HandleEvent::handle(state, event, MouseOnly)
1018}