rat_widget/
form.rs

1//! Render widgets based on a [GenericLayout].
2//!
3//! This is useful, if you have more than 3 input widgets and
4//! their accompanying labels in a row.
5//! See [LayoutForm](crate::layout::LayoutForm) for details on
6//! the layout.
7//!
8//! If the layout is split into multiple pages this also
9//! renders the page-navigation and handles scrolling
10//! through the pages.
11//!
12//! ```
13//! # use ratatui::buffer::Buffer;
14//! # use ratatui::layout::{Flex, Rect};
15//! # use ratatui::text::Span;
16//! # use ratatui::widgets::{Padding, Widget, StatefulWidget, Block};
17//! # use rat_focus::{FocusFlag, HasFocus};
18//! # use rat_text::text_input::{TextInput, TextInputState};
19//! # use rat_widget::layout::{FormLabel, FormWidget, GenericLayout, LayoutForm};
20//! use rat_widget::form::{Form, FormState};
21//! #
22//! # struct State {
23//! #     form: FormState<usize>,
24//! #     text1: TextInputState,
25//! #     text2: TextInputState,
26//! #     text3: TextInputState,
27//! # }
28//! #
29//! # let mut state = State {form: Default::default(),text1: Default::default(),text2: Default::default(),text3: Default::default()};
30//! # let area = Rect::default();
31//! # let mut buf = Buffer::empty(Rect::default());
32//! # let buf = &mut buf;
33//!
34//! // create + configure the form.
35//! let form = Form::new()
36//!     .block(Block::bordered());
37//!
38//! let layout_size = form.layout_size(area);
39//! if !state.form.valid_layout(layout_size) {
40//!     // define the layout
41//!     let mut form_layout = LayoutForm::new()
42//!             .spacing(1)
43//!             .flex(Flex::Legacy)
44//!             .line_spacing(1)
45//!             .min_label(10);
46//!
47//!     use rat_widget::layout::{FormLabel as L, FormWidget as W};
48//!
49//!     // single row
50//!     form_layout.widget(state.text1.id(), L::Str("Text 1"), W::Width(22));
51//!     // stretch to the form-width, preferred with 15, 1 row high.
52//!     form_layout.widget(state.text2.id(), L::Str("Text 2"), W::StretchX(15, 1));
53//!     // stretch to the form-width and fill vertically.
54//!     // preferred width is 15 3 rows high.
55//!     form_layout.widget(state.text3.id(), L::Str("Text 3"), W::StretchXY(15, 3));
56//!
57//!     // calculate the layout and set it.
58//!     state.form.set_layout(form_layout.build_paged(area.as_size()));
59//!  }
60//!
61//!  // create a FormBuffer from the parameters that will render
62//!  // the individual widgets.
63//!  let mut form = form
64//!     .into_buffer(area, buf, &mut state.form);
65//!
66//!  form.render(state.text1.id(),
67//!     || TextInput::new(),
68//!     &mut state.text1
69//!  );
70//!  form.render(state.text2.id(),
71//!     || TextInput::new(),
72//!     &mut state.text2
73//!  );
74//!  form.render(state.text3.id(),
75//!     || TextInput::new(),
76//!     &mut state.text3
77//!  );
78//!
79//! ```
80use crate::_private::NonExhaustive;
81use crate::layout::GenericLayout;
82use crate::util::revert_style;
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::buffer::Buffer;
89use ratatui::layout::{Alignment, Rect, Size};
90use ratatui::prelude::BlockExt;
91use ratatui::style::Style;
92use ratatui::text::{Line, Span};
93use ratatui::widgets::{Block, StatefulWidget, Widget};
94use std::borrow::Cow;
95use std::cmp::min;
96use std::hash::Hash;
97use std::rc::Rc;
98use unicode_display_width::width as unicode_width;
99
100/// Renders other widgets using a [GenericLayout].
101/// It doesn't scroll, instead it uses pages.
102///
103/// `Form` is the first stage and defines the layout and styling.
104/// At the end call [into_buffer](Form::into_buffer) to create the
105/// [FormBuffer] that allows you to render your widgets.
106///
107/// When you are done with that, call [finish](FormBuffer::finish)
108/// render layout Blocks and any navigation.
109#[derive(Debug, Clone)]
110pub struct Form<'a, W>
111where
112    W: Eq + Hash + Clone,
113{
114    layout: Option<GenericLayout<W>>,
115
116    style: Style,
117    block: Option<Block<'a>>,
118    nav_style: Option<Style>,
119    title_style: Option<Style>,
120    navigation: bool,
121    next_page: &'a str,
122    prev_page: &'a str,
123    first_page: &'a str,
124    last_page: &'a str,
125
126    auto_label: bool,
127    label_style: Option<Style>,
128    label_alignment: Option<Alignment>,
129}
130
131/// Second stage of form rendering.
132///
133/// This can render the widgets that make up the form content.
134///
135#[derive(Debug)]
136#[must_use]
137pub struct FormBuffer<'b, W>
138where
139    W: Eq + Hash + Clone,
140{
141    layout: Rc<GenericLayout<W>>,
142
143    page_area: Rect,
144    widget_area: Rect,
145    buffer: &'b mut Buffer,
146
147    auto_label: bool,
148    label_style: Option<Style>,
149    label_alignment: Option<Alignment>,
150}
151
152/// All styles for a form.
153#[derive(Debug, Clone)]
154pub struct FormStyle {
155    /// base style
156    pub style: Style,
157    /// label style.
158    pub label_style: Option<Style>,
159    /// label alignment.
160    pub label_alignment: Option<Alignment>,
161    /// navigation style.
162    pub navigation: Option<Style>,
163    /// title style.
164    pub title: Option<Style>,
165    /// Block.
166    pub block: Option<Block<'static>>,
167    /// Navigation mark.
168    pub next_page_mark: Option<&'static str>,
169    /// Navigation mark.
170    pub prev_page_mark: Option<&'static str>,
171    /// Navigation mark.
172    pub first_page_mark: Option<&'static str>,
173    /// Navigation mark.
174    pub last_page_mark: Option<&'static str>,
175
176    pub non_exhaustive: NonExhaustive,
177}
178
179/// Widget state.
180#[derive(Debug, Clone)]
181pub struct FormState<W>
182where
183    W: Eq + Hash + Clone,
184{
185    /// Page layout
186    /// __read+write__ might be overwritten from widget.
187    pub layout: Rc<GenericLayout<W>>,
188    /// Full area for the widget.
189    /// __read only__ renewed for each render.
190    pub area: Rect,
191    /// Area for the content.
192    /// __read only__ renewed for each render.
193    pub widget_area: Rect,
194    /// Area for prev-page indicator.
195    /// __read only__ renewed with each render.
196    pub prev_area: Rect,
197    /// Area for next-page indicator.
198    /// __read only__ renewed with each render.
199    pub next_area: Rect,
200
201    pub page: usize,
202
203    /// This widget has no focus of its own, but this flag
204    /// can be used to set a container state.
205    pub container: FocusFlag,
206
207    /// Mouse
208    pub mouse: MouseFlagsN,
209
210    /// Only construct with `..Default::default()`.
211    pub non_exhaustive: NonExhaustive,
212}
213
214pub(crate) mod event {
215    use rat_event::{ConsumedEvent, Outcome};
216
217    /// Result of event handling.
218    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
219    pub enum FormOutcome {
220        /// The given event has not been used at all.
221        Continue,
222        /// The event has been recognized, but the result was nil.
223        /// Further processing for this event may stop.
224        Unchanged,
225        /// The event has been recognized and there is some change
226        /// due to it.
227        /// Further processing for this event may stop.
228        /// Rendering the ui is advised.
229        Changed,
230        /// Displayed page changed.
231        Page,
232    }
233
234    impl ConsumedEvent for FormOutcome {
235        fn is_consumed(&self) -> bool {
236            *self != FormOutcome::Continue
237        }
238    }
239
240    impl From<Outcome> for FormOutcome {
241        fn from(value: Outcome) -> Self {
242            match value {
243                Outcome::Continue => FormOutcome::Continue,
244                Outcome::Unchanged => FormOutcome::Unchanged,
245                Outcome::Changed => FormOutcome::Changed,
246            }
247        }
248    }
249
250    impl From<FormOutcome> for Outcome {
251        fn from(value: FormOutcome) -> Self {
252            match value {
253                FormOutcome::Continue => Outcome::Continue,
254                FormOutcome::Unchanged => Outcome::Unchanged,
255                FormOutcome::Changed => Outcome::Changed,
256                FormOutcome::Page => Outcome::Changed,
257            }
258        }
259    }
260}
261
262impl<W> Default for Form<'_, W>
263where
264    W: Eq + Hash + Clone,
265{
266    fn default() -> Self {
267        Self {
268            layout: Default::default(),
269            style: Default::default(),
270            block: Default::default(),
271            nav_style: Default::default(),
272            title_style: Default::default(),
273            navigation: true,
274            next_page: ">>>",
275            prev_page: "<<<",
276            first_page: " [ ",
277            last_page: " ] ",
278            auto_label: true,
279            label_style: Default::default(),
280            label_alignment: Default::default(),
281        }
282    }
283}
284
285impl<'a, W> Form<'a, W>
286where
287    W: Eq + Hash + Clone,
288{
289    /// New SinglePage.
290    pub fn new() -> Self {
291        Self::default()
292    }
293
294    /// Set the layout. If no layout is set here the layout is
295    /// taken from the state.
296    pub fn layout(mut self, layout: GenericLayout<W>) -> Self {
297        self.layout = Some(layout);
298        self
299    }
300
301    /// Render the label automatically when rendering the widget.
302    ///
303    /// Default: true
304    pub fn auto_label(mut self, auto: bool) -> Self {
305        self.auto_label = auto;
306        self
307    }
308
309    /// Base style.
310    pub fn style(mut self, style: Style) -> Self {
311        self.style = style;
312        self.block = self.block.map(|v| v.style(style));
313        self
314    }
315
316    /// Style for navigation.
317    pub fn nav_style(mut self, nav_style: Style) -> Self {
318        self.nav_style = Some(nav_style);
319        self
320    }
321
322    /// Show navigation?
323    pub fn show_navigation(mut self, show: bool) -> Self {
324        self.navigation = show;
325        self
326    }
327
328    /// Style for the title.
329    pub fn title_style(mut self, title_style: Style) -> Self {
330        self.title_style = Some(title_style);
331        self
332    }
333
334    /// Block for border
335    pub fn block(mut self, block: Block<'a>) -> Self {
336        self.block = Some(block.style(self.style));
337        self
338    }
339
340    pub fn next_page_mark(mut self, txt: &'a str) -> Self {
341        self.next_page = txt;
342        self
343    }
344
345    pub fn prev_page_mark(mut self, txt: &'a str) -> Self {
346        self.prev_page = txt;
347        self
348    }
349
350    pub fn first_page_mark(mut self, txt: &'a str) -> Self {
351        self.first_page = txt;
352        self
353    }
354
355    pub fn last_page_mark(mut self, txt: &'a str) -> Self {
356        self.last_page = txt;
357        self
358    }
359
360    /// Style for auto-labels.
361    pub fn label_style(mut self, style: Style) -> Self {
362        self.label_style = Some(style);
363        self
364    }
365
366    /// Alignment for auto-labels.
367    pub fn label_alignment(mut self, alignment: Alignment) -> Self {
368        self.label_alignment = Some(alignment);
369        self
370    }
371
372    /// Set all styles.
373    pub fn styles(mut self, styles: FormStyle) -> Self {
374        self.style = styles.style;
375        if let Some(nav) = styles.navigation {
376            self.nav_style = Some(nav);
377        }
378        if let Some(title) = styles.title {
379            self.title_style = Some(title);
380        }
381        if let Some(block) = styles.block {
382            self.block = Some(block);
383        }
384        if let Some(txt) = styles.next_page_mark {
385            self.next_page = txt;
386        }
387        if let Some(txt) = styles.prev_page_mark {
388            self.prev_page = txt;
389        }
390        if let Some(txt) = styles.first_page_mark {
391            self.first_page = txt;
392        }
393        if let Some(txt) = styles.last_page_mark {
394            self.last_page = txt;
395        }
396        self.block = self.block.map(|v| v.style(styles.style));
397
398        if let Some(label) = styles.label_style {
399            self.label_style = Some(label);
400        }
401        if let Some(alignment) = styles.label_alignment {
402            self.label_alignment = Some(alignment);
403        }
404
405        self
406    }
407
408    /// Calculate the layout page size.
409    pub fn layout_size(&self, area: Rect) -> Size {
410        self.block.inner_if_some(area).as_size()
411    }
412
413    // Calculate the view area for all columns.
414    pub fn layout_area(&self, area: Rect) -> Rect {
415        if let Some(block) = &self.block {
416            block.inner(area)
417        } else {
418            area
419        }
420    }
421
422    /// Render the page navigation and create the FormBuffer
423    /// that will do the actual rendering.
424    pub fn into_buffer<'b, 's>(
425        mut self,
426        area: Rect,
427        buf: &'b mut Buffer,
428        state: &'s mut FormState<W>,
429    ) -> FormBuffer<'b, W> {
430        state.area = area;
431        state.widget_area = self.layout_area(area);
432
433        if let Some(layout) = self.layout.take() {
434            state.layout = Rc::new(layout);
435        }
436
437        let page_size = state.layout.page_size();
438        assert!(page_size.height < u16::MAX || page_size.height == u16::MAX && state.page == 0);
439        let page_area = Rect::new(
440            0,
441            (state.page as u16).saturating_mul(page_size.height),
442            page_size.width,
443            page_size.height,
444        );
445
446        if self.navigation {
447            self.render_navigation(area, buf, state);
448        }
449
450        let mut form_buf = FormBuffer {
451            layout: state.layout.clone(),
452            page_area,
453            widget_area: state.widget_area,
454            buffer: buf,
455
456            auto_label: true,
457            label_style: self.label_style,
458            label_alignment: self.label_alignment,
459        };
460        form_buf.render_block();
461        form_buf
462    }
463
464    fn render_navigation(&self, area: Rect, buf: &mut Buffer, state: &mut FormState<W>) {
465        let page_count = state.layout.page_count();
466
467        if !state.layout.is_endless() {
468            if state.page > 0 {
469                state.prev_area =
470                    Rect::new(area.x, area.y, unicode_width(self.prev_page) as u16, 1);
471            } else {
472                state.prev_area =
473                    Rect::new(area.x, area.y, unicode_width(self.first_page) as u16, 1);
474            }
475            if (state.page + 1) < page_count {
476                let p = unicode_width(self.next_page) as u16;
477                state.next_area = Rect::new(area.x + area.width.saturating_sub(p), area.y, p, 1);
478            } else {
479                let p = unicode_width(self.last_page) as u16;
480                state.next_area = Rect::new(area.x + area.width.saturating_sub(p), area.y, p, 1);
481            }
482        } else {
483            state.prev_area = Default::default();
484            state.next_area = Default::default();
485        }
486
487        let block = if page_count > 1 {
488            let title = format!(" {}/{} ", state.page + 1, page_count);
489            let block = self
490                .block
491                .clone()
492                .take()
493                .unwrap_or_else(|| Block::new().style(self.style))
494                .title_bottom(title)
495                .title_alignment(Alignment::Right);
496            if let Some(title_style) = self.title_style {
497                block.title_style(title_style)
498            } else {
499                block
500            }
501        } else {
502            self.block
503                .clone()
504                .take()
505                .unwrap_or_else(|| Block::new().style(self.style))
506        };
507        block.render(area, buf);
508
509        if !state.layout.is_endless() {
510            // active areas
511            let nav_style = self.nav_style.unwrap_or(self.style);
512            if matches!(state.mouse.hover.get(), Some(0)) {
513                buf.set_style(state.prev_area, revert_style(nav_style));
514            } else {
515                buf.set_style(state.prev_area, nav_style);
516            }
517            if state.page > 0 {
518                Span::from(self.prev_page).render(state.prev_area, buf);
519            } else {
520                Span::from(self.first_page).render(state.prev_area, buf);
521            }
522            if matches!(state.mouse.hover.get(), Some(1)) {
523                buf.set_style(state.next_area, revert_style(nav_style));
524            } else {
525                buf.set_style(state.next_area, nav_style);
526            }
527            if (state.page + 1) < page_count {
528                Span::from(self.next_page).render(state.next_area, buf);
529            } else {
530                Span::from(self.last_page).render(state.next_area, buf);
531            }
532        }
533    }
534}
535
536impl<'b, W> FormBuffer<'b, W>
537where
538    W: Eq + Hash + Clone,
539{
540    /// Is the given area visible?
541    pub fn is_visible(&self, widget: W) -> bool {
542        if let Some(idx) = self.layout.try_index_of(widget) {
543            self.locate_area(self.layout.widget(idx)).is_some()
544        } else {
545            false
546        }
547    }
548
549    /// Render all blocks for the current page.
550    fn render_block(&mut self) {
551        for (idx, block_area) in self.layout.block_area_iter().enumerate() {
552            if let Some(block_area) = self.locate_area(*block_area) {
553                if let Some(block) = self.layout.block(idx) {
554                    block.render(block_area, self.buffer);
555                }
556            }
557        }
558    }
559
560    /// Render a manual label.
561    #[inline(always)]
562    pub fn render_label<FN>(&mut self, widget: W, render_fn: FN) -> bool
563    where
564        FN: FnOnce(&Cow<'static, str>, Rect, &mut Buffer),
565    {
566        let Some(idx) = self.layout.try_index_of(widget) else {
567            return false;
568        };
569        let Some(label_area) = self.locate_label(idx) else {
570            return false;
571        };
572        if let Some(label_str) = self.layout.try_label_str(idx) {
573            render_fn(label_str, label_area, self.buffer);
574        } else {
575            render_fn(&Cow::default(), label_area, self.buffer);
576        }
577        true
578    }
579
580    /// Render a stateless widget and its label, if any.
581    #[inline(always)]
582    pub fn render_widget<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
583    where
584        FN: FnOnce() -> WW,
585        WW: Widget,
586    {
587        let Some(idx) = self.layout.try_index_of(widget) else {
588            return false;
589        };
590        if self.auto_label {
591            self.render_auto_label(idx);
592        }
593        let Some(widget_area) = self.locate_widget(idx) else {
594            return false;
595        };
596        render_fn().render(widget_area, self.buffer);
597        true
598    }
599
600    /// Render an optional stateful widget and its label, if any.
601    #[inline(always)]
602    pub fn render_opt<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
603    where
604        FN: FnOnce() -> Option<WW>,
605        WW: StatefulWidget<State = SS>,
606        SS: RelocatableState,
607    {
608        let Some(idx) = self.layout.try_index_of(widget) else {
609            return false;
610        };
611        if self.auto_label {
612            self.render_auto_label(idx);
613        }
614        let Some(widget_area) = self.locate_widget(idx) else {
615            self.hidden(state);
616            return false;
617        };
618        let widget = render_fn();
619        if let Some(widget) = widget {
620            widget.render(widget_area, self.buffer, state);
621            true
622        } else {
623            self.hidden(state);
624            false
625        }
626    }
627
628    /// Render a stateful widget and its label, if any.
629    #[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_widget(idx) else {
643            self.hidden(state);
644            return false;
645        };
646        let widget = render_fn();
647        widget.render(widget_area, self.buffer, state);
648        true
649    }
650
651    /// Render a stateful widget and its label, if any.
652    /// The closure can return a second value, which will be returned
653    /// if the widget is visible.
654    #[inline(always)]
655    #[allow(clippy::question_mark)]
656    pub fn render2<FN, WW, SS, R>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> Option<R>
657    where
658        FN: FnOnce() -> (WW, R),
659        WW: StatefulWidget<State = SS>,
660        SS: RelocatableState,
661    {
662        let Some(idx) = self.layout.try_index_of(widget) else {
663            return None;
664        };
665        if self.auto_label {
666            self.render_auto_label(idx);
667        }
668        let Some(widget_area) = self.locate_widget(idx) else {
669            self.hidden(state);
670            return None;
671        };
672        let (widget, remainder) = render_fn();
673        widget.render(widget_area, self.buffer, state);
674
675        Some(remainder)
676    }
677
678    /// Get access to the buffer during rendering a page.
679    pub fn buffer(&mut self) -> &mut Buffer {
680        self.buffer
681    }
682
683    /// Render the label with the set style and alignment.
684    #[inline(always)]
685    fn render_auto_label(&mut self, idx: usize) -> bool {
686        let Some(label_area) = self.locate_label(idx) else {
687            return false;
688        };
689        let Some(label_str) = self.layout.try_label_str(idx) else {
690            return false;
691        };
692        let mut label = Line::from(label_str.as_ref());
693        if let Some(style) = self.label_style {
694            label = label.style(style)
695        };
696        if let Some(align) = self.label_alignment {
697            label = label.alignment(align);
698        }
699        label.render(label_area, self.buffer);
700
701        true
702    }
703
704    /// Relocate the widget area to screen coordinates.
705    /// Returns None if the widget is not visible.
706    /// This clips the area to page_area.
707    #[inline]
708    fn locate_widget(&self, idx: usize) -> Option<Rect> {
709        self.locate_area(self.layout.widget(idx))
710    }
711
712    /// Relocate the label area to screen coordinates.
713    /// Returns None if the widget is not visible.
714    /// This clips the area to page_area.
715    #[inline]
716    fn locate_label(&self, idx: usize) -> Option<Rect> {
717        self.locate_area(self.layout.label(idx))
718    }
719
720    /// This will clip the area to the page_area.
721    #[inline]
722    fn locate_area(&self, area: Rect) -> Option<Rect> {
723        // clip to page
724        let area = self.page_area.intersection(area);
725        if !area.is_empty() {
726            let located = Rect::new(
727                area.x - self.page_area.x + self.widget_area.x,
728                area.y - self.page_area.y + self.widget_area.y,
729                area.width,
730                area.height,
731            );
732            // clip to render area
733            let located = self.widget_area.intersection(located);
734            if !located.is_empty() {
735                Some(located)
736            } else {
737                None
738            }
739        } else {
740            None
741        }
742    }
743
744    /// Clear the areas in the widget-state.
745    /// This is called by render_xx whenever a widget is invisible.
746    fn hidden<S>(&self, state: &mut S)
747    where
748        S: RelocatableState,
749    {
750        state.relocate((0, 0), Rect::default())
751    }
752}
753
754impl Default for FormStyle {
755    fn default() -> Self {
756        Self {
757            style: Default::default(),
758            label_style: None,
759            label_alignment: None,
760            navigation: None,
761            title: None,
762            block: None,
763            next_page_mark: None,
764            prev_page_mark: None,
765            first_page_mark: None,
766            last_page_mark: None,
767            non_exhaustive: NonExhaustive,
768        }
769    }
770}
771
772impl<W> Default for FormState<W>
773where
774    W: Eq + Hash + Clone,
775{
776    fn default() -> Self {
777        Self {
778            layout: Default::default(),
779            area: Default::default(),
780            widget_area: Default::default(),
781            prev_area: Default::default(),
782            next_area: Default::default(),
783            page: 0,
784            container: Default::default(),
785            mouse: Default::default(),
786            non_exhaustive: NonExhaustive,
787        }
788    }
789}
790
791impl<W> HasFocus for FormState<W>
792where
793    W: Eq + Hash + Clone,
794{
795    fn build(&self, _builder: &mut FocusBuilder) {
796        // no content.
797    }
798
799    fn focus(&self) -> FocusFlag {
800        self.container.clone()
801    }
802
803    fn area(&self) -> Rect {
804        self.area
805    }
806}
807
808impl<W> FormState<W>
809where
810    W: Eq + Hash + Clone,
811{
812    pub fn new() -> Self {
813        Self::default()
814    }
815
816    /// Clear the layout data and reset the page/page-count.
817    pub fn clear(&mut self) {
818        self.layout = Default::default();
819        self.page = 0;
820    }
821
822    /// Layout needs to change?
823    pub fn valid_layout(&self, size: Size) -> bool {
824        !self.layout.size_changed(size) && !self.layout.is_empty()
825    }
826
827    /// Set the layout.
828    pub fn set_layout(&mut self, layout: GenericLayout<W>) {
829        self.layout = Rc::new(layout);
830    }
831
832    /// Layout.
833    pub fn layout(&self) -> Rc<GenericLayout<W>> {
834        self.layout.clone()
835    }
836
837    /// Show the page for this widget.
838    /// If there is no widget for the given identifier, this
839    /// will set the page to 0.
840    pub fn show(&mut self, widget: W) {
841        let page = self.layout.page_of(widget).unwrap_or_default();
842        self.set_page(page);
843    }
844
845    /// Number of form pages.
846    pub fn page_count(&self) -> usize {
847        self.layout.page_count()
848    }
849
850    /// Returns the first widget for the given page.
851    pub fn first(&self, page: usize) -> Option<W> {
852        self.layout.first(page)
853    }
854
855    /// Calculates the page of the widget.
856    pub fn page_of(&self, widget: W) -> Option<usize> {
857        self.layout.page_of(widget)
858    }
859
860    /// Set the visible page.
861    pub fn set_page(&mut self, page: usize) -> bool {
862        let old_page = self.page;
863        self.page = min(page, self.page_count().saturating_sub(1));
864        old_page != self.page
865    }
866
867    /// Visible page
868    pub fn page(&self) -> usize {
869        self.page
870    }
871
872    /// Select next page. Keeps the page in bounds.
873    pub fn next_page(&mut self) -> bool {
874        let old_page = self.page;
875
876        if self.page + 1 == self.page_count() {
877            // don't change
878        } else if self.page + 1 > self.page_count() {
879            self.page = self.page_count().saturating_sub(1);
880        } else {
881            self.page += 1;
882        }
883
884        old_page != self.page
885    }
886
887    /// Select prev page.
888    pub fn prev_page(&mut self) -> bool {
889        if self.page >= 1 {
890            self.page -= 1;
891            true
892        } else if self.page > 0 {
893            self.page = 0;
894            true
895        } else {
896            false
897        }
898    }
899}
900
901impl FormState<usize> {
902    /// Focus the first widget on the active page.
903    /// This assumes the usize-key is a widget id.
904    pub fn focus_first(&self, focus: &Focus) -> bool {
905        if let Some(w) = self.first(self.page) {
906            focus.by_widget_id(w);
907            true
908        } else {
909            false
910        }
911    }
912
913    /// Show the page with the focused widget.
914    /// This assumes the usize-key is a widget id.
915    /// Does nothing if none of the widgets has the focus.
916    pub fn show_focused(&mut self, focus: &Focus) -> bool {
917        let Some(focused) = focus.focused() else {
918            return false;
919        };
920        let focused = focused.widget_id();
921        let page = self.layout.page_of(focused);
922        if let Some(page) = page {
923            self.set_page(page);
924            true
925        } else {
926            false
927        }
928    }
929}
930
931impl FormState<FocusFlag> {
932    /// Focus the first widget on the active page.
933    pub fn focus_first(&self, focus: &Focus) -> bool {
934        if let Some(w) = self.first(self.page) {
935            focus.focus(&w);
936            true
937        } else {
938            false
939        }
940    }
941
942    /// Show the page with the focused widget.
943    /// Does nothing if none of the widgets has the focus.
944    pub fn show_focused(&mut self, focus: &Focus) -> bool {
945        let Some(focused) = focus.focused() else {
946            return false;
947        };
948        let page = self.layout.page_of(focused);
949        if let Some(page) = page {
950            self.set_page(page);
951            true
952        } else {
953            false
954        }
955    }
956}
957
958impl<W> HandleEvent<crossterm::event::Event, Regular, FormOutcome> for FormState<W>
959where
960    W: Eq + Hash + Clone,
961{
962    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> FormOutcome {
963        let r = if self.container.is_focused() && !self.layout.is_endless() {
964            match event {
965                ct_event!(keycode press ALT-PageUp) => {
966                    if self.prev_page() {
967                        FormOutcome::Page
968                    } else {
969                        FormOutcome::Continue
970                    }
971                }
972                ct_event!(keycode press ALT-PageDown) => {
973                    if self.next_page() {
974                        FormOutcome::Page
975                    } else {
976                        FormOutcome::Continue
977                    }
978                }
979                _ => FormOutcome::Continue,
980            }
981        } else {
982            FormOutcome::Continue
983        };
984
985        r.or_else(|| self.handle(event, MouseOnly))
986    }
987}
988
989impl<W> HandleEvent<crossterm::event::Event, MouseOnly, FormOutcome> for FormState<W>
990where
991    W: Eq + Hash + Clone,
992{
993    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> FormOutcome {
994        if !self.layout.is_endless() {
995            match event {
996                ct_event!(mouse down Left for x,y) if self.prev_area.contains((*x, *y).into()) => {
997                    if self.prev_page() {
998                        FormOutcome::Page
999                    } else {
1000                        FormOutcome::Unchanged
1001                    }
1002                }
1003                ct_event!(mouse down Left for x,y) if self.next_area.contains((*x, *y).into()) => {
1004                    if self.next_page() {
1005                        FormOutcome::Page
1006                    } else {
1007                        FormOutcome::Unchanged
1008                    }
1009                }
1010                ct_event!(scroll down for x,y) => {
1011                    if self.area.contains((*x, *y).into()) {
1012                        if self.next_page() {
1013                            FormOutcome::Page
1014                        } else {
1015                            FormOutcome::Continue
1016                        }
1017                    } else {
1018                        FormOutcome::Continue
1019                    }
1020                }
1021                ct_event!(scroll up for x,y) => {
1022                    if self.area.contains((*x, *y).into()) {
1023                        if self.prev_page() {
1024                            FormOutcome::Page
1025                        } else {
1026                            FormOutcome::Continue
1027                        }
1028                    } else {
1029                        FormOutcome::Continue
1030                    }
1031                }
1032                ct_event!(mouse any for m)
1033                    if self.mouse.hover(&[self.prev_area, self.next_area], m) =>
1034                {
1035                    FormOutcome::Changed
1036                }
1037                _ => FormOutcome::Continue,
1038            }
1039        } else {
1040            FormOutcome::Continue
1041        }
1042    }
1043}