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