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<'a>,
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<'a>,
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        #[allow(deprecated)]
555        if let Some(popup_style) = styles.popup_style {
556            self.popup_style = popup_style;
557        } else {
558            self.popup_style = styles.popup.style;
559        }
560        #[allow(deprecated)]
561        if let Some(popup_scroll) = styles.popup_scroll {
562            self.popup_scroll = self.popup_scroll.map(|v| v.styles(popup_scroll));
563        } else if let Some(popup_scroll) = styles.popup.scroll {
564            self.popup_scroll = self.popup_scroll.map(|v| v.styles(popup_scroll));
565        }
566
567        self.popup_block = self.popup_block.map(|v| v.style(self.popup_style));
568        #[allow(deprecated)]
569        if let Some(border_style) = styles.popup_border {
570            self.popup_block = self.popup_block.map(|v| v.border_style(border_style));
571        } else if let Some(border_style) = styles.popup.border_style {
572            self.popup_block = self.popup_block.map(|v| v.border_style(border_style));
573        }
574        #[allow(deprecated)]
575        if styles.popup_block.is_some() {
576            self.popup_block = styles.popup_block;
577        } else if styles.popup.block.is_some() {
578            self.popup_block = styles.popup.block;
579        }
580
581        if styles.popup_len.is_some() {
582            self.popup_len = styles.popup_len;
583        }
584
585        self
586    }
587
588    /// Base style.
589    pub fn style(mut self, style: Style) -> Self {
590        self.style = style;
591        self.block = self.block.map(|v| v.style(self.style));
592        self
593    }
594
595    /// Style for the down button.
596    pub fn button_style(mut self, style: Style) -> Self {
597        self.button_style = Some(style);
598        self
599    }
600
601    /// Selection in the list.
602    pub fn select_style(mut self, style: Style) -> Self {
603        self.select_style = Some(style);
604        self
605    }
606
607    /// Focused style.
608    pub fn focus_style(mut self, style: Style) -> Self {
609        self.focus_style = Some(style);
610        self
611    }
612
613    /// Block for the main widget.
614    pub fn block(mut self, block: Block<'a>) -> Self {
615        self.block = Some(block);
616        self.block = self.block.map(|v| v.style(self.style));
617        self
618    }
619
620    /// Alignment of the popup.
621    ///
622    /// __Default__
623    /// Default is Left.
624    pub fn popup_alignment(mut self, alignment: Alignment) -> Self {
625        self.popup_alignment = alignment;
626        self
627    }
628
629    /// Placement of the popup.
630    ///
631    /// __Default__
632    /// Default is BelowOrAbove.
633    pub fn popup_placement(mut self, placement: Placement) -> Self {
634        self.popup_placement = placement;
635        self
636    }
637
638    /// Outer boundary for the popup.
639    pub fn popup_boundary(mut self, boundary: Rect) -> Self {
640        self.popup = self.popup.boundary(boundary);
641        self
642    }
643
644    /// Override the popup length.
645    ///
646    /// __Default__
647    /// Defaults to the number of items or 5.
648    pub fn popup_len(mut self, len: u16) -> Self {
649        self.popup_len = Some(len);
650        self
651    }
652
653    /// Base style for the popup.
654    pub fn popup_style(mut self, style: Style) -> Self {
655        self.popup_style = style;
656        self
657    }
658
659    /// Block for the popup.
660    pub fn popup_block(mut self, block: Block<'a>) -> Self {
661        self.popup_block = Some(block);
662        self
663    }
664
665    /// Scroll for the popup.
666    pub fn popup_scroll(mut self, scroll: Scroll<'a>) -> Self {
667        self.popup_scroll = Some(scroll);
668        self
669    }
670
671    /// Adds an extra offset to the widget area.
672    ///
673    /// This can be used to
674    /// * place the widget under the mouse cursor.
675    /// * align the widget not by the outer bounds but by
676    ///   the text content.
677    pub fn popup_offset(mut self, offset: (i16, i16)) -> Self {
678        self.popup = self.popup.offset(offset);
679        self
680    }
681
682    /// Sets only the x offset.
683    /// See [offset](Self::popup_offset)
684    pub fn popup_x_offset(mut self, offset: i16) -> Self {
685        self.popup = self.popup.x_offset(offset);
686        self
687    }
688
689    /// Sets only the y offset.
690    /// See [offset](Self::popup_offset)
691    pub fn popup_y_offset(mut self, offset: i16) -> Self {
692        self.popup = self.popup.y_offset(offset);
693        self
694    }
695
696    /// Sets the behaviour for selecting from the list.
697    pub fn behave_select(mut self, select: ChoiceSelect) -> Self {
698        self.behave_select = select;
699        self
700    }
701
702    /// Sets the behaviour for closing the list.
703    pub fn behave_close(mut self, close: ChoiceClose) -> Self {
704        self.behave_close = close;
705        self
706    }
707
708    /// Inherent width.
709    pub fn width(&self) -> u16 {
710        let w = self
711            .items
712            .borrow()
713            .iter()
714            .map(|v| v.width())
715            .max()
716            .unwrap_or_default();
717
718        w as u16 + block_size(&self.block).width
719    }
720
721    /// Inherent height.
722    pub fn height(&self) -> u16 {
723        1 + block_size(&self.block).height
724    }
725
726    /// Choice itself doesn't render.
727    ///
728    /// This builds the widgets from the parameters set for Choice.
729    pub fn into_widgets(self) -> (ChoiceWidget<'a, T>, ChoicePopup<'a, T>) {
730        (
731            ChoiceWidget {
732                values: self.values,
733                default_value: self.default_value,
734                items: self.items.clone(),
735                style: self.style,
736                button_style: self.button_style,
737                focus_style: self.focus_style,
738                block: self.block,
739                len: self.popup_len,
740                behave_select: self.behave_select,
741                behave_close: self.behave_close,
742                _phantom: Default::default(),
743            },
744            ChoicePopup {
745                items: self.items.clone(),
746                style: self.style,
747                select_style: self.select_style,
748                popup: self.popup,
749                popup_style: self.popup_style,
750                popup_scroll: self.popup_scroll,
751                popup_block: self.popup_block,
752                popup_alignment: self.popup_alignment,
753                popup_placement: self.popup_placement,
754                popup_len: self.popup_len,
755                _phantom: Default::default(),
756            },
757        )
758    }
759}
760
761impl<'a, T> ChoiceWidget<'a, T>
762where
763    T: PartialEq + Clone + Default,
764{
765    /// Inherent width.
766    pub fn width(&self) -> u16 {
767        let w = self
768            .items
769            .borrow()
770            .iter()
771            .map(|v| v.width())
772            .max()
773            .unwrap_or_default();
774
775        w as u16 + block_size(&self.block).width
776    }
777
778    /// Inherent height.
779    pub fn height(&self) -> u16 {
780        1 + block_size(&self.block).height
781    }
782}
783
784impl<'a, T> StatefulWidget for &ChoiceWidget<'a, T>
785where
786    T: PartialEq + Clone + Default,
787{
788    type State = ChoiceState<T>;
789
790    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
791        state.core.set_values(self.values.borrow().clone());
792        if let Some(default_value) = self.default_value.clone() {
793            state.core.set_default_value(Some(default_value));
794        }
795
796        render_choice(self, area, buf, state);
797    }
798}
799
800impl<T> StatefulWidget for ChoiceWidget<'_, T>
801where
802    T: PartialEq + Clone + Default,
803{
804    type State = ChoiceState<T>;
805
806    fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
807        state.core.set_values(self.values.take());
808        if let Some(default_value) = self.default_value.take() {
809            state.core.set_default_value(Some(default_value));
810        }
811
812        render_choice(&self, area, buf, state);
813    }
814}
815
816fn render_choice<T: PartialEq + Clone + Default>(
817    widget: &ChoiceWidget<'_, T>,
818    area: Rect,
819    buf: &mut Buffer,
820    state: &mut ChoiceState<T>,
821) {
822    state.area = area;
823    state.behave_select = widget.behave_select;
824    state.behave_close = widget.behave_close;
825
826    if !state.popup.is_active() {
827        let len = widget
828            .len
829            .unwrap_or_else(|| min(5, widget.items.borrow().len()) as u16);
830        state.popup_scroll.max_offset = widget.items.borrow().len().saturating_sub(len as usize);
831        state.popup_scroll.page_len = len as usize;
832        if let Some(selected) = state.core.selected() {
833            state.popup_scroll.scroll_to_pos(selected);
834        }
835    }
836
837    state.nav_char.clear();
838    state.nav_char.extend(widget.items.borrow().iter().map(|v| {
839        v.spans
840            .first()
841            .and_then(|v| v.content.as_ref().chars().next())
842            .map_or(Vec::default(), |c| c.to_lowercase().collect::<Vec<_>>())
843    }));
844
845    let inner = widget.block.inner_if_some(area);
846
847    state.item_area = Rect::new(
848        inner.x,
849        inner.y,
850        inner.width.saturating_sub(3),
851        inner.height,
852    );
853    state.button_area = Rect::new(
854        inner.right().saturating_sub(min(3, inner.width)),
855        inner.y,
856        3,
857        inner.height,
858    );
859
860    let style = widget.style;
861    let focus_style = widget.focus_style.unwrap_or(revert_style(widget.style));
862
863    if state.is_focused() {
864        if let Some(block) = &widget.block {
865            block.render(area, buf);
866        } else {
867            buf.set_style(inner, style);
868        }
869        buf.set_style(inner, focus_style);
870    } else {
871        if let Some(block) = &widget.block {
872            block.render(area, buf);
873        } else {
874            buf.set_style(inner, style);
875        }
876        if let Some(button_style) = widget.button_style {
877            buf.set_style(state.button_area, button_style);
878        }
879    }
880
881    if let Some(selected) = state.core.selected() {
882        if let Some(item) = widget.items.borrow().get(selected) {
883            item.render(state.item_area, buf);
884        }
885    }
886
887    let dy = if (state.button_area.height & 1) == 1 {
888        state.button_area.height / 2
889    } else {
890        state.button_area.height.saturating_sub(1) / 2
891    };
892    let bc = if state.is_popup_active() {
893        " â—† "
894    } else {
895        " â–¼ "
896    };
897    Span::from(bc).render(
898        Rect::new(state.button_area.x, state.button_area.y + dy, 3, 1),
899        buf,
900    );
901}
902
903impl<T> ChoicePopup<'_, T>
904where
905    T: PartialEq + Clone + Default,
906{
907    /// Calculate the layout for the popup before rendering.
908    /// Area is the area of the ChoiceWidget not the ChoicePopup.
909    pub fn layout(&self, area: Rect, buf: &mut Buffer, state: &mut ChoiceState<T>) -> Rect {
910        if state.popup.is_active() {
911            let len = min(
912                self.popup_len.unwrap_or(5),
913                self.items.borrow().len() as u16,
914            );
915            let padding = block_padding(&self.popup_block);
916            let popup_len = len + padding.top + padding.bottom;
917            let pop_area = Rect::new(0, 0, area.width, popup_len);
918
919            self.popup
920                .ref_constraint(
921                    self.popup_placement
922                        .into_constraint(self.popup_alignment, area),
923                )
924                .layout(pop_area, buf)
925        } else {
926            Rect::default()
927        }
928    }
929}
930
931impl<T> StatefulWidget for &ChoicePopup<'_, T>
932where
933    T: PartialEq + Clone + Default,
934{
935    type State = ChoiceState<T>;
936
937    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
938        render_popup(self, area, buf, state);
939    }
940}
941
942impl<T> StatefulWidget for ChoicePopup<'_, T>
943where
944    T: PartialEq + Clone + Default,
945{
946    type State = ChoiceState<T>;
947
948    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
949        render_popup(&self, area, buf, state);
950    }
951}
952
953fn render_popup<T: PartialEq + Clone + Default>(
954    widget: &ChoicePopup<'_, T>,
955    area: Rect,
956    buf: &mut Buffer,
957    state: &mut ChoiceState<T>,
958) {
959    if state.popup.is_active() {
960        let popup_style = widget.popup_style;
961        let select_style = widget.select_style.unwrap_or(revert_style(widget.style));
962
963        {
964            let len = min(
965                widget.popup_len.unwrap_or(5),
966                widget.items.borrow().len() as u16,
967            );
968            let padding = block_padding(&widget.popup_block);
969            let popup_len = len + padding.top + padding.bottom;
970            let pop_area = Rect::new(0, 0, area.width, popup_len);
971
972            widget
973                .popup
974                .ref_constraint(
975                    widget
976                        .popup_placement
977                        .into_constraint(widget.popup_alignment, area),
978                )
979                .render(pop_area, buf, &mut state.popup);
980        }
981
982        let sa = ScrollArea::new()
983            .style(fallback_popup_style(widget.popup_style))
984            .block(widget.popup_block.as_ref())
985            .v_scroll(widget.popup_scroll.as_ref());
986
987        let inner = sa.inner(state.popup.area, None, Some(&state.popup_scroll));
988        sa.render(
989            state.popup.area,
990            buf,
991            &mut ScrollAreaState::new().v_scroll(&mut state.popup_scroll),
992        );
993
994        state.popup_scroll.max_offset = widget
995            .items
996            .borrow()
997            .len()
998            .saturating_sub(inner.height as usize);
999        state.popup_scroll.page_len = inner.height as usize;
1000
1001        state.item_areas.clear();
1002        let mut row = inner.y;
1003        let mut idx = state.popup_scroll.offset;
1004        loop {
1005            if row >= inner.bottom() {
1006                break;
1007            }
1008
1009            let item_area = Rect::new(inner.x, row, inner.width, 1);
1010            state.item_areas.push(item_area);
1011
1012            if let Some(item) = widget.items.borrow().get(idx) {
1013                let style = if state.core.selected() == Some(idx) {
1014                    popup_style.patch(select_style)
1015                } else {
1016                    popup_style
1017                };
1018
1019                buf.set_style(item_area, style);
1020                item.render(item_area, buf);
1021            } else {
1022                // noop?
1023            }
1024
1025            row += 1;
1026            idx += 1;
1027        }
1028    } else {
1029        state.popup.clear_areas();
1030    }
1031}
1032
1033impl<T> Clone for ChoiceState<T>
1034where
1035    T: PartialEq + Clone + Default,
1036{
1037    fn clone(&self) -> Self {
1038        Self {
1039            area: self.area,
1040            nav_char: self.nav_char.clone(),
1041            item_area: self.item_area,
1042            button_area: self.button_area,
1043            item_areas: self.item_areas.clone(),
1044            core: self.core.clone(),
1045            popup: self.popup.clone(),
1046            popup_scroll: self.popup_scroll.clone(),
1047            behave_select: self.behave_select,
1048            behave_close: self.behave_close,
1049            focus: FocusFlag::named(self.focus.name()),
1050            mouse: Default::default(),
1051            non_exhaustive: NonExhaustive,
1052        }
1053    }
1054}
1055
1056impl<T> Default for ChoiceState<T>
1057where
1058    T: PartialEq + Clone + Default,
1059{
1060    fn default() -> Self {
1061        Self {
1062            area: Default::default(),
1063            nav_char: Default::default(),
1064            item_area: Default::default(),
1065            button_area: Default::default(),
1066            item_areas: Default::default(),
1067            core: Default::default(),
1068            popup: Default::default(),
1069            popup_scroll: Default::default(),
1070            behave_select: Default::default(),
1071            behave_close: Default::default(),
1072            focus: Default::default(),
1073            mouse: Default::default(),
1074            non_exhaustive: NonExhaustive,
1075        }
1076    }
1077}
1078
1079impl<T> HasFocus for ChoiceState<T>
1080where
1081    T: PartialEq + Clone + Default,
1082{
1083    fn build(&self, builder: &mut FocusBuilder) {
1084        builder.widget_with_flags(self.focus(), self.area(), 0, self.navigable());
1085        builder.widget_with_flags(self.focus(), self.popup.area, 1, Navigation::Mouse);
1086    }
1087
1088    fn focus(&self) -> FocusFlag {
1089        self.focus.clone()
1090    }
1091
1092    fn area(&self) -> Rect {
1093        self.area
1094    }
1095}
1096
1097impl<T> RelocatableState for ChoiceState<T>
1098where
1099    T: PartialEq + Clone + Default,
1100{
1101    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1102        self.area = relocate_area(self.area, shift, clip);
1103        self.item_area = relocate_area(self.item_area, shift, clip);
1104        self.button_area = relocate_area(self.button_area, shift, clip);
1105        relocate_areas(&mut self.item_areas, shift, clip);
1106        self.popup.relocate(shift, clip);
1107    }
1108}
1109
1110impl<T> ChoiceState<T>
1111where
1112    T: PartialEq + Clone + Default,
1113{
1114    pub fn new() -> Self {
1115        Self::default()
1116    }
1117
1118    pub fn named(name: &str) -> Self {
1119        Self {
1120            focus: FocusFlag::named(name),
1121            ..Default::default()
1122        }
1123    }
1124
1125    /// Popup is active?
1126    pub fn is_popup_active(&self) -> bool {
1127        self.popup.is_active()
1128    }
1129
1130    /// Flip the popup state.
1131    pub fn flip_popup_active(&mut self) {
1132        self.popup.flip_active();
1133    }
1134
1135    /// Show the popup.
1136    pub fn set_popup_active(&mut self, active: bool) -> bool {
1137        self.popup.set_active(active)
1138    }
1139
1140    /// Set a default-value other than T::default()
1141    ///
1142    /// The starting value will still be T::default()
1143    /// after this. You must call clear() to use this
1144    /// default.
1145    ///
1146    /// This default will be overridden by a default set
1147    /// on the widget.
1148    pub fn set_default_value(&mut self, default_value: Option<T>) {
1149        self.core.set_default_value(default_value);
1150    }
1151
1152    /// A default value.
1153    pub fn default_value(&self) -> &Option<T> {
1154        self.core.default_value()
1155    }
1156
1157    /// Select the given value.
1158    ///
1159    /// If the value doesn't exist in the list or the list is
1160    /// empty the value will still be set, but selected will be
1161    /// None. The list will be empty before the first render, but
1162    /// the first thing render will do is set the list of values.
1163    /// This will adjust the selected index if possible.
1164    /// It's still ok to set a value here that can not be represented.
1165    /// As long as there is no user interaction, the same value
1166    /// will be returned by value().
1167    pub fn set_value(&mut self, value: T) -> bool {
1168        self.core.set_value(value)
1169    }
1170
1171    /// Get the selected value.
1172    pub fn value(&self) -> T {
1173        self.core.value()
1174    }
1175
1176    /// Select the default value or T::default.
1177    pub fn clear(&mut self) -> bool {
1178        self.core.clear()
1179    }
1180
1181    /// Select the value at index. This will set the value
1182    /// to the given index in the value-list. If the index is
1183    /// out of bounds or the value-list is empty it will
1184    /// set selected to None and leave the value as is.
1185    /// The list is empty before the first render so this
1186    /// may not work as expected.
1187    ///
1188    /// The selected index is a best effort artefact, the main
1189    /// thing is the value itself.
1190    ///
1191    /// Use of set_value() is preferred.
1192    pub fn select(&mut self, select: usize) -> bool {
1193        self.core.set_selected(select)
1194    }
1195
1196    /// Returns the selected index or None if the
1197    /// value is not in the list or the list is empty.
1198    ///
1199    /// You can still get the value set with set_value() though.
1200    pub fn selected(&self) -> Option<usize> {
1201        self.core.selected()
1202    }
1203
1204    /// Any items?
1205    pub fn is_empty(&self) -> bool {
1206        self.core.values().is_empty()
1207    }
1208
1209    /// Number of items.
1210    pub fn len(&self) -> usize {
1211        self.core.values().len()
1212    }
1213
1214    /// Scroll offset for the item list.
1215    pub fn clear_offset(&mut self) {
1216        self.popup_scroll.set_offset(0);
1217    }
1218
1219    /// Scroll offset for the item list.
1220    pub fn set_offset(&mut self, offset: usize) -> bool {
1221        self.popup_scroll.set_offset(offset)
1222    }
1223
1224    /// Scroll offset for the item list.
1225    pub fn offset(&self) -> usize {
1226        self.popup_scroll.offset()
1227    }
1228
1229    /// Scroll offset for the item list.
1230    pub fn max_offset(&self) -> usize {
1231        self.popup_scroll.max_offset()
1232    }
1233
1234    /// Page length for the item list.
1235    pub fn page_len(&self) -> usize {
1236        self.popup_scroll.page_len()
1237    }
1238
1239    /// Scroll unit for the item list.
1240    pub fn scroll_by(&self) -> usize {
1241        self.popup_scroll.scroll_by()
1242    }
1243
1244    /// Scroll the item list to the selected value.
1245    pub fn scroll_to_selected(&mut self) -> bool {
1246        if let Some(selected) = self.core.selected() {
1247            self.popup_scroll.scroll_to_pos(selected)
1248        } else {
1249            false
1250        }
1251    }
1252}
1253
1254impl<T> ChoiceState<T>
1255where
1256    T: PartialEq + Clone + Default,
1257{
1258    /// Select by first character.
1259    pub fn select_by_char(&mut self, c: char) -> bool {
1260        if self.nav_char.is_empty() {
1261            return false;
1262        }
1263        let c = c.to_lowercase().collect::<Vec<_>>();
1264
1265        let (mut idx, end_loop) = if let Some(idx) = self.core.selected() {
1266            (idx + 1, idx)
1267        } else {
1268            if self.nav_char[0] == c {
1269                self.core.set_selected(0);
1270                return true;
1271            } else {
1272                (1, 0)
1273            }
1274        };
1275        loop {
1276            if idx >= self.nav_char.len() {
1277                idx = 0;
1278            }
1279            if idx == end_loop {
1280                break;
1281            }
1282
1283            if self.nav_char[idx] == c {
1284                self.core.set_selected(idx);
1285                return true;
1286            }
1287
1288            idx += 1;
1289        }
1290        false
1291    }
1292
1293    /// Select by first character. Reverse direction
1294    pub fn reverse_select_by_char(&mut self, c: char) -> bool {
1295        if self.nav_char.is_empty() {
1296            return false;
1297        }
1298        let c = c.to_lowercase().collect::<Vec<_>>();
1299
1300        let (mut idx, end_loop) = if let Some(idx) = self.core.selected() {
1301            if idx == 0 {
1302                (self.nav_char.len() - 1, 0)
1303            } else {
1304                (idx - 1, idx)
1305            }
1306        } else {
1307            if self.nav_char.last() == Some(&c) {
1308                self.core.set_selected(self.nav_char.len() - 1);
1309                return true;
1310            } else {
1311                (self.nav_char.len() - 1, 0)
1312            }
1313        };
1314        loop {
1315            if self.nav_char[idx] == c {
1316                self.core.set_selected(idx);
1317                return true;
1318            }
1319
1320            if idx == end_loop {
1321                break;
1322            }
1323
1324            if idx == 0 {
1325                idx = self.nav_char.len() - 1;
1326            } else {
1327                idx -= 1;
1328            }
1329        }
1330        false
1331    }
1332
1333    /// Select at position
1334    pub fn move_to(&mut self, n: usize) -> ChoiceOutcome {
1335        let old_selected = self.selected();
1336        let r1 = self.popup.set_active(true);
1337        let r2 = self.select(n);
1338        let r3 = self.scroll_to_selected();
1339        if old_selected != self.selected() {
1340            ChoiceOutcome::Value
1341        } else if r1 || r2 || r3 {
1342            ChoiceOutcome::Changed
1343        } else {
1344            ChoiceOutcome::Continue
1345        }
1346    }
1347
1348    /// Select next entry.
1349    pub fn move_down(&mut self, n: usize) -> ChoiceOutcome {
1350        if self.core.is_empty() {
1351            return ChoiceOutcome::Continue;
1352        }
1353
1354        let old_selected = self.selected();
1355        let r1 = self.popup.set_active(true);
1356        let idx = if let Some(idx) = self.core.selected() {
1357            idx + n
1358        } else {
1359            n.saturating_sub(1)
1360        };
1361        let idx = idx.clamp(0, self.len() - 1);
1362        let r2 = self.core.set_selected(idx);
1363        let r3 = self.scroll_to_selected();
1364
1365        if old_selected != self.selected() {
1366            ChoiceOutcome::Value
1367        } else if r1 || r2 || r3 {
1368            ChoiceOutcome::Changed
1369        } else {
1370            ChoiceOutcome::Continue
1371        }
1372    }
1373
1374    /// Select prev entry.
1375    pub fn move_up(&mut self, n: usize) -> ChoiceOutcome {
1376        if self.core.is_empty() {
1377            return ChoiceOutcome::Continue;
1378        }
1379
1380        let old_selected = self.selected();
1381        let r1 = self.popup.set_active(true);
1382        let idx = if let Some(idx) = self.core.selected() {
1383            idx.saturating_sub(n)
1384        } else {
1385            0
1386        };
1387        let idx = idx.clamp(0, self.len() - 1);
1388        let r2 = self.core.set_selected(idx);
1389        let r3 = self.scroll_to_selected();
1390
1391        if old_selected != self.selected() {
1392            ChoiceOutcome::Value
1393        } else if r1 || r2 || r3 {
1394            ChoiceOutcome::Changed
1395        } else {
1396            ChoiceOutcome::Continue
1397        }
1398    }
1399}
1400
1401impl<T: PartialEq + Clone + Default> HandleEvent<crossterm::event::Event, Popup, ChoiceOutcome>
1402    for ChoiceState<T>
1403{
1404    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Popup) -> ChoiceOutcome {
1405        if !self.is_focused() && self.popup.is_active() {
1406            self.popup.set_active(false);
1407            // focus change triggers the repaint.
1408        }
1409
1410        let r = if self.is_focused() {
1411            match event {
1412                ct_event!(key press ' ') | ct_event!(keycode press Enter) => {
1413                    self.flip_popup_active();
1414                    ChoiceOutcome::Changed
1415                }
1416                ct_event!(keycode press Esc) => {
1417                    if self.set_popup_active(false) {
1418                        ChoiceOutcome::Changed
1419                    } else {
1420                        ChoiceOutcome::Continue
1421                    }
1422                }
1423                ct_event!(key press c) => {
1424                    if self.select_by_char(*c) {
1425                        self.scroll_to_selected();
1426                        ChoiceOutcome::Value
1427                    } else {
1428                        ChoiceOutcome::Continue
1429                    }
1430                }
1431                ct_event!(key press SHIFT-c) => {
1432                    if self.reverse_select_by_char(*c) {
1433                        self.scroll_to_selected();
1434                        ChoiceOutcome::Value
1435                    } else {
1436                        ChoiceOutcome::Continue
1437                    }
1438                }
1439                ct_event!(keycode press Delete) | ct_event!(keycode press Backspace) => {
1440                    if self.clear() {
1441                        ChoiceOutcome::Value
1442                    } else {
1443                        ChoiceOutcome::Continue
1444                    }
1445                }
1446                ct_event!(keycode press Down) => self.move_down(1),
1447                ct_event!(keycode press Up) => self.move_up(1),
1448                ct_event!(keycode press PageUp) => self.move_up(self.page_len()),
1449                ct_event!(keycode press PageDown) => self.move_down(self.page_len()),
1450                ct_event!(keycode press Home) => self.move_to(0),
1451                ct_event!(keycode press End) => self.move_to(self.len().saturating_sub(1)),
1452                _ => ChoiceOutcome::Continue,
1453            }
1454        } else {
1455            ChoiceOutcome::Continue
1456        };
1457
1458        if !r.is_consumed() {
1459            self.handle(event, MouseOnly)
1460        } else {
1461            r
1462        }
1463    }
1464}
1465
1466impl<T: PartialEq + Clone + Default> HandleEvent<crossterm::event::Event, MouseOnly, ChoiceOutcome>
1467    for ChoiceState<T>
1468{
1469    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> ChoiceOutcome {
1470        let r0 = handle_mouse(self, event);
1471        let r1 = handle_select(self, event);
1472        let r2 = handle_close(self, event);
1473        let mut r = max(r0, max(r1, r2));
1474
1475        r = r.or_else(|| mouse_trap(event, self.popup.area).into());
1476
1477        r
1478    }
1479}
1480
1481fn handle_mouse<T: PartialEq + Clone + Default>(
1482    state: &mut ChoiceState<T>,
1483    event: &crossterm::event::Event,
1484) -> ChoiceOutcome {
1485    match event {
1486        ct_event!(mouse down Left for x,y)
1487            if state.item_area.contains((*x, *y).into())
1488                || state.button_area.contains((*x, *y).into()) =>
1489        {
1490            if !state.gained_focus() {
1491                state.flip_popup_active();
1492                ChoiceOutcome::Changed
1493            } else {
1494                // hide is down by self.popup.handle() as this click
1495                // is outside the popup area!!
1496                ChoiceOutcome::Continue
1497            }
1498        }
1499        ct_event!(mouse down Left for x,y)
1500        | ct_event!(mouse down Right for x,y)
1501        | ct_event!(mouse down Middle for x,y)
1502            if !state.item_area.contains((*x, *y).into())
1503                && !state.button_area.contains((*x, *y).into()) =>
1504        {
1505            match state.popup.handle(event, Popup) {
1506                PopupOutcome::Hide => {
1507                    state.set_popup_active(false);
1508                    ChoiceOutcome::Changed
1509                }
1510                r => r.into(),
1511            }
1512        }
1513        _ => ChoiceOutcome::Continue,
1514    }
1515}
1516
1517fn handle_select<T: PartialEq + Clone + Default>(
1518    state: &mut ChoiceState<T>,
1519    event: &crossterm::event::Event,
1520) -> ChoiceOutcome {
1521    match state.behave_select {
1522        ChoiceSelect::MouseScroll => {
1523            let mut sas = ScrollAreaState::new()
1524                .area(state.popup.area)
1525                .v_scroll(&mut state.popup_scroll);
1526            let mut r = match sas.handle(event, MouseOnly) {
1527                ScrollOutcome::Up(n) => state.move_up(n),
1528                ScrollOutcome::Down(n) => state.move_down(n),
1529                ScrollOutcome::VPos(n) => state.move_to(n),
1530                _ => ChoiceOutcome::Continue,
1531            };
1532
1533            r = r.or_else(|| match event {
1534                ct_event!(mouse down Left for x,y)
1535                    if state.popup.area.contains((*x, *y).into()) =>
1536                {
1537                    if let Some(n) = item_at(&state.item_areas, *x, *y) {
1538                        state.move_to(state.offset() + n)
1539                    } else {
1540                        ChoiceOutcome::Unchanged
1541                    }
1542                }
1543                ct_event!(mouse drag Left for x,y)
1544                    if state.popup.area.contains((*x, *y).into()) =>
1545                {
1546                    if let Some(n) = item_at(&state.item_areas, *x, *y) {
1547                        state.move_to(state.offset() + n)
1548                    } else {
1549                        ChoiceOutcome::Unchanged
1550                    }
1551                }
1552                _ => ChoiceOutcome::Continue,
1553            });
1554            r
1555        }
1556        ChoiceSelect::MouseMove => {
1557            // effect: move the content below the mouse and keep visible selection.
1558            let mut r = if let Some(selected) = state.core.selected() {
1559                let rel_sel = selected.saturating_sub(state.offset());
1560                let mut sas = ScrollAreaState::new()
1561                    .area(state.popup.area)
1562                    .v_scroll(&mut state.popup_scroll);
1563                match sas.handle(event, MouseOnly) {
1564                    ScrollOutcome::Up(n) => {
1565                        state.popup_scroll.scroll_up(n);
1566                        if state.select(state.offset() + rel_sel) {
1567                            ChoiceOutcome::Value
1568                        } else {
1569                            ChoiceOutcome::Unchanged
1570                        }
1571                    }
1572                    ScrollOutcome::Down(n) => {
1573                        state.popup_scroll.scroll_down(n);
1574                        if state.select(state.offset() + rel_sel) {
1575                            ChoiceOutcome::Value
1576                        } else {
1577                            ChoiceOutcome::Unchanged
1578                        }
1579                    }
1580                    ScrollOutcome::VPos(n) => {
1581                        if state.popup_scroll.set_offset(n) {
1582                            ChoiceOutcome::Value
1583                        } else {
1584                            ChoiceOutcome::Unchanged
1585                        }
1586                    }
1587                    _ => ChoiceOutcome::Continue,
1588                }
1589            } else {
1590                ChoiceOutcome::Continue
1591            };
1592
1593            r = r.or_else(|| match event {
1594                ct_event!(mouse moved for x,y) if state.popup.area.contains((*x, *y).into()) => {
1595                    if let Some(n) = item_at(&state.item_areas, *x, *y) {
1596                        state.move_to(state.offset() + n)
1597                    } else {
1598                        ChoiceOutcome::Unchanged
1599                    }
1600                }
1601                _ => ChoiceOutcome::Continue,
1602            });
1603            r
1604        }
1605        ChoiceSelect::MouseClick => {
1606            // effect: move the content below the mouse and keep visible selection.
1607            let mut sas = ScrollAreaState::new()
1608                .area(state.popup.area)
1609                .v_scroll(&mut state.popup_scroll);
1610            let mut r = match sas.handle(event, MouseOnly) {
1611                ScrollOutcome::Up(n) => {
1612                    if state.popup_scroll.scroll_up(n) {
1613                        ChoiceOutcome::Changed
1614                    } else {
1615                        ChoiceOutcome::Unchanged
1616                    }
1617                }
1618                ScrollOutcome::Down(n) => {
1619                    if state.popup_scroll.scroll_down(n) {
1620                        ChoiceOutcome::Changed
1621                    } else {
1622                        ChoiceOutcome::Unchanged
1623                    }
1624                }
1625                ScrollOutcome::VPos(n) => {
1626                    if state.popup_scroll.set_offset(n) {
1627                        ChoiceOutcome::Changed
1628                    } else {
1629                        ChoiceOutcome::Unchanged
1630                    }
1631                }
1632                _ => ChoiceOutcome::Continue,
1633            };
1634
1635            r = r.or_else(|| match event {
1636                ct_event!(mouse down Left for x,y)
1637                    if state.popup.area.contains((*x, *y).into()) =>
1638                {
1639                    if let Some(n) = item_at(&state.item_areas, *x, *y) {
1640                        state.move_to(state.offset() + n)
1641                    } else {
1642                        ChoiceOutcome::Unchanged
1643                    }
1644                }
1645                ct_event!(mouse drag Left for x,y)
1646                    if state.popup.area.contains((*x, *y).into()) =>
1647                {
1648                    if let Some(n) = item_at(&state.item_areas, *x, *y) {
1649                        state.move_to(state.offset() + n)
1650                    } else {
1651                        ChoiceOutcome::Unchanged
1652                    }
1653                }
1654                _ => ChoiceOutcome::Continue,
1655            });
1656            r
1657        }
1658    }
1659}
1660
1661fn handle_close<T: PartialEq + Clone + Default>(
1662    state: &mut ChoiceState<T>,
1663    event: &crossterm::event::Event,
1664) -> ChoiceOutcome {
1665    match state.behave_close {
1666        ChoiceClose::SingleClick => match event {
1667            ct_event!(mouse down Left for x,y) if state.popup.area.contains((*x, *y).into()) => {
1668                if let Some(n) = item_at(&state.item_areas, *x, *y) {
1669                    let r = state.move_to(state.offset() + n);
1670                    let s = if state.set_popup_active(false) {
1671                        ChoiceOutcome::Changed
1672                    } else {
1673                        ChoiceOutcome::Unchanged
1674                    };
1675                    max(r, s)
1676                } else {
1677                    ChoiceOutcome::Unchanged
1678                }
1679            }
1680            _ => ChoiceOutcome::Continue,
1681        },
1682        ChoiceClose::DoubleClick => match event {
1683            ct_event!(mouse any for m) if state.mouse.doubleclick(state.popup.area, m) => {
1684                if let Some(n) = item_at(&state.item_areas, m.column, m.row) {
1685                    let r = state.move_to(state.offset() + n);
1686                    let s = if state.set_popup_active(false) {
1687                        ChoiceOutcome::Changed
1688                    } else {
1689                        ChoiceOutcome::Unchanged
1690                    };
1691                    max(r, s)
1692                } else {
1693                    ChoiceOutcome::Unchanged
1694                }
1695            }
1696            _ => ChoiceOutcome::Continue,
1697        },
1698    }
1699}
1700
1701/// Handle events for the popup.
1702/// Call before other handlers to deal with intersections
1703/// with other widgets.
1704#[deprecated(since = "1.0.5", note = "use handle_events instead")]
1705pub fn handle_popup<T: PartialEq + Clone + Default>(
1706    state: &mut ChoiceState<T>,
1707    focus: bool,
1708    event: &crossterm::event::Event,
1709) -> ChoiceOutcome {
1710    state.focus.set(focus);
1711    HandleEvent::handle(state, event, Popup)
1712}
1713
1714/// Handle events for the popup.
1715/// Call before other handlers to deal with intersections
1716/// with other widgets.
1717pub fn handle_events<T: PartialEq + Clone + Default>(
1718    state: &mut ChoiceState<T>,
1719    focus: bool,
1720    event: &crossterm::event::Event,
1721) -> ChoiceOutcome {
1722    state.focus.set(focus);
1723    HandleEvent::handle(state, event, Popup)
1724}
1725
1726/// Handle only mouse-events.
1727pub fn handle_mouse_events<T: PartialEq + Clone + Default>(
1728    state: &mut ChoiceState<T>,
1729    event: &crossterm::event::Event,
1730) -> ChoiceOutcome {
1731    HandleEvent::handle(state, event, MouseOnly)
1732}