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