1use crate::_private::NonExhaustive;
81use crate::layout::GenericLayout;
82use event::FormOutcome;
83use rat_event::util::MouseFlagsN;
84use rat_event::{ConsumedEvent, HandleEvent, MouseOnly, Regular, ct_event};
85use rat_focus::{Focus, FocusBuilder, FocusFlag, HasFocus};
86use rat_reloc::RelocatableState;
87use ratatui::buffer::Buffer;
88use ratatui::layout::{Alignment, Rect, Size};
89use ratatui::prelude::BlockExt;
90use ratatui::style::Style;
91use ratatui::text::{Line, Span};
92use ratatui::widgets::{Block, StatefulWidget, Widget};
93use std::borrow::Cow;
94use std::cmp::min;
95use std::hash::Hash;
96use std::rc::Rc;
97use unicode_display_width::width as unicode_width;
98
99#[derive(Debug, Clone)]
106pub struct Form<'a, W = usize>
107where
108 W: Eq + Hash + Clone,
109{
110 layout: Option<GenericLayout<W>>,
111
112 style: Style,
113 block: Option<Block<'a>>,
114 nav_style: Option<Style>,
115 nav_hover_style: Option<Style>,
116 title_style: Option<Style>,
117 navigation: bool,
118 next_page: &'a str,
119 prev_page: &'a str,
120 first_page: &'a str,
121 last_page: &'a str,
122
123 auto_label: bool,
124 label_style: Option<Style>,
125 label_alignment: Option<Alignment>,
126}
127
128#[derive(Debug)]
133#[must_use]
134pub struct FormBuffer<'b, W>
135where
136 W: Eq + Hash + Clone,
137{
138 layout: Rc<GenericLayout<W>>,
139
140 page_area: Rect,
141 widget_area: Rect,
142 buffer: &'b mut Buffer,
143
144 auto_label: bool,
145 label_style: Option<Style>,
146 label_alignment: Option<Alignment>,
147}
148
149#[derive(Debug, Clone)]
151pub struct FormStyle {
152 pub style: Style,
154 pub block: Option<Block<'static>>,
156 pub border_style: Option<Style>,
158 pub title_style: Option<Style>,
160 pub label_style: Option<Style>,
162 pub label_alignment: Option<Alignment>,
164 pub navigation: Option<Style>,
166 pub navigation_hover: Option<Style>,
168 pub show_navigation: Option<bool>,
170 pub title: Option<Style>,
172 pub next_page_mark: Option<&'static str>,
174 pub prev_page_mark: Option<&'static str>,
176 pub first_page_mark: Option<&'static str>,
178 pub last_page_mark: Option<&'static str>,
180
181 pub non_exhaustive: NonExhaustive,
182}
183
184#[derive(Debug, Clone)]
186pub struct FormState<W = usize>
187where
188 W: Eq + Hash + Clone,
189{
190 pub layout: Rc<GenericLayout<W>>,
193 pub area: Rect,
196 pub widget_area: Rect,
199 pub prev_area: Rect,
202 pub next_area: Rect,
205
206 pub page: usize,
207
208 pub container: FocusFlag,
211
212 pub mouse: MouseFlagsN,
214
215 pub non_exhaustive: NonExhaustive,
217}
218
219pub(crate) mod event {
220 use rat_event::{ConsumedEvent, Outcome};
221
222 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
224 pub enum FormOutcome {
225 Continue,
227 Unchanged,
230 Changed,
235 Page,
237 }
238
239 impl ConsumedEvent for FormOutcome {
240 fn is_consumed(&self) -> bool {
241 *self != FormOutcome::Continue
242 }
243 }
244
245 impl From<Outcome> for FormOutcome {
246 fn from(value: Outcome) -> Self {
247 match value {
248 Outcome::Continue => FormOutcome::Continue,
249 Outcome::Unchanged => FormOutcome::Unchanged,
250 Outcome::Changed => FormOutcome::Changed,
251 }
252 }
253 }
254
255 impl From<FormOutcome> for Outcome {
256 fn from(value: FormOutcome) -> Self {
257 match value {
258 FormOutcome::Continue => Outcome::Continue,
259 FormOutcome::Unchanged => Outcome::Unchanged,
260 FormOutcome::Changed => Outcome::Changed,
261 FormOutcome::Page => Outcome::Changed,
262 }
263 }
264 }
265}
266
267impl<W> Default for Form<'_, W>
268where
269 W: Eq + Hash + Clone,
270{
271 fn default() -> Self {
272 Self {
273 layout: Default::default(),
274 style: Default::default(),
275 block: Default::default(),
276 nav_style: Default::default(),
277 nav_hover_style: Default::default(),
278 title_style: Default::default(),
279 navigation: true,
280 next_page: ">>>",
281 prev_page: "<<<",
282 first_page: " [ ",
283 last_page: " ] ",
284 auto_label: true,
285 label_style: Default::default(),
286 label_alignment: Default::default(),
287 }
288 }
289}
290
291impl<'a, W> Form<'a, W>
292where
293 W: Eq + Hash + Clone,
294{
295 pub fn new() -> Self {
297 Self::default()
298 }
299
300 pub fn layout(mut self, layout: GenericLayout<W>) -> Self {
303 self.layout = Some(layout);
304 self
305 }
306
307 pub fn set_layout(&mut self, layout: GenericLayout<W>) {
310 self.layout = Some(layout);
311 }
312
313 pub fn auto_label(mut self, auto: bool) -> Self {
317 self.auto_label = auto;
318 self
319 }
320
321 pub fn style(mut self, style: Style) -> Self {
323 self.style = style;
324 self.block = self.block.map(|v| v.style(style));
325 self
326 }
327
328 pub fn nav_style(mut self, nav_style: Style) -> Self {
330 self.nav_style = Some(nav_style);
331 self
332 }
333
334 pub fn nav_hover_style(mut self, nav_hover: Style) -> Self {
336 self.nav_hover_style = Some(nav_hover);
337 self
338 }
339
340 pub fn show_navigation(mut self, show: bool) -> Self {
342 self.navigation = show;
343 self
344 }
345
346 pub fn title_style(mut self, title_style: Style) -> Self {
348 self.title_style = Some(title_style);
349 self
350 }
351
352 pub fn block(mut self, block: Block<'a>) -> Self {
354 self.block = Some(block.style(self.style));
355 self
356 }
357
358 pub fn next_page_mark(mut self, txt: &'a str) -> Self {
359 self.next_page = txt;
360 self
361 }
362
363 pub fn prev_page_mark(mut self, txt: &'a str) -> Self {
364 self.prev_page = txt;
365 self
366 }
367
368 pub fn first_page_mark(mut self, txt: &'a str) -> Self {
369 self.first_page = txt;
370 self
371 }
372
373 pub fn last_page_mark(mut self, txt: &'a str) -> Self {
374 self.last_page = txt;
375 self
376 }
377
378 pub fn label_style(mut self, style: Style) -> Self {
380 self.label_style = Some(style);
381 self
382 }
383
384 pub fn label_alignment(mut self, alignment: Alignment) -> Self {
386 self.label_alignment = Some(alignment);
387 self
388 }
389
390 pub fn styles(mut self, styles: FormStyle) -> Self {
392 self.style = styles.style;
393 if styles.block.is_some() {
394 self.block = styles.block;
395 }
396 if let Some(border_style) = styles.border_style {
397 self.block = self.block.map(|v| v.border_style(border_style));
398 }
399 if let Some(title_style) = styles.title_style {
400 self.block = self.block.map(|v| v.title_style(title_style));
401 }
402 self.block = self.block.map(|v| v.style(self.style));
403 if let Some(nav) = styles.navigation {
404 self.nav_style = Some(nav);
405 }
406 if let Some(nav) = styles.navigation_hover {
407 self.nav_hover_style = Some(nav);
408 }
409 if let Some(navigation) = styles.show_navigation {
410 self.navigation = navigation;
411 }
412 if let Some(title) = styles.title {
413 self.title_style = Some(title);
414 }
415 if let Some(txt) = styles.next_page_mark {
416 self.next_page = txt;
417 }
418 if let Some(txt) = styles.prev_page_mark {
419 self.prev_page = txt;
420 }
421 if let Some(txt) = styles.first_page_mark {
422 self.first_page = txt;
423 }
424 if let Some(txt) = styles.last_page_mark {
425 self.last_page = txt;
426 }
427 if let Some(label) = styles.label_style {
428 self.label_style = Some(label);
429 }
430 if let Some(alignment) = styles.label_alignment {
431 self.label_alignment = Some(alignment);
432 }
433
434 self
435 }
436
437 pub fn layout_size(&self, area: Rect) -> Size {
439 self.block.inner_if_some(area).as_size()
440 }
441
442 pub fn layout_area(&self, area: Rect) -> Rect {
444 if let Some(block) = &self.block
445 && self.navigation
446 {
447 block.inner(area)
448 } else {
449 area
450 }
451 }
452
453 #[allow(clippy::needless_lifetimes)]
456 pub fn into_buffer<'b, 's>(
457 mut self,
458 area: Rect,
459 buf: &'b mut Buffer,
460 state: &'s mut FormState<W>,
461 ) -> FormBuffer<'b, W> {
462 state.area = area;
463 state.widget_area = self.layout_area(area);
464
465 if let Some(layout) = self.layout.take() {
466 state.layout = Rc::new(layout);
467 }
468
469 let page_size = state.layout.page_size();
470 assert!(page_size.height < u16::MAX || page_size.height == u16::MAX && state.page == 0);
471 let page_area = Rect::new(
472 0,
473 (state.page as u16).saturating_mul(page_size.height),
474 page_size.width,
475 page_size.height,
476 );
477
478 if self.navigation {
479 self.render_navigation(area, buf, state);
480 } else {
481 buf.set_style(area, self.style);
482 }
483
484 let mut form_buf = FormBuffer {
485 layout: state.layout.clone(),
486 page_area,
487 widget_area: state.widget_area,
488 buffer: buf,
489
490 auto_label: true,
491 label_style: self.label_style,
492 label_alignment: self.label_alignment,
493 };
494 form_buf.render_block();
495 form_buf
496 }
497
498 fn render_navigation(&self, area: Rect, buf: &mut Buffer, state: &mut FormState<W>) {
499 let page_count = state.layout.page_count();
500
501 if !state.layout.is_endless() {
502 if state.page > 0 {
503 state.prev_area =
504 Rect::new(area.x, area.y, unicode_width(self.prev_page) as u16, 1);
505 } else {
506 state.prev_area =
507 Rect::new(area.x, area.y, unicode_width(self.first_page) as u16, 1);
508 }
509 if (state.page + 1) < page_count {
510 let p = unicode_width(self.next_page) as u16;
511 state.next_area = Rect::new(area.x + area.width.saturating_sub(p), area.y, p, 1);
512 } else {
513 let p = unicode_width(self.last_page) as u16;
514 state.next_area = Rect::new(area.x + area.width.saturating_sub(p), area.y, p, 1);
515 }
516 } else {
517 state.prev_area = Default::default();
518 state.next_area = Default::default();
519 }
520
521 let block = if page_count > 1 {
522 let title = format!(" {}/{} ", state.page + 1, page_count);
523 let block = self
524 .block
525 .clone()
526 .unwrap_or_else(|| Block::new().style(self.style))
527 .title_bottom(title)
528 .title_alignment(Alignment::Right);
529 if let Some(title_style) = self.title_style {
530 block.title_style(title_style)
531 } else {
532 block
533 }
534 } else {
535 self.block
536 .clone()
537 .unwrap_or_else(|| Block::new().style(self.style))
538 };
539 block.render(area, buf);
540
541 if !state.layout.is_endless() {
542 let nav_style = self.nav_style.unwrap_or(self.style);
544 let nav_hover_style = self.nav_hover_style.unwrap_or(self.style);
545 if matches!(state.mouse.hover.get(), Some(0)) {
546 buf.set_style(state.prev_area, nav_hover_style);
547 } else {
548 buf.set_style(state.prev_area, nav_style);
549 }
550 if state.page > 0 {
551 Span::from(self.prev_page).render(state.prev_area, buf);
552 } else {
553 Span::from(self.first_page).render(state.prev_area, buf);
554 }
555 if matches!(state.mouse.hover.get(), Some(1)) {
556 buf.set_style(state.next_area, nav_hover_style);
557 } else {
558 buf.set_style(state.next_area, nav_style);
559 }
560 if (state.page + 1) < page_count {
561 Span::from(self.next_page).render(state.next_area, buf);
562 } else {
563 Span::from(self.last_page).render(state.next_area, buf);
564 }
565 }
566 }
567}
568
569impl<'b, W> FormBuffer<'b, W>
570where
571 W: Eq + Hash + Clone,
572{
573 pub fn is_visible(&self, widget: W) -> bool {
575 if let Some(idx) = self.layout.try_index_of(widget) {
576 self.locate_area(self.layout.widget(idx)).is_some()
577 } else {
578 false
579 }
580 }
581
582 fn render_block(&mut self) {
584 for (idx, block_area) in self.layout.block_area_iter().enumerate() {
585 if let Some(block_area) = self.locate_area(*block_area) {
586 if let Some(block) = self.layout.block(idx) {
587 block.render(block_area, self.buffer);
588 }
589 }
590 }
591 }
592
593 #[inline(always)]
595 pub fn render_label<FN>(&mut self, widget: W, render_fn: FN) -> bool
596 where
597 FN: FnOnce(&Cow<'static, str>, Rect, &mut Buffer),
598 {
599 let Some(idx) = self.layout.try_index_of(widget) else {
600 return false;
601 };
602 let Some(label_area) = self.locate_area(self.layout.label(idx)) else {
603 return false;
604 };
605 if let Some(label_str) = self.layout.try_label_str(idx) {
606 render_fn(label_str, label_area, self.buffer);
607 } else {
608 render_fn(&Cow::default(), label_area, self.buffer);
609 }
610 true
611 }
612
613 #[inline(always)]
615 pub fn render_widget<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
616 where
617 FN: FnOnce() -> WW,
618 WW: Widget,
619 {
620 let Some(idx) = self.layout.try_index_of(widget) else {
621 return false;
622 };
623 if self.auto_label {
624 self.render_auto_label(idx);
625 }
626
627 let Some(widget_area) = self.locate_area(self.layout.widget(idx)) else {
628 return false;
629 };
630 render_fn().render(widget_area, self.buffer);
631 true
632 }
633
634 #[inline(always)]
636 pub fn render<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
637 where
638 FN: FnOnce() -> WW,
639 WW: StatefulWidget<State = SS>,
640 SS: RelocatableState,
641 {
642 let Some(idx) = self.layout.try_index_of(widget) else {
643 return false;
644 };
645 if self.auto_label {
646 self.render_auto_label(idx);
647 }
648 let Some(widget_area) = self.locate_area(self.layout.widget(idx)) else {
649 state.relocate_hidden();
650 return false;
651 };
652 let widget = render_fn();
653 widget.render(widget_area, self.buffer, state);
654 true
655 }
656
657 #[inline(always)]
666 #[allow(clippy::question_mark)]
667 pub fn render2<FN, WW, SS, R>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> Option<R>
668 where
669 FN: FnOnce() -> (WW, R),
670 WW: StatefulWidget<State = SS>,
671 SS: RelocatableState,
672 {
673 let Some(idx) = self.layout.try_index_of(widget) else {
674 return None;
675 };
676 if self.auto_label {
677 self.render_auto_label(idx);
678 }
679 let Some(widget_area) = self.locate_area(self.layout.widget(idx)) else {
680 state.relocate_hidden();
681 return None;
682 };
683 let (widget, remainder) = render_fn();
684 widget.render(widget_area, self.buffer, state);
685
686 Some(remainder)
687 }
688
689 #[inline(always)]
691 #[deprecated(since = "2.3.0", note = "use render_popup() for popups")]
692 pub fn render_opt<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
693 where
694 FN: FnOnce() -> Option<WW>,
695 WW: StatefulWidget<State = SS>,
696 SS: RelocatableState,
697 {
698 let Some(idx) = self.layout.try_index_of(widget) else {
699 return false;
700 };
701 if self.auto_label {
702 self.render_auto_label(idx);
703 }
704 let Some(widget_area) = self.locate_area(self.layout.widget(idx)) else {
705 state.relocate_hidden();
706 return false;
707 };
708 let widget = render_fn();
709 if let Some(widget) = widget {
710 widget.render(widget_area, self.buffer, state);
711 true
712 } else {
713 state.relocate_hidden();
714 false
715 }
716 }
717
718 #[inline(always)]
722 pub fn render_popup<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
723 where
724 FN: FnOnce() -> Option<WW>,
725 WW: StatefulWidget<State = SS>,
726 SS: RelocatableState,
727 {
728 let Some(idx) = self.layout.try_index_of(widget) else {
729 return false;
730 };
731 let Some(widget_area) = self.locate_area(self.layout.widget(idx)) else {
732 state.relocate_popup_hidden();
733 return false;
734 };
735 let widget = render_fn();
736 if let Some(widget) = widget {
737 widget.render(widget_area, self.buffer, state);
738 true
739 } else {
740 state.relocate_popup_hidden();
741 false
742 }
743 }
744
745 pub fn buffer(&mut self) -> &mut Buffer {
747 self.buffer
748 }
749
750 #[inline(always)]
752 fn render_auto_label(&mut self, idx: usize) -> bool {
753 let Some(label_area) = self.locate_area(self.layout.label(idx)) else {
754 return false;
755 };
756 let Some(label_str) = self.layout.try_label_str(idx) else {
757 return false;
758 };
759 let mut label = Line::from(label_str.as_ref());
760 if let Some(style) = self.label_style {
761 label = label.style(style)
762 };
763 if let Some(align) = self.label_alignment {
764 label = label.alignment(align);
765 }
766 label.render(label_area, self.buffer);
767
768 true
769 }
770
771 pub fn locate_widget(&self, widget: W) -> Option<Rect> {
773 let Some(idx) = self.layout.try_index_of(widget) else {
774 return None;
775 };
776 self.locate_area(self.layout.widget(idx))
777 }
778
779 pub fn locate_label(&self, widget: W) -> Option<Rect> {
781 let Some(idx) = self.layout.try_index_of(widget) else {
782 return None;
783 };
784 self.locate_area(self.layout.label(idx))
785 }
786
787 #[inline]
789 pub fn locate_area(&self, area: Rect) -> Option<Rect> {
790 let area = self.page_area.intersection(area);
792 if self.page_area.intersects(area) {
793 let located = Rect::new(
794 area.x - self.page_area.x + self.widget_area.x,
795 area.y - self.page_area.y + self.widget_area.y,
796 area.width,
797 area.height,
798 );
799 let located = self.widget_area.intersection(located);
801 if self.widget_area.intersects(located) {
802 Some(located)
803 } else {
804 None
805 }
806 } else {
807 None
808 }
809 }
810}
811
812impl Default for FormStyle {
813 fn default() -> Self {
814 Self {
815 style: Default::default(),
816 label_style: Default::default(),
817 label_alignment: Default::default(),
818 navigation: Default::default(),
819 navigation_hover: Default::default(),
820 show_navigation: Default::default(),
821 title: Default::default(),
822 block: Default::default(),
823 border_style: Default::default(),
824 title_style: Default::default(),
825 next_page_mark: Default::default(),
826 prev_page_mark: Default::default(),
827 first_page_mark: Default::default(),
828 last_page_mark: Default::default(),
829 non_exhaustive: NonExhaustive,
830 }
831 }
832}
833
834impl<W> Default for FormState<W>
835where
836 W: Eq + Hash + Clone,
837{
838 fn default() -> Self {
839 Self {
840 layout: Default::default(),
841 area: Default::default(),
842 widget_area: Default::default(),
843 prev_area: Default::default(),
844 next_area: Default::default(),
845 page: 0,
846 container: Default::default(),
847 mouse: Default::default(),
848 non_exhaustive: NonExhaustive,
849 }
850 }
851}
852
853impl<W> HasFocus for FormState<W>
854where
855 W: Eq + Hash + Clone,
856{
857 fn build(&self, _builder: &mut FocusBuilder) {
858 }
860
861 fn focus(&self) -> FocusFlag {
862 self.container.clone()
863 }
864
865 fn area(&self) -> Rect {
866 self.area
867 }
868}
869
870impl<W> RelocatableState for FormState<W>
871where
872 W: Eq + Hash + Clone,
873{
874 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
875 self.area.relocate(shift, clip);
876 self.widget_area.relocate(shift, clip);
877 self.prev_area.relocate(shift, clip);
878 self.next_area.relocate(shift, clip);
879 }
880}
881
882impl<W> FormState<W>
883where
884 W: Eq + Hash + Clone,
885{
886 pub fn new() -> Self {
887 Self::default()
888 }
889
890 pub fn named(name: &str) -> Self {
891 let mut z = Self::default();
892 z.container = z.container.with_name(name);
893 z
894 }
895
896 pub fn clear(&mut self) {
898 self.layout = Default::default();
899 self.page = 0;
900 }
901
902 pub fn valid_layout(&self, size: Size) -> bool {
904 !self.layout.size_changed(size) && !self.layout.is_empty()
905 }
906
907 pub fn set_layout(&mut self, layout: GenericLayout<W>) {
909 self.layout = Rc::new(layout);
910 }
911
912 pub fn layout(&self) -> Rc<GenericLayout<W>> {
914 self.layout.clone()
915 }
916
917 pub fn show(&mut self, widget: W) {
921 let page = self.layout.page_of(widget).unwrap_or_default();
922 self.set_page(page);
923 }
924
925 pub fn page_count(&self) -> usize {
927 self.layout.page_count()
928 }
929
930 pub fn first(&self, page: usize) -> Option<W> {
932 self.layout.first(page)
933 }
934
935 pub fn page_of(&self, widget: W) -> Option<usize> {
937 self.layout.page_of(widget)
938 }
939
940 pub fn set_page(&mut self, page: usize) -> bool {
942 let old_page = self.page;
943 self.page = min(page, self.page_count().saturating_sub(1));
944 old_page != self.page
945 }
946
947 pub fn page(&self) -> usize {
949 self.page
950 }
951
952 pub fn next_page(&mut self) -> bool {
954 let old_page = self.page;
955
956 if self.page + 1 == self.page_count() {
957 } else if self.page + 1 > self.page_count() {
959 self.page = self.page_count().saturating_sub(1);
960 } else {
961 self.page += 1;
962 }
963
964 old_page != self.page
965 }
966
967 pub fn prev_page(&mut self) -> bool {
969 if self.page >= 1 {
970 self.page -= 1;
971 true
972 } else if self.page > 0 {
973 self.page = 0;
974 true
975 } else {
976 false
977 }
978 }
979}
980
981impl FormState<usize> {
982 pub fn focus_first(&self, focus: &Focus) -> bool {
985 if let Some(w) = self.first(self.page) {
986 focus.by_widget_id(w);
987 true
988 } else {
989 false
990 }
991 }
992
993 pub fn show_focused(&mut self, focus: &Focus) -> bool {
997 let Some(focused) = focus.focused() else {
998 return false;
999 };
1000 let focused = focused.widget_id();
1001 let page = self.layout.page_of(focused);
1002 if let Some(page) = page {
1003 self.set_page(page);
1004 true
1005 } else {
1006 false
1007 }
1008 }
1009}
1010
1011impl FormState<FocusFlag> {
1012 pub fn focus_first(&self, focus: &Focus) -> bool {
1014 if let Some(w) = self.first(self.page) {
1015 focus.focus(&w);
1016 true
1017 } else {
1018 false
1019 }
1020 }
1021
1022 pub fn show_focused(&mut self, focus: &Focus) -> bool {
1025 let Some(focused) = focus.focused() else {
1026 return false;
1027 };
1028 let page = self.layout.page_of(focused);
1029 if let Some(page) = page {
1030 self.set_page(page);
1031 true
1032 } else {
1033 false
1034 }
1035 }
1036}
1037
1038impl<W> HandleEvent<crossterm::event::Event, Regular, FormOutcome> for FormState<W>
1039where
1040 W: Eq + Hash + Clone,
1041{
1042 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> FormOutcome {
1043 let r = if self.container.is_focused() && !self.layout.is_endless() {
1044 match event {
1045 ct_event!(keycode press ALT-PageUp) => {
1046 if self.prev_page() {
1047 FormOutcome::Page
1048 } else {
1049 FormOutcome::Continue
1050 }
1051 }
1052 ct_event!(keycode press ALT-PageDown) => {
1053 if self.next_page() {
1054 FormOutcome::Page
1055 } else {
1056 FormOutcome::Continue
1057 }
1058 }
1059 _ => FormOutcome::Continue,
1060 }
1061 } else {
1062 FormOutcome::Continue
1063 };
1064
1065 r.or_else(|| self.handle(event, MouseOnly))
1066 }
1067}
1068
1069impl<W> HandleEvent<crossterm::event::Event, MouseOnly, FormOutcome> for FormState<W>
1070where
1071 W: Eq + Hash + Clone,
1072{
1073 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> FormOutcome {
1074 if !self.layout.is_endless() {
1075 match event {
1076 ct_event!(mouse down Left for x,y) if self.prev_area.contains((*x, *y).into()) => {
1077 if self.prev_page() {
1078 FormOutcome::Page
1079 } else {
1080 FormOutcome::Unchanged
1081 }
1082 }
1083 ct_event!(mouse down Left for x,y) if self.next_area.contains((*x, *y).into()) => {
1084 if self.next_page() {
1085 FormOutcome::Page
1086 } else {
1087 FormOutcome::Unchanged
1088 }
1089 }
1090 ct_event!(scroll down for x,y) => {
1091 if self.area.contains((*x, *y).into()) {
1092 if self.next_page() {
1093 FormOutcome::Page
1094 } else {
1095 FormOutcome::Continue
1096 }
1097 } else {
1098 FormOutcome::Continue
1099 }
1100 }
1101 ct_event!(scroll up for x,y) => {
1102 if self.area.contains((*x, *y).into()) {
1103 if self.prev_page() {
1104 FormOutcome::Page
1105 } else {
1106 FormOutcome::Continue
1107 }
1108 } else {
1109 FormOutcome::Continue
1110 }
1111 }
1112 ct_event!(mouse any for m)
1113 if self.mouse.hover(&[self.prev_area, self.next_area], m) =>
1114 {
1115 FormOutcome::Changed
1116 }
1117 _ => FormOutcome::Continue,
1118 }
1119 } else {
1120 FormOutcome::Continue
1121 }
1122 }
1123}
1124
1125pub fn handle_events<W>(
1129 state: &mut FormState<W>,
1130 _focus: bool,
1131 event: &crossterm::event::Event,
1132) -> FormOutcome
1133where
1134 W: Eq + Clone + Hash,
1135{
1136 HandleEvent::handle(state, event, Regular)
1137}
1138
1139pub fn handle_mouse_events<W>(
1141 state: &mut FormState<W>,
1142 event: &crossterm::event::Event,
1143) -> FormOutcome
1144where
1145 W: Eq + Clone + Hash,
1146{
1147 HandleEvent::handle(state, event, MouseOnly)
1148}