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