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