1use crate::_private::NonExhaustive;
2use crate::choice::{
3 Choice, ChoiceClose, ChoiceFocus, ChoicePopup, ChoiceSelect, ChoiceState, ChoiceStyle,
4 ChoiceWidget,
5};
6use crate::combobox::event::ComboboxOutcome;
7use crate::event::ChoiceOutcome;
8use crate::text::HasScreenCursor;
9use rat_event::util::{MouseFlags, item_at, mouse_trap};
10use rat_event::{ConsumedEvent, HandleEvent, MouseOnly, Popup, Regular, ct_event};
11use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
12use rat_popup::Placement;
13use rat_popup::event::PopupOutcome;
14use rat_reloc::{RelocatableState, relocate_area};
15use rat_scrolled::event::ScrollOutcome;
16use rat_scrolled::{Scroll, ScrollAreaState};
17use rat_text::TextStyle;
18use rat_text::event::TextOutcome;
19use rat_text::text_input::{TextInput, TextInputState};
20use ratatui::buffer::Buffer;
21use ratatui::layout::{Alignment, Rect};
22use ratatui::style::Style;
23use ratatui::text::Line;
24use ratatui::widgets::{Block, StatefulWidget};
25use std::cmp::max;
26
27#[derive(Debug)]
28pub struct Combobox<'a> {
29 choice: Choice<'a, String>,
30 text: TextInput<'a>,
31}
32
33#[derive(Debug)]
34pub struct ComboboxWidget<'a> {
35 choice: ChoiceWidget<'a, String>,
36 text: TextInput<'a>,
37}
38
39#[derive(Debug)]
40pub struct ComboboxPopup<'a> {
41 choice: ChoicePopup<'a, String>,
42}
43
44#[derive(Debug, Clone)]
45pub struct ComboboxStyle {
46 pub choice: ChoiceStyle,
47 pub text: TextStyle,
48
49 pub non_exhaustive: NonExhaustive,
50}
51
52#[derive(Debug)]
53pub struct ComboboxState {
54 pub area: Rect,
57 pub choice: ChoiceState<String>,
59 pub text: TextInputState,
61
62 pub focus: FocusFlag,
65 pub mouse: MouseFlags,
67
68 pub non_exhaustive: NonExhaustive,
69}
70
71pub(crate) mod event {
72 use rat_event::{ConsumedEvent, Outcome};
73 use rat_popup::event::PopupOutcome;
74
75 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
77 pub enum ComboboxOutcome {
78 Continue,
80 Unchanged,
82 Changed,
84 Value,
86 TextChanged,
88 }
89
90 impl ConsumedEvent for ComboboxOutcome {
91 fn is_consumed(&self) -> bool {
92 *self != ComboboxOutcome::Continue
93 }
94 }
95
96 impl From<Outcome> for ComboboxOutcome {
97 fn from(value: Outcome) -> Self {
98 match value {
99 Outcome::Continue => ComboboxOutcome::Continue,
100 Outcome::Unchanged => ComboboxOutcome::Unchanged,
101 Outcome::Changed => ComboboxOutcome::Changed,
102 }
103 }
104 }
105
106 impl From<ComboboxOutcome> for Outcome {
107 fn from(value: ComboboxOutcome) -> Self {
108 match value {
109 ComboboxOutcome::Continue => Outcome::Continue,
110 ComboboxOutcome::Unchanged => Outcome::Unchanged,
111 ComboboxOutcome::Changed => Outcome::Changed,
112 ComboboxOutcome::Value => Outcome::Changed,
113 ComboboxOutcome::TextChanged => Outcome::Changed,
114 }
115 }
116 }
117
118 impl From<PopupOutcome> for ComboboxOutcome {
119 fn from(value: PopupOutcome) -> Self {
120 match value {
121 PopupOutcome::Continue => ComboboxOutcome::Continue,
122 PopupOutcome::Unchanged => ComboboxOutcome::Unchanged,
123 PopupOutcome::Changed => ComboboxOutcome::Changed,
124 PopupOutcome::Hide => ComboboxOutcome::Changed,
125 }
126 }
127 }
128}
129
130impl Default for ComboboxStyle {
131 fn default() -> Self {
132 Self {
133 choice: Default::default(),
134 text: Default::default(),
135 non_exhaustive: NonExhaustive,
136 }
137 }
138}
139
140impl Default for Combobox<'_> {
141 fn default() -> Self {
142 Self {
143 choice: Choice::default().skip_item_render(true),
144 text: Default::default(),
145 }
146 }
147}
148
149impl<'a> Combobox<'a> {
150 pub fn new() -> Self {
151 Self::default()
152 }
153
154 #[inline]
156 pub fn items<V: Into<Line<'a>>>(
157 mut self,
158 items: impl IntoIterator<Item = (String, V)>,
159 ) -> Self {
160 self.choice = self.choice.items(items);
161 self
162 }
163
164 pub fn item(mut self, value: String, item: impl Into<Line<'a>>) -> Self {
166 self.choice = self.choice.item(value, item);
167 self
168 }
169
170 pub fn default_value(mut self, default: String) -> Self {
172 self.choice = self.choice.default_value(default);
173 self
174 }
175
176 pub fn styles(mut self, styles: ComboboxStyle) -> Self {
178 self.choice = self.choice.styles(styles.choice);
179 self.text = self.text.styles(styles.text);
180 self
181 }
182
183 pub fn style(mut self, style: Style) -> Self {
185 self.choice = self.choice.style(style);
186 self.text = self.text.style(style);
187 self
188 }
189
190 pub fn button_style(mut self, style: Style) -> Self {
192 self.choice = self.choice.button_style(style);
193 self
194 }
195
196 pub fn select_style(mut self, style: Style) -> Self {
198 self.choice = self.choice.select_style(style);
199 self
200 }
201
202 pub fn focus_style(mut self, style: Style) -> Self {
204 self.choice = self.choice.focus_style(style);
205 self
206 }
207
208 pub fn text_style(mut self, style: TextStyle) -> Self {
210 self.text = self.text.styles(style);
211 self
212 }
213
214 pub fn block(mut self, block: Block<'a>) -> Self {
216 self.choice = self.choice.block(block);
217 self
218 }
219
220 pub fn popup_alignment(mut self, alignment: Alignment) -> Self {
225 self.choice = self.choice.popup_alignment(alignment);
226 self
227 }
228
229 pub fn popup_placement(mut self, placement: Placement) -> Self {
234 self.choice = self.choice.popup_placement(placement);
235 self
236 }
237
238 pub fn popup_boundary(mut self, boundary: Rect) -> Self {
240 self.choice = self.choice.popup_boundary(boundary);
241 self
242 }
243
244 pub fn popup_len(mut self, len: u16) -> Self {
249 self.choice = self.choice.popup_len(len);
250 self
251 }
252
253 pub fn popup_style(mut self, style: Style) -> Self {
255 self.choice = self.choice.popup_style(style);
256 self
257 }
258
259 pub fn popup_block(mut self, block: Block<'a>) -> Self {
261 self.choice = self.choice.popup_block(block);
262 self
263 }
264
265 pub fn popup_scroll(mut self, scroll: Scroll<'a>) -> Self {
267 self.choice = self.choice.popup_scroll(scroll);
268 self
269 }
270
271 pub fn popup_offset(mut self, offset: (i16, i16)) -> Self {
278 self.choice = self.choice.popup_offset(offset);
279 self
280 }
281
282 pub fn popup_x_offset(mut self, offset: i16) -> Self {
285 self.choice = self.choice.popup_x_offset(offset);
286 self
287 }
288
289 pub fn popup_y_offset(mut self, offset: i16) -> Self {
292 self.choice = self.choice.popup_y_offset(offset);
293 self
294 }
295
296 pub fn behave_focus(mut self, focus: ChoiceFocus) -> Self {
298 self.choice = self.choice.behave_focus(focus);
299 self
300 }
301
302 pub fn behave_select(mut self, select: ChoiceSelect) -> Self {
304 self.choice = self.choice.behave_select(select);
305 self
306 }
307
308 pub fn behave_close(mut self, close: ChoiceClose) -> Self {
310 self.choice = self.choice.behave_close(close);
311 self
312 }
313
314 pub fn width(&self) -> u16 {
316 self.choice.width()
317 }
318
319 pub fn height(&self) -> u16 {
321 self.choice.height()
322 }
323
324 pub fn into_widgets(self) -> (ComboboxWidget<'a>, ComboboxPopup<'a>) {
328 let (choice, choice_popup) = self.choice.into_widgets();
329 (
330 ComboboxWidget {
331 choice,
332 text: self.text,
333 },
334 ComboboxPopup {
335 choice: choice_popup,
336 },
337 )
338 }
339}
340
341impl<'a> ComboboxWidget<'a> {
342 pub fn width(&self) -> u16 {
344 self.choice.width()
345 }
346
347 pub fn height(&self) -> u16 {
349 self.choice.height()
350 }
351}
352
353impl<'a> StatefulWidget for &ComboboxWidget<'a> {
354 type State = ComboboxState;
355
356 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
357 render_combobox(self, area, buf, state);
358 }
359}
360
361impl StatefulWidget for ComboboxWidget<'_> {
362 type State = ComboboxState;
363
364 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
365 render_combobox(&self, area, buf, state);
366 }
367}
368
369fn render_combobox(
370 widget: &ComboboxWidget<'_>,
371 area: Rect,
372 buf: &mut Buffer,
373 state: &mut ComboboxState,
374) {
375 state.area = area;
376 (&widget.choice).render(area, buf, &mut state.choice);
377 (&widget.text).render(state.choice.item_area, buf, &mut state.text);
378}
379
380impl ComboboxPopup<'_> {
381 pub fn layout(&self, area: Rect, buf: &mut Buffer, state: &mut ComboboxState) -> Rect {
384 self.choice.layout(area, buf, &mut state.choice)
385 }
386}
387
388impl StatefulWidget for &ComboboxPopup<'_> {
389 type State = ComboboxState;
390
391 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
392 render_popup(self, area, buf, state);
393 }
394}
395
396impl StatefulWidget for ComboboxPopup<'_> {
397 type State = ComboboxState;
398
399 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
400 render_popup(&self, area, buf, state);
401 }
402}
403
404fn render_popup(
405 widget: &ComboboxPopup<'_>,
406 area: Rect,
407 buf: &mut Buffer,
408 state: &mut ComboboxState,
409) {
410 (&widget.choice).render(area, buf, &mut state.choice);
411}
412
413impl Clone for ComboboxState {
414 fn clone(&self) -> Self {
415 let mut text = self.text.clone();
416 let mut choice = self.choice.clone();
417 let focus = focus_cb("", text.focus, choice.focus);
418 text.focus = focus.clone();
419 choice.focus = focus.clone();
420
421 let c = Self {
422 area: self.area,
423 choice,
424 text,
425 focus,
426 mouse: Default::default(),
427 non_exhaustive: NonExhaustive,
428 };
429 c
430 }
431}
432
433impl Default for ComboboxState {
434 fn default() -> Self {
435 let mut text = TextInputState::default();
436 let mut choice = ChoiceState::default();
437 let focus = focus_cb("", text.focus, choice.focus);
438 text.focus = focus.clone();
439 choice.focus = focus.clone();
440
441 Self {
442 area: Default::default(),
443 choice,
444 text,
445 focus,
446 mouse: Default::default(),
447 non_exhaustive: NonExhaustive,
448 }
449 }
450}
451
452fn focus_cb(name: &str, choice: FocusFlag, text: FocusFlag) -> FocusFlag {
453 let flag = FocusFlag::named(name);
454
455 let choice_clone = choice.clone();
456 let text_clone = text.clone();
457 flag.on_lost(move || {
458 choice_clone.call_on_lost();
459 text_clone.call_on_lost();
460 });
461 let choice_clone = choice.clone();
462 let text_clone = text.clone();
463 flag.on_gained(move || {
464 choice_clone.call_on_gained();
465 text_clone.call_on_gained();
466 });
467 flag
468}
469
470impl HasScreenCursor for ComboboxState {
471 fn screen_cursor(&self) -> Option<(u16, u16)> {
472 self.text.screen_cursor()
473 }
474}
475
476impl HasFocus for ComboboxState {
477 fn build(&self, builder: &mut FocusBuilder) {
478 builder.leaf_widget(self);
479 }
480
481 fn focus(&self) -> FocusFlag {
482 self.focus.clone()
483 }
484
485 fn area(&self) -> Rect {
486 self.area
487 }
488}
489
490impl RelocatableState for ComboboxState {
491 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
492 self.area = relocate_area(self.area, shift, clip);
493 self.choice.relocate(shift, clip);
494 self.text.relocate(shift, clip);
495 }
496}
497
498impl ComboboxState {
499 pub fn new() -> Self {
500 Self::default()
501 }
502
503 pub fn named(name: &str) -> Self {
504 let mut text = TextInputState::default();
505 let mut choice = ChoiceState::default();
506 let focus = focus_cb(name, text.focus, choice.focus);
507 text.focus = focus.clone();
508 choice.focus = focus.clone();
509
510 Self {
511 area: Default::default(),
512 choice,
513 text,
514 focus,
515 mouse: Default::default(),
516 non_exhaustive: NonExhaustive,
517 }
518 }
519
520 pub fn is_popup_active(&self) -> bool {
522 self.choice.is_popup_active()
523 }
524
525 pub fn flip_popup_active(&mut self) {
527 self.choice.flip_popup_active();
528 }
529
530 pub fn set_popup_active(&mut self, active: bool) -> bool {
532 self.choice.set_popup_active(active)
533 }
534
535 pub fn set_default_value(&mut self, default_value: Option<String>) {
544 self.choice.set_default_value(default_value);
545 }
546
547 pub fn default_value(&self) -> &Option<String> {
549 self.choice.default_value()
550 }
551
552 pub fn set_value(&mut self, value: impl Into<String>) -> bool {
563 let value = value.into();
564 self.text.set_value(value.clone());
565 self.choice.set_value(value)
566 }
567
568 pub fn value(&self) -> String {
570 self.text.value()
571 }
572
573 pub fn clear(&mut self) -> bool {
575 self.text.clear() || self.choice.clear()
576 }
577
578 pub fn select(&mut self, select: usize) -> bool {
590 if self.choice.select(select) {
591 self.text.set_value(self.choice.value());
592 true
593 } else {
594 false
595 }
596 }
597
598 pub fn selected(&self) -> Option<usize> {
603 self.choice.selected()
604 }
605
606 pub fn is_empty(&self) -> bool {
608 self.choice.is_empty()
609 }
610
611 pub fn len(&self) -> usize {
613 self.choice.len()
614 }
615
616 pub fn clear_offset(&mut self) {
618 self.choice.set_offset(0);
619 }
620
621 pub fn set_offset(&mut self, offset: usize) -> bool {
623 self.choice.set_offset(offset)
624 }
625
626 pub fn offset(&self) -> usize {
628 self.choice.offset()
629 }
630
631 pub fn max_offset(&self) -> usize {
633 self.choice.max_offset()
634 }
635
636 pub fn page_len(&self) -> usize {
638 self.choice.page_len()
639 }
640
641 pub fn scroll_by(&self) -> usize {
643 self.choice.scroll_by()
644 }
645
646 pub fn scroll_to_selected(&mut self) -> bool {
648 self.choice.scroll_to_selected()
649 }
650}
651
652impl ComboboxState {
653 pub fn select_by_char(&mut self, c: char) -> bool {
655 if self.choice.select_by_char(c) {
656 self.text.set_value(self.choice.value());
657 true
658 } else {
659 false
660 }
661 }
662
663 pub fn reverse_select_by_char(&mut self, c: char) -> bool {
665 if self.choice.reverse_select_by_char(c) {
666 self.text.set_value(self.choice.value());
667 true
668 } else {
669 false
670 }
671 }
672
673 pub fn move_to(&mut self, n: usize) -> ComboboxOutcome {
675 match self.choice.move_to(n) {
676 ChoiceOutcome::Continue => ComboboxOutcome::Continue,
677 ChoiceOutcome::Unchanged => ComboboxOutcome::Unchanged,
678 ChoiceOutcome::Changed => ComboboxOutcome::Changed,
679 ChoiceOutcome::Value => {
680 self.text.set_value(self.choice.value());
681 ComboboxOutcome::Value
682 }
683 }
684 }
685
686 pub fn move_down(&mut self, n: usize) -> ComboboxOutcome {
688 match self.choice.move_down(n) {
689 ChoiceOutcome::Continue => ComboboxOutcome::Continue,
690 ChoiceOutcome::Unchanged => ComboboxOutcome::Unchanged,
691 ChoiceOutcome::Changed => ComboboxOutcome::Changed,
692 ChoiceOutcome::Value => {
693 self.text.set_value(self.choice.value());
694 ComboboxOutcome::Value
695 }
696 }
697 }
698
699 pub fn move_up(&mut self, n: usize) -> ComboboxOutcome {
701 match self.choice.move_up(n) {
702 ChoiceOutcome::Continue => ComboboxOutcome::Continue,
703 ChoiceOutcome::Unchanged => ComboboxOutcome::Unchanged,
704 ChoiceOutcome::Changed => ComboboxOutcome::Changed,
705 ChoiceOutcome::Value => {
706 self.text.set_value(self.choice.value());
707 ComboboxOutcome::Value
708 }
709 }
710 }
711}
712
713impl HandleEvent<crossterm::event::Event, Popup, ComboboxOutcome> for ComboboxState {
714 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Popup) -> ComboboxOutcome {
715 let r = if self.is_focused() {
716 match event {
717 ct_event!(keycode press Esc) => {
718 if self.set_popup_active(false) {
719 ComboboxOutcome::Changed
720 } else {
721 ComboboxOutcome::Continue
722 }
723 }
724 ct_event!(keycode press Down) => self.move_down(1),
725 ct_event!(keycode press Up) => self.move_up(1),
726 ct_event!(keycode press PageUp) => self.move_up(self.page_len()),
727 ct_event!(keycode press PageDown) => self.move_down(self.page_len()),
728 ct_event!(keycode press ALT-Home) => self.move_to(0),
729 ct_event!(keycode press ALT-End) => self.move_to(self.len().saturating_sub(1)),
730 crossterm::event::Event::Key(_) => match self.text.handle(event, Regular) {
731 TextOutcome::Continue => ComboboxOutcome::Continue,
732 TextOutcome::Unchanged => ComboboxOutcome::Unchanged,
733 TextOutcome::Changed => ComboboxOutcome::Changed,
734 TextOutcome::TextChanged => ComboboxOutcome::TextChanged,
735 },
736 _ => ComboboxOutcome::Continue,
737 }
738 } else {
739 ComboboxOutcome::Continue
740 };
741
742 if !r.is_consumed() {
743 self.handle(event, MouseOnly)
744 } else {
745 r
746 }
747 }
748}
749
750impl HandleEvent<crossterm::event::Event, MouseOnly, ComboboxOutcome> for ComboboxState {
751 fn handle(
752 &mut self,
753 event: &crossterm::event::Event,
754 _qualifier: MouseOnly,
755 ) -> ComboboxOutcome {
756 let r0 = handle_mouse(self, event);
757 let r1 = handle_select(self, event);
758 let r2 = handle_close(self, event);
759 let mut r = max(r0, max(r1, r2));
760
761 r = r.or_else(|| match self.text.handle(event, MouseOnly) {
762 TextOutcome::Continue => ComboboxOutcome::Continue,
763 TextOutcome::Unchanged => ComboboxOutcome::Unchanged,
764 TextOutcome::Changed => ComboboxOutcome::Changed,
765 TextOutcome::TextChanged => ComboboxOutcome::TextChanged,
766 });
767 r = r.or_else(|| mouse_trap(event, self.choice.popup.area).into());
768
769 r
770 }
771}
772
773fn handle_mouse(state: &mut ComboboxState, event: &crossterm::event::Event) -> ComboboxOutcome {
774 match event {
775 ct_event!(mouse down Left for x,y)
776 if state.choice.button_area.contains((*x, *y).into()) =>
777 {
778 if !state.gained_focus() {
779 state.flip_popup_active();
780 ComboboxOutcome::Changed
781 } else {
782 ComboboxOutcome::Continue
785 }
786 }
787 ct_event!(mouse down Left for x,y)
788 | ct_event!(mouse down Right for x,y)
789 | ct_event!(mouse down Middle for x,y)
790 if !state.choice.item_area.contains((*x, *y).into())
791 && !state.choice.button_area.contains((*x, *y).into()) =>
792 {
793 match state.choice.popup.handle(event, Popup) {
794 PopupOutcome::Hide => {
795 state.set_popup_active(false);
796 ComboboxOutcome::Changed
797 }
798 r => r.into(),
799 }
800 }
801 _ => ComboboxOutcome::Continue,
802 }
803}
804
805fn handle_select(state: &mut ComboboxState, event: &crossterm::event::Event) -> ComboboxOutcome {
806 match state.choice.behave_select {
807 ChoiceSelect::MouseScroll => {
808 let mut sas = ScrollAreaState::new()
809 .area(state.choice.popup.area)
810 .v_scroll(&mut state.choice.popup_scroll);
811 let mut r = match sas.handle(event, MouseOnly) {
812 ScrollOutcome::Up(n) => state.move_up(n),
813 ScrollOutcome::Down(n) => state.move_down(n),
814 ScrollOutcome::VPos(n) => state.move_to(n),
815 _ => ComboboxOutcome::Continue,
816 };
817
818 r = r.or_else(|| match event {
819 ct_event!(mouse down Left for x,y)
820 if state.choice.popup.area.contains((*x, *y).into()) =>
821 {
822 if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
823 state.move_to(state.offset() + n)
824 } else {
825 ComboboxOutcome::Unchanged
826 }
827 }
828 ct_event!(mouse drag Left for x,y)
829 if state.choice.popup.area.contains((*x, *y).into()) =>
830 {
831 if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
832 state.move_to(state.offset() + n)
833 } else {
834 ComboboxOutcome::Unchanged
835 }
836 }
837 _ => ComboboxOutcome::Continue,
838 });
839 r
840 }
841 ChoiceSelect::MouseMove => {
842 let mut r = if let Some(selected) = state.choice.core.selected() {
844 let rel_sel = selected.saturating_sub(state.offset());
845 let mut sas = ScrollAreaState::new()
846 .area(state.choice.popup.area)
847 .v_scroll(&mut state.choice.popup_scroll);
848 match sas.handle(event, MouseOnly) {
849 ScrollOutcome::Up(n) => {
850 state.choice.popup_scroll.scroll_up(n);
851 if state.select(state.offset() + rel_sel) {
852 ComboboxOutcome::Value
853 } else {
854 ComboboxOutcome::Unchanged
855 }
856 }
857 ScrollOutcome::Down(n) => {
858 state.choice.popup_scroll.scroll_down(n);
859 if state.select(state.offset() + rel_sel) {
860 ComboboxOutcome::Value
861 } else {
862 ComboboxOutcome::Unchanged
863 }
864 }
865 ScrollOutcome::VPos(n) => {
866 if state.choice.popup_scroll.set_offset(n) {
867 ComboboxOutcome::Value
868 } else {
869 ComboboxOutcome::Unchanged
870 }
871 }
872 _ => ComboboxOutcome::Continue,
873 }
874 } else {
875 ComboboxOutcome::Continue
876 };
877
878 r = r.or_else(|| match event {
879 ct_event!(mouse moved for x,y)
880 if state.choice.popup.area.contains((*x, *y).into()) =>
881 {
882 if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
883 state.move_to(state.offset() + n)
884 } else {
885 ComboboxOutcome::Unchanged
886 }
887 }
888 _ => ComboboxOutcome::Continue,
889 });
890 r
891 }
892 ChoiceSelect::MouseClick => {
893 let mut sas = ScrollAreaState::new()
895 .area(state.choice.popup.area)
896 .v_scroll(&mut state.choice.popup_scroll);
897 let mut r = match sas.handle(event, MouseOnly) {
898 ScrollOutcome::Up(n) => {
899 if state.choice.popup_scroll.scroll_up(n) {
900 ComboboxOutcome::Changed
901 } else {
902 ComboboxOutcome::Unchanged
903 }
904 }
905 ScrollOutcome::Down(n) => {
906 if state.choice.popup_scroll.scroll_down(n) {
907 ComboboxOutcome::Changed
908 } else {
909 ComboboxOutcome::Unchanged
910 }
911 }
912 ScrollOutcome::VPos(n) => {
913 if state.choice.popup_scroll.set_offset(n) {
914 ComboboxOutcome::Changed
915 } else {
916 ComboboxOutcome::Unchanged
917 }
918 }
919 _ => ComboboxOutcome::Continue,
920 };
921
922 r = r.or_else(|| match event {
923 ct_event!(mouse down Left for x,y)
924 if state.choice.popup.area.contains((*x, *y).into()) =>
925 {
926 if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
927 state.move_to(state.offset() + n)
928 } else {
929 ComboboxOutcome::Unchanged
930 }
931 }
932 ct_event!(mouse drag Left for x,y)
933 if state.choice.popup.area.contains((*x, *y).into()) =>
934 {
935 if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
936 state.move_to(state.offset() + n)
937 } else {
938 ComboboxOutcome::Unchanged
939 }
940 }
941 _ => ComboboxOutcome::Continue,
942 });
943 r
944 }
945 }
946}
947
948fn handle_close(state: &mut ComboboxState, event: &crossterm::event::Event) -> ComboboxOutcome {
949 match state.choice.behave_close {
950 ChoiceClose::SingleClick => match event {
951 ct_event!(mouse down Left for x,y)
952 if state.choice.popup.area.contains((*x, *y).into()) =>
953 {
954 if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
955 let r = state.move_to(state.offset() + n);
956 let s = if state.set_popup_active(false) {
957 ComboboxOutcome::Changed
958 } else {
959 ComboboxOutcome::Unchanged
960 };
961 max(r, s)
962 } else {
963 ComboboxOutcome::Unchanged
964 }
965 }
966 _ => ComboboxOutcome::Continue,
967 },
968 ChoiceClose::DoubleClick => match event {
969 ct_event!(mouse any for m) if state.mouse.doubleclick(state.choice.popup.area, m) => {
970 if let Some(n) = item_at(&state.choice.item_areas, m.column, m.row) {
971 let r = state.move_to(state.offset() + n);
972 let s = if state.set_popup_active(false) {
973 ComboboxOutcome::Changed
974 } else {
975 ComboboxOutcome::Unchanged
976 };
977 max(r, s)
978 } else {
979 ComboboxOutcome::Unchanged
980 }
981 }
982 _ => ComboboxOutcome::Continue,
983 },
984 }
985}
986
987pub fn handle_events(
991 state: &mut ComboboxState,
992 focus: bool,
993 event: &crossterm::event::Event,
994) -> ComboboxOutcome {
995 state.focus.set(focus);
996 HandleEvent::handle(state, event, Popup)
997}
998
999pub fn handle_mouse_events(
1001 state: &mut ComboboxState,
1002 event: &crossterm::event::Event,
1003) -> ComboboxOutcome {
1004 HandleEvent::handle(state, event, MouseOnly)
1005}