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, area, 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, area, buf, state);
1006    }
1007}
1008
1009fn render_popup<T: PartialEq + Clone + Default>(
1010    widget: &ChoicePopup<'_, T>,
1011    area: Rect,
1012    buf: &mut Buffer,
1013    state: &mut ChoiceState<T>,
1014) {
1015    if state.popup.is_active() {
1016        let popup_style = widget.popup_style;
1017        let select_style = widget.select_style.unwrap_or(revert_style(widget.style));
1018
1019        {
1020            let len = min(
1021                widget.popup_len.unwrap_or(5),
1022                widget.items.borrow().len() as u16,
1023            );
1024            let padding = block_padding(&widget.popup_block);
1025            let popup_len = len + padding.top + padding.bottom;
1026            let pop_area = Rect::new(0, 0, area.width, popup_len);
1027
1028            widget
1029                .popup
1030                .ref_constraint(
1031                    widget
1032                        .popup_placement
1033                        .into_constraint(widget.popup_alignment, area),
1034                )
1035                .render(pop_area, buf, &mut state.popup);
1036        }
1037
1038        let sa = ScrollArea::new()
1039            .style(fallback_popup_style(widget.popup_style))
1040            .block(widget.popup_block.as_ref())
1041            .v_scroll(widget.popup_scroll.as_ref());
1042
1043        let inner = sa.inner(state.popup.area, None, Some(&state.popup_scroll));
1044
1045        sa.render(
1046            state.popup.area,
1047            buf,
1048            &mut ScrollAreaState::new().v_scroll(&mut state.popup_scroll),
1049        );
1050
1051        state.popup_scroll.max_offset = widget
1052            .items
1053            .borrow()
1054            .len()
1055            .saturating_sub(inner.height as usize);
1056        state.popup_scroll.page_len = inner.height as usize;
1057
1058        state.item_areas.clear();
1059        let mut row = inner.y;
1060        let mut idx = state.popup_scroll.offset;
1061
1062        let hidden_marker;
1063        let (marker_len, marker, no_marker) = {
1064            if let Some(marker) = widget.select_marker {
1065                hidden_marker = marker.to_string();
1066                if unicode_display_width::is_double_width(marker) {
1067                    (2, Some(hidden_marker.as_str()), Some("  "))
1068                } else {
1069                    (1, Some(hidden_marker.as_str()), Some(" "))
1070                }
1071            } else {
1072                (0, None, None)
1073            }
1074        };
1075        loop {
1076            if row >= inner.bottom() {
1077                break;
1078            }
1079
1080            let item_area = Rect::new(inner.x, row, inner.width, 1);
1081            state.item_areas.push(item_area);
1082
1083            let (style, marker) = if state.core.selected() == Some(idx) {
1084                (popup_style.patch(select_style), marker)
1085            } else {
1086                (popup_style, no_marker)
1087            };
1088            buf.set_style(item_area, style);
1089
1090            if let Some(item) = widget.items.borrow().get(idx) {
1091                if let Some(marker) = &marker {
1092                    marker.render(
1093                        Rect::new(item_area.x, item_area.y, marker_len, item_area.height),
1094                        buf,
1095                    );
1096                    item.render(
1097                        Rect::new(
1098                            item_area.x + marker_len,
1099                            item_area.y,
1100                            item_area.width.saturating_sub(marker_len),
1101                            item_area.height,
1102                        ),
1103                        buf,
1104                    );
1105                } else {
1106                    item.render(item_area, buf);
1107                }
1108            } else {
1109                // noop
1110            }
1111
1112            row += 1;
1113            idx += 1;
1114        }
1115    } else {
1116        state.popup.clear_areas();
1117    }
1118}
1119
1120impl<T> Clone for ChoiceState<T>
1121where
1122    T: PartialEq + Clone + Default,
1123{
1124    fn clone(&self) -> Self {
1125        let popup = self.popup.clone();
1126        let behave_focus = Rc::new(Cell::new(self.behave_focus.get()));
1127        let focus = focus_cb(
1128            popup.active.clone(),
1129            behave_focus.clone(),
1130            self.focus.new_instance(),
1131        );
1132
1133        Self {
1134            area: self.area,
1135            inner: self.inner,
1136            nav_char: self.nav_char.clone(),
1137            item_area: self.item_area,
1138            button_area: self.button_area,
1139            item_areas: self.item_areas.clone(),
1140            core: self.core.clone(),
1141            popup,
1142            popup_scroll: self.popup_scroll.clone(),
1143            behave_focus,
1144            behave_select: self.behave_select,
1145            behave_close: self.behave_close,
1146            focus,
1147            mouse: Default::default(),
1148            non_exhaustive: NonExhaustive,
1149        }
1150    }
1151}
1152
1153impl<T> Default for ChoiceState<T>
1154where
1155    T: PartialEq + Clone + Default,
1156{
1157    fn default() -> Self {
1158        let popup = PopupCoreState::default();
1159        let behave_focus = Rc::new(Cell::new(ChoiceFocus::default()));
1160        let focus = focus_cb(
1161            popup.active.clone(),
1162            behave_focus.clone(),
1163            Default::default(),
1164        );
1165
1166        Self {
1167            area: Default::default(),
1168            inner: Default::default(),
1169            nav_char: Default::default(),
1170            item_area: Default::default(),
1171            button_area: Default::default(),
1172            item_areas: Default::default(),
1173            core: Default::default(),
1174            popup,
1175            popup_scroll: Default::default(),
1176            behave_focus,
1177            behave_select: Default::default(),
1178            behave_close: Default::default(),
1179            focus,
1180            mouse: Default::default(),
1181            non_exhaustive: NonExhaustive,
1182        }
1183    }
1184}
1185
1186fn focus_cb(
1187    active: Rc<Cell<bool>>,
1188    behave_focus: Rc<Cell<ChoiceFocus>>,
1189    flag: FocusFlag,
1190) -> FocusFlag {
1191    let active_clone = active.clone();
1192    flag.on_lost(move || {
1193        if active_clone.get() {
1194            active_clone.set(false);
1195        }
1196    });
1197    flag.on_gained(move || {
1198        if !active.get() {
1199            if behave_focus.get() == ChoiceFocus::OpenOnFocusGained {
1200                active.set(true);
1201            }
1202        }
1203    });
1204    flag
1205}
1206
1207impl<T> HasFocus for ChoiceState<T>
1208where
1209    T: PartialEq + Clone + Default,
1210{
1211    fn build(&self, builder: &mut FocusBuilder) {
1212        builder.widget_with_flags(self.focus(), self.area(), 0, self.navigable());
1213        builder.widget_with_flags(self.focus(), self.popup.area, 1, Navigation::Mouse);
1214    }
1215
1216    fn focus(&self) -> FocusFlag {
1217        self.focus.clone()
1218    }
1219
1220    fn area(&self) -> Rect {
1221        self.area
1222    }
1223}
1224
1225impl<T> HasScreenCursor for ChoiceState<T>
1226where
1227    T: PartialEq + Clone + Default,
1228{
1229    fn screen_cursor(&self) -> Option<(u16, u16)> {
1230        None
1231    }
1232}
1233
1234impl<T> RelocatableState for ChoiceState<T>
1235where
1236    T: PartialEq + Clone + Default,
1237{
1238    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1239        self.area.relocate(shift, clip);
1240        self.inner.relocate(shift, clip);
1241        self.item_area.relocate(shift, clip);
1242        self.button_area.relocate(shift, clip);
1243    }
1244
1245    fn relocate_popup(&mut self, shift: (i16, i16), clip: Rect) {
1246        self.item_areas.relocate(shift, clip);
1247        self.popup.relocate_popup(shift, clip);
1248        self.popup_scroll.relocate(shift, clip);
1249    }
1250}
1251
1252impl<T> ChoiceState<T>
1253where
1254    T: PartialEq + Clone + Default,
1255{
1256    pub fn new() -> Self {
1257        Self::default()
1258    }
1259
1260    pub fn named(name: &str) -> Self {
1261        let mut z = Self::default();
1262        z.focus = z.focus.with_name(name);
1263        z
1264    }
1265
1266    /// Popup is active?
1267    pub fn is_popup_active(&self) -> bool {
1268        self.popup.is_active()
1269    }
1270
1271    /// Flip the popup state.
1272    pub fn flip_popup_active(&mut self) {
1273        self.popup.flip_active();
1274    }
1275
1276    /// Show the popup.
1277    pub fn set_popup_active(&mut self, active: bool) -> bool {
1278        self.popup.set_active(active)
1279    }
1280
1281    /// Set a default-value other than T::default()
1282    ///
1283    /// The starting value will still be T::default()
1284    /// after this. You must call clear() to use this
1285    /// default.
1286    ///
1287    /// This default will be overridden by a default set
1288    /// on the widget.
1289    pub fn set_default_value(&mut self, default_value: Option<T>) {
1290        self.core.set_default_value(default_value);
1291    }
1292
1293    /// A default value.
1294    pub fn default_value(&self) -> &Option<T> {
1295        self.core.default_value()
1296    }
1297
1298    /// Select the given value.
1299    ///
1300    /// If the value doesn't exist in the list or the list is
1301    /// empty the value will still be set, but selected will be
1302    /// None. The list will be empty before the first render, but
1303    /// the first thing render will do is set the list of values.
1304    /// This will adjust the selected index if possible.
1305    /// It's still ok to set a value here that can not be represented.
1306    /// As long as there is no user interaction, the same value
1307    /// will be returned by value().
1308    pub fn set_value(&mut self, value: T) -> bool {
1309        self.core.set_value(value)
1310    }
1311
1312    /// Get the selected value.
1313    pub fn value(&self) -> T {
1314        self.core.value()
1315    }
1316
1317    /// Select the default value or T::default.
1318    pub fn clear(&mut self) -> bool {
1319        self.core.clear()
1320    }
1321
1322    /// Select the value at index. This will set the value
1323    /// to the given index in the value-list. If the index is
1324    /// out of bounds or the value-list is empty it will
1325    /// set selected to None and leave the value as is.
1326    /// The list is empty before the first render so this
1327    /// may not work as expected.
1328    ///
1329    /// The selected index is a best effort artefact, the main
1330    /// thing is the value itself.
1331    ///
1332    /// Use of set_value() is preferred.
1333    pub fn select(&mut self, select: usize) -> bool {
1334        self.core.set_selected(select)
1335    }
1336
1337    /// Returns the selected index or None if the
1338    /// value is not in the list or the list is empty.
1339    ///
1340    /// You can still get the value set with set_value() though.
1341    pub fn selected(&self) -> Option<usize> {
1342        self.core.selected()
1343    }
1344
1345    /// Any items?
1346    pub fn is_empty(&self) -> bool {
1347        self.core.values().is_empty()
1348    }
1349
1350    /// Number of items.
1351    pub fn len(&self) -> usize {
1352        self.core.values().len()
1353    }
1354
1355    /// Scroll offset for the item list.
1356    pub fn clear_offset(&mut self) {
1357        self.popup_scroll.set_offset(0);
1358    }
1359
1360    /// Scroll offset for the item list.
1361    pub fn set_offset(&mut self, offset: usize) -> bool {
1362        self.popup_scroll.set_offset(offset)
1363    }
1364
1365    /// Scroll offset for the item list.
1366    pub fn offset(&self) -> usize {
1367        self.popup_scroll.offset()
1368    }
1369
1370    /// Scroll offset for the item list.
1371    pub fn max_offset(&self) -> usize {
1372        self.popup_scroll.max_offset()
1373    }
1374
1375    /// Page length for the item list.
1376    pub fn page_len(&self) -> usize {
1377        self.popup_scroll.page_len()
1378    }
1379
1380    /// Scroll unit for the item list.
1381    pub fn scroll_by(&self) -> usize {
1382        self.popup_scroll.scroll_by()
1383    }
1384
1385    /// Scroll the item list to the selected value.
1386    pub fn scroll_to_selected(&mut self) -> bool {
1387        if let Some(selected) = self.core.selected() {
1388            self.popup_scroll.scroll_to_pos(selected)
1389        } else {
1390            false
1391        }
1392    }
1393}
1394
1395impl<T> ChoiceState<T>
1396where
1397    T: PartialEq + Clone + Default,
1398{
1399    /// Select by first character.
1400    pub fn select_by_char(&mut self, c: char) -> bool {
1401        if self.nav_char.is_empty() {
1402            return false;
1403        }
1404        let c = c.to_lowercase().collect::<Vec<_>>();
1405
1406        let (mut idx, end_loop) = if let Some(idx) = self.core.selected() {
1407            (idx + 1, idx)
1408        } else {
1409            if self.nav_char[0] == c {
1410                self.core.set_selected(0);
1411                return true;
1412            } else {
1413                (1, 0)
1414            }
1415        };
1416        loop {
1417            if idx >= self.nav_char.len() {
1418                idx = 0;
1419            }
1420            if idx == end_loop {
1421                break;
1422            }
1423
1424            if self.nav_char[idx] == c {
1425                self.core.set_selected(idx);
1426                return true;
1427            }
1428
1429            idx += 1;
1430        }
1431        false
1432    }
1433
1434    /// Select by first character. Reverse direction
1435    pub fn reverse_select_by_char(&mut self, c: char) -> bool {
1436        if self.nav_char.is_empty() {
1437            return false;
1438        }
1439        let c = c.to_lowercase().collect::<Vec<_>>();
1440
1441        let (mut idx, end_loop) = if let Some(idx) = self.core.selected() {
1442            if idx == 0 {
1443                (self.nav_char.len() - 1, 0)
1444            } else {
1445                (idx - 1, idx)
1446            }
1447        } else {
1448            if self.nav_char.last() == Some(&c) {
1449                self.core.set_selected(self.nav_char.len() - 1);
1450                return true;
1451            } else {
1452                (self.nav_char.len() - 1, 0)
1453            }
1454        };
1455        loop {
1456            if self.nav_char[idx] == c {
1457                self.core.set_selected(idx);
1458                return true;
1459            }
1460
1461            if idx == end_loop {
1462                break;
1463            }
1464
1465            if idx == 0 {
1466                idx = self.nav_char.len() - 1;
1467            } else {
1468                idx -= 1;
1469            }
1470        }
1471        false
1472    }
1473
1474    /// Select at position
1475    pub fn move_to(&mut self, n: usize) -> ChoiceOutcome {
1476        let old_selected = self.selected();
1477        let r1 = self.popup.set_active(true);
1478        let r2 = self.select(n);
1479        let r3 = self.scroll_to_selected();
1480        if old_selected != self.selected() {
1481            ChoiceOutcome::Value
1482        } else if r1 || r2 || r3 {
1483            ChoiceOutcome::Changed
1484        } else {
1485            ChoiceOutcome::Continue
1486        }
1487    }
1488
1489    /// Select next entry.
1490    pub fn move_down(&mut self, n: usize) -> ChoiceOutcome {
1491        if self.core.is_empty() {
1492            return ChoiceOutcome::Continue;
1493        }
1494
1495        let old_selected = self.selected();
1496        let r1 = self.popup.set_active(true);
1497        let idx = if let Some(idx) = self.core.selected() {
1498            idx + n
1499        } else {
1500            n.saturating_sub(1)
1501        };
1502        let idx = idx.clamp(0, self.len() - 1);
1503        let r2 = self.core.set_selected(idx);
1504        let r3 = self.scroll_to_selected();
1505
1506        if old_selected != self.selected() {
1507            ChoiceOutcome::Value
1508        } else if r1 || r2 || r3 {
1509            ChoiceOutcome::Changed
1510        } else {
1511            ChoiceOutcome::Continue
1512        }
1513    }
1514
1515    /// Select prev entry.
1516    pub fn move_up(&mut self, n: usize) -> ChoiceOutcome {
1517        if self.core.is_empty() {
1518            return ChoiceOutcome::Continue;
1519        }
1520
1521        let old_selected = self.selected();
1522        let r1 = self.popup.set_active(true);
1523        let idx = if let Some(idx) = self.core.selected() {
1524            idx.saturating_sub(n)
1525        } else {
1526            0
1527        };
1528        let idx = idx.clamp(0, self.len() - 1);
1529        let r2 = self.core.set_selected(idx);
1530        let r3 = self.scroll_to_selected();
1531
1532        if old_selected != self.selected() {
1533            ChoiceOutcome::Value
1534        } else if r1 || r2 || r3 {
1535            ChoiceOutcome::Changed
1536        } else {
1537            ChoiceOutcome::Continue
1538        }
1539    }
1540}
1541
1542impl<T: PartialEq + Clone + Default> HandleEvent<crossterm::event::Event, Popup, ChoiceOutcome>
1543    for ChoiceState<T>
1544{
1545    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Popup) -> ChoiceOutcome {
1546        let r = if self.is_focused() {
1547            match event {
1548                ct_event!(key press ' ') | ct_event!(keycode press Enter) => {
1549                    self.flip_popup_active();
1550                    ChoiceOutcome::Changed
1551                }
1552                ct_event!(keycode press Esc) => {
1553                    if self.set_popup_active(false) {
1554                        ChoiceOutcome::Changed
1555                    } else {
1556                        ChoiceOutcome::Continue
1557                    }
1558                }
1559                ct_event!(key press c) => {
1560                    if self.select_by_char(*c) {
1561                        self.scroll_to_selected();
1562                        ChoiceOutcome::Value
1563                    } else {
1564                        ChoiceOutcome::Continue
1565                    }
1566                }
1567                ct_event!(key press SHIFT-c) => {
1568                    if self.reverse_select_by_char(*c) {
1569                        self.scroll_to_selected();
1570                        ChoiceOutcome::Value
1571                    } else {
1572                        ChoiceOutcome::Continue
1573                    }
1574                }
1575                ct_event!(keycode press Delete) | ct_event!(keycode press Backspace) => {
1576                    if self.clear() {
1577                        ChoiceOutcome::Value
1578                    } else {
1579                        ChoiceOutcome::Continue
1580                    }
1581                }
1582                ct_event!(keycode press Down) => self.move_down(1),
1583                ct_event!(keycode press Up) => self.move_up(1),
1584                ct_event!(keycode press PageUp) => self.move_up(self.page_len()),
1585                ct_event!(keycode press PageDown) => self.move_down(self.page_len()),
1586                ct_event!(keycode press Home) => self.move_to(0),
1587                ct_event!(keycode press End) => self.move_to(self.len().saturating_sub(1)),
1588                _ => ChoiceOutcome::Continue,
1589            }
1590        } else {
1591            ChoiceOutcome::Continue
1592        };
1593
1594        if !r.is_consumed() {
1595            self.handle(event, MouseOnly)
1596        } else {
1597            r
1598        }
1599    }
1600}
1601
1602impl<T: PartialEq + Clone + Default> HandleEvent<crossterm::event::Event, MouseOnly, ChoiceOutcome>
1603    for ChoiceState<T>
1604{
1605    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> ChoiceOutcome {
1606        let r0 = handle_mouse(self, event);
1607        let r1 = handle_select(self, event);
1608        let r2 = handle_close(self, event);
1609        let mut r = max(r0, max(r1, r2));
1610
1611        r = r.or_else(|| mouse_trap(event, self.popup.area).into());
1612
1613        r
1614    }
1615}
1616
1617fn handle_mouse<T: PartialEq + Clone + Default>(
1618    state: &mut ChoiceState<T>,
1619    event: &crossterm::event::Event,
1620) -> ChoiceOutcome {
1621    match event {
1622        ct_event!(mouse down Left for x,y)
1623            if state.item_area.contains((*x, *y).into())
1624                || state.button_area.contains((*x, *y).into()) =>
1625        {
1626            if !state.gained_focus() {
1627                state.flip_popup_active();
1628                ChoiceOutcome::Changed
1629            } else {
1630                // hide is down by self.popup.handle() as this click
1631                // is outside the popup area!!
1632                ChoiceOutcome::Continue
1633            }
1634        }
1635        ct_event!(mouse down Left for x,y)
1636        | ct_event!(mouse down Right for x,y)
1637        | ct_event!(mouse down Middle for x,y)
1638            if !state.item_area.contains((*x, *y).into())
1639                && !state.button_area.contains((*x, *y).into()) =>
1640        {
1641            match state.popup.handle(event, Popup) {
1642                PopupOutcome::Hide => {
1643                    state.set_popup_active(false);
1644                    ChoiceOutcome::Changed
1645                }
1646                r => r.into(),
1647            }
1648        }
1649        _ => ChoiceOutcome::Continue,
1650    }
1651}
1652
1653fn handle_select<T: PartialEq + Clone + Default>(
1654    state: &mut ChoiceState<T>,
1655    event: &crossterm::event::Event,
1656) -> ChoiceOutcome {
1657    match state.behave_select {
1658        ChoiceSelect::MouseScroll => {
1659            let mut sas = ScrollAreaState::new()
1660                .area(state.popup.area)
1661                .v_scroll(&mut state.popup_scroll);
1662            let mut r = match sas.handle(event, MouseOnly) {
1663                ScrollOutcome::Up(n) => state.move_up(n),
1664                ScrollOutcome::Down(n) => state.move_down(n),
1665                ScrollOutcome::VPos(n) => state.move_to(n),
1666                _ => ChoiceOutcome::Continue,
1667            };
1668
1669            r = r.or_else(|| match event {
1670                ct_event!(mouse down 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                ct_event!(mouse drag Left for x,y)
1680                    if state.popup.area.contains((*x, *y).into()) =>
1681                {
1682                    if let Some(n) = item_at(&state.item_areas, *x, *y) {
1683                        state.move_to(state.offset() + n)
1684                    } else {
1685                        ChoiceOutcome::Unchanged
1686                    }
1687                }
1688                _ => ChoiceOutcome::Continue,
1689            });
1690            r
1691        }
1692        ChoiceSelect::MouseMove => {
1693            // effect: move the content below the mouse and keep visible selection.
1694            let mut r = if let Some(selected) = state.core.selected() {
1695                let rel_sel = selected.saturating_sub(state.offset());
1696                let mut sas = ScrollAreaState::new()
1697                    .area(state.popup.area)
1698                    .v_scroll(&mut state.popup_scroll);
1699                match sas.handle(event, MouseOnly) {
1700                    ScrollOutcome::Up(n) => {
1701                        state.popup_scroll.scroll_up(n);
1702                        if state.select(state.offset() + rel_sel) {
1703                            ChoiceOutcome::Value
1704                        } else {
1705                            ChoiceOutcome::Unchanged
1706                        }
1707                    }
1708                    ScrollOutcome::Down(n) => {
1709                        state.popup_scroll.scroll_down(n);
1710                        if state.select(state.offset() + rel_sel) {
1711                            ChoiceOutcome::Value
1712                        } else {
1713                            ChoiceOutcome::Unchanged
1714                        }
1715                    }
1716                    ScrollOutcome::VPos(n) => {
1717                        if state.popup_scroll.set_offset(n) {
1718                            ChoiceOutcome::Value
1719                        } else {
1720                            ChoiceOutcome::Unchanged
1721                        }
1722                    }
1723                    _ => ChoiceOutcome::Continue,
1724                }
1725            } else {
1726                ChoiceOutcome::Continue
1727            };
1728
1729            r = r.or_else(|| match event {
1730                ct_event!(mouse moved for x,y) if state.popup.area.contains((*x, *y).into()) => {
1731                    if let Some(n) = item_at(&state.item_areas, *x, *y) {
1732                        state.move_to(state.offset() + n)
1733                    } else {
1734                        ChoiceOutcome::Unchanged
1735                    }
1736                }
1737                _ => ChoiceOutcome::Continue,
1738            });
1739            r
1740        }
1741        ChoiceSelect::MouseClick => {
1742            // effect: move the content below the mouse and keep visible selection.
1743            let mut sas = ScrollAreaState::new()
1744                .area(state.popup.area)
1745                .v_scroll(&mut state.popup_scroll);
1746            let mut r = match sas.handle(event, MouseOnly) {
1747                ScrollOutcome::Up(n) => {
1748                    if state.popup_scroll.scroll_up(n) {
1749                        ChoiceOutcome::Changed
1750                    } else {
1751                        ChoiceOutcome::Unchanged
1752                    }
1753                }
1754                ScrollOutcome::Down(n) => {
1755                    if state.popup_scroll.scroll_down(n) {
1756                        ChoiceOutcome::Changed
1757                    } else {
1758                        ChoiceOutcome::Unchanged
1759                    }
1760                }
1761                ScrollOutcome::VPos(n) => {
1762                    if state.popup_scroll.set_offset(n) {
1763                        ChoiceOutcome::Changed
1764                    } else {
1765                        ChoiceOutcome::Unchanged
1766                    }
1767                }
1768                _ => ChoiceOutcome::Continue,
1769            };
1770
1771            r = r.or_else(|| match event {
1772                ct_event!(mouse down 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                ct_event!(mouse drag Left for x,y)
1782                    if state.popup.area.contains((*x, *y).into()) =>
1783                {
1784                    if let Some(n) = item_at(&state.item_areas, *x, *y) {
1785                        state.move_to(state.offset() + n)
1786                    } else {
1787                        ChoiceOutcome::Unchanged
1788                    }
1789                }
1790                _ => ChoiceOutcome::Continue,
1791            });
1792            r
1793        }
1794    }
1795}
1796
1797fn handle_close<T: PartialEq + Clone + Default>(
1798    state: &mut ChoiceState<T>,
1799    event: &crossterm::event::Event,
1800) -> ChoiceOutcome {
1801    match state.behave_close {
1802        ChoiceClose::SingleClick => match event {
1803            ct_event!(mouse down Left for x,y) if state.popup.area.contains((*x, *y).into()) => {
1804                if let Some(n) = item_at(&state.item_areas, *x, *y) {
1805                    let r = state.move_to(state.offset() + n);
1806                    let s = if state.set_popup_active(false) {
1807                        ChoiceOutcome::Changed
1808                    } else {
1809                        ChoiceOutcome::Unchanged
1810                    };
1811                    max(r, s)
1812                } else {
1813                    ChoiceOutcome::Unchanged
1814                }
1815            }
1816            _ => ChoiceOutcome::Continue,
1817        },
1818        ChoiceClose::DoubleClick => match event {
1819            ct_event!(mouse any for m) if state.mouse.doubleclick(state.popup.area, m) => {
1820                if let Some(n) = item_at(&state.item_areas, m.column, m.row) {
1821                    let r = state.move_to(state.offset() + n);
1822                    let s = if state.set_popup_active(false) {
1823                        ChoiceOutcome::Changed
1824                    } else {
1825                        ChoiceOutcome::Unchanged
1826                    };
1827                    max(r, s)
1828                } else {
1829                    ChoiceOutcome::Unchanged
1830                }
1831            }
1832            _ => ChoiceOutcome::Continue,
1833        },
1834    }
1835}
1836
1837/// Handle events for the popup.
1838/// Call before other handlers to deal with intersections
1839/// with other widgets.
1840pub fn handle_events<T: PartialEq + Clone + Default>(
1841    state: &mut ChoiceState<T>,
1842    focus: bool,
1843    event: &crossterm::event::Event,
1844) -> ChoiceOutcome {
1845    state.focus.set(focus);
1846    HandleEvent::handle(state, event, Popup)
1847}
1848
1849/// Handle only mouse-events.
1850pub fn handle_mouse_events<T: PartialEq + Clone + Default>(
1851    state: &mut ChoiceState<T>,
1852    event: &crossterm::event::Event,
1853) -> ChoiceOutcome {
1854    HandleEvent::handle(state, event, MouseOnly)
1855}