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