rat_widget/
choice.rs

1//!
2//! Choice/Select widget.
3//!
4//! ```rust no_run
5//! use rat_popup::Placement;
6//! use rat_scrolled::Scroll;
7//! use rat_widget::choice::{Choice, ChoiceState};
8//! # use ratatui::prelude::*;
9//! # use ratatui::widgets::Block;
10//! # let mut buf = Buffer::default();
11//! # let mut cstate = ChoiceState::default();
12//! # let mut max_bounds: Rect = Rect::default();
13//!
14//! let (widget, popup) = Choice::new()
15//!         .item(1, "Carrots")
16//!         .item(2, "Potatoes")
17//!         .item(3, "Onions")
18//!         .item(4, "Peas")
19//!         .item(5, "Beans")
20//!         .item(6, "Tomatoes")
21//!         .popup_block(Block::bordered())
22//!         .popup_placement(Placement::AboveOrBelow)
23//!         .popup_boundary(max_bounds)
24//!         .into_widgets();
25//!  widget.render(Rect::new(3,3,15,1), &mut buf, &mut cstate);
26//!
27//!  // ... render other widgets
28//!
29//!  popup.render(Rect::new(3,3,15,1), &mut buf, &mut cstate);
30//!
31//! ```
32//!
33use crate::_private::NonExhaustive;
34use crate::choice::core::ChoiceCore;
35use crate::event::ChoiceOutcome;
36use crate::util::{block_size, revert_style};
37use rat_event::util::{item_at, mouse_trap, MouseFlags};
38use rat_event::{ct_event, ConsumedEvent, HandleEvent, MouseOnly, Popup};
39use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
40use rat_popup::event::PopupOutcome;
41use rat_popup::{Placement, PopupCore, PopupCoreState, PopupStyle};
42use rat_reloc::{relocate_area, relocate_areas, RelocatableState};
43use rat_scrolled::event::ScrollOutcome;
44use rat_scrolled::{Scroll, ScrollAreaState};
45use ratatui::buffer::Buffer;
46use ratatui::layout::{Alignment, Rect};
47use ratatui::prelude::BlockExt;
48use ratatui::style::Style;
49use ratatui::text::{Line, Span};
50#[cfg(feature = "unstable-widget-ref")]
51use ratatui::widgets::StatefulWidgetRef;
52use ratatui::widgets::{Block, StatefulWidget, Widget};
53use std::cell::RefCell;
54use std::cmp::{max, min};
55use std::marker::PhantomData;
56use std::rc::Rc;
57
58/// Enum controling the behaviour of the Choice.
59#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
60pub enum ChoiceSelect {
61    /// Change the selection with the mouse-wheel.
62    #[default]
63    MouseScroll,
64    /// Change the selection just by moving.
65    MouseMove,
66    /// Change the selection with a click only
67    MouseClick,
68}
69
70/// Enum controlling the behaviour of the Choice.
71#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
72pub enum ChoiceClose {
73    /// Close the popup with a single click.
74    #[default]
75    SingleClick,
76    /// Close the popup with double-click.
77    DoubleClick,
78}
79
80/// Choice.
81///
82/// Select one of a list. No editable mode for this widget.
83///
84/// This doesn't render itself. [into_widgets](Choice::into_widgets)
85/// creates the base part and the popup part, which are rendered
86/// separately.
87///
88#[derive(Debug, Clone)]
89pub struct Choice<'a, T>
90where
91    T: PartialEq + Clone + Default,
92{
93    values: Rc<RefCell<Vec<T>>>,
94    default_value: Option<T>,
95    items: Rc<RefCell<Vec<Line<'a>>>>,
96
97    style: Style,
98    button_style: Option<Style>,
99    select_style: Option<Style>,
100    focus_style: Option<Style>,
101    block: Option<Block<'a>>,
102
103    popup_alignment: Alignment,
104    popup_placement: Placement,
105    popup_len: Option<u16>,
106    popup: PopupCore<'a>,
107
108    behave_select: ChoiceSelect,
109    behave_close: ChoiceClose,
110}
111
112/// Renders the main widget.
113#[derive(Debug)]
114pub struct ChoiceWidget<'a, T>
115where
116    T: PartialEq,
117{
118    values: Rc<RefCell<Vec<T>>>,
119    default_value: Option<T>,
120    items: Rc<RefCell<Vec<Line<'a>>>>,
121
122    style: Style,
123    button_style: Option<Style>,
124    focus_style: Option<Style>,
125    block: Option<Block<'a>>,
126    len: Option<u16>,
127
128    behave_select: ChoiceSelect,
129    behave_close: ChoiceClose,
130
131    _phantom: PhantomData<T>,
132}
133
134/// Renders the popup. This is called after the rest
135/// of the area is rendered and overwrites to display itself.
136#[derive(Debug)]
137pub struct ChoicePopup<'a, T>
138where
139    T: PartialEq,
140{
141    items: Rc<RefCell<Vec<Line<'a>>>>,
142
143    style: Style,
144    select_style: Option<Style>,
145
146    popup_alignment: Alignment,
147    popup_placement: Placement,
148    popup_len: Option<u16>,
149    popup: PopupCore<'a>,
150
151    _phantom: PhantomData<T>,
152}
153
154/// Combined style.
155#[derive(Debug, Clone)]
156pub struct ChoiceStyle {
157    pub style: Style,
158    pub button: Option<Style>,
159    pub select: Option<Style>,
160    pub focus: Option<Style>,
161    pub block: Option<Block<'static>>,
162
163    pub popup: PopupStyle,
164    pub popup_len: Option<u16>,
165
166    pub behave_select: Option<ChoiceSelect>,
167    pub behave_close: Option<ChoiceClose>,
168
169    pub non_exhaustive: NonExhaustive,
170}
171
172/// State.
173#[derive(Debug)]
174pub struct ChoiceState<T = usize>
175where
176    T: PartialEq + Clone + Default,
177{
178    /// Total area.
179    /// __read only__. renewed with each render.
180    pub area: Rect,
181    /// First char of each item for navigation.
182    /// __read only__. renewed with each render.
183    pub nav_char: Vec<Vec<char>>,
184    /// Item area in the main widget.
185    /// __read only__. renewed with each render.
186    pub item_area: Rect,
187    /// Button area in the main widget.
188    /// __read only__. renewed with each render.
189    pub button_area: Rect,
190    /// Visible items in the popup.
191    /// __read only__. renewed with each render.
192    pub item_areas: Vec<Rect>,
193    /// Core
194    pub core: ChoiceCore<T>,
195    /// Popup state.
196    pub popup: PopupCoreState,
197    /// Behaviour for selecting from the choice popup.
198    /// __read only__ renewed with each render.
199    pub behave_select: ChoiceSelect,
200    /// Behaviour for closing the choice popup.
201    /// __read only__ renewed with each render.
202    pub behave_close: ChoiceClose,
203
204    /// Focus flag.
205    /// __read+write__
206    pub focus: FocusFlag,
207    /// Mouse util.
208    pub mouse: MouseFlags,
209
210    pub non_exhaustive: NonExhaustive,
211}
212
213pub(crate) mod event {
214    use rat_event::{ConsumedEvent, Outcome};
215    use rat_popup::event::PopupOutcome;
216
217    /// Result value for event-handling.
218    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
219    pub enum ChoiceOutcome {
220        /// The given event was not handled at all.
221        Continue,
222        /// The event was handled, no repaint necessary.
223        Unchanged,
224        /// The event was handled, repaint necessary.
225        Changed,
226        /// An item has been selected.
227        Value,
228    }
229
230    impl ConsumedEvent for ChoiceOutcome {
231        fn is_consumed(&self) -> bool {
232            *self != ChoiceOutcome::Continue
233        }
234    }
235
236    impl From<Outcome> for ChoiceOutcome {
237        fn from(value: Outcome) -> Self {
238            match value {
239                Outcome::Continue => ChoiceOutcome::Continue,
240                Outcome::Unchanged => ChoiceOutcome::Unchanged,
241                Outcome::Changed => ChoiceOutcome::Changed,
242            }
243        }
244    }
245
246    impl From<ChoiceOutcome> for Outcome {
247        fn from(value: ChoiceOutcome) -> Self {
248            match value {
249                ChoiceOutcome::Continue => Outcome::Continue,
250                ChoiceOutcome::Unchanged => Outcome::Unchanged,
251                ChoiceOutcome::Changed => Outcome::Changed,
252                ChoiceOutcome::Value => Outcome::Changed,
253            }
254        }
255    }
256
257    impl From<PopupOutcome> for ChoiceOutcome {
258        fn from(value: PopupOutcome) -> Self {
259            match value {
260                PopupOutcome::Continue => ChoiceOutcome::Continue,
261                PopupOutcome::Unchanged => ChoiceOutcome::Unchanged,
262                PopupOutcome::Changed => ChoiceOutcome::Changed,
263                PopupOutcome::Hide => ChoiceOutcome::Changed,
264            }
265        }
266    }
267}
268
269pub mod core {
270    #[derive(Debug, Default, Clone)]
271    pub struct ChoiceCore<T>
272    where
273        T: PartialEq + Clone + Default,
274    {
275        /// Values.
276        /// __read only__. renewed for each render.
277        values: Vec<T>,
278        /// Can return to default with a user interaction.
279        default_value: Option<T>,
280        /// Selected value, or a value set with set_value().
281        /// There may be a value and still no selected index,
282        /// if the values-vec is empty, or if the value is not in
283        /// values.
284        value: T,
285        /// Index of value.
286        selected: Option<usize>,
287    }
288
289    impl<T> ChoiceCore<T>
290    where
291        T: PartialEq + Clone + Default,
292    {
293        pub fn set_values(&mut self, values: Vec<T>) {
294            self.values = values;
295            // ensure integrity
296            if self.values.is_empty() {
297                self.selected = None;
298            } else {
299                self.selected = self.values.iter().position(|v| *v == self.value);
300            }
301        }
302
303        /// List of values.
304        pub fn values(&self) -> &[T] {
305            &self.values
306        }
307
308        /// Set a default-value other than T::default()
309        ///
310        /// The starting value will still be T::default()
311        /// after this. You must call clear() to use this
312        /// default.
313        pub fn set_default_value(&mut self, default_value: Option<T>) {
314            self.default_value = default_value.clone();
315        }
316
317        /// A default value.
318        pub fn default_value(&self) -> &Option<T> {
319            &self.default_value
320        }
321
322        /// Selected item index.
323        ///
324        /// This may be None and there may still be a valid value.
325        /// This can happen if a value is not in the value-list,
326        /// or if the value-list is empty before the first render.
327        ///
328        /// Use of value() is preferred.
329        pub fn selected(&self) -> Option<usize> {
330            self.selected
331        }
332
333        /// Set the selected item by index.
334        ///
335        /// If the select-idx doesn't match the list of values,
336        /// selected will be None. This may happen before the
337        /// first render, while values is still empty.
338        ///
339        /// Use of set_value() is preferred.
340        pub fn set_selected(&mut self, select: usize) -> bool {
341            let old_sel = self.selected;
342            if self.values.is_empty() {
343                self.selected = None;
344            } else {
345                if let Some(value) = self.values.get(select) {
346                    self.value = value.clone();
347                    self.selected = Some(select);
348                } else {
349                    // don't change value
350                    self.selected = None;
351                }
352            }
353            old_sel != self.selected
354        }
355
356        /// Set the value for this Choice.
357        ///
358        /// The value will be retained even if it is not in
359        /// the value-list. This can happen before the first
360        /// render while the value-list is still empty.
361        /// Or because a divergent value is set here.
362        ///
363        /// The starting value will be T::default().
364        pub fn set_value(&mut self, value: T) -> bool {
365            let old_value = self.value.clone();
366
367            self.value = value;
368            self.selected = self.values.iter().position(|v| *v == self.value);
369
370            old_value != self.value
371        }
372
373        /// Return the selected value, or any value set with set_value()
374        pub fn value(&self) -> T {
375            self.value.clone()
376        }
377
378        pub fn is_empty(&self) -> bool {
379            self.values.is_empty()
380        }
381
382        pub fn clear(&mut self) -> bool {
383            let old_selected = self.selected;
384            let old_value = self.value.clone();
385
386            if let Some(default_value) = &self.default_value {
387                self.value = default_value.clone();
388            }
389
390            self.selected = self.values.iter().position(|v| *v == self.value);
391
392            old_selected != self.selected || old_value != self.value
393        }
394    }
395}
396
397impl Default for ChoiceStyle {
398    fn default() -> Self {
399        Self {
400            style: Default::default(),
401            button: None,
402            select: None,
403            focus: None,
404            block: None,
405            popup: Default::default(),
406            popup_len: None,
407            behave_select: None,
408            behave_close: None,
409            non_exhaustive: NonExhaustive,
410        }
411    }
412}
413
414impl<T> Default for Choice<'_, T>
415where
416    T: PartialEq + Clone + Default,
417{
418    fn default() -> Self {
419        Self {
420            values: Default::default(),
421            default_value: Default::default(),
422            items: Default::default(),
423            style: Default::default(),
424            button_style: Default::default(),
425            select_style: Default::default(),
426            focus_style: Default::default(),
427            block: Default::default(),
428            popup_len: Default::default(),
429            popup_alignment: Alignment::Left,
430            popup_placement: Placement::BelowOrAbove,
431            popup: Default::default(),
432            behave_select: Default::default(),
433            behave_close: Default::default(),
434        }
435    }
436}
437
438impl<'a> Choice<'a, usize> {
439    /// Add items with auto-generated values.
440    #[inline]
441    pub fn auto_items<V: Into<Line<'a>>>(self, items: impl IntoIterator<Item = V>) -> Self {
442        {
443            let mut values = self.values.borrow_mut();
444            let mut itemz = self.items.borrow_mut();
445
446            values.clear();
447            itemz.clear();
448
449            for (k, v) in items.into_iter().enumerate() {
450                values.push(k);
451                itemz.push(v.into());
452            }
453        }
454
455        self
456    }
457
458    /// Add an item with an auto generated value.
459    pub fn auto_item(self, item: impl Into<Line<'a>>) -> Self {
460        let idx = self.values.borrow().len();
461        self.values.borrow_mut().push(idx);
462        self.items.borrow_mut().push(item.into());
463        self
464    }
465}
466
467impl<'a, T> Choice<'a, T>
468where
469    T: PartialEq + Clone + Default,
470{
471    pub fn new() -> Self {
472        Self::default()
473    }
474
475    /// Button text.
476    #[inline]
477    pub fn items<V: Into<Line<'a>>>(self, items: impl IntoIterator<Item = (T, V)>) -> Self {
478        {
479            let mut values = self.values.borrow_mut();
480            let mut itemz = self.items.borrow_mut();
481
482            values.clear();
483            itemz.clear();
484
485            for (k, v) in items.into_iter() {
486                values.push(k);
487                itemz.push(v.into());
488            }
489        }
490
491        self
492    }
493
494    /// Add an item.
495    pub fn item(self, value: T, item: impl Into<Line<'a>>) -> Self {
496        self.values.borrow_mut().push(value);
497        self.items.borrow_mut().push(item.into());
498        self
499    }
500
501    /// Can return to default with user interaction.
502    pub fn default_value(mut self, default: T) -> Self {
503        self.default_value = Some(default);
504        self
505    }
506
507    /// Combined styles.
508    pub fn styles(mut self, styles: ChoiceStyle) -> Self {
509        self.style = styles.style;
510        if styles.button.is_some() {
511            self.button_style = styles.button;
512        }
513        if styles.select.is_some() {
514            self.select_style = styles.select;
515        }
516        if styles.focus.is_some() {
517            self.focus_style = styles.focus;
518        }
519        if styles.block.is_some() {
520            self.block = styles.block;
521        }
522        if let Some(select) = styles.behave_select {
523            self.behave_select = select;
524        }
525        if let Some(close) = styles.behave_close {
526            self.behave_close = close;
527        }
528        self.block = self.block.map(|v| v.style(self.style));
529        if let Some(alignment) = styles.popup.alignment {
530            self.popup_alignment = alignment;
531        }
532        if let Some(placement) = styles.popup.placement {
533            self.popup_placement = placement;
534        }
535        if styles.popup_len.is_some() {
536            self.popup_len = styles.popup_len;
537        }
538        self.popup = self.popup.styles(styles.popup);
539        self
540    }
541
542    /// Base style.
543    pub fn style(mut self, style: Style) -> Self {
544        self.style = style;
545        self.block = self.block.map(|v| v.style(self.style));
546        self
547    }
548
549    /// Style for the down button.
550    pub fn button_style(mut self, style: Style) -> Self {
551        self.button_style = Some(style);
552        self
553    }
554
555    /// Selection in the list.
556    pub fn select_style(mut self, style: Style) -> Self {
557        self.select_style = Some(style);
558        self
559    }
560
561    /// Focused style.
562    pub fn focus_style(mut self, style: Style) -> Self {
563        self.focus_style = Some(style);
564        self
565    }
566
567    /// Block for the main widget.
568    pub fn block(mut self, block: Block<'a>) -> Self {
569        self.block = Some(block);
570        self.block = self.block.map(|v| v.style(self.style));
571        self
572    }
573
574    /// Alignment of the popup.
575    ///
576    /// __Default__
577    /// Default is Left.
578    pub fn popup_alignment(mut self, alignment: Alignment) -> Self {
579        self.popup_alignment = alignment;
580        self
581    }
582
583    /// Placement of the popup.
584    ///
585    /// __Default__
586    /// Default is BelowOrAbove.
587    pub fn popup_placement(mut self, placement: Placement) -> Self {
588        self.popup_placement = placement;
589        self
590    }
591
592    /// Outer boundary for the popup.
593    pub fn popup_boundary(mut self, boundary: Rect) -> Self {
594        self.popup = self.popup.boundary(boundary);
595        self
596    }
597
598    /// Override the popup length.
599    ///
600    /// __Default__
601    /// Defaults to the number of items or 5.
602    pub fn popup_len(mut self, len: u16) -> Self {
603        self.popup_len = Some(len);
604        self
605    }
606
607    /// Base style for the popup.
608    pub fn popup_style(mut self, style: Style) -> Self {
609        self.popup = self.popup.style(style);
610        self
611    }
612
613    /// Block for the popup.
614    pub fn popup_block(mut self, block: Block<'a>) -> Self {
615        self.popup = self.popup.block(block);
616        self
617    }
618
619    /// Scroll for the popup.
620    pub fn popup_scroll(mut self, scroll: Scroll<'a>) -> Self {
621        self.popup = self.popup.v_scroll(scroll);
622        self
623    }
624
625    /// Adds an extra offset to the widget area.
626    ///
627    /// This can be used to
628    /// * place the widget under the mouse cursor.
629    /// * align the widget not by the outer bounds but by
630    ///   the text content.
631    pub fn popup_offset(mut self, offset: (i16, i16)) -> Self {
632        self.popup = self.popup.offset(offset);
633        self
634    }
635
636    /// Sets only the x offset.
637    /// See [offset](Self::popup_offset)
638    pub fn popup_x_offset(mut self, offset: i16) -> Self {
639        self.popup = self.popup.x_offset(offset);
640        self
641    }
642
643    /// Sets only the y offset.
644    /// See [offset](Self::popup_offset)
645    pub fn popup_y_offset(mut self, offset: i16) -> Self {
646        self.popup = self.popup.y_offset(offset);
647        self
648    }
649
650    /// Sets the behaviour for selecting from the list.
651    pub fn behave_select(mut self, select: ChoiceSelect) -> Self {
652        self.behave_select = select;
653        self
654    }
655
656    /// Sets the behaviour for closing the list.
657    pub fn behave_close(mut self, close: ChoiceClose) -> Self {
658        self.behave_close = close;
659        self
660    }
661
662    /// Inherent width.
663    pub fn width(&self) -> u16 {
664        let w = self
665            .items
666            .borrow()
667            .iter()
668            .map(|v| v.width())
669            .max()
670            .unwrap_or_default();
671
672        w as u16 + block_size(&self.block).width
673    }
674
675    /// Inherent height.
676    pub fn height(&self) -> u16 {
677        1 + block_size(&self.block).height
678    }
679
680    /// Choice itself doesn't render.
681    ///
682    /// This builds the widgets from the parameters set for Choice.
683    pub fn into_widgets(self) -> (ChoiceWidget<'a, T>, ChoicePopup<'a, T>) {
684        (
685            ChoiceWidget {
686                values: self.values,
687                default_value: self.default_value,
688                items: self.items.clone(),
689                style: self.style,
690                button_style: self.button_style,
691                focus_style: self.focus_style,
692                block: self.block,
693                len: self.popup_len,
694                behave_select: self.behave_select,
695                behave_close: self.behave_close,
696                _phantom: Default::default(),
697            },
698            ChoicePopup {
699                items: self.items.clone(),
700                style: self.style,
701                select_style: self.select_style,
702                popup: self.popup,
703                popup_alignment: self.popup_alignment,
704                popup_placement: self.popup_placement,
705                popup_len: self.popup_len,
706                _phantom: Default::default(),
707            },
708        )
709    }
710}
711
712impl<'a, T> ChoiceWidget<'a, T>
713where
714    T: PartialEq + Clone + Default,
715{
716    /// Inherent width.
717    pub fn width(&self) -> u16 {
718        let w = self
719            .items
720            .borrow()
721            .iter()
722            .map(|v| v.width())
723            .max()
724            .unwrap_or_default();
725
726        w as u16 + block_size(&self.block).width
727    }
728
729    /// Inherent height.
730    pub fn height(&self) -> u16 {
731        1 + block_size(&self.block).height
732    }
733}
734
735#[cfg(feature = "unstable-widget-ref")]
736impl<'a, T> StatefulWidgetRef for ChoiceWidget<'a, T>
737where
738    T: PartialEq + Clone + Default,
739{
740    type State = ChoiceState<T>;
741
742    fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
743        state.core.set_values(self.values.borrow().clone());
744        if let Some(default_value) = self.default_value.clone() {
745            state.core.set_default_value(Some(default_value));
746        }
747
748        render_choice(self, area, buf, state);
749    }
750}
751
752impl<T> StatefulWidget for ChoiceWidget<'_, T>
753where
754    T: PartialEq + Clone + Default,
755{
756    type State = ChoiceState<T>;
757
758    fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
759        state.core.set_values(self.values.take());
760        if let Some(default_value) = self.default_value.take() {
761            state.core.set_default_value(Some(default_value));
762        }
763
764        render_choice(&self, area, buf, state);
765    }
766}
767
768fn render_choice<T: PartialEq + Clone + Default>(
769    widget: &ChoiceWidget<'_, T>,
770    area: Rect,
771    buf: &mut Buffer,
772    state: &mut ChoiceState<T>,
773) {
774    state.area = area;
775    state.behave_select = widget.behave_select;
776    state.behave_close = widget.behave_close;
777
778    if !state.popup.is_active() {
779        let len = widget
780            .len
781            .unwrap_or_else(|| min(5, widget.items.borrow().len()) as u16);
782        state.popup.v_scroll.max_offset = widget.items.borrow().len().saturating_sub(len as usize);
783        state.popup.v_scroll.page_len = len as usize;
784        if let Some(selected) = state.core.selected() {
785            state.popup.v_scroll.scroll_to_pos(selected);
786        }
787    }
788
789    state.nav_char.clear();
790    state.nav_char.extend(widget.items.borrow().iter().map(|v| {
791        v.spans
792            .first()
793            .and_then(|v| v.content.as_ref().chars().next())
794            .map_or(Vec::default(), |c| c.to_lowercase().collect::<Vec<_>>())
795    }));
796
797    let inner = widget.block.inner_if_some(area);
798
799    state.item_area = Rect::new(
800        inner.x,
801        inner.y,
802        inner.width.saturating_sub(3),
803        inner.height,
804    );
805    state.button_area = Rect::new(
806        inner.right().saturating_sub(min(3, inner.width)),
807        inner.y,
808        3,
809        inner.height,
810    );
811
812    let style = widget.style;
813    let focus_style = widget.focus_style.unwrap_or(revert_style(widget.style));
814
815    if state.is_focused() {
816        if widget.block.is_some() {
817            widget.block.render(area, buf);
818        } else {
819            buf.set_style(inner, style);
820        }
821        buf.set_style(inner, focus_style);
822    } else {
823        if widget.block.is_some() {
824            widget.block.render(area, buf);
825        } else {
826            buf.set_style(inner, style);
827        }
828        if let Some(button_style) = widget.button_style {
829            buf.set_style(state.button_area, button_style);
830        }
831    }
832
833    if let Some(selected) = state.core.selected() {
834        if let Some(item) = widget.items.borrow().get(selected) {
835            item.render(state.item_area, buf);
836        }
837    }
838
839    let dy = if (state.button_area.height & 1) == 1 {
840        state.button_area.height / 2
841    } else {
842        state.button_area.height.saturating_sub(1) / 2
843    };
844    let bc = if state.is_popup_active() {
845        " â—† "
846    } else {
847        " â–¼ "
848    };
849    Span::from(bc).render(
850        Rect::new(state.button_area.x, state.button_area.y + dy, 3, 1),
851        buf,
852    );
853}
854
855impl<T> ChoicePopup<'_, T>
856where
857    T: PartialEq + Clone + Default,
858{
859    /// Calculate the layout for the popup before rendering.
860    /// Area is the area of the ChoiceWidget not the ChoicePopup.
861    pub fn layout(&self, area: Rect, buf: &mut Buffer, state: &mut ChoiceState<T>) -> Rect {
862        if state.popup.is_active() {
863            let len = min(
864                self.popup_len.unwrap_or(5),
865                self.items.borrow().len() as u16,
866            );
867            let popup_len = len + self.popup.get_block_size().height;
868            let pop_area = Rect::new(0, 0, area.width, popup_len);
869
870            self.popup
871                .ref_constraint(
872                    self.popup_placement
873                        .into_constraint(self.popup_alignment, area),
874                )
875                .layout(pop_area, buf)
876        } else {
877            Rect::default()
878        }
879    }
880}
881
882#[cfg(feature = "unstable-widget-ref")]
883impl<T> StatefulWidgetRef for ChoicePopup<'_, T>
884where
885    T: PartialEq + Clone + Default,
886{
887    type State = ChoiceState<T>;
888
889    fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
890        render_popup(&self, area, buf, state);
891    }
892}
893
894impl<T> StatefulWidget for ChoicePopup<'_, T>
895where
896    T: PartialEq + Clone + Default,
897{
898    type State = ChoiceState<T>;
899
900    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
901        render_popup(&self, area, buf, state);
902    }
903}
904
905fn render_popup<T: PartialEq + Clone + Default>(
906    widget: &ChoicePopup<'_, T>,
907    area: Rect,
908    buf: &mut Buffer,
909    state: &mut ChoiceState<T>,
910) {
911    if state.popup.is_active() {
912        let len = min(
913            widget.popup_len.unwrap_or(5),
914            widget.items.borrow().len() as u16,
915        );
916        let popup_len = len + widget.popup.get_block_size().height;
917        let pop_area = Rect::new(0, 0, area.width, popup_len);
918
919        let popup_style = widget.popup.style;
920        let select_style = widget.select_style.unwrap_or(revert_style(widget.style));
921
922        widget
923            .popup
924            .ref_constraint(
925                widget
926                    .popup_placement
927                    .into_constraint(widget.popup_alignment, area),
928            )
929            .render(pop_area, buf, &mut state.popup);
930
931        let inner = state.popup.widget_area;
932
933        state.popup.v_scroll.max_offset = widget
934            .items
935            .borrow()
936            .len()
937            .saturating_sub(inner.height as usize);
938        state.popup.v_scroll.page_len = inner.height as usize;
939
940        state.item_areas.clear();
941        let mut row = inner.y;
942        let mut idx = state.popup.v_scroll.offset;
943        loop {
944            if row >= inner.bottom() {
945                break;
946            }
947
948            let item_area = Rect::new(inner.x, row, inner.width, 1);
949            state.item_areas.push(item_area);
950
951            if let Some(item) = widget.items.borrow().get(idx) {
952                let style = if state.core.selected() == Some(idx) {
953                    popup_style.patch(select_style)
954                } else {
955                    popup_style
956                };
957
958                buf.set_style(item_area, style);
959                item.render(item_area, buf);
960            } else {
961                // noop?
962            }
963
964            row += 1;
965            idx += 1;
966        }
967    } else {
968        state.popup.clear_areas();
969    }
970}
971
972impl<T> Clone for ChoiceState<T>
973where
974    T: PartialEq + Clone + Default,
975{
976    fn clone(&self) -> Self {
977        Self {
978            area: self.area,
979            nav_char: self.nav_char.clone(),
980            item_area: self.item_area,
981            button_area: self.button_area,
982            item_areas: self.item_areas.clone(),
983            core: self.core.clone(),
984            popup: self.popup.clone(),
985            behave_select: self.behave_select,
986            behave_close: self.behave_close,
987            focus: FocusFlag::named(self.focus.name()),
988            mouse: Default::default(),
989            non_exhaustive: NonExhaustive,
990        }
991    }
992}
993
994impl<T> Default for ChoiceState<T>
995where
996    T: PartialEq + Clone + Default,
997{
998    fn default() -> Self {
999        Self {
1000            area: Default::default(),
1001            nav_char: Default::default(),
1002            item_area: Default::default(),
1003            button_area: Default::default(),
1004            item_areas: Default::default(),
1005            core: Default::default(),
1006            popup: Default::default(),
1007            behave_select: Default::default(),
1008            behave_close: Default::default(),
1009            focus: Default::default(),
1010            mouse: Default::default(),
1011            non_exhaustive: NonExhaustive,
1012        }
1013    }
1014}
1015
1016impl<T> HasFocus for ChoiceState<T>
1017where
1018    T: PartialEq + Clone + Default,
1019{
1020    fn build(&self, builder: &mut FocusBuilder) {
1021        builder.widget_with_flags(self.focus(), self.area(), 0, self.navigable());
1022        builder.widget_with_flags(self.focus(), self.popup.area, 1, Navigation::Mouse);
1023    }
1024
1025    fn focus(&self) -> FocusFlag {
1026        self.focus.clone()
1027    }
1028
1029    fn area(&self) -> Rect {
1030        self.area
1031    }
1032}
1033
1034impl<T> RelocatableState for ChoiceState<T>
1035where
1036    T: PartialEq + Clone + Default,
1037{
1038    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1039        self.area = relocate_area(self.area, shift, clip);
1040        self.item_area = relocate_area(self.item_area, shift, clip);
1041        self.button_area = relocate_area(self.button_area, shift, clip);
1042        relocate_areas(&mut self.item_areas, shift, clip);
1043        self.popup.relocate(shift, clip);
1044    }
1045}
1046
1047impl<T> ChoiceState<T>
1048where
1049    T: PartialEq + Clone + Default,
1050{
1051    pub fn new() -> Self {
1052        Self::default()
1053    }
1054
1055    pub fn named(name: &str) -> Self {
1056        Self {
1057            focus: FocusFlag::named(name),
1058            ..Default::default()
1059        }
1060    }
1061
1062    /// Popup is active?
1063    pub fn is_popup_active(&self) -> bool {
1064        self.popup.is_active()
1065    }
1066
1067    /// Flip the popup state.
1068    pub fn flip_popup_active(&mut self) {
1069        self.popup.flip_active();
1070    }
1071
1072    /// Show the popup.
1073    pub fn set_popup_active(&mut self, active: bool) -> bool {
1074        self.popup.set_active(active)
1075    }
1076
1077    /// Set a default-value other than T::default()
1078    ///
1079    /// The starting value will still be T::default()
1080    /// after this. You must call clear() to use this
1081    /// default.
1082    ///
1083    /// This default will be overridden by a default set
1084    /// on the widget.
1085    pub fn set_default_value(&mut self, default_value: Option<T>) {
1086        self.core.set_default_value(default_value);
1087    }
1088
1089    /// A default value.
1090    pub fn default_value(&self) -> &Option<T> {
1091        self.core.default_value()
1092    }
1093
1094    /// Select the given value.
1095    ///
1096    /// If the value doesn't exist in the list or the list is
1097    /// empty the value will still be set, but selected will be
1098    /// None. The list will be empty before the first render, but
1099    /// the first thing render will do is set the list of values.
1100    /// This will adjust the selected index if possible.
1101    /// It's still ok to set a value here that can not be represented.
1102    /// As long as there is no user interaction, the same value
1103    /// will be returned by value().
1104    pub fn set_value(&mut self, value: T) -> bool {
1105        self.core.set_value(value)
1106    }
1107
1108    /// Get the selected value.
1109    pub fn value(&self) -> T {
1110        self.core.value()
1111    }
1112
1113    /// Select the default value or T::default.
1114    pub fn clear(&mut self) -> bool {
1115        self.core.clear()
1116    }
1117
1118    /// Select the value at index. This will set the value
1119    /// to the given index in the value-list. If the index is
1120    /// out of bounds or the value-list is empty it will
1121    /// set selected to None and leave the value as is.
1122    /// The list is empty before the first render so this
1123    /// may not work as expected.
1124    ///
1125    /// The selected index is a best effort artefact, the main
1126    /// thing is the value itself.
1127    ///
1128    /// Use of set_value() is preferred.
1129    pub fn select(&mut self, select: usize) -> bool {
1130        self.core.set_selected(select)
1131    }
1132
1133    /// Returns the selected index or None if the
1134    /// value is not in the list or the list is empty.
1135    ///
1136    /// You can still get the value set with set_value() though.
1137    pub fn selected(&self) -> Option<usize> {
1138        self.core.selected()
1139    }
1140
1141    /// Any items?
1142    pub fn is_empty(&self) -> bool {
1143        self.core.values().is_empty()
1144    }
1145
1146    /// Number of items.
1147    pub fn len(&self) -> usize {
1148        self.core.values().len()
1149    }
1150
1151    /// Scroll offset for the item list.
1152    pub fn clear_offset(&mut self) {
1153        self.popup.v_scroll.set_offset(0);
1154    }
1155
1156    /// Scroll offset for the item list.
1157    pub fn set_offset(&mut self, offset: usize) -> bool {
1158        self.popup.v_scroll.set_offset(offset)
1159    }
1160
1161    /// Scroll offset for the item list.
1162    pub fn offset(&self) -> usize {
1163        self.popup.v_scroll.offset()
1164    }
1165
1166    /// Scroll offset for the item list.
1167    pub fn max_offset(&self) -> usize {
1168        self.popup.v_scroll.max_offset()
1169    }
1170
1171    /// Page length for the item list.
1172    pub fn page_len(&self) -> usize {
1173        self.popup.v_scroll.page_len()
1174    }
1175
1176    /// Scroll unit for the item list.
1177    pub fn scroll_by(&self) -> usize {
1178        self.popup.v_scroll.scroll_by()
1179    }
1180
1181    /// Scroll the item list to the selected value.
1182    pub fn scroll_to_selected(&mut self) -> bool {
1183        if let Some(selected) = self.core.selected() {
1184            self.popup.v_scroll.scroll_to_pos(selected)
1185        } else {
1186            false
1187        }
1188    }
1189}
1190
1191impl<T> ChoiceState<T>
1192where
1193    T: PartialEq + Clone + Default,
1194{
1195    /// Select by first character.
1196    pub fn select_by_char(&mut self, c: char) -> bool {
1197        if self.nav_char.is_empty() {
1198            return false;
1199        }
1200        let c = c.to_lowercase().collect::<Vec<_>>();
1201
1202        let (mut idx, end_loop) = if let Some(idx) = self.core.selected() {
1203            (idx + 1, idx)
1204        } else {
1205            if self.nav_char[0] == c {
1206                self.core.set_selected(0);
1207                return true;
1208            } else {
1209                (1, 0)
1210            }
1211        };
1212        loop {
1213            if idx >= self.nav_char.len() {
1214                idx = 0;
1215            }
1216            if idx == end_loop {
1217                break;
1218            }
1219
1220            if self.nav_char[idx] == c {
1221                self.core.set_selected(idx);
1222                return true;
1223            }
1224
1225            idx += 1;
1226        }
1227        false
1228    }
1229
1230    /// Select by first character. Reverse direction
1231    pub fn reverse_select_by_char(&mut self, c: char) -> bool {
1232        if self.nav_char.is_empty() {
1233            return false;
1234        }
1235        let c = c.to_lowercase().collect::<Vec<_>>();
1236
1237        let (mut idx, end_loop) = if let Some(idx) = self.core.selected() {
1238            if idx == 0 {
1239                (self.nav_char.len() - 1, 0)
1240            } else {
1241                (idx - 1, idx)
1242            }
1243        } else {
1244            if self.nav_char.last() == Some(&c) {
1245                self.core.set_selected(self.nav_char.len() - 1);
1246                return true;
1247            } else {
1248                (self.nav_char.len() - 1, 0)
1249            }
1250        };
1251        loop {
1252            if self.nav_char[idx] == c {
1253                self.core.set_selected(idx);
1254                return true;
1255            }
1256
1257            if idx == end_loop {
1258                break;
1259            }
1260
1261            if idx == 0 {
1262                idx = self.nav_char.len() - 1;
1263            } else {
1264                idx -= 1;
1265            }
1266        }
1267        false
1268    }
1269
1270    /// Select at position
1271    pub fn move_to(&mut self, n: usize) -> ChoiceOutcome {
1272        let old_selected = self.selected();
1273        let r1 = self.popup.set_active(true);
1274        let r2 = self.select(n);
1275        let r3 = self.scroll_to_selected();
1276        if old_selected != self.selected() {
1277            ChoiceOutcome::Value
1278        } else if r1 || r2 || r3 {
1279            ChoiceOutcome::Changed
1280        } else {
1281            ChoiceOutcome::Continue
1282        }
1283    }
1284
1285    /// Select next entry.
1286    pub fn move_down(&mut self, n: usize) -> ChoiceOutcome {
1287        if self.core.is_empty() {
1288            return ChoiceOutcome::Continue;
1289        }
1290
1291        let old_selected = self.selected();
1292        let r1 = self.popup.set_active(true);
1293        let idx = if let Some(idx) = self.core.selected() {
1294            idx + n
1295        } else {
1296            n.saturating_sub(1)
1297        };
1298        let idx = idx.clamp(0, self.len() - 1);
1299        let r2 = self.core.set_selected(idx);
1300        let r3 = self.scroll_to_selected();
1301
1302        if old_selected != self.selected() {
1303            ChoiceOutcome::Value
1304        } else if r1 || r2 || r3 {
1305            ChoiceOutcome::Changed
1306        } else {
1307            ChoiceOutcome::Continue
1308        }
1309    }
1310
1311    /// Select prev entry.
1312    pub fn move_up(&mut self, n: usize) -> ChoiceOutcome {
1313        if self.core.is_empty() {
1314            return ChoiceOutcome::Continue;
1315        }
1316
1317        let old_selected = self.selected();
1318        let r1 = self.popup.set_active(true);
1319        let idx = if let Some(idx) = self.core.selected() {
1320            idx.saturating_sub(n)
1321        } else {
1322            0
1323        };
1324        let idx = idx.clamp(0, self.len() - 1);
1325        let r2 = self.core.set_selected(idx);
1326        let r3 = self.scroll_to_selected();
1327
1328        if old_selected != self.selected() {
1329            ChoiceOutcome::Value
1330        } else if r1 || r2 || r3 {
1331            ChoiceOutcome::Changed
1332        } else {
1333            ChoiceOutcome::Continue
1334        }
1335    }
1336}
1337
1338impl<T: PartialEq + Clone + Default> HandleEvent<crossterm::event::Event, Popup, ChoiceOutcome>
1339    for ChoiceState<T>
1340{
1341    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Popup) -> ChoiceOutcome {
1342        if self.lost_focus() {
1343            self.set_popup_active(false);
1344            // focus change triggers the repaint.
1345        }
1346
1347        let r = if self.is_focused() {
1348            match event {
1349                ct_event!(key press ' ') | ct_event!(keycode press Enter) => {
1350                    self.flip_popup_active();
1351                    ChoiceOutcome::Changed
1352                }
1353                ct_event!(keycode press Esc) => {
1354                    if self.set_popup_active(false) {
1355                        ChoiceOutcome::Changed
1356                    } else {
1357                        ChoiceOutcome::Continue
1358                    }
1359                }
1360                ct_event!(key press c) => {
1361                    if self.select_by_char(*c) {
1362                        self.scroll_to_selected();
1363                        ChoiceOutcome::Value
1364                    } else {
1365                        ChoiceOutcome::Continue
1366                    }
1367                }
1368                ct_event!(key press SHIFT-c) => {
1369                    if self.reverse_select_by_char(*c) {
1370                        self.scroll_to_selected();
1371                        ChoiceOutcome::Value
1372                    } else {
1373                        ChoiceOutcome::Continue
1374                    }
1375                }
1376                ct_event!(keycode press Delete) | ct_event!(keycode press Backspace) => {
1377                    if self.clear() {
1378                        ChoiceOutcome::Value
1379                    } else {
1380                        ChoiceOutcome::Continue
1381                    }
1382                }
1383                ct_event!(keycode press Down) => self.move_down(1),
1384                ct_event!(keycode press Up) => self.move_up(1),
1385                ct_event!(keycode press PageUp) => self.move_up(self.page_len()),
1386                ct_event!(keycode press PageDown) => self.move_down(self.page_len()),
1387                ct_event!(keycode press Home) => self.move_to(0),
1388                ct_event!(keycode press End) => self.move_to(self.len().saturating_sub(1)),
1389                _ => ChoiceOutcome::Continue,
1390            }
1391        } else {
1392            ChoiceOutcome::Continue
1393        };
1394
1395        if !r.is_consumed() {
1396            self.handle(event, MouseOnly)
1397        } else {
1398            r
1399        }
1400    }
1401}
1402
1403impl<T: PartialEq + Clone + Default> HandleEvent<crossterm::event::Event, MouseOnly, ChoiceOutcome>
1404    for ChoiceState<T>
1405{
1406    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> ChoiceOutcome {
1407        let r0 = handle_mouse(self, event);
1408        let r1 = handle_select(self, event);
1409        let r2 = handle_close(self, event);
1410        let mut r = max(r0, max(r1, r2));
1411
1412        r = r.or_else(|| mouse_trap(event, self.popup.area).into());
1413
1414        self.popup.active.set_lost(false);
1415        self.popup.active.set_gained(false);
1416        r
1417    }
1418}
1419
1420fn handle_mouse<T: PartialEq + Clone + Default>(
1421    state: &mut ChoiceState<T>,
1422    event: &crossterm::event::Event,
1423) -> ChoiceOutcome {
1424    match event {
1425        ct_event!(mouse down Left for x,y)
1426            if state.item_area.contains((*x, *y).into())
1427                || state.button_area.contains((*x, *y).into()) =>
1428        {
1429            if !state.gained_focus() && !state.popup.active.lost() {
1430                state.flip_popup_active();
1431                ChoiceOutcome::Changed
1432            } else {
1433                // hide is down by self.popup.handle() as this click
1434                // is outside the popup area!!
1435                ChoiceOutcome::Continue
1436            }
1437        }
1438        ct_event!(mouse down Left for x,y)
1439        | ct_event!(mouse down Right for x,y)
1440        | ct_event!(mouse down Middle for x,y)
1441            if !state.item_area.contains((*x, *y).into())
1442                && !state.button_area.contains((*x, *y).into()) =>
1443        {
1444            match state.popup.handle(event, Popup) {
1445                PopupOutcome::Hide => {
1446                    state.set_popup_active(false);
1447                    ChoiceOutcome::Changed
1448                }
1449                r => r.into(),
1450            }
1451        }
1452        _ => ChoiceOutcome::Continue,
1453    }
1454}
1455
1456fn handle_select<T: PartialEq + Clone + Default>(
1457    state: &mut ChoiceState<T>,
1458    event: &crossterm::event::Event,
1459) -> ChoiceOutcome {
1460    match state.behave_select {
1461        ChoiceSelect::MouseScroll => {
1462            let mut sas = ScrollAreaState::new()
1463                .area(state.popup.area)
1464                .v_scroll(&mut state.popup.v_scroll);
1465            let mut r = match sas.handle(event, MouseOnly) {
1466                ScrollOutcome::Up(n) => state.move_up(n),
1467                ScrollOutcome::Down(n) => state.move_down(n),
1468                ScrollOutcome::VPos(n) => state.move_to(n),
1469                _ => ChoiceOutcome::Continue,
1470            };
1471
1472            r = r.or_else(|| match event {
1473                ct_event!(mouse down Left for x,y)
1474                    if state.popup.widget_area.contains((*x, *y).into()) =>
1475                {
1476                    if let Some(n) = item_at(&state.item_areas, *x, *y) {
1477                        state.move_to(state.offset() + n)
1478                    } else {
1479                        ChoiceOutcome::Unchanged
1480                    }
1481                }
1482                ct_event!(mouse drag Left for x,y)
1483                    if state.popup.widget_area.contains((*x, *y).into()) =>
1484                {
1485                    if let Some(n) = item_at(&state.item_areas, *x, *y) {
1486                        state.move_to(state.offset() + n)
1487                    } else {
1488                        ChoiceOutcome::Unchanged
1489                    }
1490                }
1491                _ => ChoiceOutcome::Continue,
1492            });
1493            r
1494        }
1495        ChoiceSelect::MouseMove => {
1496            // effect: move the content below the mouse and keep visible selection.
1497            let mut r = if let Some(selected) = state.core.selected() {
1498                let rel_sel = selected.saturating_sub(state.offset());
1499                let mut sas = ScrollAreaState::new()
1500                    .area(state.popup.area)
1501                    .v_scroll(&mut state.popup.v_scroll);
1502                match sas.handle(event, MouseOnly) {
1503                    ScrollOutcome::Up(n) => {
1504                        state.popup.v_scroll.scroll_up(n);
1505                        if state.select(state.offset() + rel_sel) {
1506                            ChoiceOutcome::Value
1507                        } else {
1508                            ChoiceOutcome::Unchanged
1509                        }
1510                    }
1511                    ScrollOutcome::Down(n) => {
1512                        state.popup.v_scroll.scroll_down(n);
1513                        if state.select(state.offset() + rel_sel) {
1514                            ChoiceOutcome::Value
1515                        } else {
1516                            ChoiceOutcome::Unchanged
1517                        }
1518                    }
1519                    ScrollOutcome::VPos(n) => {
1520                        if state.popup.v_scroll.set_offset(n) {
1521                            ChoiceOutcome::Value
1522                        } else {
1523                            ChoiceOutcome::Unchanged
1524                        }
1525                    }
1526                    _ => ChoiceOutcome::Continue,
1527                }
1528            } else {
1529                ChoiceOutcome::Continue
1530            };
1531
1532            r = r.or_else(|| match event {
1533                ct_event!(mouse moved for x,y)
1534                    if state.popup.widget_area.contains((*x, *y).into()) =>
1535                {
1536                    if let Some(n) = item_at(&state.item_areas, *x, *y) {
1537                        state.move_to(state.offset() + n)
1538                    } else {
1539                        ChoiceOutcome::Unchanged
1540                    }
1541                }
1542                _ => ChoiceOutcome::Continue,
1543            });
1544            r
1545        }
1546        ChoiceSelect::MouseClick => {
1547            // effect: move the content below the mouse and keep visible selection.
1548            let mut sas = ScrollAreaState::new()
1549                .area(state.popup.area)
1550                .v_scroll(&mut state.popup.v_scroll);
1551            let mut r = match sas.handle(event, MouseOnly) {
1552                ScrollOutcome::Up(n) => {
1553                    if state.popup.v_scroll.scroll_up(n) {
1554                        ChoiceOutcome::Changed
1555                    } else {
1556                        ChoiceOutcome::Unchanged
1557                    }
1558                }
1559                ScrollOutcome::Down(n) => {
1560                    if state.popup.v_scroll.scroll_down(n) {
1561                        ChoiceOutcome::Changed
1562                    } else {
1563                        ChoiceOutcome::Unchanged
1564                    }
1565                }
1566                ScrollOutcome::VPos(n) => {
1567                    if state.popup.v_scroll.set_offset(n) {
1568                        ChoiceOutcome::Changed
1569                    } else {
1570                        ChoiceOutcome::Unchanged
1571                    }
1572                }
1573                _ => ChoiceOutcome::Continue,
1574            };
1575
1576            r = r.or_else(|| match event {
1577                ct_event!(mouse down Left for x,y)
1578                    if state.popup.widget_area.contains((*x, *y).into()) =>
1579                {
1580                    if let Some(n) = item_at(&state.item_areas, *x, *y) {
1581                        state.move_to(state.offset() + n)
1582                    } else {
1583                        ChoiceOutcome::Unchanged
1584                    }
1585                }
1586                ct_event!(mouse drag Left for x,y)
1587                    if state.popup.widget_area.contains((*x, *y).into()) =>
1588                {
1589                    if let Some(n) = item_at(&state.item_areas, *x, *y) {
1590                        state.move_to(state.offset() + n)
1591                    } else {
1592                        ChoiceOutcome::Unchanged
1593                    }
1594                }
1595                _ => ChoiceOutcome::Continue,
1596            });
1597            r
1598        }
1599    }
1600}
1601
1602fn handle_close<T: PartialEq + Clone + Default>(
1603    state: &mut ChoiceState<T>,
1604    event: &crossterm::event::Event,
1605) -> ChoiceOutcome {
1606    match state.behave_close {
1607        ChoiceClose::SingleClick => match event {
1608            ct_event!(mouse down Left for x,y)
1609                if state.popup.widget_area.contains((*x, *y).into()) =>
1610            {
1611                if let Some(n) = item_at(&state.item_areas, *x, *y) {
1612                    let r = state.move_to(state.offset() + n);
1613                    let s = if state.set_popup_active(false) {
1614                        ChoiceOutcome::Changed
1615                    } else {
1616                        ChoiceOutcome::Unchanged
1617                    };
1618                    max(r, s)
1619                } else {
1620                    ChoiceOutcome::Unchanged
1621                }
1622            }
1623            _ => ChoiceOutcome::Continue,
1624        },
1625        ChoiceClose::DoubleClick => match event {
1626            ct_event!(mouse any for m) if state.mouse.doubleclick(state.popup.widget_area, m) => {
1627                if let Some(n) = item_at(&state.item_areas, m.column, m.row) {
1628                    let r = state.move_to(state.offset() + n);
1629                    let s = if state.set_popup_active(false) {
1630                        ChoiceOutcome::Changed
1631                    } else {
1632                        ChoiceOutcome::Unchanged
1633                    };
1634                    max(r, s)
1635                } else {
1636                    ChoiceOutcome::Unchanged
1637                }
1638            }
1639            _ => ChoiceOutcome::Continue,
1640        },
1641    }
1642}
1643
1644/// Handle events for the popup.
1645/// Call before other handlers to deal with intersections
1646/// with other widgets.
1647pub fn handle_popup<T: PartialEq + Clone + Default>(
1648    state: &mut ChoiceState<T>,
1649    focus: bool,
1650    event: &crossterm::event::Event,
1651) -> ChoiceOutcome {
1652    state.focus.set(focus);
1653    HandleEvent::handle(state, event, Popup)
1654}
1655
1656/// Handle only mouse-events.
1657pub fn handle_mouse_events<T: PartialEq + Clone + Default>(
1658    state: &mut ChoiceState<T>,
1659    event: &crossterm::event::Event,
1660) -> ChoiceOutcome {
1661    HandleEvent::handle(state, event, MouseOnly)
1662}