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, Navigation};
12use rat_popup::Placement;
13use rat_popup::event::PopupOutcome;
14use rat_reloc::RelocatableState;
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, Clone)]
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 inner: Rect,
59 pub choice: ChoiceState<String>,
61 pub text: TextInputState,
63
64 pub focus: FocusFlag,
67 pub mouse: MouseFlags,
69
70 pub non_exhaustive: NonExhaustive,
71}
72
73pub(crate) mod event {
74 use rat_event::{ConsumedEvent, Outcome};
75 use rat_popup::event::PopupOutcome;
76
77 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
79 pub enum ComboboxOutcome {
80 Continue,
82 Unchanged,
84 Changed,
86 Value,
88 TextChanged,
90 }
91
92 impl ConsumedEvent for ComboboxOutcome {
93 fn is_consumed(&self) -> bool {
94 *self != ComboboxOutcome::Continue
95 }
96 }
97
98 impl From<Outcome> for ComboboxOutcome {
99 fn from(value: Outcome) -> Self {
100 match value {
101 Outcome::Continue => ComboboxOutcome::Continue,
102 Outcome::Unchanged => ComboboxOutcome::Unchanged,
103 Outcome::Changed => ComboboxOutcome::Changed,
104 }
105 }
106 }
107
108 impl From<ComboboxOutcome> for Outcome {
109 fn from(value: ComboboxOutcome) -> Self {
110 match value {
111 ComboboxOutcome::Continue => Outcome::Continue,
112 ComboboxOutcome::Unchanged => Outcome::Unchanged,
113 ComboboxOutcome::Changed => Outcome::Changed,
114 ComboboxOutcome::Value => Outcome::Changed,
115 ComboboxOutcome::TextChanged => Outcome::Changed,
116 }
117 }
118 }
119
120 impl From<PopupOutcome> for ComboboxOutcome {
121 fn from(value: PopupOutcome) -> Self {
122 match value {
123 PopupOutcome::Continue => ComboboxOutcome::Continue,
124 PopupOutcome::Unchanged => ComboboxOutcome::Unchanged,
125 PopupOutcome::Changed => ComboboxOutcome::Changed,
126 PopupOutcome::Hide => ComboboxOutcome::Changed,
127 }
128 }
129 }
130}
131
132impl Default for ComboboxStyle {
133 fn default() -> Self {
134 Self {
135 choice: Default::default(),
136 text: Default::default(),
137 non_exhaustive: NonExhaustive,
138 }
139 }
140}
141
142impl Default for Combobox<'_> {
143 fn default() -> Self {
144 Self {
145 choice: Choice::default().skip_item_render(true),
146 text: Default::default(),
147 }
148 }
149}
150
151impl<'a> Combobox<'a> {
152 pub fn new() -> Self {
153 Self::default()
154 }
155
156 #[inline]
158 pub fn items<V: Into<Line<'a>>>(
159 mut self,
160 items: impl IntoIterator<Item = (String, V)>,
161 ) -> Self {
162 self.choice = self.choice.items(items);
163 self
164 }
165
166 pub fn item(mut self, value: String, item: impl Into<Line<'a>>) -> Self {
168 self.choice = self.choice.item(value, item);
169 self
170 }
171
172 pub fn default_value(mut self, default: String) -> Self {
174 self.choice = self.choice.default_value(default);
175 self
176 }
177
178 pub fn styles(mut self, styles: ComboboxStyle) -> Self {
180 self.choice = self.choice.styles(styles.choice);
181 self.text = self.text.styles(styles.text);
182 self
183 }
184
185 pub fn style(mut self, style: Style) -> Self {
187 self.choice = self.choice.style(style);
188 self.text = self.text.style(style);
189 self
190 }
191
192 pub fn button_style(mut self, style: Style) -> Self {
194 self.choice = self.choice.button_style(style);
195 self
196 }
197
198 pub fn select_style(mut self, style: Style) -> Self {
200 self.choice = self.choice.select_style(style);
201 self
202 }
203
204 pub fn focus_style(mut self, style: Style) -> Self {
206 self.choice = self.choice.focus_style(style);
207 self
208 }
209
210 pub fn text_style(mut self, style: TextStyle) -> Self {
212 self.text = self.text.styles(style);
213 self
214 }
215
216 pub fn block(mut self, block: Block<'a>) -> Self {
218 self.choice = self.choice.block(block);
219 self
220 }
221
222 pub fn popup_alignment(mut self, alignment: Alignment) -> Self {
227 self.choice = self.choice.popup_alignment(alignment);
228 self
229 }
230
231 pub fn popup_placement(mut self, placement: Placement) -> Self {
236 self.choice = self.choice.popup_placement(placement);
237 self
238 }
239
240 pub fn popup_boundary(mut self, boundary: Rect) -> Self {
242 self.choice = self.choice.popup_boundary(boundary);
243 self
244 }
245
246 pub fn popup_len(mut self, len: u16) -> Self {
251 self.choice = self.choice.popup_len(len);
252 self
253 }
254
255 pub fn popup_style(mut self, style: Style) -> Self {
257 self.choice = self.choice.popup_style(style);
258 self
259 }
260
261 pub fn popup_block(mut self, block: Block<'a>) -> Self {
263 self.choice = self.choice.popup_block(block);
264 self
265 }
266
267 pub fn popup_scroll(mut self, scroll: Scroll<'a>) -> Self {
269 self.choice = self.choice.popup_scroll(scroll);
270 self
271 }
272
273 pub fn popup_offset(mut self, offset: (i16, i16)) -> Self {
280 self.choice = self.choice.popup_offset(offset);
281 self
282 }
283
284 pub fn popup_x_offset(mut self, offset: i16) -> Self {
287 self.choice = self.choice.popup_x_offset(offset);
288 self
289 }
290
291 pub fn popup_y_offset(mut self, offset: i16) -> Self {
294 self.choice = self.choice.popup_y_offset(offset);
295 self
296 }
297
298 pub fn behave_focus(mut self, focus: ChoiceFocus) -> Self {
300 self.choice = self.choice.behave_focus(focus);
301 self
302 }
303
304 pub fn behave_select(mut self, select: ChoiceSelect) -> Self {
306 self.choice = self.choice.behave_select(select);
307 self
308 }
309
310 pub fn behave_close(mut self, close: ChoiceClose) -> Self {
312 self.choice = self.choice.behave_close(close);
313 self
314 }
315
316 pub fn width(&self) -> u16 {
318 self.choice.width()
319 }
320
321 pub fn height(&self) -> u16 {
323 self.choice.height()
324 }
325
326 pub fn into_widgets(self) -> (ComboboxWidget<'a>, ComboboxPopup<'a>) {
330 let (choice, choice_popup) = self.choice.into_widgets();
331 (
332 ComboboxWidget {
333 choice,
334 text: self.text,
335 },
336 ComboboxPopup {
337 choice: choice_popup,
338 },
339 )
340 }
341}
342
343impl<'a> ComboboxWidget<'a> {
344 pub fn width(&self) -> u16 {
346 self.choice.width()
347 }
348
349 pub fn height(&self) -> u16 {
351 self.choice.height()
352 }
353}
354
355impl<'a> StatefulWidget for &ComboboxWidget<'a> {
356 type State = ComboboxState;
357
358 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
359 render_combobox(self, area, buf, state);
360 }
361}
362
363impl StatefulWidget for ComboboxWidget<'_> {
364 type State = ComboboxState;
365
366 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
367 render_combobox(&self, area, buf, state);
368 }
369}
370
371fn render_combobox(
372 widget: &ComboboxWidget<'_>,
373 area: Rect,
374 buf: &mut Buffer,
375 state: &mut ComboboxState,
376) {
377 state.area = area;
378 (&widget.choice).render(area, buf, &mut state.choice);
379 state.inner = state.choice.inner;
380 (&widget.text).render(state.choice.item_area, buf, &mut state.text);
381}
382
383impl ComboboxPopup<'_> {
384 pub fn layout(&self, area: Rect, buf: &mut Buffer, state: &mut ComboboxState) -> Rect {
387 self.choice.layout(area, buf, &mut state.choice)
388 }
389}
390
391impl StatefulWidget for &ComboboxPopup<'_> {
392 type State = ComboboxState;
393
394 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
395 render_popup(self, area, buf, state);
396 }
397}
398
399impl StatefulWidget for ComboboxPopup<'_> {
400 type State = ComboboxState;
401
402 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
403 render_popup(&self, area, buf, state);
404 }
405}
406
407fn render_popup(
408 widget: &ComboboxPopup<'_>,
409 area: Rect,
410 buf: &mut Buffer,
411 state: &mut ComboboxState,
412) {
413 (&widget.choice).render(area, buf, &mut state.choice);
414}
415
416impl Clone for ComboboxState {
417 fn clone(&self) -> Self {
418 let mut text = self.text.clone();
419 let mut choice = self.choice.clone();
420 let focus = focus_cb(self.focus.new_instance(), text.focus, choice.focus);
421 text.focus = focus.clone();
422 choice.focus = focus.clone();
423
424 Self {
425 area: self.area,
426 inner: self.inner,
427 choice,
428 text,
429 focus,
430 mouse: Default::default(),
431 non_exhaustive: NonExhaustive,
432 }
433 }
434}
435
436impl Default for ComboboxState {
437 fn default() -> Self {
438 let mut text = TextInputState::default();
439 let mut choice = ChoiceState::default();
440 let focus = focus_cb(FocusFlag::default(), text.focus, choice.focus);
441 text.focus = focus.clone();
442 choice.focus = focus.clone();
443
444 Self {
445 area: Default::default(),
446 inner: Default::default(),
447 choice,
448 text,
449 focus,
450 mouse: Default::default(),
451 non_exhaustive: NonExhaustive,
452 }
453 }
454}
455
456fn focus_cb(flag: FocusFlag, choice: FocusFlag, text: FocusFlag) -> FocusFlag {
457 let choice_clone = choice.clone();
458 let text_clone = text.clone();
459 flag.on_lost(move || {
460 choice_clone.call_on_lost();
461 text_clone.call_on_lost();
462 });
463 let choice_clone = choice.clone();
464 let text_clone = text.clone();
465 flag.on_gained(move || {
466 choice_clone.call_on_gained();
467 text_clone.call_on_gained();
468 });
469 flag
470}
471
472impl HasScreenCursor for ComboboxState {
473 fn screen_cursor(&self) -> Option<(u16, u16)> {
474 self.text.screen_cursor()
475 }
476}
477
478impl HasFocus for ComboboxState {
479 fn build(&self, builder: &mut FocusBuilder) {
480 builder.widget_with_flags(self.focus(), self.area(), 0, self.navigable());
481 builder.widget_with_flags(self.focus(), self.choice.popup.area, 1, Navigation::Mouse);
482 }
483
484 fn focus(&self) -> FocusFlag {
485 self.focus.clone()
486 }
487
488 fn area(&self) -> Rect {
489 self.area
490 }
491}
492
493impl RelocatableState for ComboboxState {
494 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
495 self.area.relocate(shift, clip);
496 self.inner.relocate(shift, clip);
497 self.choice.relocate(shift, clip);
498 self.text.relocate(shift, clip);
499 }
500
501 fn relocate_popup(&mut self, shift: (i16, i16), clip: Rect) {
502 self.choice.relocate_popup(shift, clip);
503 }
504}
505
506impl ComboboxState {
507 pub fn new() -> Self {
508 Self::default()
509 }
510
511 pub fn named(name: &str) -> Self {
512 let mut text = TextInputState::default();
513 let mut choice = ChoiceState::default();
514 let focus = focus_cb(FocusFlag::new().with_name(name), text.focus, choice.focus);
515 text.focus = focus.clone();
516 choice.focus = focus.clone();
517
518 Self {
519 area: Default::default(),
520 inner: Default::default(),
521 choice,
522 text,
523 focus,
524 mouse: Default::default(),
525 non_exhaustive: NonExhaustive,
526 }
527 }
528
529 pub fn is_popup_active(&self) -> bool {
531 self.choice.is_popup_active()
532 }
533
534 pub fn flip_popup_active(&mut self) {
536 self.choice.flip_popup_active();
537 }
538
539 pub fn set_popup_active(&mut self, active: bool) -> bool {
541 self.choice.set_popup_active(active)
542 }
543
544 pub fn set_default_value(&mut self, default_value: Option<String>) {
553 self.choice.set_default_value(default_value);
554 }
555
556 pub fn default_value(&self) -> &Option<String> {
558 self.choice.default_value()
559 }
560
561 pub fn set_value(&mut self, value: impl Into<String>) -> bool {
572 let value = value.into();
573 self.text.set_value(value.clone());
574 self.choice.set_value(value)
575 }
576
577 pub fn value(&self) -> String {
579 self.text.value()
580 }
581
582 pub fn clear(&mut self) -> bool {
584 self.text.clear() || self.choice.clear()
585 }
586
587 pub fn select(&mut self, select: usize) -> bool {
599 if self.choice.select(select) {
600 self.text.set_value(self.choice.value());
601 true
602 } else {
603 false
604 }
605 }
606
607 pub fn selected(&self) -> Option<usize> {
612 self.choice.selected()
613 }
614
615 pub fn is_empty(&self) -> bool {
617 self.choice.is_empty()
618 }
619
620 pub fn len(&self) -> usize {
622 self.choice.len()
623 }
624
625 pub fn clear_offset(&mut self) {
627 self.choice.set_offset(0);
628 }
629
630 pub fn set_offset(&mut self, offset: usize) -> bool {
632 self.choice.set_offset(offset)
633 }
634
635 pub fn offset(&self) -> usize {
637 self.choice.offset()
638 }
639
640 pub fn max_offset(&self) -> usize {
642 self.choice.max_offset()
643 }
644
645 pub fn page_len(&self) -> usize {
647 self.choice.page_len()
648 }
649
650 pub fn scroll_by(&self) -> usize {
652 self.choice.scroll_by()
653 }
654
655 pub fn scroll_to_selected(&mut self) -> bool {
657 self.choice.scroll_to_selected()
658 }
659}
660
661impl ComboboxState {
662 pub fn select_by_char(&mut self, c: char) -> bool {
664 if self.choice.select_by_char(c) {
665 self.text.set_value(self.choice.value());
666 true
667 } else {
668 false
669 }
670 }
671
672 pub fn reverse_select_by_char(&mut self, c: char) -> bool {
674 if self.choice.reverse_select_by_char(c) {
675 self.text.set_value(self.choice.value());
676 true
677 } else {
678 false
679 }
680 }
681
682 pub fn move_to(&mut self, n: usize) -> ComboboxOutcome {
684 match self.choice.move_to(n) {
685 ChoiceOutcome::Continue => ComboboxOutcome::Continue,
686 ChoiceOutcome::Unchanged => ComboboxOutcome::Unchanged,
687 ChoiceOutcome::Changed => ComboboxOutcome::Changed,
688 ChoiceOutcome::Value => {
689 self.text.set_value(self.choice.value());
690 ComboboxOutcome::Value
691 }
692 }
693 }
694
695 pub fn move_down(&mut self, n: usize) -> ComboboxOutcome {
697 match self.choice.move_down(n) {
698 ChoiceOutcome::Continue => ComboboxOutcome::Continue,
699 ChoiceOutcome::Unchanged => ComboboxOutcome::Unchanged,
700 ChoiceOutcome::Changed => ComboboxOutcome::Changed,
701 ChoiceOutcome::Value => {
702 self.text.set_value(self.choice.value());
703 ComboboxOutcome::Value
704 }
705 }
706 }
707
708 pub fn move_up(&mut self, n: usize) -> ComboboxOutcome {
710 match self.choice.move_up(n) {
711 ChoiceOutcome::Continue => ComboboxOutcome::Continue,
712 ChoiceOutcome::Unchanged => ComboboxOutcome::Unchanged,
713 ChoiceOutcome::Changed => ComboboxOutcome::Changed,
714 ChoiceOutcome::Value => {
715 self.text.set_value(self.choice.value());
716 ComboboxOutcome::Value
717 }
718 }
719 }
720}
721
722impl HandleEvent<crossterm::event::Event, Popup, ComboboxOutcome> for ComboboxState {
723 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Popup) -> ComboboxOutcome {
724 let r = if self.is_focused() {
725 match event {
726 ct_event!(keycode press Enter) => {
727 self.flip_popup_active();
728 ComboboxOutcome::Changed
729 }
730 ct_event!(keycode press Esc) => {
731 if self.set_popup_active(false) {
732 ComboboxOutcome::Changed
733 } else {
734 ComboboxOutcome::Continue
735 }
736 }
737 ct_event!(keycode press Down) => self.move_down(1),
738 ct_event!(keycode press Up) => self.move_up(1),
739 ct_event!(keycode press PageUp) => self.move_up(self.page_len()),
740 ct_event!(keycode press PageDown) => self.move_down(self.page_len()),
741 ct_event!(keycode press ALT-Home) => self.move_to(0),
742 ct_event!(keycode press ALT-End) => self.move_to(self.len().saturating_sub(1)),
743 crossterm::event::Event::Key(_) => match self.text.handle(event, Regular) {
744 TextOutcome::Continue => ComboboxOutcome::Continue,
745 TextOutcome::Unchanged => ComboboxOutcome::Unchanged,
746 TextOutcome::Changed => ComboboxOutcome::Changed,
747 TextOutcome::TextChanged => ComboboxOutcome::TextChanged,
748 },
749 _ => ComboboxOutcome::Continue,
750 }
751 } else {
752 ComboboxOutcome::Continue
753 };
754
755 if !r.is_consumed() {
756 self.handle(event, MouseOnly)
757 } else {
758 r
759 }
760 }
761}
762
763impl HandleEvent<crossterm::event::Event, MouseOnly, ComboboxOutcome> for ComboboxState {
764 fn handle(
765 &mut self,
766 event: &crossterm::event::Event,
767 _qualifier: MouseOnly,
768 ) -> ComboboxOutcome {
769 let r0 = handle_mouse(self, event);
770 let r1 = handle_select(self, event);
771 let r2 = handle_close(self, event);
772 let mut r = max(r0, max(r1, r2));
773
774 r = r.or_else(|| match self.text.handle(event, MouseOnly) {
775 TextOutcome::Continue => ComboboxOutcome::Continue,
776 TextOutcome::Unchanged => ComboboxOutcome::Unchanged,
777 TextOutcome::Changed => ComboboxOutcome::Changed,
778 TextOutcome::TextChanged => ComboboxOutcome::TextChanged,
779 });
780 r = r.or_else(|| mouse_trap(event, self.choice.popup.area).into());
781
782 r
783 }
784}
785
786fn handle_mouse(state: &mut ComboboxState, event: &crossterm::event::Event) -> ComboboxOutcome {
787 match event {
788 ct_event!(mouse down Left for x,y)
789 if state.choice.button_area.contains((*x, *y).into()) =>
790 {
791 if !state.gained_focus() {
792 state.flip_popup_active();
793 ComboboxOutcome::Changed
794 } else {
795 ComboboxOutcome::Continue
798 }
799 }
800 ct_event!(mouse down Left for x,y)
801 | ct_event!(mouse down Right for x,y)
802 | ct_event!(mouse down Middle for x,y)
803 if !state.choice.item_area.contains((*x, *y).into())
804 && !state.choice.button_area.contains((*x, *y).into()) =>
805 {
806 match state.choice.popup.handle(event, Popup) {
807 PopupOutcome::Hide => {
808 state.set_popup_active(false);
809 ComboboxOutcome::Changed
810 }
811 r => r.into(),
812 }
813 }
814 _ => ComboboxOutcome::Continue,
815 }
816}
817
818fn handle_select(state: &mut ComboboxState, event: &crossterm::event::Event) -> ComboboxOutcome {
819 match state.choice.behave_select {
820 ChoiceSelect::MouseScroll => {
821 let mut sas = ScrollAreaState::new()
822 .area(state.choice.popup.area)
823 .v_scroll(&mut state.choice.popup_scroll);
824 let mut r = match sas.handle(event, MouseOnly) {
825 ScrollOutcome::Up(n) => state.move_up(n),
826 ScrollOutcome::Down(n) => state.move_down(n),
827 ScrollOutcome::VPos(n) => state.move_to(n),
828 _ => ComboboxOutcome::Continue,
829 };
830
831 r = r.or_else(|| match event {
832 ct_event!(mouse down Left for x,y)
833 if state.choice.popup.area.contains((*x, *y).into()) =>
834 {
835 if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
836 state.move_to(state.offset() + n)
837 } else {
838 ComboboxOutcome::Unchanged
839 }
840 }
841 ct_event!(mouse drag Left for x,y)
842 if state.choice.popup.area.contains((*x, *y).into()) =>
843 {
844 if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
845 state.move_to(state.offset() + n)
846 } else {
847 ComboboxOutcome::Unchanged
848 }
849 }
850 _ => ComboboxOutcome::Continue,
851 });
852 r
853 }
854 ChoiceSelect::MouseMove => {
855 let mut r = if let Some(selected) = state.choice.core.selected() {
857 let rel_sel = selected.saturating_sub(state.offset());
858 let mut sas = ScrollAreaState::new()
859 .area(state.choice.popup.area)
860 .v_scroll(&mut state.choice.popup_scroll);
861 match sas.handle(event, MouseOnly) {
862 ScrollOutcome::Up(n) => {
863 state.choice.popup_scroll.scroll_up(n);
864 if state.select(state.offset() + rel_sel) {
865 ComboboxOutcome::Value
866 } else {
867 ComboboxOutcome::Unchanged
868 }
869 }
870 ScrollOutcome::Down(n) => {
871 state.choice.popup_scroll.scroll_down(n);
872 if state.select(state.offset() + rel_sel) {
873 ComboboxOutcome::Value
874 } else {
875 ComboboxOutcome::Unchanged
876 }
877 }
878 ScrollOutcome::VPos(n) => {
879 if state.choice.popup_scroll.set_offset(n) {
880 ComboboxOutcome::Value
881 } else {
882 ComboboxOutcome::Unchanged
883 }
884 }
885 _ => ComboboxOutcome::Continue,
886 }
887 } else {
888 ComboboxOutcome::Continue
889 };
890
891 r = r.or_else(|| match event {
892 ct_event!(mouse moved for x,y)
893 if state.choice.popup.area.contains((*x, *y).into()) =>
894 {
895 if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
896 state.move_to(state.offset() + n)
897 } else {
898 ComboboxOutcome::Unchanged
899 }
900 }
901 _ => ComboboxOutcome::Continue,
902 });
903 r
904 }
905 ChoiceSelect::MouseClick => {
906 let mut sas = ScrollAreaState::new()
908 .area(state.choice.popup.area)
909 .v_scroll(&mut state.choice.popup_scroll);
910 let mut r = match sas.handle(event, MouseOnly) {
911 ScrollOutcome::Up(n) => {
912 if state.choice.popup_scroll.scroll_up(n) {
913 ComboboxOutcome::Changed
914 } else {
915 ComboboxOutcome::Unchanged
916 }
917 }
918 ScrollOutcome::Down(n) => {
919 if state.choice.popup_scroll.scroll_down(n) {
920 ComboboxOutcome::Changed
921 } else {
922 ComboboxOutcome::Unchanged
923 }
924 }
925 ScrollOutcome::VPos(n) => {
926 if state.choice.popup_scroll.set_offset(n) {
927 ComboboxOutcome::Changed
928 } else {
929 ComboboxOutcome::Unchanged
930 }
931 }
932 _ => ComboboxOutcome::Continue,
933 };
934
935 r = r.or_else(|| match event {
936 ct_event!(mouse down Left for x,y)
937 if state.choice.popup.area.contains((*x, *y).into()) =>
938 {
939 if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
940 state.move_to(state.offset() + n)
941 } else {
942 ComboboxOutcome::Unchanged
943 }
944 }
945 ct_event!(mouse drag Left for x,y)
946 if state.choice.popup.area.contains((*x, *y).into()) =>
947 {
948 if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
949 state.move_to(state.offset() + n)
950 } else {
951 ComboboxOutcome::Unchanged
952 }
953 }
954 _ => ComboboxOutcome::Continue,
955 });
956 r
957 }
958 }
959}
960
961fn handle_close(state: &mut ComboboxState, event: &crossterm::event::Event) -> ComboboxOutcome {
962 match state.choice.behave_close {
963 ChoiceClose::SingleClick => match event {
964 ct_event!(mouse down Left for x,y)
965 if state.choice.popup.area.contains((*x, *y).into()) =>
966 {
967 if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
968 let r = state.move_to(state.offset() + n);
969 let s = if state.set_popup_active(false) {
970 ComboboxOutcome::Changed
971 } else {
972 ComboboxOutcome::Unchanged
973 };
974 max(r, s)
975 } else {
976 ComboboxOutcome::Unchanged
977 }
978 }
979 _ => ComboboxOutcome::Continue,
980 },
981 ChoiceClose::DoubleClick => match event {
982 ct_event!(mouse any for m) if state.mouse.doubleclick(state.choice.popup.area, m) => {
983 if let Some(n) = item_at(&state.choice.item_areas, m.column, m.row) {
984 let r = state.move_to(state.offset() + n);
985 let s = if state.set_popup_active(false) {
986 ComboboxOutcome::Changed
987 } else {
988 ComboboxOutcome::Unchanged
989 };
990 max(r, s)
991 } else {
992 ComboboxOutcome::Unchanged
993 }
994 }
995 _ => ComboboxOutcome::Continue,
996 },
997 }
998}
999
1000pub fn handle_events(
1004 state: &mut ComboboxState,
1005 focus: bool,
1006 event: &crossterm::event::Event,
1007) -> ComboboxOutcome {
1008 state.focus.set(focus);
1009 HandleEvent::handle(state, event, Popup)
1010}
1011
1012pub fn handle_mouse_events(
1014 state: &mut ComboboxState,
1015 event: &crossterm::event::Event,
1016) -> ComboboxOutcome {
1017 HandleEvent::handle(state, event, MouseOnly)
1018}