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