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::{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 ChoiceFocus {
59 #[default]
61 SeparateOpen,
62 OpenOnFocusGained,
64}
65
66#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
68pub enum ChoiceSelect {
69 #[default]
71 MouseScroll,
72 MouseMove,
74 MouseClick,
76}
77
78#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
80pub enum ChoiceClose {
81 #[default]
83 SingleClick,
84 DoubleClick,
86}
87
88#[derive(Debug, Clone)]
97pub struct Choice<'a, T>
98where
99 T: PartialEq + Clone + Default,
100{
101 values: Rc<RefCell<Vec<T>>>,
102 default_value: Option<T>,
103 items: Rc<RefCell<Vec<Line<'a>>>>,
104
105 style: Style,
106 button_style: Option<Style>,
107 select_style: Option<Style>,
108 focus_style: Option<Style>,
109 block: Option<Block<'a>>,
110 skip_item_render: bool,
111
112 popup_alignment: Alignment,
113 popup_placement: Placement,
114 popup_len: Option<u16>,
115 popup: PopupCore,
116 popup_style: Style,
117 popup_scroll: Option<Scroll<'a>>,
118 popup_block: Option<Block<'a>>,
119
120 behave_focus: ChoiceFocus,
121 behave_select: ChoiceSelect,
122 behave_close: ChoiceClose,
123}
124
125#[derive(Debug)]
127pub struct ChoiceWidget<'a, T>
128where
129 T: PartialEq,
130{
131 values: Rc<RefCell<Vec<T>>>,
132 default_value: Option<T>,
133 items: Rc<RefCell<Vec<Line<'a>>>>,
134
135 style: Style,
136 button_style: Option<Style>,
137 focus_style: Option<Style>,
138 block: Option<Block<'a>>,
139 skip_item_render: bool,
140 len: Option<u16>,
141
142 behave_focus: ChoiceFocus,
143 behave_select: ChoiceSelect,
144 behave_close: ChoiceClose,
145}
146
147#[derive(Debug)]
150pub struct ChoicePopup<'a, T>
151where
152 T: PartialEq,
153{
154 items: Rc<RefCell<Vec<Line<'a>>>>,
155
156 style: Style,
157 select_style: Option<Style>,
158
159 popup_alignment: Alignment,
160 popup_placement: Placement,
161 popup_len: Option<u16>,
162 popup: PopupCore,
163 popup_style: Style,
164 popup_scroll: Option<Scroll<'a>>,
165 popup_block: Option<Block<'a>>,
166
167 _phantom: PhantomData<T>,
168}
169
170#[derive(Debug, Clone)]
172pub struct ChoiceStyle {
173 pub style: Style,
174 pub button: Option<Style>,
175 pub select: Option<Style>,
176 pub focus: Option<Style>,
177 pub block: Option<Block<'static>>,
178
179 pub popup: PopupStyle,
180 pub popup_style: Option<Style>,
181 pub popup_border: Option<Style>,
182 pub popup_scroll: Option<ScrollStyle>,
183 pub popup_block: Option<Block<'static>>,
184 pub popup_len: Option<u16>,
185
186 pub behave_focus: Option<ChoiceFocus>,
187 pub behave_select: Option<ChoiceSelect>,
188 pub behave_close: Option<ChoiceClose>,
189
190 pub non_exhaustive: NonExhaustive,
191}
192
193#[derive(Debug)]
195pub struct ChoiceState<T = usize>
196where
197 T: PartialEq + Clone + Default,
198{
199 pub area: Rect,
202 pub nav_char: Vec<Vec<char>>,
205 pub item_area: Rect,
208 pub button_area: Rect,
211 pub item_areas: Vec<Rect>,
214 pub core: ChoiceCore<T>,
216 pub popup: PopupCoreState,
218 pub popup_scroll: ScrollState,
220 pub behave_focus: Rc<Cell<ChoiceFocus>>,
222 pub behave_select: ChoiceSelect,
225 pub behave_close: ChoiceClose,
228
229 pub focus: FocusFlag,
232 pub mouse: MouseFlags,
234
235 pub non_exhaustive: NonExhaustive,
236}
237
238pub(crate) mod event {
239 use rat_event::{ConsumedEvent, Outcome};
240 use rat_popup::event::PopupOutcome;
241
242 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
244 pub enum ChoiceOutcome {
245 Continue,
247 Unchanged,
249 Changed,
251 Value,
253 }
254
255 impl ConsumedEvent for ChoiceOutcome {
256 fn is_consumed(&self) -> bool {
257 *self != ChoiceOutcome::Continue
258 }
259 }
260
261 impl From<Outcome> for ChoiceOutcome {
262 fn from(value: Outcome) -> Self {
263 match value {
264 Outcome::Continue => ChoiceOutcome::Continue,
265 Outcome::Unchanged => ChoiceOutcome::Unchanged,
266 Outcome::Changed => ChoiceOutcome::Changed,
267 }
268 }
269 }
270
271 impl From<ChoiceOutcome> for Outcome {
272 fn from(value: ChoiceOutcome) -> Self {
273 match value {
274 ChoiceOutcome::Continue => Outcome::Continue,
275 ChoiceOutcome::Unchanged => Outcome::Unchanged,
276 ChoiceOutcome::Changed => Outcome::Changed,
277 ChoiceOutcome::Value => Outcome::Changed,
278 }
279 }
280 }
281
282 impl From<PopupOutcome> for ChoiceOutcome {
283 fn from(value: PopupOutcome) -> Self {
284 match value {
285 PopupOutcome::Continue => ChoiceOutcome::Continue,
286 PopupOutcome::Unchanged => ChoiceOutcome::Unchanged,
287 PopupOutcome::Changed => ChoiceOutcome::Changed,
288 PopupOutcome::Hide => ChoiceOutcome::Changed,
289 }
290 }
291 }
292}
293
294pub mod core {
295 #[derive(Debug, Default, Clone)]
296 pub struct ChoiceCore<T>
297 where
298 T: PartialEq + Clone + Default,
299 {
300 values: Vec<T>,
303 default_value: Option<T>,
305 value: T,
310 selected: Option<usize>,
312 }
313
314 impl<T> ChoiceCore<T>
315 where
316 T: PartialEq + Clone + Default,
317 {
318 pub fn set_values(&mut self, values: Vec<T>) {
319 self.values = values;
320 if self.values.is_empty() {
322 self.selected = None;
323 } else {
324 self.selected = self.values.iter().position(|v| *v == self.value);
325 }
326 }
327
328 pub fn values(&self) -> &[T] {
330 &self.values
331 }
332
333 pub fn set_default_value(&mut self, default_value: Option<T>) {
339 self.default_value = default_value.clone();
340 }
341
342 pub fn default_value(&self) -> &Option<T> {
344 &self.default_value
345 }
346
347 pub fn selected(&self) -> Option<usize> {
355 self.selected
356 }
357
358 pub fn set_selected(&mut self, select: usize) -> bool {
366 let old_sel = self.selected;
367 if self.values.is_empty() {
368 self.selected = None;
369 } else {
370 if let Some(value) = self.values.get(select) {
371 self.value = value.clone();
372 self.selected = Some(select);
373 } else {
374 self.selected = None;
376 }
377 }
378 old_sel != self.selected
379 }
380
381 pub fn set_value(&mut self, value: T) -> bool {
390 let old_value = self.value.clone();
391
392 self.value = value;
393 self.selected = self.values.iter().position(|v| *v == self.value);
394
395 old_value != self.value
396 }
397
398 pub fn value(&self) -> T {
400 self.value.clone()
401 }
402
403 pub fn is_empty(&self) -> bool {
404 self.values.is_empty()
405 }
406
407 pub fn clear(&mut self) -> bool {
408 let old_selected = self.selected;
409 let old_value = self.value.clone();
410
411 if let Some(default_value) = &self.default_value {
412 self.value = default_value.clone();
413 }
414
415 self.selected = self.values.iter().position(|v| *v == self.value);
416
417 old_selected != self.selected || old_value != self.value
418 }
419 }
420}
421
422impl Default for ChoiceStyle {
423 fn default() -> Self {
424 Self {
425 style: Default::default(),
426 button: Default::default(),
427 select: Default::default(),
428 focus: Default::default(),
429 block: Default::default(),
430 popup: Default::default(),
431 popup_style: Default::default(),
432 popup_border: Default::default(),
433 popup_scroll: Default::default(),
434 popup_block: Default::default(),
435 popup_len: Default::default(),
436 behave_focus: Default::default(),
437 behave_select: Default::default(),
438 behave_close: Default::default(),
439 non_exhaustive: NonExhaustive,
440 }
441 }
442}
443
444impl<T> Default for Choice<'_, T>
445where
446 T: PartialEq + Clone + Default,
447{
448 fn default() -> Self {
449 Self {
450 values: Default::default(),
451 default_value: Default::default(),
452 items: Default::default(),
453 style: Default::default(),
454 button_style: Default::default(),
455 select_style: Default::default(),
456 focus_style: Default::default(),
457 block: Default::default(),
458 popup_len: Default::default(),
459 popup_alignment: Alignment::Left,
460 popup_placement: Placement::BelowOrAbove,
461 popup: Default::default(),
462 popup_style: Default::default(),
463 popup_scroll: Default::default(),
464 popup_block: Default::default(),
465 behave_focus: Default::default(),
466 behave_select: Default::default(),
467 behave_close: Default::default(),
468 skip_item_render: Default::default(),
469 }
470 }
471}
472
473impl<'a> Choice<'a, usize> {
474 #[inline]
476 pub fn auto_items<V: Into<Line<'a>>>(self, items: impl IntoIterator<Item = V>) -> Self {
477 {
478 let mut values = self.values.borrow_mut();
479 let mut itemz = self.items.borrow_mut();
480
481 values.clear();
482 itemz.clear();
483
484 for (k, v) in items.into_iter().enumerate() {
485 values.push(k);
486 itemz.push(v.into());
487 }
488 }
489
490 self
491 }
492
493 pub fn auto_item(self, item: impl Into<Line<'a>>) -> Self {
495 let idx = self.values.borrow().len();
496 self.values.borrow_mut().push(idx);
497 self.items.borrow_mut().push(item.into());
498 self
499 }
500}
501
502impl<'a, T> Choice<'a, T>
503where
504 T: PartialEq + Clone + Default,
505{
506 pub fn new() -> Self {
507 Self::default()
508 }
509
510 #[inline]
512 pub fn items<V: Into<Line<'a>>>(self, items: impl IntoIterator<Item = (T, V)>) -> Self {
513 {
514 let mut values = self.values.borrow_mut();
515 let mut itemz = self.items.borrow_mut();
516
517 values.clear();
518 itemz.clear();
519
520 for (k, v) in items.into_iter() {
521 values.push(k);
522 itemz.push(v.into());
523 }
524 }
525
526 self
527 }
528
529 pub fn item(self, value: T, item: impl Into<Line<'a>>) -> Self {
531 self.values.borrow_mut().push(value);
532 self.items.borrow_mut().push(item.into());
533 self
534 }
535
536 pub fn default_value(mut self, default: T) -> Self {
538 self.default_value = Some(default);
539 self
540 }
541
542 pub fn styles(mut self, styles: ChoiceStyle) -> Self {
544 self.style = styles.style;
545 self.block = self.block.map(|v| v.style(self.style));
546
547 if styles.button.is_some() {
548 self.button_style = styles.button;
549 }
550 if styles.select.is_some() {
551 self.select_style = styles.select;
552 }
553 if styles.focus.is_some() {
554 self.focus_style = styles.focus;
555 }
556 if styles.block.is_some() {
557 self.block = styles.block;
558 }
559 if let Some(select) = styles.behave_focus {
560 self.behave_focus = select;
561 }
562 if let Some(select) = styles.behave_select {
563 self.behave_select = select;
564 }
565 if let Some(close) = styles.behave_close {
566 self.behave_close = close;
567 }
568 if let Some(alignment) = styles.popup.alignment {
569 self.popup_alignment = alignment;
570 }
571 if let Some(placement) = styles.popup.placement {
572 self.popup_placement = placement;
573 }
574 self.popup = self.popup.styles(styles.popup.clone());
575 if let Some(popup_style) = styles.popup_style {
576 self.popup_style = popup_style;
577 }
578 if let Some(popup_scroll) = styles.popup_scroll {
579 self.popup_scroll = self.popup_scroll.map(|v| v.styles(popup_scroll));
580 }
581 self.popup_block = self.popup_block.map(|v| v.style(self.popup_style));
582 if let Some(border_style) = styles.popup_border {
583 self.popup_block = self.popup_block.map(|v| v.border_style(border_style));
584 }
585 if styles.popup_block.is_some() {
586 self.popup_block = styles.popup_block;
587 }
588 if styles.popup_len.is_some() {
589 self.popup_len = styles.popup_len;
590 }
591
592 self
593 }
594
595 pub fn style(mut self, style: Style) -> Self {
597 self.style = style;
598 self.block = self.block.map(|v| v.style(self.style));
599 self
600 }
601
602 pub fn button_style(mut self, style: Style) -> Self {
604 self.button_style = Some(style);
605 self
606 }
607
608 pub fn select_style(mut self, style: Style) -> Self {
610 self.select_style = Some(style);
611 self
612 }
613
614 pub fn focus_style(mut self, style: Style) -> Self {
616 self.focus_style = Some(style);
617 self
618 }
619
620 pub fn block(mut self, block: Block<'a>) -> Self {
622 self.block = Some(block);
623 self.block = self.block.map(|v| v.style(self.style));
624 self
625 }
626
627 pub fn skip_item_render(mut self, skip: bool) -> Self {
630 self.skip_item_render = skip;
631 self
632 }
633
634 pub fn popup_alignment(mut self, alignment: Alignment) -> Self {
639 self.popup_alignment = alignment;
640 self
641 }
642
643 pub fn popup_placement(mut self, placement: Placement) -> Self {
648 self.popup_placement = placement;
649 self
650 }
651
652 pub fn popup_boundary(mut self, boundary: Rect) -> Self {
654 self.popup = self.popup.boundary(boundary);
655 self
656 }
657
658 pub fn popup_len(mut self, len: u16) -> Self {
663 self.popup_len = Some(len);
664 self
665 }
666
667 pub fn popup_style(mut self, style: Style) -> Self {
669 self.popup_style = style;
670 self
671 }
672
673 pub fn popup_block(mut self, block: Block<'a>) -> Self {
675 self.popup_block = Some(block);
676 self
677 }
678
679 pub fn popup_scroll(mut self, scroll: Scroll<'a>) -> Self {
681 self.popup_scroll = Some(scroll);
682 self
683 }
684
685 pub fn popup_offset(mut self, offset: (i16, i16)) -> Self {
692 self.popup = self.popup.offset(offset);
693 self
694 }
695
696 pub fn popup_x_offset(mut self, offset: i16) -> Self {
699 self.popup = self.popup.x_offset(offset);
700 self
701 }
702
703 pub fn popup_y_offset(mut self, offset: i16) -> Self {
706 self.popup = self.popup.y_offset(offset);
707 self
708 }
709
710 pub fn behave_focus(mut self, focus: ChoiceFocus) -> Self {
712 self.behave_focus = focus;
713 self
714 }
715
716 pub fn behave_select(mut self, select: ChoiceSelect) -> Self {
718 self.behave_select = select;
719 self
720 }
721
722 pub fn behave_close(mut self, close: ChoiceClose) -> Self {
724 self.behave_close = close;
725 self
726 }
727
728 pub fn width(&self) -> u16 {
730 let w = self
731 .items
732 .borrow()
733 .iter()
734 .map(|v| v.width())
735 .max()
736 .unwrap_or_default();
737
738 w as u16 + block_size(&self.block).width
739 }
740
741 pub fn height(&self) -> u16 {
743 1 + block_size(&self.block).height
744 }
745
746 pub fn into_widgets(self) -> (ChoiceWidget<'a, T>, ChoicePopup<'a, T>) {
750 (
751 ChoiceWidget {
752 values: self.values,
753 default_value: self.default_value,
754 items: self.items.clone(),
755 style: self.style,
756 button_style: self.button_style,
757 focus_style: self.focus_style,
758 block: self.block,
759 skip_item_render: self.skip_item_render,
760 len: self.popup_len,
761 behave_focus: self.behave_focus,
762 behave_select: self.behave_select,
763 behave_close: self.behave_close,
764 },
765 ChoicePopup {
766 items: self.items.clone(),
767 style: self.style,
768 select_style: self.select_style,
769 popup: self.popup,
770 popup_style: self.popup_style,
771 popup_scroll: self.popup_scroll,
772 popup_block: self.popup_block,
773 popup_alignment: self.popup_alignment,
774 popup_placement: self.popup_placement,
775 popup_len: self.popup_len,
776 _phantom: Default::default(),
777 },
778 )
779 }
780}
781
782impl<'a, T> ChoiceWidget<'a, T>
783where
784 T: PartialEq + Clone + Default,
785{
786 pub fn width(&self) -> u16 {
788 let w = self
789 .items
790 .borrow()
791 .iter()
792 .map(|v| v.width())
793 .max()
794 .unwrap_or_default();
795
796 w as u16 + block_size(&self.block).width
797 }
798
799 pub fn height(&self) -> u16 {
801 1 + block_size(&self.block).height
802 }
803}
804
805impl<'a, T> StatefulWidget for &ChoiceWidget<'a, T>
806where
807 T: PartialEq + Clone + Default,
808{
809 type State = ChoiceState<T>;
810
811 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
812 state.core.set_values(self.values.borrow().clone());
813 if let Some(default_value) = self.default_value.clone() {
814 state.core.set_default_value(Some(default_value));
815 }
816
817 render_choice(self, area, buf, state);
818 }
819}
820
821impl<T> StatefulWidget for ChoiceWidget<'_, T>
822where
823 T: PartialEq + Clone + Default,
824{
825 type State = ChoiceState<T>;
826
827 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
828 state.core.set_values(self.values.take());
829 if let Some(default_value) = self.default_value.take() {
830 state.core.set_default_value(Some(default_value));
831 }
832
833 render_choice(&self, area, buf, state);
834 }
835}
836
837fn render_choice<T: PartialEq + Clone + Default>(
838 widget: &ChoiceWidget<'_, T>,
839 area: Rect,
840 buf: &mut Buffer,
841 state: &mut ChoiceState<T>,
842) {
843 state.area = area;
844 state.behave_focus.set(widget.behave_focus);
845 state.behave_select = widget.behave_select;
846 state.behave_close = widget.behave_close;
847
848 if !state.popup.is_active() {
849 let len = widget
850 .len
851 .unwrap_or_else(|| min(5, widget.items.borrow().len()) as u16);
852 state.popup_scroll.max_offset = widget.items.borrow().len().saturating_sub(len as usize);
853 state.popup_scroll.page_len = len as usize;
854 if let Some(selected) = state.core.selected() {
855 state.popup_scroll.scroll_to_pos(selected);
856 }
857 }
858
859 state.nav_char.clear();
860 state.nav_char.extend(widget.items.borrow().iter().map(|v| {
861 v.spans
862 .first()
863 .and_then(|v| v.content.as_ref().chars().next())
864 .map_or(Vec::default(), |c| c.to_lowercase().collect::<Vec<_>>())
865 }));
866
867 let inner = widget.block.inner_if_some(area);
868
869 state.item_area = Rect::new(
870 inner.x,
871 inner.y,
872 inner.width.saturating_sub(3),
873 inner.height,
874 );
875 state.button_area = Rect::new(
876 inner.right().saturating_sub(min(3, inner.width)),
877 inner.y,
878 3,
879 inner.height,
880 );
881
882 let style = widget.style;
883 let focus_style = widget.focus_style.unwrap_or(revert_style(widget.style));
884
885 if state.is_focused() {
886 if let Some(block) = &widget.block {
887 block.render(area, buf);
888 } else {
889 buf.set_style(inner, style);
890 }
891 buf.set_style(inner, focus_style);
892 } else {
893 if let Some(block) = &widget.block {
894 block.render(area, buf);
895 } else {
896 buf.set_style(inner, style);
897 }
898 if let Some(button_style) = widget.button_style {
899 buf.set_style(state.button_area, button_style);
900 }
901 }
902
903 if !widget.skip_item_render {
904 if let Some(selected) = state.core.selected() {
905 if let Some(item) = widget.items.borrow().get(selected) {
906 item.render(state.item_area, buf);
907 }
908 }
909 }
910
911 let dy = if (state.button_area.height & 1) == 1 {
912 state.button_area.height / 2
913 } else {
914 state.button_area.height.saturating_sub(1) / 2
915 };
916 let bc = if state.is_popup_active() {
917 " â—† "
918 } else {
919 " â–¼ "
920 };
921 Span::from(bc).render(
922 Rect::new(state.button_area.x, state.button_area.y + dy, 3, 1),
923 buf,
924 );
925}
926
927impl<T> ChoicePopup<'_, T>
928where
929 T: PartialEq + Clone + Default,
930{
931 pub fn layout(&self, area: Rect, buf: &mut Buffer, state: &mut ChoiceState<T>) -> Rect {
934 if state.popup.is_active() {
935 let len = min(
936 self.popup_len.unwrap_or(5),
937 self.items.borrow().len() as u16,
938 );
939 let padding = block_padding(&self.popup_block);
940 let popup_len = len + padding.top + padding.bottom;
941 let pop_area = Rect::new(0, 0, area.width, popup_len);
942
943 self.popup
944 .ref_constraint(
945 self.popup_placement
946 .into_constraint(self.popup_alignment, area),
947 )
948 .layout(pop_area, buf)
949 } else {
950 Rect::default()
951 }
952 }
953}
954
955impl<T> StatefulWidget for &ChoicePopup<'_, T>
956where
957 T: PartialEq + Clone + Default,
958{
959 type State = ChoiceState<T>;
960
961 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
962 render_popup(self, area, buf, state);
963 }
964}
965
966impl<T> StatefulWidget for ChoicePopup<'_, T>
967where
968 T: PartialEq + Clone + Default,
969{
970 type State = ChoiceState<T>;
971
972 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
973 render_popup(&self, area, buf, state);
974 }
975}
976
977fn render_popup<T: PartialEq + Clone + Default>(
978 widget: &ChoicePopup<'_, T>,
979 area: Rect,
980 buf: &mut Buffer,
981 state: &mut ChoiceState<T>,
982) {
983 if state.popup.is_active() {
984 let popup_style = widget.popup_style;
985 let select_style = widget.select_style.unwrap_or(revert_style(widget.style));
986
987 {
988 let len = min(
989 widget.popup_len.unwrap_or(5),
990 widget.items.borrow().len() as u16,
991 );
992 let padding = block_padding(&widget.popup_block);
993 let popup_len = len + padding.top + padding.bottom;
994 let pop_area = Rect::new(0, 0, area.width, popup_len);
995
996 widget
997 .popup
998 .ref_constraint(
999 widget
1000 .popup_placement
1001 .into_constraint(widget.popup_alignment, area),
1002 )
1003 .render(pop_area, buf, &mut state.popup);
1004 }
1005
1006 let sa = ScrollArea::new()
1007 .style(fallback_popup_style(widget.popup_style))
1008 .block(widget.popup_block.as_ref())
1009 .v_scroll(widget.popup_scroll.as_ref());
1010
1011 let inner = sa.inner(state.popup.area, None, Some(&state.popup_scroll));
1012
1013 sa.render(
1014 state.popup.area,
1015 buf,
1016 &mut ScrollAreaState::new().v_scroll(&mut state.popup_scroll),
1017 );
1018
1019 state.popup_scroll.max_offset = widget
1020 .items
1021 .borrow()
1022 .len()
1023 .saturating_sub(inner.height as usize);
1024 state.popup_scroll.page_len = inner.height as usize;
1025
1026 state.item_areas.clear();
1027 let mut row = inner.y;
1028 let mut idx = state.popup_scroll.offset;
1029 loop {
1030 if row >= inner.bottom() {
1031 break;
1032 }
1033
1034 let item_area = Rect::new(inner.x, row, inner.width, 1);
1035 state.item_areas.push(item_area);
1036
1037 if let Some(item) = widget.items.borrow().get(idx) {
1038 let style = if state.core.selected() == Some(idx) {
1039 popup_style.patch(select_style)
1040 } else {
1041 popup_style
1042 };
1043
1044 buf.set_style(item_area, style);
1045 item.render(item_area, buf);
1046 } else {
1047 }
1049
1050 row += 1;
1051 idx += 1;
1052 }
1053 } else {
1054 state.popup.clear_areas();
1055 }
1056}
1057
1058impl<T> Clone for ChoiceState<T>
1059where
1060 T: PartialEq + Clone + Default,
1061{
1062 fn clone(&self) -> Self {
1063 let popup = self.popup.clone();
1064 let behave_focus = Rc::new(Cell::new(self.behave_focus.get()));
1065 let focus = focus_cb(
1066 popup.active.clone(),
1067 behave_focus.clone(),
1068 Default::default(),
1069 );
1070
1071 Self {
1072 area: self.area,
1073 nav_char: self.nav_char.clone(),
1074 item_area: self.item_area,
1075 button_area: self.button_area,
1076 item_areas: self.item_areas.clone(),
1077 core: self.core.clone(),
1078 popup,
1079 popup_scroll: self.popup_scroll.clone(),
1080 behave_focus,
1081 behave_select: self.behave_select,
1082 behave_close: self.behave_close,
1083 focus,
1084 mouse: Default::default(),
1085 non_exhaustive: NonExhaustive,
1086 }
1087 }
1088}
1089
1090impl<T> Default for ChoiceState<T>
1091where
1092 T: PartialEq + Clone + Default,
1093{
1094 fn default() -> Self {
1095 let popup = PopupCoreState::default();
1096 let behave_focus = Rc::new(Cell::new(ChoiceFocus::default()));
1097 let focus = focus_cb(
1098 popup.active.clone(),
1099 behave_focus.clone(),
1100 Default::default(),
1101 );
1102
1103 Self {
1104 area: Default::default(),
1105 nav_char: Default::default(),
1106 item_area: Default::default(),
1107 button_area: Default::default(),
1108 item_areas: Default::default(),
1109 core: Default::default(),
1110 popup,
1111 popup_scroll: Default::default(),
1112 behave_focus,
1113 behave_select: Default::default(),
1114 behave_close: Default::default(),
1115 focus,
1116 mouse: Default::default(),
1117 non_exhaustive: NonExhaustive,
1118 }
1119 }
1120}
1121
1122fn focus_cb(
1123 active: Rc<Cell<bool>>,
1124 behave_focus: Rc<Cell<ChoiceFocus>>,
1125 flag: FocusFlag,
1126) -> FocusFlag {
1127 let active_clone = active.clone();
1128 flag.on_lost(move || {
1129 if active_clone.get() {
1130 active_clone.set(false);
1131 }
1132 });
1133 flag.on_gained(move || {
1134 if !active.get() {
1135 if behave_focus.get() == ChoiceFocus::OpenOnFocusGained {
1136 active.set(true);
1137 }
1138 }
1139 });
1140 flag
1141}
1142
1143impl<T> HasFocus for ChoiceState<T>
1144where
1145 T: PartialEq + Clone + Default,
1146{
1147 fn build(&self, builder: &mut FocusBuilder) {
1148 builder.widget_with_flags(self.focus(), self.area(), 0, self.navigable());
1149 builder.widget_with_flags(self.focus(), self.popup.area, 1, Navigation::Mouse);
1150 }
1151
1152 fn focus(&self) -> FocusFlag {
1153 self.focus.clone()
1154 }
1155
1156 fn area(&self) -> Rect {
1157 self.area
1158 }
1159}
1160
1161impl<T> RelocatableState for ChoiceState<T>
1162where
1163 T: PartialEq + Clone + Default,
1164{
1165 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1166 self.area = relocate_area(self.area, shift, clip);
1167 self.item_area = relocate_area(self.item_area, shift, clip);
1168 self.button_area = relocate_area(self.button_area, shift, clip);
1169 relocate_areas(&mut self.item_areas, shift, clip);
1170 self.popup.relocate(shift, clip);
1171 }
1172}
1173
1174impl<T> ChoiceState<T>
1175where
1176 T: PartialEq + Clone + Default,
1177{
1178 pub fn new() -> Self {
1179 Self::default()
1180 }
1181
1182 pub fn named(name: &str) -> Self {
1183 let mut z = Self::default();
1184 z.focus = focus_cb(
1185 z.popup.active.clone(),
1186 z.behave_focus.clone(),
1187 FocusFlag::named(name),
1188 );
1189 z
1190 }
1191
1192 pub fn is_popup_active(&self) -> bool {
1194 self.popup.is_active()
1195 }
1196
1197 pub fn flip_popup_active(&mut self) {
1199 self.popup.flip_active();
1200 }
1201
1202 pub fn set_popup_active(&mut self, active: bool) -> bool {
1204 self.popup.set_active(active)
1205 }
1206
1207 pub fn set_default_value(&mut self, default_value: Option<T>) {
1216 self.core.set_default_value(default_value);
1217 }
1218
1219 pub fn default_value(&self) -> &Option<T> {
1221 self.core.default_value()
1222 }
1223
1224 pub fn set_value(&mut self, value: T) -> bool {
1235 self.core.set_value(value)
1236 }
1237
1238 pub fn value(&self) -> T {
1240 self.core.value()
1241 }
1242
1243 pub fn clear(&mut self) -> bool {
1245 self.core.clear()
1246 }
1247
1248 pub fn select(&mut self, select: usize) -> bool {
1260 self.core.set_selected(select)
1261 }
1262
1263 pub fn selected(&self) -> Option<usize> {
1268 self.core.selected()
1269 }
1270
1271 pub fn is_empty(&self) -> bool {
1273 self.core.values().is_empty()
1274 }
1275
1276 pub fn len(&self) -> usize {
1278 self.core.values().len()
1279 }
1280
1281 pub fn clear_offset(&mut self) {
1283 self.popup_scroll.set_offset(0);
1284 }
1285
1286 pub fn set_offset(&mut self, offset: usize) -> bool {
1288 self.popup_scroll.set_offset(offset)
1289 }
1290
1291 pub fn offset(&self) -> usize {
1293 self.popup_scroll.offset()
1294 }
1295
1296 pub fn max_offset(&self) -> usize {
1298 self.popup_scroll.max_offset()
1299 }
1300
1301 pub fn page_len(&self) -> usize {
1303 self.popup_scroll.page_len()
1304 }
1305
1306 pub fn scroll_by(&self) -> usize {
1308 self.popup_scroll.scroll_by()
1309 }
1310
1311 pub fn scroll_to_selected(&mut self) -> bool {
1313 if let Some(selected) = self.core.selected() {
1314 self.popup_scroll.scroll_to_pos(selected)
1315 } else {
1316 false
1317 }
1318 }
1319}
1320
1321impl<T> ChoiceState<T>
1322where
1323 T: PartialEq + Clone + Default,
1324{
1325 pub fn select_by_char(&mut self, c: char) -> bool {
1327 if self.nav_char.is_empty() {
1328 return false;
1329 }
1330 let c = c.to_lowercase().collect::<Vec<_>>();
1331
1332 let (mut idx, end_loop) = if let Some(idx) = self.core.selected() {
1333 (idx + 1, idx)
1334 } else {
1335 if self.nav_char[0] == c {
1336 self.core.set_selected(0);
1337 return true;
1338 } else {
1339 (1, 0)
1340 }
1341 };
1342 loop {
1343 if idx >= self.nav_char.len() {
1344 idx = 0;
1345 }
1346 if idx == end_loop {
1347 break;
1348 }
1349
1350 if self.nav_char[idx] == c {
1351 self.core.set_selected(idx);
1352 return true;
1353 }
1354
1355 idx += 1;
1356 }
1357 false
1358 }
1359
1360 pub fn reverse_select_by_char(&mut self, c: char) -> bool {
1362 if self.nav_char.is_empty() {
1363 return false;
1364 }
1365 let c = c.to_lowercase().collect::<Vec<_>>();
1366
1367 let (mut idx, end_loop) = if let Some(idx) = self.core.selected() {
1368 if idx == 0 {
1369 (self.nav_char.len() - 1, 0)
1370 } else {
1371 (idx - 1, idx)
1372 }
1373 } else {
1374 if self.nav_char.last() == Some(&c) {
1375 self.core.set_selected(self.nav_char.len() - 1);
1376 return true;
1377 } else {
1378 (self.nav_char.len() - 1, 0)
1379 }
1380 };
1381 loop {
1382 if self.nav_char[idx] == c {
1383 self.core.set_selected(idx);
1384 return true;
1385 }
1386
1387 if idx == end_loop {
1388 break;
1389 }
1390
1391 if idx == 0 {
1392 idx = self.nav_char.len() - 1;
1393 } else {
1394 idx -= 1;
1395 }
1396 }
1397 false
1398 }
1399
1400 pub fn move_to(&mut self, n: usize) -> ChoiceOutcome {
1402 let old_selected = self.selected();
1403 let r1 = self.popup.set_active(true);
1404 let r2 = self.select(n);
1405 let r3 = self.scroll_to_selected();
1406 if old_selected != self.selected() {
1407 ChoiceOutcome::Value
1408 } else if r1 || r2 || r3 {
1409 ChoiceOutcome::Changed
1410 } else {
1411 ChoiceOutcome::Continue
1412 }
1413 }
1414
1415 pub fn move_down(&mut self, n: usize) -> ChoiceOutcome {
1417 if self.core.is_empty() {
1418 return ChoiceOutcome::Continue;
1419 }
1420
1421 let old_selected = self.selected();
1422 let r1 = self.popup.set_active(true);
1423 let idx = if let Some(idx) = self.core.selected() {
1424 idx + n
1425 } else {
1426 n.saturating_sub(1)
1427 };
1428 let idx = idx.clamp(0, self.len() - 1);
1429 let r2 = self.core.set_selected(idx);
1430 let r3 = self.scroll_to_selected();
1431
1432 if old_selected != self.selected() {
1433 ChoiceOutcome::Value
1434 } else if r1 || r2 || r3 {
1435 ChoiceOutcome::Changed
1436 } else {
1437 ChoiceOutcome::Continue
1438 }
1439 }
1440
1441 pub fn move_up(&mut self, n: usize) -> ChoiceOutcome {
1443 if self.core.is_empty() {
1444 return ChoiceOutcome::Continue;
1445 }
1446
1447 let old_selected = self.selected();
1448 let r1 = self.popup.set_active(true);
1449 let idx = if let Some(idx) = self.core.selected() {
1450 idx.saturating_sub(n)
1451 } else {
1452 0
1453 };
1454 let idx = idx.clamp(0, self.len() - 1);
1455 let r2 = self.core.set_selected(idx);
1456 let r3 = self.scroll_to_selected();
1457
1458 if old_selected != self.selected() {
1459 ChoiceOutcome::Value
1460 } else if r1 || r2 || r3 {
1461 ChoiceOutcome::Changed
1462 } else {
1463 ChoiceOutcome::Continue
1464 }
1465 }
1466}
1467
1468impl<T: PartialEq + Clone + Default> HandleEvent<crossterm::event::Event, Popup, ChoiceOutcome>
1469 for ChoiceState<T>
1470{
1471 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Popup) -> ChoiceOutcome {
1472 let r = if self.is_focused() {
1473 match event {
1474 ct_event!(key press ' ') | ct_event!(keycode press Enter) => {
1475 self.flip_popup_active();
1476 ChoiceOutcome::Changed
1477 }
1478 ct_event!(keycode press Esc) => {
1479 if self.set_popup_active(false) {
1480 ChoiceOutcome::Changed
1481 } else {
1482 ChoiceOutcome::Continue
1483 }
1484 }
1485 ct_event!(key press c) => {
1486 if self.select_by_char(*c) {
1487 self.scroll_to_selected();
1488 ChoiceOutcome::Value
1489 } else {
1490 ChoiceOutcome::Continue
1491 }
1492 }
1493 ct_event!(key press SHIFT-c) => {
1494 if self.reverse_select_by_char(*c) {
1495 self.scroll_to_selected();
1496 ChoiceOutcome::Value
1497 } else {
1498 ChoiceOutcome::Continue
1499 }
1500 }
1501 ct_event!(keycode press Delete) | ct_event!(keycode press Backspace) => {
1502 if self.clear() {
1503 ChoiceOutcome::Value
1504 } else {
1505 ChoiceOutcome::Continue
1506 }
1507 }
1508 ct_event!(keycode press Down) => self.move_down(1),
1509 ct_event!(keycode press Up) => self.move_up(1),
1510 ct_event!(keycode press PageUp) => self.move_up(self.page_len()),
1511 ct_event!(keycode press PageDown) => self.move_down(self.page_len()),
1512 ct_event!(keycode press Home) => self.move_to(0),
1513 ct_event!(keycode press End) => self.move_to(self.len().saturating_sub(1)),
1514 _ => ChoiceOutcome::Continue,
1515 }
1516 } else {
1517 ChoiceOutcome::Continue
1518 };
1519
1520 if !r.is_consumed() {
1521 self.handle(event, MouseOnly)
1522 } else {
1523 r
1524 }
1525 }
1526}
1527
1528impl<T: PartialEq + Clone + Default> HandleEvent<crossterm::event::Event, MouseOnly, ChoiceOutcome>
1529 for ChoiceState<T>
1530{
1531 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> ChoiceOutcome {
1532 let r0 = handle_mouse(self, event);
1533 let r1 = handle_select(self, event);
1534 let r2 = handle_close(self, event);
1535 let mut r = max(r0, max(r1, r2));
1536
1537 r = r.or_else(|| mouse_trap(event, self.popup.area).into());
1538
1539 r
1540 }
1541}
1542
1543fn handle_mouse<T: PartialEq + Clone + Default>(
1544 state: &mut ChoiceState<T>,
1545 event: &crossterm::event::Event,
1546) -> ChoiceOutcome {
1547 match event {
1548 ct_event!(mouse down Left for x,y)
1549 if state.item_area.contains((*x, *y).into())
1550 || state.button_area.contains((*x, *y).into()) =>
1551 {
1552 if !state.gained_focus() {
1553 state.flip_popup_active();
1554 ChoiceOutcome::Changed
1555 } else {
1556 ChoiceOutcome::Continue
1559 }
1560 }
1561 ct_event!(mouse down Left for x,y)
1562 | ct_event!(mouse down Right for x,y)
1563 | ct_event!(mouse down Middle for x,y)
1564 if !state.item_area.contains((*x, *y).into())
1565 && !state.button_area.contains((*x, *y).into()) =>
1566 {
1567 match state.popup.handle(event, Popup) {
1568 PopupOutcome::Hide => {
1569 state.set_popup_active(false);
1570 ChoiceOutcome::Changed
1571 }
1572 r => r.into(),
1573 }
1574 }
1575 _ => ChoiceOutcome::Continue,
1576 }
1577}
1578
1579fn handle_select<T: PartialEq + Clone + Default>(
1580 state: &mut ChoiceState<T>,
1581 event: &crossterm::event::Event,
1582) -> ChoiceOutcome {
1583 match state.behave_select {
1584 ChoiceSelect::MouseScroll => {
1585 let mut sas = ScrollAreaState::new()
1586 .area(state.popup.area)
1587 .v_scroll(&mut state.popup_scroll);
1588 let mut r = match sas.handle(event, MouseOnly) {
1589 ScrollOutcome::Up(n) => state.move_up(n),
1590 ScrollOutcome::Down(n) => state.move_down(n),
1591 ScrollOutcome::VPos(n) => state.move_to(n),
1592 _ => ChoiceOutcome::Continue,
1593 };
1594
1595 r = r.or_else(|| match event {
1596 ct_event!(mouse down Left for x,y)
1597 if state.popup.area.contains((*x, *y).into()) =>
1598 {
1599 if let Some(n) = item_at(&state.item_areas, *x, *y) {
1600 state.move_to(state.offset() + n)
1601 } else {
1602 ChoiceOutcome::Unchanged
1603 }
1604 }
1605 ct_event!(mouse drag Left for x,y)
1606 if state.popup.area.contains((*x, *y).into()) =>
1607 {
1608 if let Some(n) = item_at(&state.item_areas, *x, *y) {
1609 state.move_to(state.offset() + n)
1610 } else {
1611 ChoiceOutcome::Unchanged
1612 }
1613 }
1614 _ => ChoiceOutcome::Continue,
1615 });
1616 r
1617 }
1618 ChoiceSelect::MouseMove => {
1619 let mut r = if let Some(selected) = state.core.selected() {
1621 let rel_sel = selected.saturating_sub(state.offset());
1622 let mut sas = ScrollAreaState::new()
1623 .area(state.popup.area)
1624 .v_scroll(&mut state.popup_scroll);
1625 match sas.handle(event, MouseOnly) {
1626 ScrollOutcome::Up(n) => {
1627 state.popup_scroll.scroll_up(n);
1628 if state.select(state.offset() + rel_sel) {
1629 ChoiceOutcome::Value
1630 } else {
1631 ChoiceOutcome::Unchanged
1632 }
1633 }
1634 ScrollOutcome::Down(n) => {
1635 state.popup_scroll.scroll_down(n);
1636 if state.select(state.offset() + rel_sel) {
1637 ChoiceOutcome::Value
1638 } else {
1639 ChoiceOutcome::Unchanged
1640 }
1641 }
1642 ScrollOutcome::VPos(n) => {
1643 if state.popup_scroll.set_offset(n) {
1644 ChoiceOutcome::Value
1645 } else {
1646 ChoiceOutcome::Unchanged
1647 }
1648 }
1649 _ => ChoiceOutcome::Continue,
1650 }
1651 } else {
1652 ChoiceOutcome::Continue
1653 };
1654
1655 r = r.or_else(|| match event {
1656 ct_event!(mouse moved for x,y) if state.popup.area.contains((*x, *y).into()) => {
1657 if let Some(n) = item_at(&state.item_areas, *x, *y) {
1658 state.move_to(state.offset() + n)
1659 } else {
1660 ChoiceOutcome::Unchanged
1661 }
1662 }
1663 _ => ChoiceOutcome::Continue,
1664 });
1665 r
1666 }
1667 ChoiceSelect::MouseClick => {
1668 let mut sas = ScrollAreaState::new()
1670 .area(state.popup.area)
1671 .v_scroll(&mut state.popup_scroll);
1672 let mut r = match sas.handle(event, MouseOnly) {
1673 ScrollOutcome::Up(n) => {
1674 if state.popup_scroll.scroll_up(n) {
1675 ChoiceOutcome::Changed
1676 } else {
1677 ChoiceOutcome::Unchanged
1678 }
1679 }
1680 ScrollOutcome::Down(n) => {
1681 if state.popup_scroll.scroll_down(n) {
1682 ChoiceOutcome::Changed
1683 } else {
1684 ChoiceOutcome::Unchanged
1685 }
1686 }
1687 ScrollOutcome::VPos(n) => {
1688 if state.popup_scroll.set_offset(n) {
1689 ChoiceOutcome::Changed
1690 } else {
1691 ChoiceOutcome::Unchanged
1692 }
1693 }
1694 _ => ChoiceOutcome::Continue,
1695 };
1696
1697 r = r.or_else(|| match event {
1698 ct_event!(mouse down Left for x,y)
1699 if state.popup.area.contains((*x, *y).into()) =>
1700 {
1701 if let Some(n) = item_at(&state.item_areas, *x, *y) {
1702 state.move_to(state.offset() + n)
1703 } else {
1704 ChoiceOutcome::Unchanged
1705 }
1706 }
1707 ct_event!(mouse drag Left for x,y)
1708 if state.popup.area.contains((*x, *y).into()) =>
1709 {
1710 if let Some(n) = item_at(&state.item_areas, *x, *y) {
1711 state.move_to(state.offset() + n)
1712 } else {
1713 ChoiceOutcome::Unchanged
1714 }
1715 }
1716 _ => ChoiceOutcome::Continue,
1717 });
1718 r
1719 }
1720 }
1721}
1722
1723fn handle_close<T: PartialEq + Clone + Default>(
1724 state: &mut ChoiceState<T>,
1725 event: &crossterm::event::Event,
1726) -> ChoiceOutcome {
1727 match state.behave_close {
1728 ChoiceClose::SingleClick => match event {
1729 ct_event!(mouse down Left for x,y) if state.popup.area.contains((*x, *y).into()) => {
1730 if let Some(n) = item_at(&state.item_areas, *x, *y) {
1731 let r = state.move_to(state.offset() + n);
1732 let s = if state.set_popup_active(false) {
1733 ChoiceOutcome::Changed
1734 } else {
1735 ChoiceOutcome::Unchanged
1736 };
1737 max(r, s)
1738 } else {
1739 ChoiceOutcome::Unchanged
1740 }
1741 }
1742 _ => ChoiceOutcome::Continue,
1743 },
1744 ChoiceClose::DoubleClick => match event {
1745 ct_event!(mouse any for m) if state.mouse.doubleclick(state.popup.area, m) => {
1746 if let Some(n) = item_at(&state.item_areas, m.column, m.row) {
1747 let r = state.move_to(state.offset() + n);
1748 let s = if state.set_popup_active(false) {
1749 ChoiceOutcome::Changed
1750 } else {
1751 ChoiceOutcome::Unchanged
1752 };
1753 max(r, s)
1754 } else {
1755 ChoiceOutcome::Unchanged
1756 }
1757 }
1758 _ => ChoiceOutcome::Continue,
1759 },
1760 }
1761}
1762
1763pub fn handle_events<T: PartialEq + Clone + Default>(
1767 state: &mut ChoiceState<T>,
1768 focus: bool,
1769 event: &crossterm::event::Event,
1770) -> ChoiceOutcome {
1771 state.focus.set(focus);
1772 HandleEvent::handle(state, event, Popup)
1773}
1774
1775pub fn handle_mouse_events<T: PartialEq + Clone + Default>(
1777 state: &mut ChoiceState<T>,
1778 event: &crossterm::event::Event,
1779) -> ChoiceOutcome {
1780 HandleEvent::handle(state, event, MouseOnly)
1781}