1use crate::_private::NonExhaustive;
84use crate::layout::GenericLayout;
85use crate::util::revert_style;
86use event::FormOutcome;
87use rat_event::util::MouseFlagsN;
88use rat_event::{ConsumedEvent, HandleEvent, MouseOnly, Regular, ct_event};
89use rat_focus::{Focus, FocusBuilder, FocusFlag, HasFocus};
90use rat_reloc::RelocatableState;
91use ratatui::buffer::Buffer;
92use ratatui::layout::{Alignment, Rect, Size};
93use ratatui::prelude::BlockExt;
94use ratatui::style::Style;
95use ratatui::text::{Line, Span};
96use ratatui::widgets::{Block, StatefulWidget, Widget};
97use std::borrow::Cow;
98use std::cmp::min;
99use std::hash::Hash;
100use std::rc::Rc;
101use unicode_display_width::width as unicode_width;
102
103#[derive(Debug, Clone)]
113pub struct Form<'a, W>
114where
115 W: Eq + Hash + Clone,
116{
117 layout: Option<GenericLayout<W>>,
118
119 style: Style,
120 block: Option<Block<'a>>,
121 nav_style: Option<Style>,
122 title_style: Option<Style>,
123 next_page: &'a str,
124 prev_page: &'a str,
125 first_page: &'a str,
126 last_page: &'a str,
127
128 auto_label: bool,
129 label_style: Option<Style>,
130 label_alignment: Option<Alignment>,
131}
132
133#[derive(Debug)]
138#[must_use]
139pub struct FormBuffer<'a, 'b, W>
140where
141 W: Eq + Hash + Clone,
142{
143 layout: Rc<GenericLayout<W>>,
144
145 area: Rect,
146 page_area: Rect,
147 widget_area: Rect,
148 buffer: &'b mut Buffer,
149
150 style: Style,
151 block: Option<Block<'a>>,
152 nav_style: Option<Style>,
153 title_style: Option<Style>,
154 next_page: &'a str,
155 prev_page: &'a str,
156 first_page: &'a str,
157 last_page: &'a str,
158
159 auto_label: bool,
160 label_style: Option<Style>,
161 label_alignment: Option<Alignment>,
162
163 destruct: bool,
164}
165
166#[derive(Debug, Clone)]
168pub struct FormStyle {
169 pub style: Style,
170 pub label_style: Option<Style>,
171 pub label_alignment: Option<Alignment>,
172 pub navigation: Option<Style>,
173 pub title: Option<Style>,
174 pub block: Option<Block<'static>>,
175 pub next_page_mark: Option<&'static str>,
176 pub prev_page_mark: Option<&'static str>,
177 pub first_page_mark: Option<&'static str>,
178 pub last_page_mark: Option<&'static str>,
179 pub non_exhaustive: NonExhaustive,
180}
181
182#[derive(Debug, Clone)]
184pub struct FormState<W>
185where
186 W: Eq + Hash + Clone,
187{
188 pub layout: Rc<GenericLayout<W>>,
191 pub area: Rect,
194 pub widget_area: Rect,
197 pub prev_area: Rect,
200 pub next_area: Rect,
203
204 pub page: usize,
205
206 pub container: FocusFlag,
209
210 pub mouse: MouseFlagsN,
212
213 pub non_exhaustive: NonExhaustive,
215}
216
217pub(crate) mod event {
218 use rat_event::{ConsumedEvent, Outcome};
219
220 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
222 pub enum FormOutcome {
223 Continue,
225 Unchanged,
228 Changed,
233 Page,
235 }
236
237 impl ConsumedEvent for FormOutcome {
238 fn is_consumed(&self) -> bool {
239 *self != FormOutcome::Continue
240 }
241 }
242
243 impl From<Outcome> for FormOutcome {
244 fn from(value: Outcome) -> Self {
245 match value {
246 Outcome::Continue => FormOutcome::Continue,
247 Outcome::Unchanged => FormOutcome::Unchanged,
248 Outcome::Changed => FormOutcome::Changed,
249 }
250 }
251 }
252
253 impl From<FormOutcome> for Outcome {
254 fn from(value: FormOutcome) -> Self {
255 match value {
256 FormOutcome::Continue => Outcome::Continue,
257 FormOutcome::Unchanged => Outcome::Unchanged,
258 FormOutcome::Changed => Outcome::Changed,
259 FormOutcome::Page => Outcome::Changed,
260 }
261 }
262 }
263}
264
265impl<W> Default for Form<'_, W>
266where
267 W: Eq + Hash + Clone,
268{
269 fn default() -> Self {
270 Self {
271 layout: Default::default(),
272 style: Default::default(),
273 block: Default::default(),
274 nav_style: Default::default(),
275 title_style: Default::default(),
276 next_page: ">>>",
277 prev_page: "<<<",
278 first_page: " [ ",
279 last_page: " ] ",
280 auto_label: true,
281 label_style: Default::default(),
282 label_alignment: Default::default(),
283 }
284 }
285}
286
287impl<'a, W> Form<'a, W>
288where
289 W: Eq + Hash + Clone,
290{
291 pub fn new() -> Self {
293 Self::default()
294 }
295
296 pub fn layout(mut self, layout: GenericLayout<W>) -> Self {
299 self.layout = Some(layout);
300 self
301 }
302
303 pub fn auto_label(mut self, auto: bool) -> Self {
307 self.auto_label = auto;
308 self
309 }
310
311 pub fn style(mut self, style: Style) -> Self {
313 self.style = style;
314 self.block = self.block.map(|v| v.style(style));
315 self
316 }
317
318 pub fn nav_style(mut self, nav_style: Style) -> Self {
320 self.nav_style = Some(nav_style);
321 self
322 }
323
324 pub fn title_style(mut self, title_style: Style) -> Self {
326 self.title_style = Some(title_style);
327 self
328 }
329
330 pub fn block(mut self, block: Block<'a>) -> Self {
332 self.block = Some(block.style(self.style));
333 self
334 }
335
336 pub fn next_page_mark(mut self, txt: &'a str) -> Self {
337 self.next_page = txt;
338 self
339 }
340
341 pub fn prev_page_mark(mut self, txt: &'a str) -> Self {
342 self.prev_page = txt;
343 self
344 }
345
346 pub fn first_page_mark(mut self, txt: &'a str) -> Self {
347 self.first_page = txt;
348 self
349 }
350
351 pub fn last_page_mark(mut self, txt: &'a str) -> Self {
352 self.last_page = txt;
353 self
354 }
355
356 pub fn label_style(mut self, style: Style) -> Self {
358 self.label_style = Some(style);
359 self
360 }
361
362 pub fn label_alignment(mut self, alignment: Alignment) -> Self {
364 self.label_alignment = Some(alignment);
365 self
366 }
367
368 pub fn styles(mut self, styles: FormStyle) -> Self {
370 self.style = styles.style;
371 if let Some(nav) = styles.navigation {
372 self.nav_style = Some(nav);
373 }
374 if let Some(title) = styles.title {
375 self.title_style = Some(title);
376 }
377 if let Some(block) = styles.block {
378 self.block = Some(block);
379 }
380 if let Some(txt) = styles.next_page_mark {
381 self.next_page = txt;
382 }
383 if let Some(txt) = styles.prev_page_mark {
384 self.prev_page = txt;
385 }
386 if let Some(txt) = styles.first_page_mark {
387 self.first_page = txt;
388 }
389 if let Some(txt) = styles.last_page_mark {
390 self.last_page = txt;
391 }
392 self.block = self.block.map(|v| v.style(styles.style));
393
394 if let Some(label) = styles.label_style {
395 self.label_style = Some(label);
396 }
397 if let Some(alignment) = styles.label_alignment {
398 self.label_alignment = Some(alignment);
399 }
400
401 self
402 }
403
404 pub fn layout_size(&self, area: Rect) -> Size {
406 self.block.inner_if_some(area).as_size()
407 }
408
409 pub fn layout_area(&self, area: Rect) -> Rect {
411 if let Some(block) = &self.block {
412 block.inner(area)
413 } else {
414 area
415 }
416 }
417
418 pub fn into_buffer<'b, 's>(
421 mut self,
422 area: Rect,
423 buf: &'b mut Buffer,
424 state: &'s mut FormState<W>,
425 ) -> FormBuffer<'a, 'b, W> {
426 state.area = area;
427 state.widget_area = self.layout_area(area);
428
429 if let Some(layout) = self.layout.take() {
430 state.layout = Rc::new(layout);
431 }
432
433 let page_size = state.layout.page_size();
434 assert!(page_size.height < u16::MAX || page_size.height == u16::MAX && state.page == 0);
435 let page_area = Rect::new(
436 0,
437 (state.page as u16).saturating_mul(page_size.height),
438 page_size.width,
439 page_size.height,
440 );
441
442 FormBuffer {
443 layout: state.layout.clone(),
444 area,
445 page_area,
446 widget_area: state.widget_area,
447 buffer: buf,
448 style: self.style,
449 block: self.block,
450 nav_style: self.nav_style,
451 title_style: self.title_style,
452 next_page: self.next_page,
453 prev_page: self.prev_page,
454 first_page: self.first_page,
455 last_page: self.last_page,
456 auto_label: true,
457 label_style: self.label_style,
458 label_alignment: self.label_alignment,
459 destruct: false,
460 }
461 }
462}
463
464impl<'a, 'b, W> Drop for FormBuffer<'a, 'b, W>
465where
466 W: Eq + Hash + Clone,
467{
468 fn drop(&mut self) {
469 if !self.destruct {
470 panic!("FormBuffer must be used by into_widget()");
471 }
472 }
473}
474
475impl<'a, 'b, W> FormBuffer<'a, 'b, W>
476where
477 W: Eq + Hash + Clone,
478{
479 pub fn is_visible(&self, widget: W) -> bool {
481 if let Some(idx) = self.layout.try_index_of(widget) {
482 self.locate_area(self.layout.widget(idx)).is_some()
483 } else {
484 false
485 }
486 }
487
488 fn render_block(&mut self) {
490 for (idx, block_area) in self.layout.block_area_iter().enumerate() {
491 if let Some(block_area) = self.locate_area(*block_area) {
492 if let Some(block) = self.layout.block(idx) {
493 block.render(block_area, self.buffer);
494 }
495 }
496 }
497 }
498
499 #[inline(always)]
501 pub fn render_label<FN>(&mut self, widget: W, render_fn: FN) -> bool
502 where
503 FN: FnOnce(&Cow<'static, str>, Rect, &mut Buffer),
504 {
505 let Some(idx) = self.layout.try_index_of(widget) else {
506 return false;
507 };
508 let Some(label_area) = self.locate_label(idx) else {
509 return false;
510 };
511 if let Some(label_str) = self.layout.try_label_str(idx) {
512 render_fn(label_str, label_area, self.buffer);
513 } else {
514 render_fn(&Cow::default(), label_area, self.buffer);
515 }
516 true
517 }
518
519 #[inline(always)]
521 pub fn render_widget<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
522 where
523 FN: FnOnce() -> WW,
524 WW: Widget,
525 {
526 let Some(idx) = self.layout.try_index_of(widget) else {
527 return false;
528 };
529 if self.auto_label {
530 self.render_auto_label(idx);
531 }
532 let Some(widget_area) = self.locate_widget(idx) else {
533 return false;
534 };
535 render_fn().render(widget_area, self.buffer);
536 true
537 }
538
539 #[inline(always)]
541 pub fn render_opt<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
542 where
543 FN: FnOnce() -> Option<WW>,
544 WW: StatefulWidget<State = SS>,
545 SS: RelocatableState,
546 {
547 let Some(idx) = self.layout.try_index_of(widget) else {
548 return false;
549 };
550 if self.auto_label {
551 self.render_auto_label(idx);
552 }
553 let Some(widget_area) = self.locate_widget(idx) else {
554 self.hidden(state);
555 return false;
556 };
557 let widget = render_fn();
558 if let Some(widget) = widget {
559 widget.render(widget_area, self.buffer, state);
560 true
561 } else {
562 self.hidden(state);
563 false
564 }
565 }
566
567 #[inline(always)]
569 pub fn render<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
570 where
571 FN: FnOnce() -> WW,
572 WW: StatefulWidget<State = SS>,
573 SS: RelocatableState,
574 {
575 let Some(idx) = self.layout.try_index_of(widget) else {
576 return false;
577 };
578 if self.auto_label {
579 self.render_auto_label(idx);
580 }
581 let Some(widget_area) = self.locate_widget(idx) else {
582 self.hidden(state);
583 return false;
584 };
585 let widget = render_fn();
586 widget.render(widget_area, self.buffer, state);
587 true
588 }
589
590 #[inline(always)]
594 #[allow(clippy::question_mark)]
595 pub fn render2<FN, WW, SS, R>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> Option<R>
596 where
597 FN: FnOnce() -> (WW, R),
598 WW: StatefulWidget<State = SS>,
599 SS: RelocatableState,
600 {
601 let Some(idx) = self.layout.try_index_of(widget) else {
602 return None;
603 };
604 if self.auto_label {
605 self.render_auto_label(idx);
606 }
607 let Some(widget_area) = self.locate_widget(idx) else {
608 self.hidden(state);
609 return None;
610 };
611 let (widget, remainder) = render_fn();
612 widget.render(widget_area, self.buffer, state);
613
614 Some(remainder)
615 }
616
617 pub fn buffer(&mut self) -> &mut Buffer {
619 self.buffer
620 }
621
622 #[allow(deprecated)]
626 pub fn finish(mut self, state: &mut FormState<W>) {
627 self.render_block();
628 self.destruct = true;
629
630 let page_count = state.layout.page_count();
631
632 if !state.layout.is_endless() {
633 if state.page > 0 {
634 state.prev_area = Rect::new(
635 self.area.x,
636 self.area.y,
637 unicode_width(self.prev_page) as u16,
638 1,
639 );
640 } else {
641 state.prev_area = Rect::new(
642 self.area.x,
643 self.area.y,
644 unicode_width(self.first_page) as u16,
645 1,
646 );
647 }
648 if (state.page + 1) < page_count {
649 let p = unicode_width(self.next_page) as u16;
650 state.next_area = Rect::new(
651 self.area.x + self.area.width.saturating_sub(p),
652 self.area.y,
653 p,
654 1,
655 );
656 } else {
657 let p = unicode_width(self.last_page) as u16;
658 state.next_area = Rect::new(
659 self.area.x + self.area.width.saturating_sub(p),
660 self.area.y,
661 p,
662 1,
663 );
664 }
665 } else {
666 state.prev_area = Default::default();
667 state.next_area = Default::default();
668 }
669
670 let block = if page_count > 1 {
671 let title = format!(" {}/{} ", state.page + 1, page_count);
672 let block = self
673 .block
674 .take()
675 .unwrap_or_else(|| Block::new().style(self.style))
676 .title_bottom(title)
677 .title_alignment(Alignment::Right);
678 if let Some(title_style) = self.title_style {
679 block.title_style(title_style)
680 } else {
681 block
682 }
683 } else {
684 self.block
685 .take()
686 .unwrap_or_else(|| Block::new().style(self.style))
687 };
688 block.render(self.area, self.buffer);
689
690 if !state.layout.is_endless() {
691 let nav_style = self.nav_style.unwrap_or(self.style);
693 if matches!(state.mouse.hover.get(), Some(0)) {
694 self.buffer
695 .set_style(state.prev_area, revert_style(nav_style));
696 } else {
697 self.buffer.set_style(state.prev_area, nav_style);
698 }
699 if state.page > 0 {
700 Span::from(self.prev_page).render(state.prev_area, self.buffer);
701 } else {
702 Span::from(self.first_page).render(state.prev_area, self.buffer);
703 }
704 if matches!(state.mouse.hover.get(), Some(1)) {
705 self.buffer
706 .set_style(state.next_area, revert_style(nav_style));
707 } else {
708 self.buffer.set_style(state.next_area, nav_style);
709 }
710 if (state.page + 1) < page_count {
711 Span::from(self.next_page).render(state.next_area, self.buffer);
712 } else {
713 Span::from(self.last_page).render(state.next_area, self.buffer);
714 }
715 }
716 }
717
718 #[inline(always)]
720 fn render_auto_label(&mut self, idx: usize) -> bool {
721 let Some(label_area) = self.locate_label(idx) else {
722 return false;
723 };
724 let Some(label_str) = self.layout.try_label_str(idx) else {
725 return false;
726 };
727 let mut label = Line::from(label_str.as_ref());
728 if let Some(style) = self.label_style {
729 label = label.style(style)
730 };
731 if let Some(align) = self.label_alignment {
732 label = label.alignment(align);
733 }
734 label.render(label_area, self.buffer);
735
736 true
737 }
738
739 #[inline]
743 fn locate_widget(&self, idx: usize) -> Option<Rect> {
744 self.locate_area(self.layout.widget(idx))
745 }
746
747 #[inline]
751 fn locate_label(&self, idx: usize) -> Option<Rect> {
752 self.locate_area(self.layout.label(idx))
753 }
754
755 #[inline]
757 fn locate_area(&self, area: Rect) -> Option<Rect> {
758 let area = self.page_area.intersection(area);
760 if !area.is_empty() {
761 let located = Rect::new(
762 area.x - self.page_area.x + self.widget_area.x,
763 area.y - self.page_area.y + self.widget_area.y,
764 area.width,
765 area.height,
766 );
767 let located = self.widget_area.intersection(located);
769 if !located.is_empty() {
770 Some(located)
771 } else {
772 None
773 }
774 } else {
775 None
776 }
777 }
778
779 fn hidden<S>(&self, state: &mut S)
782 where
783 S: RelocatableState,
784 {
785 state.relocate((0, 0), Rect::default())
786 }
787}
788
789impl Default for FormStyle {
790 fn default() -> Self {
791 Self {
792 style: Default::default(),
793 label_style: None,
794 label_alignment: None,
795 navigation: None,
796 title: None,
797 block: None,
798 next_page_mark: None,
799 prev_page_mark: None,
800 first_page_mark: None,
801 last_page_mark: None,
802 non_exhaustive: NonExhaustive,
803 }
804 }
805}
806
807impl<W> Default for FormState<W>
808where
809 W: Eq + Hash + Clone,
810{
811 fn default() -> Self {
812 Self {
813 layout: Default::default(),
814 area: Default::default(),
815 widget_area: Default::default(),
816 prev_area: Default::default(),
817 next_area: Default::default(),
818 page: 0,
819 container: Default::default(),
820 mouse: Default::default(),
821 non_exhaustive: NonExhaustive,
822 }
823 }
824}
825
826impl<W> HasFocus for FormState<W>
827where
828 W: Eq + Hash + Clone,
829{
830 fn build(&self, _builder: &mut FocusBuilder) {
831 }
833
834 fn focus(&self) -> FocusFlag {
835 self.container.clone()
836 }
837
838 fn area(&self) -> Rect {
839 self.area
840 }
841}
842
843impl<W> FormState<W>
844where
845 W: Eq + Hash + Clone,
846{
847 pub fn new() -> Self {
848 Self::default()
849 }
850
851 pub fn clear(&mut self) {
853 self.layout = Default::default();
854 self.page = 0;
855 }
856
857 pub fn valid_layout(&self, size: Size) -> bool {
859 !self.layout.size_changed(size) && !self.layout.is_empty()
860 }
861
862 pub fn set_layout(&mut self, layout: GenericLayout<W>) {
864 self.layout = Rc::new(layout);
865 }
866
867 pub fn layout(&self) -> Rc<GenericLayout<W>> {
869 self.layout.clone()
870 }
871
872 pub fn show(&mut self, widget: W) {
876 let page = self.layout.page_of(widget).unwrap_or_default();
877 self.set_page(page);
878 }
879
880 pub fn page_count(&self) -> usize {
882 self.layout.page_count()
883 }
884
885 pub fn first(&self, page: usize) -> Option<W> {
887 self.layout.first(page)
888 }
889
890 pub fn page_of(&self, widget: W) -> Option<usize> {
892 self.layout.page_of(widget)
893 }
894
895 pub fn set_page(&mut self, page: usize) -> bool {
897 let old_page = self.page;
898 self.page = min(page, self.page_count().saturating_sub(1));
899 old_page != self.page
900 }
901
902 pub fn page(&self) -> usize {
904 self.page
905 }
906
907 pub fn next_page(&mut self) -> bool {
909 let old_page = self.page;
910
911 if self.page + 1 == self.page_count() {
912 } else if self.page + 1 > self.page_count() {
914 self.page = self.page_count().saturating_sub(1);
915 } else {
916 self.page += 1;
917 }
918
919 old_page != self.page
920 }
921
922 pub fn prev_page(&mut self) -> bool {
924 if self.page >= 1 {
925 self.page -= 1;
926 true
927 } else if self.page > 0 {
928 self.page = 0;
929 true
930 } else {
931 false
932 }
933 }
934}
935
936impl FormState<usize> {
937 pub fn focus_first(&self, focus: &Focus) -> bool {
940 if let Some(w) = self.first(self.page) {
941 focus.by_widget_id(w);
942 true
943 } else {
944 false
945 }
946 }
947
948 pub fn show_focused(&mut self, focus: &Focus) -> bool {
952 let Some(focused) = focus.focused() else {
953 return false;
954 };
955 let focused = focused.widget_id();
956 let page = self.layout.page_of(focused);
957 if let Some(page) = page {
958 self.set_page(page);
959 true
960 } else {
961 false
962 }
963 }
964}
965
966impl FormState<FocusFlag> {
967 pub fn focus_first(&self, focus: &Focus) -> bool {
969 if let Some(w) = self.first(self.page) {
970 focus.focus(&w);
971 true
972 } else {
973 false
974 }
975 }
976
977 pub fn show_focused(&mut self, focus: &Focus) -> bool {
980 let Some(focused) = focus.focused() else {
981 return false;
982 };
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<W> HandleEvent<crossterm::event::Event, Regular, FormOutcome> for FormState<W>
994where
995 W: Eq + Hash + Clone,
996{
997 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> FormOutcome {
998 let r = if self.container.is_focused() && !self.layout.is_endless() {
999 match event {
1000 ct_event!(keycode press ALT-PageUp) => {
1001 if self.prev_page() {
1002 FormOutcome::Page
1003 } else {
1004 FormOutcome::Continue
1005 }
1006 }
1007 ct_event!(keycode press ALT-PageDown) => {
1008 if self.next_page() {
1009 FormOutcome::Page
1010 } else {
1011 FormOutcome::Continue
1012 }
1013 }
1014 _ => FormOutcome::Continue,
1015 }
1016 } else {
1017 FormOutcome::Continue
1018 };
1019
1020 r.or_else(|| self.handle(event, MouseOnly))
1021 }
1022}
1023
1024impl<W> HandleEvent<crossterm::event::Event, MouseOnly, FormOutcome> for FormState<W>
1025where
1026 W: Eq + Hash + Clone,
1027{
1028 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> FormOutcome {
1029 if !self.layout.is_endless() {
1030 match event {
1031 ct_event!(mouse down Left for x,y) if self.prev_area.contains((*x, *y).into()) => {
1032 if self.prev_page() {
1033 FormOutcome::Page
1034 } else {
1035 FormOutcome::Unchanged
1036 }
1037 }
1038 ct_event!(mouse down Left for x,y) if self.next_area.contains((*x, *y).into()) => {
1039 if self.next_page() {
1040 FormOutcome::Page
1041 } else {
1042 FormOutcome::Unchanged
1043 }
1044 }
1045 ct_event!(scroll down for x,y) => {
1046 if self.area.contains((*x, *y).into()) {
1047 if self.next_page() {
1048 FormOutcome::Page
1049 } else {
1050 FormOutcome::Continue
1051 }
1052 } else {
1053 FormOutcome::Continue
1054 }
1055 }
1056 ct_event!(scroll up for x,y) => {
1057 if self.area.contains((*x, *y).into()) {
1058 if self.prev_page() {
1059 FormOutcome::Page
1060 } else {
1061 FormOutcome::Continue
1062 }
1063 } else {
1064 FormOutcome::Continue
1065 }
1066 }
1067 ct_event!(mouse any for m)
1068 if self.mouse.hover(&[self.prev_area, self.next_area], m) =>
1069 {
1070 FormOutcome::Changed
1071 }
1072 _ => FormOutcome::Continue,
1073 }
1074 } else {
1075 FormOutcome::Continue
1076 }
1077 }
1078}