1use crate::_private::NonExhaustive;
34use crate::choice::core::ChoiceCore;
35use crate::event::ChoiceOutcome;
36use crate::util::{block_size, revert_style};
37use rat_event::util::{item_at, mouse_trap, MouseFlags};
38use rat_event::{ct_event, ConsumedEvent, HandleEvent, MouseOnly, Popup};
39use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
40use rat_popup::event::PopupOutcome;
41use rat_popup::{Placement, PopupCore, PopupCoreState, PopupStyle};
42use rat_reloc::{relocate_area, relocate_areas, RelocatableState};
43use rat_scrolled::event::ScrollOutcome;
44use rat_scrolled::{Scroll, ScrollAreaState};
45use ratatui::buffer::Buffer;
46use ratatui::layout::{Alignment, Rect};
47use ratatui::prelude::BlockExt;
48use ratatui::style::Style;
49use ratatui::text::{Line, Span};
50#[cfg(feature = "unstable-widget-ref")]
51use ratatui::widgets::StatefulWidgetRef;
52use ratatui::widgets::{Block, StatefulWidget, Widget};
53use std::cell::RefCell;
54use std::cmp::{max, min};
55use std::marker::PhantomData;
56use std::rc::Rc;
57
58#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
60pub enum ChoiceSelect {
61 #[default]
63 MouseScroll,
64 MouseMove,
66 MouseClick,
68}
69
70#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
72pub enum ChoiceClose {
73 #[default]
75 SingleClick,
76 DoubleClick,
78}
79
80#[derive(Debug, Clone)]
89pub struct Choice<'a, T>
90where
91 T: PartialEq + Clone + Default,
92{
93 values: Rc<RefCell<Vec<T>>>,
94 default_value: Option<T>,
95 items: Rc<RefCell<Vec<Line<'a>>>>,
96
97 style: Style,
98 button_style: Option<Style>,
99 select_style: Option<Style>,
100 focus_style: Option<Style>,
101 block: Option<Block<'a>>,
102
103 popup_alignment: Alignment,
104 popup_placement: Placement,
105 popup_len: Option<u16>,
106 popup: PopupCore<'a>,
107
108 behave_select: ChoiceSelect,
109 behave_close: ChoiceClose,
110}
111
112#[derive(Debug)]
114pub struct ChoiceWidget<'a, T>
115where
116 T: PartialEq,
117{
118 values: Rc<RefCell<Vec<T>>>,
119 default_value: Option<T>,
120 items: Rc<RefCell<Vec<Line<'a>>>>,
121
122 style: Style,
123 button_style: Option<Style>,
124 focus_style: Option<Style>,
125 block: Option<Block<'a>>,
126 len: Option<u16>,
127
128 behave_select: ChoiceSelect,
129 behave_close: ChoiceClose,
130
131 _phantom: PhantomData<T>,
132}
133
134#[derive(Debug)]
137pub struct ChoicePopup<'a, T>
138where
139 T: PartialEq,
140{
141 items: Rc<RefCell<Vec<Line<'a>>>>,
142
143 style: Style,
144 select_style: Option<Style>,
145
146 popup_alignment: Alignment,
147 popup_placement: Placement,
148 popup_len: Option<u16>,
149 popup: PopupCore<'a>,
150
151 _phantom: PhantomData<T>,
152}
153
154#[derive(Debug, Clone)]
156pub struct ChoiceStyle {
157 pub style: Style,
158 pub button: Option<Style>,
159 pub select: Option<Style>,
160 pub focus: Option<Style>,
161 pub block: Option<Block<'static>>,
162
163 pub popup: PopupStyle,
164 pub popup_len: Option<u16>,
165
166 pub behave_select: Option<ChoiceSelect>,
167 pub behave_close: Option<ChoiceClose>,
168
169 pub non_exhaustive: NonExhaustive,
170}
171
172#[derive(Debug)]
174pub struct ChoiceState<T = usize>
175where
176 T: PartialEq + Clone + Default,
177{
178 pub area: Rect,
181 pub nav_char: Vec<Vec<char>>,
184 pub item_area: Rect,
187 pub button_area: Rect,
190 pub item_areas: Vec<Rect>,
193 pub core: ChoiceCore<T>,
195 pub popup: PopupCoreState,
197 pub behave_select: ChoiceSelect,
200 pub behave_close: ChoiceClose,
203
204 pub focus: FocusFlag,
207 pub mouse: MouseFlags,
209
210 pub non_exhaustive: NonExhaustive,
211}
212
213pub(crate) mod event {
214 use rat_event::{ConsumedEvent, Outcome};
215 use rat_popup::event::PopupOutcome;
216
217 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
219 pub enum ChoiceOutcome {
220 Continue,
222 Unchanged,
224 Changed,
226 Value,
228 }
229
230 impl ConsumedEvent for ChoiceOutcome {
231 fn is_consumed(&self) -> bool {
232 *self != ChoiceOutcome::Continue
233 }
234 }
235
236 impl From<Outcome> for ChoiceOutcome {
237 fn from(value: Outcome) -> Self {
238 match value {
239 Outcome::Continue => ChoiceOutcome::Continue,
240 Outcome::Unchanged => ChoiceOutcome::Unchanged,
241 Outcome::Changed => ChoiceOutcome::Changed,
242 }
243 }
244 }
245
246 impl From<ChoiceOutcome> for Outcome {
247 fn from(value: ChoiceOutcome) -> Self {
248 match value {
249 ChoiceOutcome::Continue => Outcome::Continue,
250 ChoiceOutcome::Unchanged => Outcome::Unchanged,
251 ChoiceOutcome::Changed => Outcome::Changed,
252 ChoiceOutcome::Value => Outcome::Changed,
253 }
254 }
255 }
256
257 impl From<PopupOutcome> for ChoiceOutcome {
258 fn from(value: PopupOutcome) -> Self {
259 match value {
260 PopupOutcome::Continue => ChoiceOutcome::Continue,
261 PopupOutcome::Unchanged => ChoiceOutcome::Unchanged,
262 PopupOutcome::Changed => ChoiceOutcome::Changed,
263 PopupOutcome::Hide => ChoiceOutcome::Changed,
264 }
265 }
266 }
267}
268
269pub mod core {
270 #[derive(Debug, Default, Clone)]
271 pub struct ChoiceCore<T>
272 where
273 T: PartialEq + Clone + Default,
274 {
275 values: Vec<T>,
278 default_value: Option<T>,
280 value: T,
285 selected: Option<usize>,
287 }
288
289 impl<T> ChoiceCore<T>
290 where
291 T: PartialEq + Clone + Default,
292 {
293 pub fn set_values(&mut self, values: Vec<T>) {
294 self.values = values;
295 if self.values.is_empty() {
297 self.selected = None;
298 } else {
299 self.selected = self.values.iter().position(|v| *v == self.value);
300 }
301 }
302
303 pub fn values(&self) -> &[T] {
305 &self.values
306 }
307
308 pub fn set_default_value(&mut self, default_value: Option<T>) {
314 self.default_value = default_value.clone();
315 }
316
317 pub fn default_value(&self) -> &Option<T> {
319 &self.default_value
320 }
321
322 pub fn selected(&self) -> Option<usize> {
330 self.selected
331 }
332
333 pub fn set_selected(&mut self, select: usize) -> bool {
341 let old_sel = self.selected;
342 if self.values.is_empty() {
343 self.selected = None;
344 } else {
345 if let Some(value) = self.values.get(select) {
346 self.value = value.clone();
347 self.selected = Some(select);
348 } else {
349 self.selected = None;
351 }
352 }
353 old_sel != self.selected
354 }
355
356 pub fn set_value(&mut self, value: T) -> bool {
365 let old_value = self.value.clone();
366
367 self.value = value;
368 self.selected = self.values.iter().position(|v| *v == self.value);
369
370 old_value != self.value
371 }
372
373 pub fn value(&self) -> T {
375 self.value.clone()
376 }
377
378 pub fn is_empty(&self) -> bool {
379 self.values.is_empty()
380 }
381
382 pub fn clear(&mut self) -> bool {
383 let old_selected = self.selected;
384 let old_value = self.value.clone();
385
386 if let Some(default_value) = &self.default_value {
387 self.value = default_value.clone();
388 }
389
390 self.selected = self.values.iter().position(|v| *v == self.value);
391
392 old_selected != self.selected || old_value != self.value
393 }
394 }
395}
396
397impl Default for ChoiceStyle {
398 fn default() -> Self {
399 Self {
400 style: Default::default(),
401 button: None,
402 select: None,
403 focus: None,
404 block: None,
405 popup: Default::default(),
406 popup_len: None,
407 behave_select: None,
408 behave_close: None,
409 non_exhaustive: NonExhaustive,
410 }
411 }
412}
413
414impl<T> Default for Choice<'_, T>
415where
416 T: PartialEq + Clone + Default,
417{
418 fn default() -> Self {
419 Self {
420 values: Default::default(),
421 default_value: Default::default(),
422 items: Default::default(),
423 style: Default::default(),
424 button_style: Default::default(),
425 select_style: Default::default(),
426 focus_style: Default::default(),
427 block: Default::default(),
428 popup_len: Default::default(),
429 popup_alignment: Alignment::Left,
430 popup_placement: Placement::BelowOrAbove,
431 popup: Default::default(),
432 behave_select: Default::default(),
433 behave_close: Default::default(),
434 }
435 }
436}
437
438impl<'a> Choice<'a, usize> {
439 #[inline]
441 pub fn auto_items<V: Into<Line<'a>>>(self, items: impl IntoIterator<Item = V>) -> Self {
442 {
443 let mut values = self.values.borrow_mut();
444 let mut itemz = self.items.borrow_mut();
445
446 values.clear();
447 itemz.clear();
448
449 for (k, v) in items.into_iter().enumerate() {
450 values.push(k);
451 itemz.push(v.into());
452 }
453 }
454
455 self
456 }
457
458 pub fn auto_item(self, item: impl Into<Line<'a>>) -> Self {
460 let idx = self.values.borrow().len();
461 self.values.borrow_mut().push(idx);
462 self.items.borrow_mut().push(item.into());
463 self
464 }
465}
466
467impl<'a, T> Choice<'a, T>
468where
469 T: PartialEq + Clone + Default,
470{
471 pub fn new() -> Self {
472 Self::default()
473 }
474
475 #[inline]
477 pub fn items<V: Into<Line<'a>>>(self, items: impl IntoIterator<Item = (T, V)>) -> Self {
478 {
479 let mut values = self.values.borrow_mut();
480 let mut itemz = self.items.borrow_mut();
481
482 values.clear();
483 itemz.clear();
484
485 for (k, v) in items.into_iter() {
486 values.push(k);
487 itemz.push(v.into());
488 }
489 }
490
491 self
492 }
493
494 pub fn item(self, value: T, item: impl Into<Line<'a>>) -> Self {
496 self.values.borrow_mut().push(value);
497 self.items.borrow_mut().push(item.into());
498 self
499 }
500
501 pub fn default_value(mut self, default: T) -> Self {
503 self.default_value = Some(default);
504 self
505 }
506
507 pub fn styles(mut self, styles: ChoiceStyle) -> Self {
509 self.style = styles.style;
510 if styles.button.is_some() {
511 self.button_style = styles.button;
512 }
513 if styles.select.is_some() {
514 self.select_style = styles.select;
515 }
516 if styles.focus.is_some() {
517 self.focus_style = styles.focus;
518 }
519 if styles.block.is_some() {
520 self.block = styles.block;
521 }
522 if let Some(select) = styles.behave_select {
523 self.behave_select = select;
524 }
525 if let Some(close) = styles.behave_close {
526 self.behave_close = close;
527 }
528 self.block = self.block.map(|v| v.style(self.style));
529 if let Some(alignment) = styles.popup.alignment {
530 self.popup_alignment = alignment;
531 }
532 if let Some(placement) = styles.popup.placement {
533 self.popup_placement = placement;
534 }
535 if styles.popup_len.is_some() {
536 self.popup_len = styles.popup_len;
537 }
538 self.popup = self.popup.styles(styles.popup);
539 self
540 }
541
542 pub fn style(mut self, style: Style) -> Self {
544 self.style = style;
545 self.block = self.block.map(|v| v.style(self.style));
546 self
547 }
548
549 pub fn button_style(mut self, style: Style) -> Self {
551 self.button_style = Some(style);
552 self
553 }
554
555 pub fn select_style(mut self, style: Style) -> Self {
557 self.select_style = Some(style);
558 self
559 }
560
561 pub fn focus_style(mut self, style: Style) -> Self {
563 self.focus_style = Some(style);
564 self
565 }
566
567 pub fn block(mut self, block: Block<'a>) -> Self {
569 self.block = Some(block);
570 self.block = self.block.map(|v| v.style(self.style));
571 self
572 }
573
574 pub fn popup_alignment(mut self, alignment: Alignment) -> Self {
579 self.popup_alignment = alignment;
580 self
581 }
582
583 pub fn popup_placement(mut self, placement: Placement) -> Self {
588 self.popup_placement = placement;
589 self
590 }
591
592 pub fn popup_boundary(mut self, boundary: Rect) -> Self {
594 self.popup = self.popup.boundary(boundary);
595 self
596 }
597
598 pub fn popup_len(mut self, len: u16) -> Self {
603 self.popup_len = Some(len);
604 self
605 }
606
607 pub fn popup_style(mut self, style: Style) -> Self {
609 self.popup = self.popup.style(style);
610 self
611 }
612
613 pub fn popup_block(mut self, block: Block<'a>) -> Self {
615 self.popup = self.popup.block(block);
616 self
617 }
618
619 pub fn popup_scroll(mut self, scroll: Scroll<'a>) -> Self {
621 self.popup = self.popup.v_scroll(scroll);
622 self
623 }
624
625 pub fn popup_offset(mut self, offset: (i16, i16)) -> Self {
632 self.popup = self.popup.offset(offset);
633 self
634 }
635
636 pub fn popup_x_offset(mut self, offset: i16) -> Self {
639 self.popup = self.popup.x_offset(offset);
640 self
641 }
642
643 pub fn popup_y_offset(mut self, offset: i16) -> Self {
646 self.popup = self.popup.y_offset(offset);
647 self
648 }
649
650 pub fn behave_select(mut self, select: ChoiceSelect) -> Self {
652 self.behave_select = select;
653 self
654 }
655
656 pub fn behave_close(mut self, close: ChoiceClose) -> Self {
658 self.behave_close = close;
659 self
660 }
661
662 pub fn width(&self) -> u16 {
664 let w = self
665 .items
666 .borrow()
667 .iter()
668 .map(|v| v.width())
669 .max()
670 .unwrap_or_default();
671
672 w as u16 + block_size(&self.block).width
673 }
674
675 pub fn height(&self) -> u16 {
677 1 + block_size(&self.block).height
678 }
679
680 pub fn into_widgets(self) -> (ChoiceWidget<'a, T>, ChoicePopup<'a, T>) {
684 (
685 ChoiceWidget {
686 values: self.values,
687 default_value: self.default_value,
688 items: self.items.clone(),
689 style: self.style,
690 button_style: self.button_style,
691 focus_style: self.focus_style,
692 block: self.block,
693 len: self.popup_len,
694 behave_select: self.behave_select,
695 behave_close: self.behave_close,
696 _phantom: Default::default(),
697 },
698 ChoicePopup {
699 items: self.items.clone(),
700 style: self.style,
701 select_style: self.select_style,
702 popup: self.popup,
703 popup_alignment: self.popup_alignment,
704 popup_placement: self.popup_placement,
705 popup_len: self.popup_len,
706 _phantom: Default::default(),
707 },
708 )
709 }
710}
711
712impl<'a, T> ChoiceWidget<'a, T>
713where
714 T: PartialEq + Clone + Default,
715{
716 pub fn width(&self) -> u16 {
718 let w = self
719 .items
720 .borrow()
721 .iter()
722 .map(|v| v.width())
723 .max()
724 .unwrap_or_default();
725
726 w as u16 + block_size(&self.block).width
727 }
728
729 pub fn height(&self) -> u16 {
731 1 + block_size(&self.block).height
732 }
733}
734
735#[cfg(feature = "unstable-widget-ref")]
736impl<'a, T> StatefulWidgetRef for ChoiceWidget<'a, T>
737where
738 T: PartialEq + Clone + Default,
739{
740 type State = ChoiceState<T>;
741
742 fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
743 state.core.set_values(self.values.borrow().clone());
744 if let Some(default_value) = self.default_value.clone() {
745 state.core.set_default_value(Some(default_value));
746 }
747
748 render_choice(self, area, buf, state);
749 }
750}
751
752impl<T> StatefulWidget for ChoiceWidget<'_, T>
753where
754 T: PartialEq + Clone + Default,
755{
756 type State = ChoiceState<T>;
757
758 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
759 state.core.set_values(self.values.take());
760 if let Some(default_value) = self.default_value.take() {
761 state.core.set_default_value(Some(default_value));
762 }
763
764 render_choice(&self, area, buf, state);
765 }
766}
767
768fn render_choice<T: PartialEq + Clone + Default>(
769 widget: &ChoiceWidget<'_, T>,
770 area: Rect,
771 buf: &mut Buffer,
772 state: &mut ChoiceState<T>,
773) {
774 state.area = area;
775 state.behave_select = widget.behave_select;
776 state.behave_close = widget.behave_close;
777
778 if !state.popup.is_active() {
779 let len = widget
780 .len
781 .unwrap_or_else(|| min(5, widget.items.borrow().len()) as u16);
782 state.popup.v_scroll.max_offset = widget.items.borrow().len().saturating_sub(len as usize);
783 state.popup.v_scroll.page_len = len as usize;
784 if let Some(selected) = state.core.selected() {
785 state.popup.v_scroll.scroll_to_pos(selected);
786 }
787 }
788
789 state.nav_char.clear();
790 state.nav_char.extend(widget.items.borrow().iter().map(|v| {
791 v.spans
792 .first()
793 .and_then(|v| v.content.as_ref().chars().next())
794 .map_or(Vec::default(), |c| c.to_lowercase().collect::<Vec<_>>())
795 }));
796
797 let inner = widget.block.inner_if_some(area);
798
799 state.item_area = Rect::new(
800 inner.x,
801 inner.y,
802 inner.width.saturating_sub(3),
803 inner.height,
804 );
805 state.button_area = Rect::new(
806 inner.right().saturating_sub(min(3, inner.width)),
807 inner.y,
808 3,
809 inner.height,
810 );
811
812 let style = widget.style;
813 let focus_style = widget.focus_style.unwrap_or(revert_style(widget.style));
814
815 if state.is_focused() {
816 if widget.block.is_some() {
817 widget.block.render(area, buf);
818 } else {
819 buf.set_style(inner, style);
820 }
821 buf.set_style(inner, focus_style);
822 } else {
823 if widget.block.is_some() {
824 widget.block.render(area, buf);
825 } else {
826 buf.set_style(inner, style);
827 }
828 if let Some(button_style) = widget.button_style {
829 buf.set_style(state.button_area, button_style);
830 }
831 }
832
833 if let Some(selected) = state.core.selected() {
834 if let Some(item) = widget.items.borrow().get(selected) {
835 item.render(state.item_area, buf);
836 }
837 }
838
839 let dy = if (state.button_area.height & 1) == 1 {
840 state.button_area.height / 2
841 } else {
842 state.button_area.height.saturating_sub(1) / 2
843 };
844 let bc = if state.is_popup_active() {
845 " â—† "
846 } else {
847 " â–¼ "
848 };
849 Span::from(bc).render(
850 Rect::new(state.button_area.x, state.button_area.y + dy, 3, 1),
851 buf,
852 );
853}
854
855impl<T> ChoicePopup<'_, T>
856where
857 T: PartialEq + Clone + Default,
858{
859 pub fn layout(&self, area: Rect, buf: &mut Buffer, state: &mut ChoiceState<T>) -> Rect {
862 if state.popup.is_active() {
863 let len = min(
864 self.popup_len.unwrap_or(5),
865 self.items.borrow().len() as u16,
866 );
867 let popup_len = len + self.popup.get_block_size().height;
868 let pop_area = Rect::new(0, 0, area.width, popup_len);
869
870 self.popup
871 .ref_constraint(
872 self.popup_placement
873 .into_constraint(self.popup_alignment, area),
874 )
875 .layout(pop_area, buf)
876 } else {
877 Rect::default()
878 }
879 }
880}
881
882#[cfg(feature = "unstable-widget-ref")]
883impl<T> StatefulWidgetRef for ChoicePopup<'_, T>
884where
885 T: PartialEq + Clone + Default,
886{
887 type State = ChoiceState<T>;
888
889 fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
890 render_popup(&self, area, buf, state);
891 }
892}
893
894impl<T> StatefulWidget for ChoicePopup<'_, T>
895where
896 T: PartialEq + Clone + Default,
897{
898 type State = ChoiceState<T>;
899
900 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
901 render_popup(&self, area, buf, state);
902 }
903}
904
905fn render_popup<T: PartialEq + Clone + Default>(
906 widget: &ChoicePopup<'_, T>,
907 area: Rect,
908 buf: &mut Buffer,
909 state: &mut ChoiceState<T>,
910) {
911 if state.popup.is_active() {
912 let len = min(
913 widget.popup_len.unwrap_or(5),
914 widget.items.borrow().len() as u16,
915 );
916 let popup_len = len + widget.popup.get_block_size().height;
917 let pop_area = Rect::new(0, 0, area.width, popup_len);
918
919 let popup_style = widget.popup.style;
920 let select_style = widget.select_style.unwrap_or(revert_style(widget.style));
921
922 widget
923 .popup
924 .ref_constraint(
925 widget
926 .popup_placement
927 .into_constraint(widget.popup_alignment, area),
928 )
929 .render(pop_area, buf, &mut state.popup);
930
931 let inner = state.popup.widget_area;
932
933 state.popup.v_scroll.max_offset = widget
934 .items
935 .borrow()
936 .len()
937 .saturating_sub(inner.height as usize);
938 state.popup.v_scroll.page_len = inner.height as usize;
939
940 state.item_areas.clear();
941 let mut row = inner.y;
942 let mut idx = state.popup.v_scroll.offset;
943 loop {
944 if row >= inner.bottom() {
945 break;
946 }
947
948 let item_area = Rect::new(inner.x, row, inner.width, 1);
949 state.item_areas.push(item_area);
950
951 if let Some(item) = widget.items.borrow().get(idx) {
952 let style = if state.core.selected() == Some(idx) {
953 popup_style.patch(select_style)
954 } else {
955 popup_style
956 };
957
958 buf.set_style(item_area, style);
959 item.render(item_area, buf);
960 } else {
961 }
963
964 row += 1;
965 idx += 1;
966 }
967 } else {
968 state.popup.clear_areas();
969 }
970}
971
972impl<T> Clone for ChoiceState<T>
973where
974 T: PartialEq + Clone + Default,
975{
976 fn clone(&self) -> Self {
977 Self {
978 area: self.area,
979 nav_char: self.nav_char.clone(),
980 item_area: self.item_area,
981 button_area: self.button_area,
982 item_areas: self.item_areas.clone(),
983 core: self.core.clone(),
984 popup: self.popup.clone(),
985 behave_select: self.behave_select,
986 behave_close: self.behave_close,
987 focus: FocusFlag::named(self.focus.name()),
988 mouse: Default::default(),
989 non_exhaustive: NonExhaustive,
990 }
991 }
992}
993
994impl<T> Default for ChoiceState<T>
995where
996 T: PartialEq + Clone + Default,
997{
998 fn default() -> Self {
999 Self {
1000 area: Default::default(),
1001 nav_char: Default::default(),
1002 item_area: Default::default(),
1003 button_area: Default::default(),
1004 item_areas: Default::default(),
1005 core: Default::default(),
1006 popup: Default::default(),
1007 behave_select: Default::default(),
1008 behave_close: Default::default(),
1009 focus: Default::default(),
1010 mouse: Default::default(),
1011 non_exhaustive: NonExhaustive,
1012 }
1013 }
1014}
1015
1016impl<T> HasFocus for ChoiceState<T>
1017where
1018 T: PartialEq + Clone + Default,
1019{
1020 fn build(&self, builder: &mut FocusBuilder) {
1021 builder.widget_with_flags(self.focus(), self.area(), 0, self.navigable());
1022 builder.widget_with_flags(self.focus(), self.popup.area, 1, Navigation::Mouse);
1023 }
1024
1025 fn focus(&self) -> FocusFlag {
1026 self.focus.clone()
1027 }
1028
1029 fn area(&self) -> Rect {
1030 self.area
1031 }
1032}
1033
1034impl<T> RelocatableState for ChoiceState<T>
1035where
1036 T: PartialEq + Clone + Default,
1037{
1038 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1039 self.area = relocate_area(self.area, shift, clip);
1040 self.item_area = relocate_area(self.item_area, shift, clip);
1041 self.button_area = relocate_area(self.button_area, shift, clip);
1042 relocate_areas(&mut self.item_areas, shift, clip);
1043 self.popup.relocate(shift, clip);
1044 }
1045}
1046
1047impl<T> ChoiceState<T>
1048where
1049 T: PartialEq + Clone + Default,
1050{
1051 pub fn new() -> Self {
1052 Self::default()
1053 }
1054
1055 pub fn named(name: &str) -> Self {
1056 Self {
1057 focus: FocusFlag::named(name),
1058 ..Default::default()
1059 }
1060 }
1061
1062 pub fn is_popup_active(&self) -> bool {
1064 self.popup.is_active()
1065 }
1066
1067 pub fn flip_popup_active(&mut self) {
1069 self.popup.flip_active();
1070 }
1071
1072 pub fn set_popup_active(&mut self, active: bool) -> bool {
1074 self.popup.set_active(active)
1075 }
1076
1077 pub fn set_default_value(&mut self, default_value: Option<T>) {
1086 self.core.set_default_value(default_value);
1087 }
1088
1089 pub fn default_value(&self) -> &Option<T> {
1091 self.core.default_value()
1092 }
1093
1094 pub fn set_value(&mut self, value: T) -> bool {
1105 self.core.set_value(value)
1106 }
1107
1108 pub fn value(&self) -> T {
1110 self.core.value()
1111 }
1112
1113 pub fn clear(&mut self) -> bool {
1115 self.core.clear()
1116 }
1117
1118 pub fn select(&mut self, select: usize) -> bool {
1130 self.core.set_selected(select)
1131 }
1132
1133 pub fn selected(&self) -> Option<usize> {
1138 self.core.selected()
1139 }
1140
1141 pub fn is_empty(&self) -> bool {
1143 self.core.values().is_empty()
1144 }
1145
1146 pub fn len(&self) -> usize {
1148 self.core.values().len()
1149 }
1150
1151 pub fn clear_offset(&mut self) {
1153 self.popup.v_scroll.set_offset(0);
1154 }
1155
1156 pub fn set_offset(&mut self, offset: usize) -> bool {
1158 self.popup.v_scroll.set_offset(offset)
1159 }
1160
1161 pub fn offset(&self) -> usize {
1163 self.popup.v_scroll.offset()
1164 }
1165
1166 pub fn max_offset(&self) -> usize {
1168 self.popup.v_scroll.max_offset()
1169 }
1170
1171 pub fn page_len(&self) -> usize {
1173 self.popup.v_scroll.page_len()
1174 }
1175
1176 pub fn scroll_by(&self) -> usize {
1178 self.popup.v_scroll.scroll_by()
1179 }
1180
1181 pub fn scroll_to_selected(&mut self) -> bool {
1183 if let Some(selected) = self.core.selected() {
1184 self.popup.v_scroll.scroll_to_pos(selected)
1185 } else {
1186 false
1187 }
1188 }
1189}
1190
1191impl<T> ChoiceState<T>
1192where
1193 T: PartialEq + Clone + Default,
1194{
1195 pub fn select_by_char(&mut self, c: char) -> bool {
1197 if self.nav_char.is_empty() {
1198 return false;
1199 }
1200 let c = c.to_lowercase().collect::<Vec<_>>();
1201
1202 let (mut idx, end_loop) = if let Some(idx) = self.core.selected() {
1203 (idx + 1, idx)
1204 } else {
1205 if self.nav_char[0] == c {
1206 self.core.set_selected(0);
1207 return true;
1208 } else {
1209 (1, 0)
1210 }
1211 };
1212 loop {
1213 if idx >= self.nav_char.len() {
1214 idx = 0;
1215 }
1216 if idx == end_loop {
1217 break;
1218 }
1219
1220 if self.nav_char[idx] == c {
1221 self.core.set_selected(idx);
1222 return true;
1223 }
1224
1225 idx += 1;
1226 }
1227 false
1228 }
1229
1230 pub fn reverse_select_by_char(&mut self, c: char) -> bool {
1232 if self.nav_char.is_empty() {
1233 return false;
1234 }
1235 let c = c.to_lowercase().collect::<Vec<_>>();
1236
1237 let (mut idx, end_loop) = if let Some(idx) = self.core.selected() {
1238 if idx == 0 {
1239 (self.nav_char.len() - 1, 0)
1240 } else {
1241 (idx - 1, idx)
1242 }
1243 } else {
1244 if self.nav_char.last() == Some(&c) {
1245 self.core.set_selected(self.nav_char.len() - 1);
1246 return true;
1247 } else {
1248 (self.nav_char.len() - 1, 0)
1249 }
1250 };
1251 loop {
1252 if self.nav_char[idx] == c {
1253 self.core.set_selected(idx);
1254 return true;
1255 }
1256
1257 if idx == end_loop {
1258 break;
1259 }
1260
1261 if idx == 0 {
1262 idx = self.nav_char.len() - 1;
1263 } else {
1264 idx -= 1;
1265 }
1266 }
1267 false
1268 }
1269
1270 pub fn move_to(&mut self, n: usize) -> ChoiceOutcome {
1272 let old_selected = self.selected();
1273 let r1 = self.popup.set_active(true);
1274 let r2 = self.select(n);
1275 let r3 = self.scroll_to_selected();
1276 if old_selected != self.selected() {
1277 ChoiceOutcome::Value
1278 } else if r1 || r2 || r3 {
1279 ChoiceOutcome::Changed
1280 } else {
1281 ChoiceOutcome::Continue
1282 }
1283 }
1284
1285 pub fn move_down(&mut self, n: usize) -> ChoiceOutcome {
1287 if self.core.is_empty() {
1288 return ChoiceOutcome::Continue;
1289 }
1290
1291 let old_selected = self.selected();
1292 let r1 = self.popup.set_active(true);
1293 let idx = if let Some(idx) = self.core.selected() {
1294 idx + n
1295 } else {
1296 n.saturating_sub(1)
1297 };
1298 let idx = idx.clamp(0, self.len() - 1);
1299 let r2 = self.core.set_selected(idx);
1300 let r3 = self.scroll_to_selected();
1301
1302 if old_selected != self.selected() {
1303 ChoiceOutcome::Value
1304 } else if r1 || r2 || r3 {
1305 ChoiceOutcome::Changed
1306 } else {
1307 ChoiceOutcome::Continue
1308 }
1309 }
1310
1311 pub fn move_up(&mut self, n: usize) -> ChoiceOutcome {
1313 if self.core.is_empty() {
1314 return ChoiceOutcome::Continue;
1315 }
1316
1317 let old_selected = self.selected();
1318 let r1 = self.popup.set_active(true);
1319 let idx = if let Some(idx) = self.core.selected() {
1320 idx.saturating_sub(n)
1321 } else {
1322 0
1323 };
1324 let idx = idx.clamp(0, self.len() - 1);
1325 let r2 = self.core.set_selected(idx);
1326 let r3 = self.scroll_to_selected();
1327
1328 if old_selected != self.selected() {
1329 ChoiceOutcome::Value
1330 } else if r1 || r2 || r3 {
1331 ChoiceOutcome::Changed
1332 } else {
1333 ChoiceOutcome::Continue
1334 }
1335 }
1336}
1337
1338impl<T: PartialEq + Clone + Default> HandleEvent<crossterm::event::Event, Popup, ChoiceOutcome>
1339 for ChoiceState<T>
1340{
1341 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Popup) -> ChoiceOutcome {
1342 if self.lost_focus() {
1343 self.set_popup_active(false);
1344 }
1346
1347 let r = if self.is_focused() {
1348 match event {
1349 ct_event!(key press ' ') | ct_event!(keycode press Enter) => {
1350 self.flip_popup_active();
1351 ChoiceOutcome::Changed
1352 }
1353 ct_event!(keycode press Esc) => {
1354 if self.set_popup_active(false) {
1355 ChoiceOutcome::Changed
1356 } else {
1357 ChoiceOutcome::Continue
1358 }
1359 }
1360 ct_event!(key press c) => {
1361 if self.select_by_char(*c) {
1362 self.scroll_to_selected();
1363 ChoiceOutcome::Value
1364 } else {
1365 ChoiceOutcome::Continue
1366 }
1367 }
1368 ct_event!(key press SHIFT-c) => {
1369 if self.reverse_select_by_char(*c) {
1370 self.scroll_to_selected();
1371 ChoiceOutcome::Value
1372 } else {
1373 ChoiceOutcome::Continue
1374 }
1375 }
1376 ct_event!(keycode press Delete) | ct_event!(keycode press Backspace) => {
1377 if self.clear() {
1378 ChoiceOutcome::Value
1379 } else {
1380 ChoiceOutcome::Continue
1381 }
1382 }
1383 ct_event!(keycode press Down) => self.move_down(1),
1384 ct_event!(keycode press Up) => self.move_up(1),
1385 ct_event!(keycode press PageUp) => self.move_up(self.page_len()),
1386 ct_event!(keycode press PageDown) => self.move_down(self.page_len()),
1387 ct_event!(keycode press Home) => self.move_to(0),
1388 ct_event!(keycode press End) => self.move_to(self.len().saturating_sub(1)),
1389 _ => ChoiceOutcome::Continue,
1390 }
1391 } else {
1392 ChoiceOutcome::Continue
1393 };
1394
1395 if !r.is_consumed() {
1396 self.handle(event, MouseOnly)
1397 } else {
1398 r
1399 }
1400 }
1401}
1402
1403impl<T: PartialEq + Clone + Default> HandleEvent<crossterm::event::Event, MouseOnly, ChoiceOutcome>
1404 for ChoiceState<T>
1405{
1406 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> ChoiceOutcome {
1407 let r0 = handle_mouse(self, event);
1408 let r1 = handle_select(self, event);
1409 let r2 = handle_close(self, event);
1410 let mut r = max(r0, max(r1, r2));
1411
1412 r = r.or_else(|| mouse_trap(event, self.popup.area).into());
1413
1414 self.popup.active.set_lost(false);
1415 self.popup.active.set_gained(false);
1416 r
1417 }
1418}
1419
1420fn handle_mouse<T: PartialEq + Clone + Default>(
1421 state: &mut ChoiceState<T>,
1422 event: &crossterm::event::Event,
1423) -> ChoiceOutcome {
1424 match event {
1425 ct_event!(mouse down Left for x,y)
1426 if state.item_area.contains((*x, *y).into())
1427 || state.button_area.contains((*x, *y).into()) =>
1428 {
1429 if !state.gained_focus() && !state.popup.active.lost() {
1430 state.flip_popup_active();
1431 ChoiceOutcome::Changed
1432 } else {
1433 ChoiceOutcome::Continue
1436 }
1437 }
1438 ct_event!(mouse down Left for x,y)
1439 | ct_event!(mouse down Right for x,y)
1440 | ct_event!(mouse down Middle for x,y)
1441 if !state.item_area.contains((*x, *y).into())
1442 && !state.button_area.contains((*x, *y).into()) =>
1443 {
1444 match state.popup.handle(event, Popup) {
1445 PopupOutcome::Hide => {
1446 state.set_popup_active(false);
1447 ChoiceOutcome::Changed
1448 }
1449 r => r.into(),
1450 }
1451 }
1452 _ => ChoiceOutcome::Continue,
1453 }
1454}
1455
1456fn handle_select<T: PartialEq + Clone + Default>(
1457 state: &mut ChoiceState<T>,
1458 event: &crossterm::event::Event,
1459) -> ChoiceOutcome {
1460 match state.behave_select {
1461 ChoiceSelect::MouseScroll => {
1462 let mut sas = ScrollAreaState::new()
1463 .area(state.popup.area)
1464 .v_scroll(&mut state.popup.v_scroll);
1465 let mut r = match sas.handle(event, MouseOnly) {
1466 ScrollOutcome::Up(n) => state.move_up(n),
1467 ScrollOutcome::Down(n) => state.move_down(n),
1468 ScrollOutcome::VPos(n) => state.move_to(n),
1469 _ => ChoiceOutcome::Continue,
1470 };
1471
1472 r = r.or_else(|| match event {
1473 ct_event!(mouse down Left for x,y)
1474 if state.popup.widget_area.contains((*x, *y).into()) =>
1475 {
1476 if let Some(n) = item_at(&state.item_areas, *x, *y) {
1477 state.move_to(state.offset() + n)
1478 } else {
1479 ChoiceOutcome::Unchanged
1480 }
1481 }
1482 ct_event!(mouse drag Left for x,y)
1483 if state.popup.widget_area.contains((*x, *y).into()) =>
1484 {
1485 if let Some(n) = item_at(&state.item_areas, *x, *y) {
1486 state.move_to(state.offset() + n)
1487 } else {
1488 ChoiceOutcome::Unchanged
1489 }
1490 }
1491 _ => ChoiceOutcome::Continue,
1492 });
1493 r
1494 }
1495 ChoiceSelect::MouseMove => {
1496 let mut r = if let Some(selected) = state.core.selected() {
1498 let rel_sel = selected.saturating_sub(state.offset());
1499 let mut sas = ScrollAreaState::new()
1500 .area(state.popup.area)
1501 .v_scroll(&mut state.popup.v_scroll);
1502 match sas.handle(event, MouseOnly) {
1503 ScrollOutcome::Up(n) => {
1504 state.popup.v_scroll.scroll_up(n);
1505 if state.select(state.offset() + rel_sel) {
1506 ChoiceOutcome::Value
1507 } else {
1508 ChoiceOutcome::Unchanged
1509 }
1510 }
1511 ScrollOutcome::Down(n) => {
1512 state.popup.v_scroll.scroll_down(n);
1513 if state.select(state.offset() + rel_sel) {
1514 ChoiceOutcome::Value
1515 } else {
1516 ChoiceOutcome::Unchanged
1517 }
1518 }
1519 ScrollOutcome::VPos(n) => {
1520 if state.popup.v_scroll.set_offset(n) {
1521 ChoiceOutcome::Value
1522 } else {
1523 ChoiceOutcome::Unchanged
1524 }
1525 }
1526 _ => ChoiceOutcome::Continue,
1527 }
1528 } else {
1529 ChoiceOutcome::Continue
1530 };
1531
1532 r = r.or_else(|| match event {
1533 ct_event!(mouse moved for x,y)
1534 if state.popup.widget_area.contains((*x, *y).into()) =>
1535 {
1536 if let Some(n) = item_at(&state.item_areas, *x, *y) {
1537 state.move_to(state.offset() + n)
1538 } else {
1539 ChoiceOutcome::Unchanged
1540 }
1541 }
1542 _ => ChoiceOutcome::Continue,
1543 });
1544 r
1545 }
1546 ChoiceSelect::MouseClick => {
1547 let mut sas = ScrollAreaState::new()
1549 .area(state.popup.area)
1550 .v_scroll(&mut state.popup.v_scroll);
1551 let mut r = match sas.handle(event, MouseOnly) {
1552 ScrollOutcome::Up(n) => {
1553 if state.popup.v_scroll.scroll_up(n) {
1554 ChoiceOutcome::Changed
1555 } else {
1556 ChoiceOutcome::Unchanged
1557 }
1558 }
1559 ScrollOutcome::Down(n) => {
1560 if state.popup.v_scroll.scroll_down(n) {
1561 ChoiceOutcome::Changed
1562 } else {
1563 ChoiceOutcome::Unchanged
1564 }
1565 }
1566 ScrollOutcome::VPos(n) => {
1567 if state.popup.v_scroll.set_offset(n) {
1568 ChoiceOutcome::Changed
1569 } else {
1570 ChoiceOutcome::Unchanged
1571 }
1572 }
1573 _ => ChoiceOutcome::Continue,
1574 };
1575
1576 r = r.or_else(|| match event {
1577 ct_event!(mouse down Left for x,y)
1578 if state.popup.widget_area.contains((*x, *y).into()) =>
1579 {
1580 if let Some(n) = item_at(&state.item_areas, *x, *y) {
1581 state.move_to(state.offset() + n)
1582 } else {
1583 ChoiceOutcome::Unchanged
1584 }
1585 }
1586 ct_event!(mouse drag Left for x,y)
1587 if state.popup.widget_area.contains((*x, *y).into()) =>
1588 {
1589 if let Some(n) = item_at(&state.item_areas, *x, *y) {
1590 state.move_to(state.offset() + n)
1591 } else {
1592 ChoiceOutcome::Unchanged
1593 }
1594 }
1595 _ => ChoiceOutcome::Continue,
1596 });
1597 r
1598 }
1599 }
1600}
1601
1602fn handle_close<T: PartialEq + Clone + Default>(
1603 state: &mut ChoiceState<T>,
1604 event: &crossterm::event::Event,
1605) -> ChoiceOutcome {
1606 match state.behave_close {
1607 ChoiceClose::SingleClick => match event {
1608 ct_event!(mouse down Left for x,y)
1609 if state.popup.widget_area.contains((*x, *y).into()) =>
1610 {
1611 if let Some(n) = item_at(&state.item_areas, *x, *y) {
1612 let r = state.move_to(state.offset() + n);
1613 let s = if state.set_popup_active(false) {
1614 ChoiceOutcome::Changed
1615 } else {
1616 ChoiceOutcome::Unchanged
1617 };
1618 max(r, s)
1619 } else {
1620 ChoiceOutcome::Unchanged
1621 }
1622 }
1623 _ => ChoiceOutcome::Continue,
1624 },
1625 ChoiceClose::DoubleClick => match event {
1626 ct_event!(mouse any for m) if state.mouse.doubleclick(state.popup.widget_area, m) => {
1627 if let Some(n) = item_at(&state.item_areas, m.column, m.row) {
1628 let r = state.move_to(state.offset() + n);
1629 let s = if state.set_popup_active(false) {
1630 ChoiceOutcome::Changed
1631 } else {
1632 ChoiceOutcome::Unchanged
1633 };
1634 max(r, s)
1635 } else {
1636 ChoiceOutcome::Unchanged
1637 }
1638 }
1639 _ => ChoiceOutcome::Continue,
1640 },
1641 }
1642}
1643
1644pub fn handle_popup<T: PartialEq + Clone + Default>(
1648 state: &mut ChoiceState<T>,
1649 focus: bool,
1650 event: &crossterm::event::Event,
1651) -> ChoiceOutcome {
1652 state.focus.set(focus);
1653 HandleEvent::handle(state, event, Popup)
1654}
1655
1656pub fn handle_mouse_events<T: PartialEq + Clone + Default>(
1658 state: &mut ChoiceState<T>,
1659 event: &crossterm::event::Event,
1660) -> ChoiceOutcome {
1661 HandleEvent::handle(state, event, MouseOnly)
1662}