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