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