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