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)]
668 #[allow(clippy::question_mark)]
669 pub fn render2<FN, WW, SS, R>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> Option<R>
670 where
671 FN: FnOnce() -> (WW, R),
672 WW: StatefulWidget<State = SS>,
673 SS: RelocatableState,
674 {
675 let Some(idx) = self.layout.try_index_of(widget) else {
676 return None;
677 };
678 if self.auto_label {
679 self.render_auto_label(idx);
680 }
681 let Some(widget_area) = self.locate_area(self.layout.widget(idx)) else {
682 state.relocate_hidden();
683 return None;
684 };
685 let (widget, remainder) = render_fn();
686 widget.render(widget_area, self.buffer, state);
687
688 Some(remainder)
689 }
690
691 #[inline(always)]
693 #[deprecated(since = "2.3.0", note = "use render_popup() for popups")]
694 pub fn render_opt<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
695 where
696 FN: FnOnce() -> Option<WW>,
697 WW: StatefulWidget<State = SS>,
698 SS: RelocatableState,
699 {
700 let Some(idx) = self.layout.try_index_of(widget) else {
701 return false;
702 };
703 if self.auto_label {
704 self.render_auto_label(idx);
705 }
706 let Some(widget_area) = self.locate_area(self.layout.widget(idx)) else {
707 state.relocate_hidden();
708 return false;
709 };
710 let widget = render_fn();
711 if let Some(widget) = widget {
712 widget.render(widget_area, self.buffer, state);
713 true
714 } else {
715 state.relocate_hidden();
716 false
717 }
718 }
719
720 #[inline(always)]
724 pub fn render_popup<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
725 where
726 FN: FnOnce() -> Option<WW>,
727 WW: StatefulWidget<State = SS>,
728 SS: RelocatableState,
729 {
730 let Some(idx) = self.layout.try_index_of(widget) else {
731 return false;
732 };
733 let Some(widget_area) = self.locate_area(self.layout.widget(idx)) else {
734 state.relocate_popup_hidden();
735 return false;
736 };
737 let widget = render_fn();
738 if let Some(widget) = widget {
739 widget.render(widget_area, self.buffer, state);
740 true
741 } else {
742 state.relocate_popup_hidden();
743 false
744 }
745 }
746
747 pub fn buffer(&mut self) -> &mut Buffer {
749 self.buffer
750 }
751
752 #[inline(always)]
754 fn render_auto_label(&mut self, idx: usize) -> bool {
755 let Some(label_area) = self.locate_area(self.layout.label(idx)) else {
756 return false;
757 };
758 let Some(label_str) = self.layout.try_label_str(idx) else {
759 return false;
760 };
761 let mut label = Line::from(label_str.as_ref());
762 if let Some(style) = self.label_style {
763 label = label.style(style)
764 };
765 if let Some(align) = self.label_alignment {
766 label = label.alignment(align);
767 }
768 label.render(label_area, self.buffer);
769
770 true
771 }
772
773 pub fn locate_widget(&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.widget(idx))
779 }
780
781 pub fn locate_label(&self, widget: W) -> Option<Rect> {
783 let Some(idx) = self.layout.try_index_of(widget) else {
784 return None;
785 };
786 self.locate_area(self.layout.label(idx))
787 }
788
789 #[inline]
791 pub fn locate_area(&self, area: Rect) -> Option<Rect> {
792 let area = self.page_area.intersection(area);
794 if self.page_area.intersects(area) {
795 let located = Rect::new(
796 area.x - self.page_area.x + self.widget_area.x,
797 area.y - self.page_area.y + self.widget_area.y,
798 area.width,
799 area.height,
800 );
801 let located = self.widget_area.intersection(located);
803 if self.widget_area.intersects(located) {
804 Some(located)
805 } else {
806 None
807 }
808 } else {
809 None
810 }
811 }
812}
813
814impl Default for FormStyle {
815 fn default() -> Self {
816 Self {
817 style: Default::default(),
818 label_style: Default::default(),
819 label_alignment: Default::default(),
820 navigation: Default::default(),
821 navigation_hover: Default::default(),
822 show_navigation: Default::default(),
823 title: Default::default(),
824 block: Default::default(),
825 border_style: Default::default(),
826 title_style: Default::default(),
827 next_page_mark: Default::default(),
828 prev_page_mark: Default::default(),
829 first_page_mark: Default::default(),
830 last_page_mark: Default::default(),
831 non_exhaustive: NonExhaustive,
832 }
833 }
834}
835
836impl<W> Default for FormState<W>
837where
838 W: Eq + Hash + Clone,
839{
840 fn default() -> Self {
841 Self {
842 layout: Default::default(),
843 area: Default::default(),
844 widget_area: Default::default(),
845 prev_area: Default::default(),
846 next_area: Default::default(),
847 page: 0,
848 container: Default::default(),
849 mouse: Default::default(),
850 non_exhaustive: NonExhaustive,
851 }
852 }
853}
854
855impl<W> HasFocus for FormState<W>
856where
857 W: Eq + Hash + Clone,
858{
859 fn build(&self, _builder: &mut FocusBuilder) {
860 }
862
863 fn focus(&self) -> FocusFlag {
864 self.container.clone()
865 }
866
867 fn area(&self) -> Rect {
868 self.area
869 }
870}
871
872impl<W> RelocatableState for FormState<W>
873where
874 W: Eq + Hash + Clone,
875{
876 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
877 self.area.relocate(shift, clip);
878 self.widget_area.relocate(shift, clip);
879 self.prev_area.relocate(shift, clip);
880 self.next_area.relocate(shift, clip);
881 }
882}
883
884impl<W> FormState<W>
885where
886 W: Eq + Hash + Clone,
887{
888 pub fn new() -> Self {
889 Self::default()
890 }
891
892 pub fn named(name: &str) -> Self {
893 let mut z = Self::default();
894 z.container = z.container.with_name(name);
895 z
896 }
897
898 pub fn clear(&mut self) {
900 self.layout = Default::default();
901 self.page = 0;
902 }
903
904 pub fn valid_layout(&self, size: Size) -> bool {
906 !self.layout.size_changed(size) && !self.layout.is_empty()
907 }
908
909 pub fn set_layout(&mut self, layout: GenericLayout<W>) {
911 self.layout = Rc::new(layout);
912 }
913
914 pub fn layout(&self) -> Rc<GenericLayout<W>> {
916 self.layout.clone()
917 }
918
919 pub fn show(&mut self, widget: W) {
923 let page = self.layout.page_of(widget).unwrap_or_default();
924 self.set_page(page);
925 }
926
927 pub fn page_count(&self) -> usize {
929 self.layout.page_count()
930 }
931
932 pub fn first(&self, page: usize) -> Option<W> {
934 self.layout.first(page)
935 }
936
937 pub fn page_of(&self, widget: W) -> Option<usize> {
939 self.layout.page_of(widget)
940 }
941
942 pub fn set_page(&mut self, page: usize) -> bool {
944 let old_page = self.page;
945 self.page = min(page, self.page_count().saturating_sub(1));
946 old_page != self.page
947 }
948
949 pub fn page(&self) -> usize {
951 self.page
952 }
953
954 pub fn next_page(&mut self) -> bool {
956 let old_page = self.page;
957
958 if self.page + 1 == self.page_count() {
959 } else if self.page + 1 > self.page_count() {
961 self.page = self.page_count().saturating_sub(1);
962 } else {
963 self.page += 1;
964 }
965
966 old_page != self.page
967 }
968
969 pub fn prev_page(&mut self) -> bool {
971 if self.page >= 1 {
972 self.page -= 1;
973 true
974 } else if self.page > 0 {
975 self.page = 0;
976 true
977 } else {
978 false
979 }
980 }
981}
982
983impl FormState<usize> {
984 pub fn focus_first(&self, focus: &Focus) -> bool {
987 if let Some(w) = self.first(self.page) {
988 focus.by_widget_id(w);
989 true
990 } else {
991 false
992 }
993 }
994
995 pub fn show_focused(&mut self, focus: &Focus) -> bool {
999 let Some(focused) = focus.focused() else {
1000 return false;
1001 };
1002 let focused = focused.widget_id();
1003 let page = self.layout.page_of(focused);
1004 if let Some(page) = page {
1005 self.set_page(page);
1006 true
1007 } else {
1008 false
1009 }
1010 }
1011}
1012
1013impl FormState<FocusFlag> {
1014 pub fn focus_first(&self, focus: &Focus) -> bool {
1016 if let Some(w) = self.first(self.page) {
1017 focus.focus(&w);
1018 true
1019 } else {
1020 false
1021 }
1022 }
1023
1024 pub fn show_focused(&mut self, focus: &Focus) -> bool {
1027 let Some(focused) = focus.focused() else {
1028 return false;
1029 };
1030 let page = self.layout.page_of(focused);
1031 if let Some(page) = page {
1032 self.set_page(page);
1033 true
1034 } else {
1035 false
1036 }
1037 }
1038}
1039
1040impl<W> HandleEvent<Event, Regular, FormOutcome> for FormState<W>
1041where
1042 W: Eq + Hash + Clone,
1043{
1044 fn handle(&mut self, event: &Event, _qualifier: Regular) -> FormOutcome {
1045 let r = if self.container.is_focused() && !self.layout.is_endless() {
1046 match event {
1047 ct_event!(keycode press ALT-PageUp) => {
1048 if self.prev_page() {
1049 FormOutcome::Page
1050 } else {
1051 FormOutcome::Continue
1052 }
1053 }
1054 ct_event!(keycode press ALT-PageDown) => {
1055 if self.next_page() {
1056 FormOutcome::Page
1057 } else {
1058 FormOutcome::Continue
1059 }
1060 }
1061 _ => FormOutcome::Continue,
1062 }
1063 } else {
1064 FormOutcome::Continue
1065 };
1066
1067 r.or_else(|| self.handle(event, MouseOnly))
1068 }
1069}
1070
1071impl<W> HandleEvent<Event, MouseOnly, FormOutcome> for FormState<W>
1072where
1073 W: Eq + Hash + Clone,
1074{
1075 fn handle(&mut self, event: &Event, _qualifier: MouseOnly) -> FormOutcome {
1076 if !self.layout.is_endless() {
1077 match event {
1078 ct_event!(mouse down Left for x,y) if self.prev_area.contains((*x, *y).into()) => {
1079 if self.prev_page() {
1080 FormOutcome::Page
1081 } else {
1082 FormOutcome::Unchanged
1083 }
1084 }
1085 ct_event!(mouse down Left for x,y) if self.next_area.contains((*x, *y).into()) => {
1086 if self.next_page() {
1087 FormOutcome::Page
1088 } else {
1089 FormOutcome::Unchanged
1090 }
1091 }
1092 ct_event!(scroll down for x,y) => {
1093 if self.area.contains((*x, *y).into()) {
1094 if self.next_page() {
1095 FormOutcome::Page
1096 } else {
1097 FormOutcome::Continue
1098 }
1099 } else {
1100 FormOutcome::Continue
1101 }
1102 }
1103 ct_event!(scroll up for x,y) => {
1104 if self.area.contains((*x, *y).into()) {
1105 if self.prev_page() {
1106 FormOutcome::Page
1107 } else {
1108 FormOutcome::Continue
1109 }
1110 } else {
1111 FormOutcome::Continue
1112 }
1113 }
1114 ct_event!(mouse any for m)
1115 if self.mouse.hover(&[self.prev_area, self.next_area], m) =>
1116 {
1117 FormOutcome::Changed
1118 }
1119 _ => FormOutcome::Continue,
1120 }
1121 } else {
1122 FormOutcome::Continue
1123 }
1124 }
1125}
1126
1127pub fn handle_events<W>(state: &mut FormState<W>, _focus: bool, event: &Event) -> FormOutcome
1131where
1132 W: Eq + Clone + Hash,
1133{
1134 HandleEvent::handle(state, event, Regular)
1135}
1136
1137pub fn handle_mouse_events<W>(state: &mut FormState<W>, event: &Event) -> FormOutcome
1139where
1140 W: Eq + Clone + Hash,
1141{
1142 HandleEvent::handle(state, event, MouseOnly)
1143}