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 if styles.button.is_some() {
546 self.button_style = styles.button;
547 }
548 if styles.select.is_some() {
549 self.select_style = styles.select;
550 }
551 if styles.focus.is_some() {
552 self.focus_style = styles.focus;
553 }
554 if styles.block.is_some() {
555 self.block = styles.block;
556 }
557 if let Some(select) = styles.behave_focus {
558 self.behave_focus = select;
559 }
560 if let Some(select) = styles.behave_select {
561 self.behave_select = select;
562 }
563 if let Some(close) = styles.behave_close {
564 self.behave_close = close;
565 }
566 self.block = self.block.map(|v| v.style(self.style));
567 if let Some(alignment) = styles.popup.alignment {
568 self.popup_alignment = alignment;
569 }
570 if let Some(placement) = styles.popup.placement {
571 self.popup_placement = placement;
572 }
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
582 self.popup_block = self.popup_block.map(|v| v.style(self.popup_style));
583 if let Some(border_style) = styles.popup_border {
584 self.popup_block = self.popup_block.map(|v| v.border_style(border_style));
585 }
586 if styles.popup_block.is_some() {
587 self.popup_block = styles.popup_block;
588 }
589
590 if styles.popup_len.is_some() {
591 self.popup_len = styles.popup_len;
592 }
593
594 self
595 }
596
597 pub fn style(mut self, style: Style) -> Self {
599 self.style = style;
600 self.block = self.block.map(|v| v.style(self.style));
601 self
602 }
603
604 pub fn button_style(mut self, style: Style) -> Self {
606 self.button_style = Some(style);
607 self
608 }
609
610 pub fn select_style(mut self, style: Style) -> Self {
612 self.select_style = Some(style);
613 self
614 }
615
616 pub fn focus_style(mut self, style: Style) -> Self {
618 self.focus_style = Some(style);
619 self
620 }
621
622 pub fn block(mut self, block: Block<'a>) -> Self {
624 self.block = Some(block);
625 self.block = self.block.map(|v| v.style(self.style));
626 self
627 }
628
629 pub fn skip_item_render(mut self, skip: bool) -> Self {
632 self.skip_item_render = skip;
633 self
634 }
635
636 pub fn popup_alignment(mut self, alignment: Alignment) -> Self {
641 self.popup_alignment = alignment;
642 self
643 }
644
645 pub fn popup_placement(mut self, placement: Placement) -> Self {
650 self.popup_placement = placement;
651 self
652 }
653
654 pub fn popup_boundary(mut self, boundary: Rect) -> Self {
656 self.popup = self.popup.boundary(boundary);
657 self
658 }
659
660 pub fn popup_len(mut self, len: u16) -> Self {
665 self.popup_len = Some(len);
666 self
667 }
668
669 pub fn popup_style(mut self, style: Style) -> Self {
671 self.popup_style = style;
672 self
673 }
674
675 pub fn popup_block(mut self, block: Block<'a>) -> Self {
677 self.popup_block = Some(block);
678 self
679 }
680
681 pub fn popup_scroll(mut self, scroll: Scroll<'a>) -> Self {
683 self.popup_scroll = Some(scroll);
684 self
685 }
686
687 pub fn popup_offset(mut self, offset: (i16, i16)) -> Self {
694 self.popup = self.popup.offset(offset);
695 self
696 }
697
698 pub fn popup_x_offset(mut self, offset: i16) -> Self {
701 self.popup = self.popup.x_offset(offset);
702 self
703 }
704
705 pub fn popup_y_offset(mut self, offset: i16) -> Self {
708 self.popup = self.popup.y_offset(offset);
709 self
710 }
711
712 pub fn behave_focus(mut self, focus: ChoiceFocus) -> Self {
714 self.behave_focus = focus;
715 self
716 }
717
718 pub fn behave_select(mut self, select: ChoiceSelect) -> Self {
720 self.behave_select = select;
721 self
722 }
723
724 pub fn behave_close(mut self, close: ChoiceClose) -> Self {
726 self.behave_close = close;
727 self
728 }
729
730 pub fn width(&self) -> u16 {
732 let w = self
733 .items
734 .borrow()
735 .iter()
736 .map(|v| v.width())
737 .max()
738 .unwrap_or_default();
739
740 w as u16 + block_size(&self.block).width
741 }
742
743 pub fn height(&self) -> u16 {
745 1 + block_size(&self.block).height
746 }
747
748 pub fn into_widgets(self) -> (ChoiceWidget<'a, T>, ChoicePopup<'a, T>) {
752 (
753 ChoiceWidget {
754 values: self.values,
755 default_value: self.default_value,
756 items: self.items.clone(),
757 style: self.style,
758 button_style: self.button_style,
759 focus_style: self.focus_style,
760 block: self.block,
761 skip_item_render: self.skip_item_render,
762 len: self.popup_len,
763 behave_focus: self.behave_focus,
764 behave_select: self.behave_select,
765 behave_close: self.behave_close,
766 },
767 ChoicePopup {
768 items: self.items.clone(),
769 style: self.style,
770 select_style: self.select_style,
771 popup: self.popup,
772 popup_style: self.popup_style,
773 popup_scroll: self.popup_scroll,
774 popup_block: self.popup_block,
775 popup_alignment: self.popup_alignment,
776 popup_placement: self.popup_placement,
777 popup_len: self.popup_len,
778 _phantom: Default::default(),
779 },
780 )
781 }
782}
783
784impl<'a, T> ChoiceWidget<'a, T>
785where
786 T: PartialEq + Clone + Default,
787{
788 pub fn width(&self) -> u16 {
790 let w = self
791 .items
792 .borrow()
793 .iter()
794 .map(|v| v.width())
795 .max()
796 .unwrap_or_default();
797
798 w as u16 + block_size(&self.block).width
799 }
800
801 pub fn height(&self) -> u16 {
803 1 + block_size(&self.block).height
804 }
805}
806
807impl<'a, T> StatefulWidget for &ChoiceWidget<'a, T>
808where
809 T: PartialEq + Clone + Default,
810{
811 type State = ChoiceState<T>;
812
813 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
814 state.core.set_values(self.values.borrow().clone());
815 if let Some(default_value) = self.default_value.clone() {
816 state.core.set_default_value(Some(default_value));
817 }
818
819 render_choice(self, area, buf, state);
820 }
821}
822
823impl<T> StatefulWidget for ChoiceWidget<'_, T>
824where
825 T: PartialEq + Clone + Default,
826{
827 type State = ChoiceState<T>;
828
829 fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
830 state.core.set_values(self.values.take());
831 if let Some(default_value) = self.default_value.take() {
832 state.core.set_default_value(Some(default_value));
833 }
834
835 render_choice(&self, area, buf, state);
836 }
837}
838
839fn render_choice<T: PartialEq + Clone + Default>(
840 widget: &ChoiceWidget<'_, T>,
841 area: Rect,
842 buf: &mut Buffer,
843 state: &mut ChoiceState<T>,
844) {
845 state.area = area;
846 state.behave_focus.set(widget.behave_focus);
847 state.behave_select = widget.behave_select;
848 state.behave_close = widget.behave_close;
849
850 if !state.popup.is_active() {
851 let len = widget
852 .len
853 .unwrap_or_else(|| min(5, widget.items.borrow().len()) as u16);
854 state.popup_scroll.max_offset = widget.items.borrow().len().saturating_sub(len as usize);
855 state.popup_scroll.page_len = len as usize;
856 if let Some(selected) = state.core.selected() {
857 state.popup_scroll.scroll_to_pos(selected);
858 }
859 }
860
861 state.nav_char.clear();
862 state.nav_char.extend(widget.items.borrow().iter().map(|v| {
863 v.spans
864 .first()
865 .and_then(|v| v.content.as_ref().chars().next())
866 .map_or(Vec::default(), |c| c.to_lowercase().collect::<Vec<_>>())
867 }));
868
869 let inner = widget.block.inner_if_some(area);
870
871 state.item_area = Rect::new(
872 inner.x,
873 inner.y,
874 inner.width.saturating_sub(3),
875 inner.height,
876 );
877 state.button_area = Rect::new(
878 inner.right().saturating_sub(min(3, inner.width)),
879 inner.y,
880 3,
881 inner.height,
882 );
883
884 let style = widget.style;
885 let focus_style = widget.focus_style.unwrap_or(revert_style(widget.style));
886
887 if state.is_focused() {
888 if let Some(block) = &widget.block {
889 block.render(area, buf);
890 } else {
891 buf.set_style(inner, style);
892 }
893 buf.set_style(inner, focus_style);
894 } else {
895 if let Some(block) = &widget.block {
896 block.render(area, buf);
897 } else {
898 buf.set_style(inner, style);
899 }
900 if let Some(button_style) = widget.button_style {
901 buf.set_style(state.button_area, button_style);
902 }
903 }
904
905 if !widget.skip_item_render {
906 if let Some(selected) = state.core.selected() {
907 if let Some(item) = widget.items.borrow().get(selected) {
908 item.render(state.item_area, buf);
909 }
910 }
911 }
912
913 let dy = if (state.button_area.height & 1) == 1 {
914 state.button_area.height / 2
915 } else {
916 state.button_area.height.saturating_sub(1) / 2
917 };
918 let bc = if state.is_popup_active() {
919 " â—† "
920 } else {
921 " â–¼ "
922 };
923 Span::from(bc).render(
924 Rect::new(state.button_area.x, state.button_area.y + dy, 3, 1),
925 buf,
926 );
927}
928
929impl<T> ChoicePopup<'_, T>
930where
931 T: PartialEq + Clone + Default,
932{
933 pub fn layout(&self, area: Rect, buf: &mut Buffer, state: &mut ChoiceState<T>) -> Rect {
936 if state.popup.is_active() {
937 let len = min(
938 self.popup_len.unwrap_or(5),
939 self.items.borrow().len() as u16,
940 );
941 let padding = block_padding(&self.popup_block);
942 let popup_len = len + padding.top + padding.bottom;
943 let pop_area = Rect::new(0, 0, area.width, popup_len);
944
945 self.popup
946 .ref_constraint(
947 self.popup_placement
948 .into_constraint(self.popup_alignment, area),
949 )
950 .layout(pop_area, buf)
951 } else {
952 Rect::default()
953 }
954 }
955}
956
957impl<T> StatefulWidget for &ChoicePopup<'_, T>
958where
959 T: PartialEq + Clone + Default,
960{
961 type State = ChoiceState<T>;
962
963 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
964 render_popup(self, area, buf, state);
965 }
966}
967
968impl<T> StatefulWidget for ChoicePopup<'_, T>
969where
970 T: PartialEq + Clone + Default,
971{
972 type State = ChoiceState<T>;
973
974 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
975 render_popup(&self, area, buf, state);
976 }
977}
978
979fn render_popup<T: PartialEq + Clone + Default>(
980 widget: &ChoicePopup<'_, T>,
981 area: Rect,
982 buf: &mut Buffer,
983 state: &mut ChoiceState<T>,
984) {
985 if state.popup.is_active() {
986 let popup_style = widget.popup_style;
987 let select_style = widget.select_style.unwrap_or(revert_style(widget.style));
988
989 {
990 let len = min(
991 widget.popup_len.unwrap_or(5),
992 widget.items.borrow().len() as u16,
993 );
994 let padding = block_padding(&widget.popup_block);
995 let popup_len = len + padding.top + padding.bottom;
996 let pop_area = Rect::new(0, 0, area.width, popup_len);
997
998 widget
999 .popup
1000 .ref_constraint(
1001 widget
1002 .popup_placement
1003 .into_constraint(widget.popup_alignment, area),
1004 )
1005 .render(pop_area, buf, &mut state.popup);
1006 }
1007
1008 let sa = ScrollArea::new()
1009 .style(fallback_popup_style(widget.popup_style))
1010 .block(widget.popup_block.as_ref())
1011 .v_scroll(widget.popup_scroll.as_ref());
1012
1013 let inner = sa.inner(state.popup.area, None, Some(&state.popup_scroll));
1014
1015 sa.render(
1016 state.popup.area,
1017 buf,
1018 &mut ScrollAreaState::new().v_scroll(&mut state.popup_scroll),
1019 );
1020
1021 state.popup_scroll.max_offset = widget
1022 .items
1023 .borrow()
1024 .len()
1025 .saturating_sub(inner.height as usize);
1026 state.popup_scroll.page_len = inner.height as usize;
1027
1028 state.item_areas.clear();
1029 let mut row = inner.y;
1030 let mut idx = state.popup_scroll.offset;
1031 loop {
1032 if row >= inner.bottom() {
1033 break;
1034 }
1035
1036 let item_area = Rect::new(inner.x, row, inner.width, 1);
1037 state.item_areas.push(item_area);
1038
1039 if let Some(item) = widget.items.borrow().get(idx) {
1040 let style = if state.core.selected() == Some(idx) {
1041 popup_style.patch(select_style)
1042 } else {
1043 popup_style
1044 };
1045
1046 buf.set_style(item_area, style);
1047 item.render(item_area, buf);
1048 } else {
1049 }
1051
1052 row += 1;
1053 idx += 1;
1054 }
1055 } else {
1056 state.popup.clear_areas();
1057 }
1058}
1059
1060impl<T> Clone for ChoiceState<T>
1061where
1062 T: PartialEq + Clone + Default,
1063{
1064 fn clone(&self) -> Self {
1065 let popup = self.popup.clone();
1066 let behave_focus = Rc::new(Cell::new(self.behave_focus.get()));
1067 let focus = focus_cb(
1068 popup.active.clone(),
1069 behave_focus.clone(),
1070 Default::default(),
1071 );
1072
1073 Self {
1074 area: self.area,
1075 nav_char: self.nav_char.clone(),
1076 item_area: self.item_area,
1077 button_area: self.button_area,
1078 item_areas: self.item_areas.clone(),
1079 core: self.core.clone(),
1080 popup,
1081 popup_scroll: self.popup_scroll.clone(),
1082 behave_focus,
1083 behave_select: self.behave_select,
1084 behave_close: self.behave_close,
1085 focus,
1086 mouse: Default::default(),
1087 non_exhaustive: NonExhaustive,
1088 }
1089 }
1090}
1091
1092impl<T> Default for ChoiceState<T>
1093where
1094 T: PartialEq + Clone + Default,
1095{
1096 fn default() -> Self {
1097 let popup = PopupCoreState::default();
1098 let behave_focus = Rc::new(Cell::new(ChoiceFocus::default()));
1099 let focus = focus_cb(
1100 popup.active.clone(),
1101 behave_focus.clone(),
1102 Default::default(),
1103 );
1104
1105 Self {
1106 area: Default::default(),
1107 nav_char: Default::default(),
1108 item_area: Default::default(),
1109 button_area: Default::default(),
1110 item_areas: Default::default(),
1111 core: Default::default(),
1112 popup,
1113 popup_scroll: Default::default(),
1114 behave_focus,
1115 behave_select: Default::default(),
1116 behave_close: Default::default(),
1117 focus,
1118 mouse: Default::default(),
1119 non_exhaustive: NonExhaustive,
1120 }
1121 }
1122}
1123
1124fn focus_cb(
1125 active: Rc<Cell<bool>>,
1126 behave_focus: Rc<Cell<ChoiceFocus>>,
1127 flag: FocusFlag,
1128) -> FocusFlag {
1129 let active_clone = active.clone();
1130 flag.on_lost(move || {
1131 if active_clone.get() {
1132 active_clone.set(false);
1133 }
1134 });
1135 flag.on_gained(move || {
1136 if !active.get() {
1137 if behave_focus.get() == ChoiceFocus::OpenOnFocusGained {
1138 active.set(true);
1139 }
1140 }
1141 });
1142 flag
1143}
1144
1145impl<T> HasFocus for ChoiceState<T>
1146where
1147 T: PartialEq + Clone + Default,
1148{
1149 fn build(&self, builder: &mut FocusBuilder) {
1150 builder.widget_with_flags(self.focus(), self.area(), 0, self.navigable());
1151 builder.widget_with_flags(self.focus(), self.popup.area, 1, Navigation::Mouse);
1152 }
1153
1154 fn focus(&self) -> FocusFlag {
1155 self.focus.clone()
1156 }
1157
1158 fn area(&self) -> Rect {
1159 self.area
1160 }
1161}
1162
1163impl<T> RelocatableState for ChoiceState<T>
1164where
1165 T: PartialEq + Clone + Default,
1166{
1167 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
1168 self.area = relocate_area(self.area, shift, clip);
1169 self.item_area = relocate_area(self.item_area, shift, clip);
1170 self.button_area = relocate_area(self.button_area, shift, clip);
1171 relocate_areas(&mut self.item_areas, shift, clip);
1172 self.popup.relocate(shift, clip);
1173 }
1174}
1175
1176impl<T> ChoiceState<T>
1177where
1178 T: PartialEq + Clone + Default,
1179{
1180 pub fn new() -> Self {
1181 Self::default()
1182 }
1183
1184 pub fn named(name: &str) -> Self {
1185 let mut z = Self::default();
1186 z.focus = focus_cb(
1187 z.popup.active.clone(),
1188 z.behave_focus.clone(),
1189 FocusFlag::named(name),
1190 );
1191 z
1192 }
1193
1194 pub fn is_popup_active(&self) -> bool {
1196 self.popup.is_active()
1197 }
1198
1199 pub fn flip_popup_active(&mut self) {
1201 self.popup.flip_active();
1202 }
1203
1204 pub fn set_popup_active(&mut self, active: bool) -> bool {
1206 self.popup.set_active(active)
1207 }
1208
1209 pub fn set_default_value(&mut self, default_value: Option<T>) {
1218 self.core.set_default_value(default_value);
1219 }
1220
1221 pub fn default_value(&self) -> &Option<T> {
1223 self.core.default_value()
1224 }
1225
1226 pub fn set_value(&mut self, value: T) -> bool {
1237 self.core.set_value(value)
1238 }
1239
1240 pub fn value(&self) -> T {
1242 self.core.value()
1243 }
1244
1245 pub fn clear(&mut self) -> bool {
1247 self.core.clear()
1248 }
1249
1250 pub fn select(&mut self, select: usize) -> bool {
1262 self.core.set_selected(select)
1263 }
1264
1265 pub fn selected(&self) -> Option<usize> {
1270 self.core.selected()
1271 }
1272
1273 pub fn is_empty(&self) -> bool {
1275 self.core.values().is_empty()
1276 }
1277
1278 pub fn len(&self) -> usize {
1280 self.core.values().len()
1281 }
1282
1283 pub fn clear_offset(&mut self) {
1285 self.popup_scroll.set_offset(0);
1286 }
1287
1288 pub fn set_offset(&mut self, offset: usize) -> bool {
1290 self.popup_scroll.set_offset(offset)
1291 }
1292
1293 pub fn offset(&self) -> usize {
1295 self.popup_scroll.offset()
1296 }
1297
1298 pub fn max_offset(&self) -> usize {
1300 self.popup_scroll.max_offset()
1301 }
1302
1303 pub fn page_len(&self) -> usize {
1305 self.popup_scroll.page_len()
1306 }
1307
1308 pub fn scroll_by(&self) -> usize {
1310 self.popup_scroll.scroll_by()
1311 }
1312
1313 pub fn scroll_to_selected(&mut self) -> bool {
1315 if let Some(selected) = self.core.selected() {
1316 self.popup_scroll.scroll_to_pos(selected)
1317 } else {
1318 false
1319 }
1320 }
1321}
1322
1323impl<T> ChoiceState<T>
1324where
1325 T: PartialEq + Clone + Default,
1326{
1327 pub fn select_by_char(&mut self, c: char) -> bool {
1329 if self.nav_char.is_empty() {
1330 return false;
1331 }
1332 let c = c.to_lowercase().collect::<Vec<_>>();
1333
1334 let (mut idx, end_loop) = if let Some(idx) = self.core.selected() {
1335 (idx + 1, idx)
1336 } else {
1337 if self.nav_char[0] == c {
1338 self.core.set_selected(0);
1339 return true;
1340 } else {
1341 (1, 0)
1342 }
1343 };
1344 loop {
1345 if idx >= self.nav_char.len() {
1346 idx = 0;
1347 }
1348 if idx == end_loop {
1349 break;
1350 }
1351
1352 if self.nav_char[idx] == c {
1353 self.core.set_selected(idx);
1354 return true;
1355 }
1356
1357 idx += 1;
1358 }
1359 false
1360 }
1361
1362 pub fn reverse_select_by_char(&mut self, c: char) -> bool {
1364 if self.nav_char.is_empty() {
1365 return false;
1366 }
1367 let c = c.to_lowercase().collect::<Vec<_>>();
1368
1369 let (mut idx, end_loop) = if let Some(idx) = self.core.selected() {
1370 if idx == 0 {
1371 (self.nav_char.len() - 1, 0)
1372 } else {
1373 (idx - 1, idx)
1374 }
1375 } else {
1376 if self.nav_char.last() == Some(&c) {
1377 self.core.set_selected(self.nav_char.len() - 1);
1378 return true;
1379 } else {
1380 (self.nav_char.len() - 1, 0)
1381 }
1382 };
1383 loop {
1384 if self.nav_char[idx] == c {
1385 self.core.set_selected(idx);
1386 return true;
1387 }
1388
1389 if idx == end_loop {
1390 break;
1391 }
1392
1393 if idx == 0 {
1394 idx = self.nav_char.len() - 1;
1395 } else {
1396 idx -= 1;
1397 }
1398 }
1399 false
1400 }
1401
1402 pub fn move_to(&mut self, n: usize) -> ChoiceOutcome {
1404 let old_selected = self.selected();
1405 let r1 = self.popup.set_active(true);
1406 let r2 = self.select(n);
1407 let r3 = self.scroll_to_selected();
1408 if old_selected != self.selected() {
1409 ChoiceOutcome::Value
1410 } else if r1 || r2 || r3 {
1411 ChoiceOutcome::Changed
1412 } else {
1413 ChoiceOutcome::Continue
1414 }
1415 }
1416
1417 pub fn move_down(&mut self, n: usize) -> ChoiceOutcome {
1419 if self.core.is_empty() {
1420 return ChoiceOutcome::Continue;
1421 }
1422
1423 let old_selected = self.selected();
1424 let r1 = self.popup.set_active(true);
1425 let idx = if let Some(idx) = self.core.selected() {
1426 idx + n
1427 } else {
1428 n.saturating_sub(1)
1429 };
1430 let idx = idx.clamp(0, self.len() - 1);
1431 let r2 = self.core.set_selected(idx);
1432 let r3 = self.scroll_to_selected();
1433
1434 if old_selected != self.selected() {
1435 ChoiceOutcome::Value
1436 } else if r1 || r2 || r3 {
1437 ChoiceOutcome::Changed
1438 } else {
1439 ChoiceOutcome::Continue
1440 }
1441 }
1442
1443 pub fn move_up(&mut self, n: usize) -> ChoiceOutcome {
1445 if self.core.is_empty() {
1446 return ChoiceOutcome::Continue;
1447 }
1448
1449 let old_selected = self.selected();
1450 let r1 = self.popup.set_active(true);
1451 let idx = if let Some(idx) = self.core.selected() {
1452 idx.saturating_sub(n)
1453 } else {
1454 0
1455 };
1456 let idx = idx.clamp(0, self.len() - 1);
1457 let r2 = self.core.set_selected(idx);
1458 let r3 = self.scroll_to_selected();
1459
1460 if old_selected != self.selected() {
1461 ChoiceOutcome::Value
1462 } else if r1 || r2 || r3 {
1463 ChoiceOutcome::Changed
1464 } else {
1465 ChoiceOutcome::Continue
1466 }
1467 }
1468}
1469
1470impl<T: PartialEq + Clone + Default> HandleEvent<crossterm::event::Event, Popup, ChoiceOutcome>
1471 for ChoiceState<T>
1472{
1473 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Popup) -> ChoiceOutcome {
1474 let r = if self.is_focused() {
1475 match event {
1476 ct_event!(key press ' ') | ct_event!(keycode press Enter) => {
1477 self.flip_popup_active();
1478 ChoiceOutcome::Changed
1479 }
1480 ct_event!(keycode press Esc) => {
1481 if self.set_popup_active(false) {
1482 ChoiceOutcome::Changed
1483 } else {
1484 ChoiceOutcome::Continue
1485 }
1486 }
1487 ct_event!(key press c) => {
1488 if self.select_by_char(*c) {
1489 self.scroll_to_selected();
1490 ChoiceOutcome::Value
1491 } else {
1492 ChoiceOutcome::Continue
1493 }
1494 }
1495 ct_event!(key press SHIFT-c) => {
1496 if self.reverse_select_by_char(*c) {
1497 self.scroll_to_selected();
1498 ChoiceOutcome::Value
1499 } else {
1500 ChoiceOutcome::Continue
1501 }
1502 }
1503 ct_event!(keycode press Delete) | ct_event!(keycode press Backspace) => {
1504 if self.clear() {
1505 ChoiceOutcome::Value
1506 } else {
1507 ChoiceOutcome::Continue
1508 }
1509 }
1510 ct_event!(keycode press Down) => self.move_down(1),
1511 ct_event!(keycode press Up) => self.move_up(1),
1512 ct_event!(keycode press PageUp) => self.move_up(self.page_len()),
1513 ct_event!(keycode press PageDown) => self.move_down(self.page_len()),
1514 ct_event!(keycode press Home) => self.move_to(0),
1515 ct_event!(keycode press End) => self.move_to(self.len().saturating_sub(1)),
1516 _ => ChoiceOutcome::Continue,
1517 }
1518 } else {
1519 ChoiceOutcome::Continue
1520 };
1521
1522 if !r.is_consumed() {
1523 self.handle(event, MouseOnly)
1524 } else {
1525 r
1526 }
1527 }
1528}
1529
1530impl<T: PartialEq + Clone + Default> HandleEvent<crossterm::event::Event, MouseOnly, ChoiceOutcome>
1531 for ChoiceState<T>
1532{
1533 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> ChoiceOutcome {
1534 let r0 = handle_mouse(self, event);
1535 let r1 = handle_select(self, event);
1536 let r2 = handle_close(self, event);
1537 let mut r = max(r0, max(r1, r2));
1538
1539 r = r.or_else(|| mouse_trap(event, self.popup.area).into());
1540
1541 r
1542 }
1543}
1544
1545fn handle_mouse<T: PartialEq + Clone + Default>(
1546 state: &mut ChoiceState<T>,
1547 event: &crossterm::event::Event,
1548) -> ChoiceOutcome {
1549 match event {
1550 ct_event!(mouse down Left for x,y)
1551 if state.item_area.contains((*x, *y).into())
1552 || state.button_area.contains((*x, *y).into()) =>
1553 {
1554 if !state.gained_focus() {
1555 state.flip_popup_active();
1556 ChoiceOutcome::Changed
1557 } else {
1558 ChoiceOutcome::Continue
1561 }
1562 }
1563 ct_event!(mouse down Left for x,y)
1564 | ct_event!(mouse down Right for x,y)
1565 | ct_event!(mouse down Middle for x,y)
1566 if !state.item_area.contains((*x, *y).into())
1567 && !state.button_area.contains((*x, *y).into()) =>
1568 {
1569 match state.popup.handle(event, Popup) {
1570 PopupOutcome::Hide => {
1571 state.set_popup_active(false);
1572 ChoiceOutcome::Changed
1573 }
1574 r => r.into(),
1575 }
1576 }
1577 _ => ChoiceOutcome::Continue,
1578 }
1579}
1580
1581fn handle_select<T: PartialEq + Clone + Default>(
1582 state: &mut ChoiceState<T>,
1583 event: &crossterm::event::Event,
1584) -> ChoiceOutcome {
1585 match state.behave_select {
1586 ChoiceSelect::MouseScroll => {
1587 let mut sas = ScrollAreaState::new()
1588 .area(state.popup.area)
1589 .v_scroll(&mut state.popup_scroll);
1590 let mut r = match sas.handle(event, MouseOnly) {
1591 ScrollOutcome::Up(n) => state.move_up(n),
1592 ScrollOutcome::Down(n) => state.move_down(n),
1593 ScrollOutcome::VPos(n) => state.move_to(n),
1594 _ => ChoiceOutcome::Continue,
1595 };
1596
1597 r = r.or_else(|| match event {
1598 ct_event!(mouse down Left for x,y)
1599 if state.popup.area.contains((*x, *y).into()) =>
1600 {
1601 if let Some(n) = item_at(&state.item_areas, *x, *y) {
1602 state.move_to(state.offset() + n)
1603 } else {
1604 ChoiceOutcome::Unchanged
1605 }
1606 }
1607 ct_event!(mouse drag Left for x,y)
1608 if state.popup.area.contains((*x, *y).into()) =>
1609 {
1610 if let Some(n) = item_at(&state.item_areas, *x, *y) {
1611 state.move_to(state.offset() + n)
1612 } else {
1613 ChoiceOutcome::Unchanged
1614 }
1615 }
1616 _ => ChoiceOutcome::Continue,
1617 });
1618 r
1619 }
1620 ChoiceSelect::MouseMove => {
1621 let mut r = if let Some(selected) = state.core.selected() {
1623 let rel_sel = selected.saturating_sub(state.offset());
1624 let mut sas = ScrollAreaState::new()
1625 .area(state.popup.area)
1626 .v_scroll(&mut state.popup_scroll);
1627 match sas.handle(event, MouseOnly) {
1628 ScrollOutcome::Up(n) => {
1629 state.popup_scroll.scroll_up(n);
1630 if state.select(state.offset() + rel_sel) {
1631 ChoiceOutcome::Value
1632 } else {
1633 ChoiceOutcome::Unchanged
1634 }
1635 }
1636 ScrollOutcome::Down(n) => {
1637 state.popup_scroll.scroll_down(n);
1638 if state.select(state.offset() + rel_sel) {
1639 ChoiceOutcome::Value
1640 } else {
1641 ChoiceOutcome::Unchanged
1642 }
1643 }
1644 ScrollOutcome::VPos(n) => {
1645 if state.popup_scroll.set_offset(n) {
1646 ChoiceOutcome::Value
1647 } else {
1648 ChoiceOutcome::Unchanged
1649 }
1650 }
1651 _ => ChoiceOutcome::Continue,
1652 }
1653 } else {
1654 ChoiceOutcome::Continue
1655 };
1656
1657 r = r.or_else(|| match event {
1658 ct_event!(mouse moved for x,y) if state.popup.area.contains((*x, *y).into()) => {
1659 if let Some(n) = item_at(&state.item_areas, *x, *y) {
1660 state.move_to(state.offset() + n)
1661 } else {
1662 ChoiceOutcome::Unchanged
1663 }
1664 }
1665 _ => ChoiceOutcome::Continue,
1666 });
1667 r
1668 }
1669 ChoiceSelect::MouseClick => {
1670 let mut sas = ScrollAreaState::new()
1672 .area(state.popup.area)
1673 .v_scroll(&mut state.popup_scroll);
1674 let mut r = match sas.handle(event, MouseOnly) {
1675 ScrollOutcome::Up(n) => {
1676 if state.popup_scroll.scroll_up(n) {
1677 ChoiceOutcome::Changed
1678 } else {
1679 ChoiceOutcome::Unchanged
1680 }
1681 }
1682 ScrollOutcome::Down(n) => {
1683 if state.popup_scroll.scroll_down(n) {
1684 ChoiceOutcome::Changed
1685 } else {
1686 ChoiceOutcome::Unchanged
1687 }
1688 }
1689 ScrollOutcome::VPos(n) => {
1690 if state.popup_scroll.set_offset(n) {
1691 ChoiceOutcome::Changed
1692 } else {
1693 ChoiceOutcome::Unchanged
1694 }
1695 }
1696 _ => ChoiceOutcome::Continue,
1697 };
1698
1699 r = r.or_else(|| match event {
1700 ct_event!(mouse down Left for x,y)
1701 if state.popup.area.contains((*x, *y).into()) =>
1702 {
1703 if let Some(n) = item_at(&state.item_areas, *x, *y) {
1704 state.move_to(state.offset() + n)
1705 } else {
1706 ChoiceOutcome::Unchanged
1707 }
1708 }
1709 ct_event!(mouse drag Left for x,y)
1710 if state.popup.area.contains((*x, *y).into()) =>
1711 {
1712 if let Some(n) = item_at(&state.item_areas, *x, *y) {
1713 state.move_to(state.offset() + n)
1714 } else {
1715 ChoiceOutcome::Unchanged
1716 }
1717 }
1718 _ => ChoiceOutcome::Continue,
1719 });
1720 r
1721 }
1722 }
1723}
1724
1725fn handle_close<T: PartialEq + Clone + Default>(
1726 state: &mut ChoiceState<T>,
1727 event: &crossterm::event::Event,
1728) -> ChoiceOutcome {
1729 match state.behave_close {
1730 ChoiceClose::SingleClick => match event {
1731 ct_event!(mouse down Left for x,y) if state.popup.area.contains((*x, *y).into()) => {
1732 if let Some(n) = item_at(&state.item_areas, *x, *y) {
1733 let r = state.move_to(state.offset() + n);
1734 let s = if state.set_popup_active(false) {
1735 ChoiceOutcome::Changed
1736 } else {
1737 ChoiceOutcome::Unchanged
1738 };
1739 max(r, s)
1740 } else {
1741 ChoiceOutcome::Unchanged
1742 }
1743 }
1744 _ => ChoiceOutcome::Continue,
1745 },
1746 ChoiceClose::DoubleClick => match event {
1747 ct_event!(mouse any for m) if state.mouse.doubleclick(state.popup.area, m) => {
1748 if let Some(n) = item_at(&state.item_areas, m.column, m.row) {
1749 let r = state.move_to(state.offset() + n);
1750 let s = if state.set_popup_active(false) {
1751 ChoiceOutcome::Changed
1752 } else {
1753 ChoiceOutcome::Unchanged
1754 };
1755 max(r, s)
1756 } else {
1757 ChoiceOutcome::Unchanged
1758 }
1759 }
1760 _ => ChoiceOutcome::Continue,
1761 },
1762 }
1763}
1764
1765pub fn handle_events<T: PartialEq + Clone + Default>(
1769 state: &mut ChoiceState<T>,
1770 focus: bool,
1771 event: &crossterm::event::Event,
1772) -> ChoiceOutcome {
1773 state.focus.set(focus);
1774 HandleEvent::handle(state, event, Popup)
1775}
1776
1777pub fn handle_mouse_events<T: PartialEq + Clone + Default>(
1779 state: &mut ChoiceState<T>,
1780 event: &crossterm::event::Event,
1781) -> ChoiceOutcome {
1782 HandleEvent::handle(state, event, MouseOnly)
1783}