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: impl Into<String>, item: impl Into<Line<'a>>) -> Self {
168 self.choice = self.choice.item(value.into(), 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(Rect::default(), 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 }
497
498 fn relocate_popup(&mut self, shift: (i16, i16), clip: Rect) {
499 self.area.relocate(shift, clip);
500 self.inner.relocate(shift, clip);
501 self.choice.relocate(shift, clip);
502 self.text.relocate(shift, clip);
503 self.choice.relocate_popup(shift, clip);
504 }
505}
506
507impl ComboboxState {
508 pub fn new() -> Self {
509 Self::default()
510 }
511
512 pub fn named(name: &str) -> Self {
513 let mut text = TextInputState::default();
514 let mut choice = ChoiceState::default();
515 let focus = focus_cb(FocusFlag::new().with_name(name), text.focus, choice.focus);
516 text.focus = focus.clone();
517 choice.focus = focus.clone();
518
519 Self {
520 area: Default::default(),
521 inner: Default::default(),
522 choice,
523 text,
524 focus,
525 mouse: Default::default(),
526 non_exhaustive: NonExhaustive,
527 }
528 }
529
530 pub fn is_popup_active(&self) -> bool {
532 self.choice.is_popup_active()
533 }
534
535 pub fn flip_popup_active(&mut self) {
537 self.choice.flip_popup_active();
538 }
539
540 pub fn set_popup_active(&mut self, active: bool) -> bool {
542 self.choice.set_popup_active(active)
543 }
544
545 pub fn set_default_value(&mut self, default_value: Option<String>) {
554 self.choice.set_default_value(default_value);
555 }
556
557 pub fn default_value(&self) -> &Option<String> {
559 self.choice.default_value()
560 }
561
562 pub fn set_value(&mut self, value: impl Into<String>) -> bool {
573 let value = value.into();
574 self.text.set_value(value.clone());
575 self.choice.set_value(value)
576 }
577
578 pub fn value(&self) -> String {
580 self.text.value()
581 }
582
583 pub fn clear(&mut self) -> bool {
585 self.text.clear() || self.choice.clear()
586 }
587
588 pub fn select(&mut self, select: usize) -> bool {
600 if self.choice.select(select) {
601 self.text.set_value(self.choice.value());
602 true
603 } else {
604 false
605 }
606 }
607
608 pub fn selected(&self) -> Option<usize> {
613 self.choice.selected()
614 }
615
616 pub fn is_empty(&self) -> bool {
618 self.choice.is_empty()
619 }
620
621 pub fn len(&self) -> usize {
623 self.choice.len()
624 }
625
626 pub fn clear_offset(&mut self) {
628 self.choice.set_offset(0);
629 }
630
631 pub fn set_offset(&mut self, offset: usize) -> bool {
633 self.choice.set_offset(offset)
634 }
635
636 pub fn offset(&self) -> usize {
638 self.choice.offset()
639 }
640
641 pub fn max_offset(&self) -> usize {
643 self.choice.max_offset()
644 }
645
646 pub fn page_len(&self) -> usize {
648 self.choice.page_len()
649 }
650
651 pub fn scroll_by(&self) -> usize {
653 self.choice.scroll_by()
654 }
655
656 pub fn scroll_to_selected(&mut self) -> bool {
658 self.choice.scroll_to_selected()
659 }
660}
661
662impl ComboboxState {
663 pub fn select_by_char(&mut self, c: char) -> bool {
665 if self.choice.select_by_char(c) {
666 self.text.set_value(self.choice.value());
667 true
668 } else {
669 false
670 }
671 }
672
673 pub fn reverse_select_by_char(&mut self, c: char) -> bool {
675 if self.choice.reverse_select_by_char(c) {
676 self.text.set_value(self.choice.value());
677 true
678 } else {
679 false
680 }
681 }
682
683 pub fn move_to(&mut self, n: usize) -> ComboboxOutcome {
685 match self.choice.move_to(n) {
686 ChoiceOutcome::Continue => ComboboxOutcome::Continue,
687 ChoiceOutcome::Unchanged => ComboboxOutcome::Unchanged,
688 ChoiceOutcome::Changed => ComboboxOutcome::Changed,
689 ChoiceOutcome::Value => {
690 self.text.set_value(self.choice.value());
691 ComboboxOutcome::Value
692 }
693 }
694 }
695
696 pub fn move_down(&mut self, n: usize) -> ComboboxOutcome {
698 match self.choice.move_down(n) {
699 ChoiceOutcome::Continue => ComboboxOutcome::Continue,
700 ChoiceOutcome::Unchanged => ComboboxOutcome::Unchanged,
701 ChoiceOutcome::Changed => ComboboxOutcome::Changed,
702 ChoiceOutcome::Value => {
703 self.text.set_value(self.choice.value());
704 ComboboxOutcome::Value
705 }
706 }
707 }
708
709 pub fn move_up(&mut self, n: usize) -> ComboboxOutcome {
711 match self.choice.move_up(n) {
712 ChoiceOutcome::Continue => ComboboxOutcome::Continue,
713 ChoiceOutcome::Unchanged => ComboboxOutcome::Unchanged,
714 ChoiceOutcome::Changed => ComboboxOutcome::Changed,
715 ChoiceOutcome::Value => {
716 self.text.set_value(self.choice.value());
717 ComboboxOutcome::Value
718 }
719 }
720 }
721}
722
723impl HandleEvent<crossterm::event::Event, Popup, ComboboxOutcome> for ComboboxState {
724 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Popup) -> ComboboxOutcome {
725 let r = if self.is_focused() {
726 match event {
727 ct_event!(keycode press Enter) => {
728 self.flip_popup_active();
729 ComboboxOutcome::Changed
730 }
731 ct_event!(keycode press Esc) => {
732 if self.set_popup_active(false) {
733 ComboboxOutcome::Changed
734 } else {
735 ComboboxOutcome::Continue
736 }
737 }
738 ct_event!(keycode press Down) => self.move_down(1),
739 ct_event!(keycode press Up) => self.move_up(1),
740 ct_event!(keycode press PageUp) => self.move_up(self.page_len()),
741 ct_event!(keycode press PageDown) => self.move_down(self.page_len()),
742 ct_event!(keycode press ALT-Home) => self.move_to(0),
743 ct_event!(keycode press ALT-End) => self.move_to(self.len().saturating_sub(1)),
744 crossterm::event::Event::Key(_) => match self.text.handle(event, Regular) {
745 TextOutcome::Continue => ComboboxOutcome::Continue,
746 TextOutcome::Unchanged => ComboboxOutcome::Unchanged,
747 TextOutcome::Changed => ComboboxOutcome::Changed,
748 TextOutcome::TextChanged => ComboboxOutcome::TextChanged,
749 },
750 _ => ComboboxOutcome::Continue,
751 }
752 } else {
753 ComboboxOutcome::Continue
754 };
755
756 if !r.is_consumed() {
757 self.handle(event, MouseOnly)
758 } else {
759 r
760 }
761 }
762}
763
764impl HandleEvent<crossterm::event::Event, MouseOnly, ComboboxOutcome> for ComboboxState {
765 fn handle(
766 &mut self,
767 event: &crossterm::event::Event,
768 _qualifier: MouseOnly,
769 ) -> ComboboxOutcome {
770 let r0 = handle_mouse(self, event);
771 let r1 = handle_select(self, event);
772 let r2 = handle_close(self, event);
773 let mut r = max(r0, max(r1, r2));
774
775 r = r.or_else(|| match self.text.handle(event, MouseOnly) {
776 TextOutcome::Continue => ComboboxOutcome::Continue,
777 TextOutcome::Unchanged => ComboboxOutcome::Unchanged,
778 TextOutcome::Changed => ComboboxOutcome::Changed,
779 TextOutcome::TextChanged => ComboboxOutcome::TextChanged,
780 });
781 r = r.or_else(|| mouse_trap(event, self.choice.popup.area).into());
782
783 r
784 }
785}
786
787fn handle_mouse(state: &mut ComboboxState, event: &crossterm::event::Event) -> ComboboxOutcome {
788 match event {
789 ct_event!(mouse down Left for x,y)
790 if state.choice.button_area.contains((*x, *y).into()) =>
791 {
792 if !state.gained_focus() {
793 state.flip_popup_active();
794 ComboboxOutcome::Changed
795 } else {
796 ComboboxOutcome::Continue
799 }
800 }
801 ct_event!(mouse down Left for x,y)
802 | ct_event!(mouse down Right for x,y)
803 | ct_event!(mouse down Middle for x,y)
804 if !state.choice.item_area.contains((*x, *y).into())
805 && !state.choice.button_area.contains((*x, *y).into()) =>
806 {
807 match state.choice.popup.handle(event, Popup) {
808 PopupOutcome::Hide => {
809 state.set_popup_active(false);
810 ComboboxOutcome::Changed
811 }
812 r => r.into(),
813 }
814 }
815 _ => ComboboxOutcome::Continue,
816 }
817}
818
819fn handle_select(state: &mut ComboboxState, event: &crossterm::event::Event) -> ComboboxOutcome {
820 match state.choice.behave_select {
821 ChoiceSelect::MouseScroll => {
822 let mut sas = ScrollAreaState::new()
823 .area(state.choice.popup.area)
824 .v_scroll(&mut state.choice.popup_scroll);
825 let mut r = match sas.handle(event, MouseOnly) {
826 ScrollOutcome::Up(n) => state.move_up(n),
827 ScrollOutcome::Down(n) => state.move_down(n),
828 ScrollOutcome::VPos(n) => state.move_to(n),
829 _ => ComboboxOutcome::Continue,
830 };
831
832 r = r.or_else(|| match event {
833 ct_event!(mouse down Left for x,y)
834 if state.choice.popup.area.contains((*x, *y).into()) =>
835 {
836 if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
837 state.move_to(state.offset() + n)
838 } else {
839 ComboboxOutcome::Unchanged
840 }
841 }
842 ct_event!(mouse drag Left for x,y)
843 if state.choice.popup.area.contains((*x, *y).into()) =>
844 {
845 if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
846 state.move_to(state.offset() + n)
847 } else {
848 ComboboxOutcome::Unchanged
849 }
850 }
851 _ => ComboboxOutcome::Continue,
852 });
853 r
854 }
855 ChoiceSelect::MouseMove => {
856 let mut r = if let Some(selected) = state.choice.core.selected() {
858 let rel_sel = selected.saturating_sub(state.offset());
859 let mut sas = ScrollAreaState::new()
860 .area(state.choice.popup.area)
861 .v_scroll(&mut state.choice.popup_scroll);
862 match sas.handle(event, MouseOnly) {
863 ScrollOutcome::Up(n) => {
864 state.choice.popup_scroll.scroll_up(n);
865 if state.select(state.offset() + rel_sel) {
866 ComboboxOutcome::Value
867 } else {
868 ComboboxOutcome::Unchanged
869 }
870 }
871 ScrollOutcome::Down(n) => {
872 state.choice.popup_scroll.scroll_down(n);
873 if state.select(state.offset() + rel_sel) {
874 ComboboxOutcome::Value
875 } else {
876 ComboboxOutcome::Unchanged
877 }
878 }
879 ScrollOutcome::VPos(n) => {
880 if state.choice.popup_scroll.set_offset(n) {
881 ComboboxOutcome::Value
882 } else {
883 ComboboxOutcome::Unchanged
884 }
885 }
886 _ => ComboboxOutcome::Continue,
887 }
888 } else {
889 ComboboxOutcome::Continue
890 };
891
892 r = r.or_else(|| match event {
893 ct_event!(mouse moved for x,y)
894 if state.choice.popup.area.contains((*x, *y).into()) =>
895 {
896 if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
897 state.move_to(state.offset() + n)
898 } else {
899 ComboboxOutcome::Unchanged
900 }
901 }
902 _ => ComboboxOutcome::Continue,
903 });
904 r
905 }
906 ChoiceSelect::MouseClick => {
907 let mut sas = ScrollAreaState::new()
909 .area(state.choice.popup.area)
910 .v_scroll(&mut state.choice.popup_scroll);
911 let mut r = match sas.handle(event, MouseOnly) {
912 ScrollOutcome::Up(n) => {
913 if state.choice.popup_scroll.scroll_up(n) {
914 ComboboxOutcome::Changed
915 } else {
916 ComboboxOutcome::Unchanged
917 }
918 }
919 ScrollOutcome::Down(n) => {
920 if state.choice.popup_scroll.scroll_down(n) {
921 ComboboxOutcome::Changed
922 } else {
923 ComboboxOutcome::Unchanged
924 }
925 }
926 ScrollOutcome::VPos(n) => {
927 if state.choice.popup_scroll.set_offset(n) {
928 ComboboxOutcome::Changed
929 } else {
930 ComboboxOutcome::Unchanged
931 }
932 }
933 _ => ComboboxOutcome::Continue,
934 };
935
936 r = r.or_else(|| match event {
937 ct_event!(mouse down Left for x,y)
938 if state.choice.popup.area.contains((*x, *y).into()) =>
939 {
940 if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
941 state.move_to(state.offset() + n)
942 } else {
943 ComboboxOutcome::Unchanged
944 }
945 }
946 ct_event!(mouse drag Left for x,y)
947 if state.choice.popup.area.contains((*x, *y).into()) =>
948 {
949 if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
950 state.move_to(state.offset() + n)
951 } else {
952 ComboboxOutcome::Unchanged
953 }
954 }
955 _ => ComboboxOutcome::Continue,
956 });
957 r
958 }
959 }
960}
961
962fn handle_close(state: &mut ComboboxState, event: &crossterm::event::Event) -> ComboboxOutcome {
963 match state.choice.behave_close {
964 ChoiceClose::SingleClick => match event {
965 ct_event!(mouse down Left for x,y)
966 if state.choice.popup.area.contains((*x, *y).into()) =>
967 {
968 if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
969 let r = state.move_to(state.offset() + n);
970 let s = if state.set_popup_active(false) {
971 ComboboxOutcome::Changed
972 } else {
973 ComboboxOutcome::Unchanged
974 };
975 max(r, s)
976 } else {
977 ComboboxOutcome::Unchanged
978 }
979 }
980 _ => ComboboxOutcome::Continue,
981 },
982 ChoiceClose::DoubleClick => match event {
983 ct_event!(mouse any for m) if state.mouse.doubleclick(state.choice.popup.area, m) => {
984 if let Some(n) = item_at(&state.choice.item_areas, m.column, m.row) {
985 let r = state.move_to(state.offset() + n);
986 let s = if state.set_popup_active(false) {
987 ComboboxOutcome::Changed
988 } else {
989 ComboboxOutcome::Unchanged
990 };
991 max(r, s)
992 } else {
993 ComboboxOutcome::Unchanged
994 }
995 }
996 _ => ComboboxOutcome::Continue,
997 },
998 }
999}
1000
1001pub fn handle_events(
1005 state: &mut ComboboxState,
1006 focus: bool,
1007 event: &crossterm::event::Event,
1008) -> ComboboxOutcome {
1009 state.focus.set(focus);
1010 HandleEvent::handle(state, event, Popup)
1011}
1012
1013pub fn handle_mouse_events(
1015 state: &mut ComboboxState,
1016 event: &crossterm::event::Event,
1017) -> ComboboxOutcome {
1018 HandleEvent::handle(state, event, MouseOnly)
1019}