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