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 auto_label(mut self, auto: bool) -> Self {
311 self.auto_label = auto;
312 self
313 }
314
315 pub fn style(mut self, style: Style) -> Self {
317 self.style = style;
318 self.block = self.block.map(|v| v.style(style));
319 self
320 }
321
322 pub fn nav_style(mut self, nav_style: Style) -> Self {
324 self.nav_style = Some(nav_style);
325 self
326 }
327
328 pub fn nav_hover_style(mut self, nav_hover: Style) -> Self {
330 self.nav_hover_style = Some(nav_hover);
331 self
332 }
333
334 pub fn show_navigation(mut self, show: bool) -> Self {
336 self.navigation = show;
337 self
338 }
339
340 pub fn title_style(mut self, title_style: Style) -> Self {
342 self.title_style = Some(title_style);
343 self
344 }
345
346 pub fn block(mut self, block: Block<'a>) -> Self {
348 self.block = Some(block.style(self.style));
349 self
350 }
351
352 pub fn next_page_mark(mut self, txt: &'a str) -> Self {
353 self.next_page = txt;
354 self
355 }
356
357 pub fn prev_page_mark(mut self, txt: &'a str) -> Self {
358 self.prev_page = txt;
359 self
360 }
361
362 pub fn first_page_mark(mut self, txt: &'a str) -> Self {
363 self.first_page = txt;
364 self
365 }
366
367 pub fn last_page_mark(mut self, txt: &'a str) -> Self {
368 self.last_page = txt;
369 self
370 }
371
372 pub fn label_style(mut self, style: Style) -> Self {
374 self.label_style = Some(style);
375 self
376 }
377
378 pub fn label_alignment(mut self, alignment: Alignment) -> Self {
380 self.label_alignment = Some(alignment);
381 self
382 }
383
384 pub fn styles(mut self, styles: FormStyle) -> Self {
386 self.style = styles.style;
387 if styles.block.is_some() {
388 self.block = styles.block;
389 }
390 if let Some(border_style) = styles.border_style {
391 self.block = self.block.map(|v| v.border_style(border_style));
392 }
393 if let Some(title_style) = styles.title_style {
394 self.block = self.block.map(|v| v.title_style(title_style));
395 }
396 self.block = self.block.map(|v| v.style(self.style));
397 if let Some(nav) = styles.navigation {
398 self.nav_style = Some(nav);
399 }
400 if let Some(nav) = styles.navigation_hover {
401 self.nav_hover_style = Some(nav);
402 }
403 if let Some(navigation) = styles.show_navigation {
404 self.navigation = navigation;
405 }
406 if let Some(title) = styles.title {
407 self.title_style = Some(title);
408 }
409 if let Some(txt) = styles.next_page_mark {
410 self.next_page = txt;
411 }
412 if let Some(txt) = styles.prev_page_mark {
413 self.prev_page = txt;
414 }
415 if let Some(txt) = styles.first_page_mark {
416 self.first_page = txt;
417 }
418 if let Some(txt) = styles.last_page_mark {
419 self.last_page = txt;
420 }
421 if let Some(label) = styles.label_style {
422 self.label_style = Some(label);
423 }
424 if let Some(alignment) = styles.label_alignment {
425 self.label_alignment = Some(alignment);
426 }
427
428 self
429 }
430
431 pub fn layout_size(&self, area: Rect) -> Size {
433 self.block.inner_if_some(area).as_size()
434 }
435
436 pub fn layout_area(&self, area: Rect) -> Rect {
438 if let Some(block) = &self.block
439 && self.navigation
440 {
441 block.inner(area)
442 } else {
443 area
444 }
445 }
446
447 #[allow(clippy::needless_lifetimes)]
450 pub fn into_buffer<'b, 's>(
451 mut self,
452 area: Rect,
453 buf: &'b mut Buffer,
454 state: &'s mut FormState<W>,
455 ) -> FormBuffer<'b, W> {
456 state.area = area;
457 state.widget_area = self.layout_area(area);
458
459 if let Some(layout) = self.layout.take() {
460 state.layout = Rc::new(layout);
461 }
462
463 let page_size = state.layout.page_size();
464 assert!(page_size.height < u16::MAX || page_size.height == u16::MAX && state.page == 0);
465 let page_area = Rect::new(
466 0,
467 (state.page as u16).saturating_mul(page_size.height),
468 page_size.width,
469 page_size.height,
470 );
471
472 if self.navigation {
473 self.render_navigation(area, buf, state);
474 } else {
475 buf.set_style(area, self.style);
476 }
477
478 let mut form_buf = FormBuffer {
479 layout: state.layout.clone(),
480 page_area,
481 widget_area: state.widget_area,
482 buffer: buf,
483
484 auto_label: true,
485 label_style: self.label_style,
486 label_alignment: self.label_alignment,
487 };
488 form_buf.render_block();
489 form_buf
490 }
491
492 fn render_navigation(&self, area: Rect, buf: &mut Buffer, state: &mut FormState<W>) {
493 let page_count = state.layout.page_count();
494
495 if !state.layout.is_endless() {
496 if state.page > 0 {
497 state.prev_area =
498 Rect::new(area.x, area.y, unicode_width(self.prev_page) as u16, 1);
499 } else {
500 state.prev_area =
501 Rect::new(area.x, area.y, unicode_width(self.first_page) as u16, 1);
502 }
503 if (state.page + 1) < page_count {
504 let p = unicode_width(self.next_page) as u16;
505 state.next_area = Rect::new(area.x + area.width.saturating_sub(p), area.y, p, 1);
506 } else {
507 let p = unicode_width(self.last_page) as u16;
508 state.next_area = Rect::new(area.x + area.width.saturating_sub(p), area.y, p, 1);
509 }
510 } else {
511 state.prev_area = Default::default();
512 state.next_area = Default::default();
513 }
514
515 let block = if page_count > 1 {
516 let title = format!(" {}/{} ", state.page + 1, page_count);
517 let block = self
518 .block
519 .clone()
520 .unwrap_or_else(|| Block::new().style(self.style))
521 .title_bottom(title)
522 .title_alignment(Alignment::Right);
523 if let Some(title_style) = self.title_style {
524 block.title_style(title_style)
525 } else {
526 block
527 }
528 } else {
529 self.block
530 .clone()
531 .unwrap_or_else(|| Block::new().style(self.style))
532 };
533 block.render(area, buf);
534
535 if !state.layout.is_endless() {
536 let nav_style = self.nav_style.unwrap_or(self.style);
538 let nav_hover_style = self.nav_hover_style.unwrap_or(self.style);
539 if matches!(state.mouse.hover.get(), Some(0)) {
540 buf.set_style(state.prev_area, nav_hover_style);
541 } else {
542 buf.set_style(state.prev_area, nav_style);
543 }
544 if state.page > 0 {
545 Span::from(self.prev_page).render(state.prev_area, buf);
546 } else {
547 Span::from(self.first_page).render(state.prev_area, buf);
548 }
549 if matches!(state.mouse.hover.get(), Some(1)) {
550 buf.set_style(state.next_area, nav_hover_style);
551 } else {
552 buf.set_style(state.next_area, nav_style);
553 }
554 if (state.page + 1) < page_count {
555 Span::from(self.next_page).render(state.next_area, buf);
556 } else {
557 Span::from(self.last_page).render(state.next_area, buf);
558 }
559 }
560 }
561}
562
563impl<'b, W> FormBuffer<'b, W>
564where
565 W: Eq + Hash + Clone,
566{
567 pub fn is_visible(&self, widget: W) -> bool {
569 if let Some(idx) = self.layout.try_index_of(widget) {
570 self.locate_area(self.layout.widget(idx)).is_some()
571 } else {
572 false
573 }
574 }
575
576 fn render_block(&mut self) {
578 for (idx, block_area) in self.layout.block_area_iter().enumerate() {
579 if let Some(block_area) = self.locate_area(*block_area) {
580 if let Some(block) = self.layout.block(idx) {
581 block.render(block_area, self.buffer);
582 }
583 }
584 }
585 }
586
587 #[inline(always)]
589 pub fn render_label<FN>(&mut self, widget: W, render_fn: FN) -> bool
590 where
591 FN: FnOnce(&Cow<'static, str>, Rect, &mut Buffer),
592 {
593 let Some(idx) = self.layout.try_index_of(widget) else {
594 return false;
595 };
596 let Some(label_area) = self.locate_area(self.layout.label(idx)) else {
597 return false;
598 };
599 if let Some(label_str) = self.layout.try_label_str(idx) {
600 render_fn(label_str, label_area, self.buffer);
601 } else {
602 render_fn(&Cow::default(), label_area, self.buffer);
603 }
604 true
605 }
606
607 #[inline(always)]
609 pub fn render_widget<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
610 where
611 FN: FnOnce() -> WW,
612 WW: Widget,
613 {
614 let Some(idx) = self.layout.try_index_of(widget) else {
615 return false;
616 };
617 if self.auto_label {
618 self.render_auto_label(idx);
619 }
620
621 let Some(widget_area) = self.locate_area(self.layout.widget(idx)) else {
622 return false;
623 };
624 render_fn().render(widget_area, self.buffer);
625 true
626 }
627
628 #[inline(always)]
630 pub fn render<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
631 where
632 FN: FnOnce() -> WW,
633 WW: StatefulWidget<State = SS>,
634 SS: RelocatableState,
635 {
636 let Some(idx) = self.layout.try_index_of(widget) else {
637 return false;
638 };
639 if self.auto_label {
640 self.render_auto_label(idx);
641 }
642 let Some(widget_area) = self.locate_area(self.layout.widget(idx)) else {
643 state.relocate_hidden();
644 return false;
645 };
646 let widget = render_fn();
647 widget.render(widget_area, self.buffer, state);
648 true
649 }
650
651 #[inline(always)]
660 #[allow(clippy::question_mark)]
661 pub fn render2<FN, WW, SS, R>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> Option<R>
662 where
663 FN: FnOnce() -> (WW, R),
664 WW: StatefulWidget<State = SS>,
665 SS: RelocatableState,
666 {
667 let Some(idx) = self.layout.try_index_of(widget) else {
668 return None;
669 };
670 if self.auto_label {
671 self.render_auto_label(idx);
672 }
673 let Some(widget_area) = self.locate_area(self.layout.widget(idx)) else {
674 state.relocate_hidden();
675 return None;
676 };
677 let (widget, remainder) = render_fn();
678 widget.render(widget_area, self.buffer, state);
679
680 Some(remainder)
681 }
682
683 #[inline(always)]
685 #[deprecated(since = "2.3.0", note = "use render_popup() for popups")]
686 pub fn render_opt<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
687 where
688 FN: FnOnce() -> Option<WW>,
689 WW: StatefulWidget<State = SS>,
690 SS: RelocatableState,
691 {
692 let Some(idx) = self.layout.try_index_of(widget) else {
693 return false;
694 };
695 if self.auto_label {
696 self.render_auto_label(idx);
697 }
698 let Some(widget_area) = self.locate_area(self.layout.widget(idx)) else {
699 state.relocate_hidden();
700 return false;
701 };
702 let widget = render_fn();
703 if let Some(widget) = widget {
704 widget.render(widget_area, self.buffer, state);
705 true
706 } else {
707 state.relocate_hidden();
708 false
709 }
710 }
711
712 #[inline(always)]
716 pub fn render_popup<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
717 where
718 FN: FnOnce() -> Option<WW>,
719 WW: StatefulWidget<State = SS>,
720 SS: RelocatableState,
721 {
722 let Some(idx) = self.layout.try_index_of(widget) else {
723 return false;
724 };
725 let Some(widget_area) = self.locate_area(self.layout.widget(idx)) else {
726 state.relocate_popup_hidden();
727 return false;
728 };
729 let widget = render_fn();
730 if let Some(widget) = widget {
731 widget.render(widget_area, self.buffer, state);
732 true
733 } else {
734 state.relocate_popup_hidden();
735 false
736 }
737 }
738
739 pub fn buffer(&mut self) -> &mut Buffer {
741 self.buffer
742 }
743
744 #[inline(always)]
746 fn render_auto_label(&mut self, idx: usize) -> bool {
747 let Some(label_area) = self.locate_area(self.layout.label(idx)) else {
748 return false;
749 };
750 let Some(label_str) = self.layout.try_label_str(idx) else {
751 return false;
752 };
753 let mut label = Line::from(label_str.as_ref());
754 if let Some(style) = self.label_style {
755 label = label.style(style)
756 };
757 if let Some(align) = self.label_alignment {
758 label = label.alignment(align);
759 }
760 label.render(label_area, self.buffer);
761
762 true
763 }
764
765 pub fn locate_widget(&self, widget: W) -> Option<Rect> {
767 let Some(idx) = self.layout.try_index_of(widget) else {
768 return None;
769 };
770 self.locate_area(self.layout.widget(idx))
771 }
772
773 pub fn locate_label(&self, widget: W) -> Option<Rect> {
775 let Some(idx) = self.layout.try_index_of(widget) else {
776 return None;
777 };
778 self.locate_area(self.layout.label(idx))
779 }
780
781 #[inline]
783 pub fn locate_area(&self, area: Rect) -> Option<Rect> {
784 let area = self.page_area.intersection(area);
786 if self.page_area.intersects(area) {
787 let located = Rect::new(
788 area.x - self.page_area.x + self.widget_area.x,
789 area.y - self.page_area.y + self.widget_area.y,
790 area.width,
791 area.height,
792 );
793 let located = self.widget_area.intersection(located);
795 if self.widget_area.intersects(located) {
796 Some(located)
797 } else {
798 None
799 }
800 } else {
801 None
802 }
803 }
804}
805
806impl Default for FormStyle {
807 fn default() -> Self {
808 Self {
809 style: Default::default(),
810 label_style: Default::default(),
811 label_alignment: Default::default(),
812 navigation: Default::default(),
813 navigation_hover: Default::default(),
814 show_navigation: Default::default(),
815 title: Default::default(),
816 block: Default::default(),
817 border_style: Default::default(),
818 title_style: Default::default(),
819 next_page_mark: Default::default(),
820 prev_page_mark: Default::default(),
821 first_page_mark: Default::default(),
822 last_page_mark: Default::default(),
823 non_exhaustive: NonExhaustive,
824 }
825 }
826}
827
828impl<W> Default for FormState<W>
829where
830 W: Eq + Hash + Clone,
831{
832 fn default() -> Self {
833 Self {
834 layout: Default::default(),
835 area: Default::default(),
836 widget_area: Default::default(),
837 prev_area: Default::default(),
838 next_area: Default::default(),
839 page: 0,
840 container: Default::default(),
841 mouse: Default::default(),
842 non_exhaustive: NonExhaustive,
843 }
844 }
845}
846
847impl<W> HasFocus for FormState<W>
848where
849 W: Eq + Hash + Clone,
850{
851 fn build(&self, _builder: &mut FocusBuilder) {
852 }
854
855 fn focus(&self) -> FocusFlag {
856 self.container.clone()
857 }
858
859 fn area(&self) -> Rect {
860 self.area
861 }
862}
863
864impl<W> FormState<W>
865where
866 W: Eq + Hash + Clone,
867{
868 pub fn new() -> Self {
869 Self::default()
870 }
871
872 pub fn named(name: &str) -> Self {
873 let mut z = Self::default();
874 z.container = z.container.with_name(name);
875 z
876 }
877
878 pub fn clear(&mut self) {
880 self.layout = Default::default();
881 self.page = 0;
882 }
883
884 pub fn valid_layout(&self, size: Size) -> bool {
886 !self.layout.size_changed(size) && !self.layout.is_empty()
887 }
888
889 pub fn set_layout(&mut self, layout: GenericLayout<W>) {
891 self.layout = Rc::new(layout);
892 }
893
894 pub fn layout(&self) -> Rc<GenericLayout<W>> {
896 self.layout.clone()
897 }
898
899 pub fn show(&mut self, widget: W) {
903 let page = self.layout.page_of(widget).unwrap_or_default();
904 self.set_page(page);
905 }
906
907 pub fn page_count(&self) -> usize {
909 self.layout.page_count()
910 }
911
912 pub fn first(&self, page: usize) -> Option<W> {
914 self.layout.first(page)
915 }
916
917 pub fn page_of(&self, widget: W) -> Option<usize> {
919 self.layout.page_of(widget)
920 }
921
922 pub fn set_page(&mut self, page: usize) -> bool {
924 let old_page = self.page;
925 self.page = min(page, self.page_count().saturating_sub(1));
926 old_page != self.page
927 }
928
929 pub fn page(&self) -> usize {
931 self.page
932 }
933
934 pub fn next_page(&mut self) -> bool {
936 let old_page = self.page;
937
938 if self.page + 1 == self.page_count() {
939 } else if self.page + 1 > self.page_count() {
941 self.page = self.page_count().saturating_sub(1);
942 } else {
943 self.page += 1;
944 }
945
946 old_page != self.page
947 }
948
949 pub fn prev_page(&mut self) -> bool {
951 if self.page >= 1 {
952 self.page -= 1;
953 true
954 } else if self.page > 0 {
955 self.page = 0;
956 true
957 } else {
958 false
959 }
960 }
961}
962
963impl FormState<usize> {
964 pub fn focus_first(&self, focus: &Focus) -> bool {
967 if let Some(w) = self.first(self.page) {
968 focus.by_widget_id(w);
969 true
970 } else {
971 false
972 }
973 }
974
975 pub fn show_focused(&mut self, focus: &Focus) -> bool {
979 let Some(focused) = focus.focused() else {
980 return false;
981 };
982 let focused = focused.widget_id();
983 let page = self.layout.page_of(focused);
984 if let Some(page) = page {
985 self.set_page(page);
986 true
987 } else {
988 false
989 }
990 }
991}
992
993impl FormState<FocusFlag> {
994 pub fn focus_first(&self, focus: &Focus) -> bool {
996 if let Some(w) = self.first(self.page) {
997 focus.focus(&w);
998 true
999 } else {
1000 false
1001 }
1002 }
1003
1004 pub fn show_focused(&mut self, focus: &Focus) -> bool {
1007 let Some(focused) = focus.focused() else {
1008 return false;
1009 };
1010 let page = self.layout.page_of(focused);
1011 if let Some(page) = page {
1012 self.set_page(page);
1013 true
1014 } else {
1015 false
1016 }
1017 }
1018}
1019
1020impl<W> HandleEvent<crossterm::event::Event, Regular, FormOutcome> for FormState<W>
1021where
1022 W: Eq + Hash + Clone,
1023{
1024 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> FormOutcome {
1025 let r = if self.container.is_focused() && !self.layout.is_endless() {
1026 match event {
1027 ct_event!(keycode press ALT-PageUp) => {
1028 if self.prev_page() {
1029 FormOutcome::Page
1030 } else {
1031 FormOutcome::Continue
1032 }
1033 }
1034 ct_event!(keycode press ALT-PageDown) => {
1035 if self.next_page() {
1036 FormOutcome::Page
1037 } else {
1038 FormOutcome::Continue
1039 }
1040 }
1041 _ => FormOutcome::Continue,
1042 }
1043 } else {
1044 FormOutcome::Continue
1045 };
1046
1047 r.or_else(|| self.handle(event, MouseOnly))
1048 }
1049}
1050
1051impl<W> HandleEvent<crossterm::event::Event, MouseOnly, FormOutcome> for FormState<W>
1052where
1053 W: Eq + Hash + Clone,
1054{
1055 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> FormOutcome {
1056 if !self.layout.is_endless() {
1057 match event {
1058 ct_event!(mouse down Left for x,y) if self.prev_area.contains((*x, *y).into()) => {
1059 if self.prev_page() {
1060 FormOutcome::Page
1061 } else {
1062 FormOutcome::Unchanged
1063 }
1064 }
1065 ct_event!(mouse down Left for x,y) if self.next_area.contains((*x, *y).into()) => {
1066 if self.next_page() {
1067 FormOutcome::Page
1068 } else {
1069 FormOutcome::Unchanged
1070 }
1071 }
1072 ct_event!(scroll down for x,y) => {
1073 if self.area.contains((*x, *y).into()) {
1074 if self.next_page() {
1075 FormOutcome::Page
1076 } else {
1077 FormOutcome::Continue
1078 }
1079 } else {
1080 FormOutcome::Continue
1081 }
1082 }
1083 ct_event!(scroll up for x,y) => {
1084 if self.area.contains((*x, *y).into()) {
1085 if self.prev_page() {
1086 FormOutcome::Page
1087 } else {
1088 FormOutcome::Continue
1089 }
1090 } else {
1091 FormOutcome::Continue
1092 }
1093 }
1094 ct_event!(mouse any for m)
1095 if self.mouse.hover(&[self.prev_area, self.next_area], m) =>
1096 {
1097 FormOutcome::Changed
1098 }
1099 _ => FormOutcome::Continue,
1100 }
1101 } else {
1102 FormOutcome::Continue
1103 }
1104 }
1105}
1106
1107pub fn handle_events<W>(
1111 state: &mut FormState<W>,
1112 _focus: bool,
1113 event: &crossterm::event::Event,
1114) -> FormOutcome
1115where
1116 W: Eq + Clone + Hash,
1117{
1118 HandleEvent::handle(state, event, Regular)
1119}
1120
1121pub fn handle_mouse_events<W>(
1123 state: &mut FormState<W>,
1124 event: &crossterm::event::Event,
1125) -> FormOutcome
1126where
1127 W: Eq + Clone + Hash,
1128{
1129 HandleEvent::handle(state, event, MouseOnly)
1130}