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