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            block.inner(area)
419        } else {
420            area
421        }
422    }
423
424    /// Render the page navigation and create the FormBuffer
425    /// that will do the actual rendering.
426    #[allow(clippy::needless_lifetimes)]
427    pub fn into_buffer<'b, 's>(
428        mut self,
429        area: Rect,
430        buf: &'b mut Buffer,
431        state: &'s mut FormState<W>,
432    ) -> FormBuffer<'b, W> {
433        state.area = area;
434        state.widget_area = self.layout_area(area);
435
436        if let Some(layout) = self.layout.take() {
437            state.layout = Rc::new(layout);
438        }
439
440        let page_size = state.layout.page_size();
441        assert!(page_size.height < u16::MAX || page_size.height == u16::MAX && state.page == 0);
442        let page_area = Rect::new(
443            0,
444            (state.page as u16).saturating_mul(page_size.height),
445            page_size.width,
446            page_size.height,
447        );
448
449        if self.navigation {
450            self.render_navigation(area, buf, state);
451        }
452
453        let mut form_buf = FormBuffer {
454            layout: state.layout.clone(),
455            page_area,
456            widget_area: state.widget_area,
457            buffer: buf,
458
459            auto_label: true,
460            label_style: self.label_style,
461            label_alignment: self.label_alignment,
462        };
463        form_buf.render_block();
464        form_buf
465    }
466
467    fn render_navigation(&self, area: Rect, buf: &mut Buffer, state: &mut FormState<W>) {
468        let page_count = state.layout.page_count();
469
470        if !state.layout.is_endless() {
471            if state.page > 0 {
472                state.prev_area =
473                    Rect::new(area.x, area.y, unicode_width(self.prev_page) as u16, 1);
474            } else {
475                state.prev_area =
476                    Rect::new(area.x, area.y, unicode_width(self.first_page) as u16, 1);
477            }
478            if (state.page + 1) < page_count {
479                let p = unicode_width(self.next_page) as u16;
480                state.next_area = Rect::new(area.x + area.width.saturating_sub(p), area.y, p, 1);
481            } else {
482                let p = unicode_width(self.last_page) as u16;
483                state.next_area = Rect::new(area.x + area.width.saturating_sub(p), area.y, p, 1);
484            }
485        } else {
486            state.prev_area = Default::default();
487            state.next_area = Default::default();
488        }
489
490        let block = if page_count > 1 {
491            let title = format!(" {}/{} ", state.page + 1, page_count);
492            let block = self
493                .block
494                .clone()
495                .unwrap_or_else(|| Block::new().style(self.style))
496                .title_bottom(title)
497                .title_alignment(Alignment::Right);
498            if let Some(title_style) = self.title_style {
499                block.title_style(title_style)
500            } else {
501                block
502            }
503        } else {
504            self.block
505                .clone()
506                .unwrap_or_else(|| Block::new().style(self.style))
507        };
508        block.render(area, buf);
509
510        if !state.layout.is_endless() {
511            // active areas
512            let nav_style = self.nav_style.unwrap_or(self.style);
513            if matches!(state.mouse.hover.get(), Some(0)) {
514                buf.set_style(state.prev_area, revert_style(nav_style));
515            } else {
516                buf.set_style(state.prev_area, nav_style);
517            }
518            if state.page > 0 {
519                Span::from(self.prev_page).render(state.prev_area, buf);
520            } else {
521                Span::from(self.first_page).render(state.prev_area, buf);
522            }
523            if matches!(state.mouse.hover.get(), Some(1)) {
524                buf.set_style(state.next_area, revert_style(nav_style));
525            } else {
526                buf.set_style(state.next_area, nav_style);
527            }
528            if (state.page + 1) < page_count {
529                Span::from(self.next_page).render(state.next_area, buf);
530            } else {
531                Span::from(self.last_page).render(state.next_area, buf);
532            }
533        }
534    }
535}
536
537impl<'b, W> FormBuffer<'b, W>
538where
539    W: Eq + Hash + Clone,
540{
541    /// Is the given area visible?
542    pub fn is_visible(&self, widget: W) -> bool {
543        if let Some(idx) = self.layout.try_index_of(widget) {
544            self.locate_area(self.layout.widget(idx)).is_some()
545        } else {
546            false
547        }
548    }
549
550    /// Render all blocks for the current page.
551    fn render_block(&mut self) {
552        for (idx, block_area) in self.layout.block_area_iter().enumerate() {
553            if let Some(block_area) = self.locate_area(*block_area) {
554                if let Some(block) = self.layout.block(idx) {
555                    block.render(block_area, self.buffer);
556                }
557            }
558        }
559    }
560
561    /// Render a manual label.
562    #[inline(always)]
563    pub fn render_label<FN>(&mut self, widget: W, render_fn: FN) -> bool
564    where
565        FN: FnOnce(&Cow<'static, str>, Rect, &mut Buffer),
566    {
567        let Some(idx) = self.layout.try_index_of(widget) else {
568            return false;
569        };
570        let Some(label_area) = self.locate_area(self.layout.label(idx)) else {
571            return false;
572        };
573        if let Some(label_str) = self.layout.try_label_str(idx) {
574            render_fn(label_str, label_area, self.buffer);
575        } else {
576            render_fn(&Cow::default(), label_area, self.buffer);
577        }
578        true
579    }
580
581    /// Render a stateless widget and its label, if any.
582    #[inline(always)]
583    pub fn render_widget<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
584    where
585        FN: FnOnce() -> WW,
586        WW: Widget,
587    {
588        let Some(idx) = self.layout.try_index_of(widget) else {
589            return false;
590        };
591        if self.auto_label {
592            self.render_auto_label(idx);
593        }
594
595        let Some(widget_area) = self.locate_area(self.layout.widget(idx)) else {
596            return false;
597        };
598        render_fn().render(widget_area, self.buffer);
599        true
600    }
601
602    /// Render an optional stateful widget and its label, if any.
603    #[inline(always)]
604    pub fn render_opt<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
605    where
606        FN: FnOnce() -> Option<WW>,
607        WW: StatefulWidget<State = SS>,
608        SS: RelocatableState,
609    {
610        let Some(idx) = self.layout.try_index_of(widget) else {
611            return false;
612        };
613        if self.auto_label {
614            self.render_auto_label(idx);
615        }
616        let Some(widget_area) = self.locate_area(self.layout.widget(idx)) else {
617            state.relocate_hidden();
618            return false;
619        };
620        let widget = render_fn();
621        if let Some(widget) = widget {
622            widget.render(widget_area, self.buffer, state);
623            true
624        } else {
625            state.relocate_hidden();
626            false
627        }
628    }
629
630    /// Render a stateful widget and its label, if any.
631    #[inline(always)]
632    pub fn render<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
633    where
634        FN: FnOnce() -> WW,
635        WW: StatefulWidget<State = SS>,
636        SS: RelocatableState,
637    {
638        let Some(idx) = self.layout.try_index_of(widget) else {
639            return false;
640        };
641        if self.auto_label {
642            self.render_auto_label(idx);
643        }
644        let Some(widget_area) = self.locate_area(self.layout.widget(idx)) else {
645            state.relocate_hidden();
646            return false;
647        };
648        let widget = render_fn();
649        widget.render(widget_area, self.buffer, state);
650        true
651    }
652
653    /// Render a stateful widget and its label, if any.
654    /// The closure can return a second value, which will be returned
655    /// if the widget is visible.
656    #[inline(always)]
657    #[allow(clippy::question_mark)]
658    pub fn render2<FN, WW, SS, R>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> Option<R>
659    where
660        FN: FnOnce() -> (WW, R),
661        WW: StatefulWidget<State = SS>,
662        SS: RelocatableState,
663    {
664        let Some(idx) = self.layout.try_index_of(widget) else {
665            return None;
666        };
667        if self.auto_label {
668            self.render_auto_label(idx);
669        }
670        let Some(widget_area) = self.locate_area(self.layout.widget(idx)) else {
671            state.relocate_hidden();
672            return None;
673        };
674        let (widget, remainder) = render_fn();
675        widget.render(widget_area, self.buffer, state);
676
677        Some(remainder)
678    }
679
680    /// Get access to the buffer during rendering a page.
681    pub fn buffer(&mut self) -> &mut Buffer {
682        self.buffer
683    }
684
685    /// Render the label with the set style and alignment.
686    #[inline(always)]
687    fn render_auto_label(&mut self, idx: usize) -> bool {
688        let Some(label_area) = self.locate_area(self.layout.label(idx)) else {
689            return false;
690        };
691        let Some(label_str) = self.layout.try_label_str(idx) else {
692            return false;
693        };
694        let mut label = Line::from(label_str.as_ref());
695        if let Some(style) = self.label_style {
696            label = label.style(style)
697        };
698        if let Some(align) = self.label_alignment {
699            label = label.alignment(align);
700        }
701        label.render(label_area, self.buffer);
702
703        true
704    }
705
706    /// Get the area for the given widget.
707    pub fn locate_widget(&self, widget: W) -> Option<Rect> {
708        let Some(idx) = self.layout.try_index_of(widget) else {
709            return None;
710        };
711        self.locate_area(self.layout.widget(idx))
712    }
713
714    /// Get the area for the label of the given widget.
715    pub fn locate_label(&self, widget: W) -> Option<Rect> {
716        let Some(idx) = self.layout.try_index_of(widget) else {
717            return None;
718        };
719        self.locate_area(self.layout.label(idx))
720    }
721
722    /// This will clip the area to the page_area.
723    #[inline]
724    pub fn locate_area(&self, area: Rect) -> Option<Rect> {
725        // clip to page
726        let area = self.page_area.intersection(area);
727        if self.page_area.intersects(area) {
728            let located = Rect::new(
729                area.x - self.page_area.x + self.widget_area.x,
730                area.y - self.page_area.y + self.widget_area.y,
731                area.width,
732                area.height,
733            );
734            // clip to render area
735            let located = self.widget_area.intersection(located);
736            if self.widget_area.intersects(located) {
737                Some(located)
738            } else {
739                None
740            }
741        } else {
742            None
743        }
744    }
745}
746
747impl Default for FormStyle {
748    fn default() -> Self {
749        Self {
750            style: Default::default(),
751            label_style: None,
752            label_alignment: None,
753            navigation: None,
754            show_navigation: None,
755            title: None,
756            block: None,
757            next_page_mark: None,
758            prev_page_mark: None,
759            first_page_mark: None,
760            last_page_mark: None,
761            non_exhaustive: NonExhaustive,
762        }
763    }
764}
765
766impl<W> Default for FormState<W>
767where
768    W: Eq + Hash + Clone,
769{
770    fn default() -> Self {
771        Self {
772            layout: Default::default(),
773            area: Default::default(),
774            widget_area: Default::default(),
775            prev_area: Default::default(),
776            next_area: Default::default(),
777            page: 0,
778            container: Default::default(),
779            mouse: Default::default(),
780            non_exhaustive: NonExhaustive,
781        }
782    }
783}
784
785impl<W> HasFocus for FormState<W>
786where
787    W: Eq + Hash + Clone,
788{
789    fn build(&self, _builder: &mut FocusBuilder) {
790        // no content.
791    }
792
793    fn focus(&self) -> FocusFlag {
794        self.container.clone()
795    }
796
797    fn area(&self) -> Rect {
798        self.area
799    }
800}
801
802impl<W> FormState<W>
803where
804    W: Eq + Hash + Clone,
805{
806    pub fn new() -> Self {
807        Self::default()
808    }
809
810    /// Clear the layout data and reset the page/page-count.
811    pub fn clear(&mut self) {
812        self.layout = Default::default();
813        self.page = 0;
814    }
815
816    /// Layout needs to change?
817    pub fn valid_layout(&self, size: Size) -> bool {
818        !self.layout.size_changed(size) && !self.layout.is_empty()
819    }
820
821    /// Set the layout.
822    pub fn set_layout(&mut self, layout: GenericLayout<W>) {
823        self.layout = Rc::new(layout);
824    }
825
826    /// Layout.
827    pub fn layout(&self) -> Rc<GenericLayout<W>> {
828        self.layout.clone()
829    }
830
831    /// Show the page for this widget.
832    /// If there is no widget for the given identifier, this
833    /// will set the page to 0.
834    pub fn show(&mut self, widget: W) {
835        let page = self.layout.page_of(widget).unwrap_or_default();
836        self.set_page(page);
837    }
838
839    /// Number of form pages.
840    pub fn page_count(&self) -> usize {
841        self.layout.page_count()
842    }
843
844    /// Returns the first widget for the given page.
845    pub fn first(&self, page: usize) -> Option<W> {
846        self.layout.first(page)
847    }
848
849    /// Calculates the page of the widget.
850    pub fn page_of(&self, widget: W) -> Option<usize> {
851        self.layout.page_of(widget)
852    }
853
854    /// Set the visible page.
855    pub fn set_page(&mut self, page: usize) -> bool {
856        let old_page = self.page;
857        self.page = min(page, self.page_count().saturating_sub(1));
858        old_page != self.page
859    }
860
861    /// Visible page
862    pub fn page(&self) -> usize {
863        self.page
864    }
865
866    /// Select next page. Keeps the page in bounds.
867    pub fn next_page(&mut self) -> bool {
868        let old_page = self.page;
869
870        if self.page + 1 == self.page_count() {
871            // don't change
872        } else if self.page + 1 > self.page_count() {
873            self.page = self.page_count().saturating_sub(1);
874        } else {
875            self.page += 1;
876        }
877
878        old_page != self.page
879    }
880
881    /// Select prev page.
882    pub fn prev_page(&mut self) -> bool {
883        if self.page >= 1 {
884            self.page -= 1;
885            true
886        } else if self.page > 0 {
887            self.page = 0;
888            true
889        } else {
890            false
891        }
892    }
893}
894
895impl FormState<usize> {
896    /// Focus the first widget on the active page.
897    /// This assumes the usize-key is a widget id.
898    pub fn focus_first(&self, focus: &Focus) -> bool {
899        if let Some(w) = self.first(self.page) {
900            focus.by_widget_id(w);
901            true
902        } else {
903            false
904        }
905    }
906
907    /// Show the page with the focused widget.
908    /// This assumes the usize-key is a widget id.
909    /// Does nothing if none of the widgets has the focus.
910    pub fn show_focused(&mut self, focus: &Focus) -> bool {
911        let Some(focused) = focus.focused() else {
912            return false;
913        };
914        let focused = focused.widget_id();
915        let page = self.layout.page_of(focused);
916        if let Some(page) = page {
917            self.set_page(page);
918            true
919        } else {
920            false
921        }
922    }
923}
924
925impl FormState<FocusFlag> {
926    /// Focus the first widget on the active page.
927    pub fn focus_first(&self, focus: &Focus) -> bool {
928        if let Some(w) = self.first(self.page) {
929            focus.focus(&w);
930            true
931        } else {
932            false
933        }
934    }
935
936    /// Show the page with the focused widget.
937    /// Does nothing if none of the widgets has the focus.
938    pub fn show_focused(&mut self, focus: &Focus) -> bool {
939        let Some(focused) = focus.focused() else {
940            return false;
941        };
942        let page = self.layout.page_of(focused);
943        if let Some(page) = page {
944            self.set_page(page);
945            true
946        } else {
947            false
948        }
949    }
950}
951
952impl<W> HandleEvent<crossterm::event::Event, Regular, FormOutcome> for FormState<W>
953where
954    W: Eq + Hash + Clone,
955{
956    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> FormOutcome {
957        let r = if self.container.is_focused() && !self.layout.is_endless() {
958            match event {
959                ct_event!(keycode press ALT-PageUp) => {
960                    if self.prev_page() {
961                        FormOutcome::Page
962                    } else {
963                        FormOutcome::Continue
964                    }
965                }
966                ct_event!(keycode press ALT-PageDown) => {
967                    if self.next_page() {
968                        FormOutcome::Page
969                    } else {
970                        FormOutcome::Continue
971                    }
972                }
973                _ => FormOutcome::Continue,
974            }
975        } else {
976            FormOutcome::Continue
977        };
978
979        r.or_else(|| self.handle(event, MouseOnly))
980    }
981}
982
983impl<W> HandleEvent<crossterm::event::Event, MouseOnly, FormOutcome> for FormState<W>
984where
985    W: Eq + Hash + Clone,
986{
987    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> FormOutcome {
988        if !self.layout.is_endless() {
989            match event {
990                ct_event!(mouse down Left for x,y) if self.prev_area.contains((*x, *y).into()) => {
991                    if self.prev_page() {
992                        FormOutcome::Page
993                    } else {
994                        FormOutcome::Unchanged
995                    }
996                }
997                ct_event!(mouse down Left for x,y) if self.next_area.contains((*x, *y).into()) => {
998                    if self.next_page() {
999                        FormOutcome::Page
1000                    } else {
1001                        FormOutcome::Unchanged
1002                    }
1003                }
1004                ct_event!(scroll down for x,y) => {
1005                    if self.area.contains((*x, *y).into()) {
1006                        if self.next_page() {
1007                            FormOutcome::Page
1008                        } else {
1009                            FormOutcome::Continue
1010                        }
1011                    } else {
1012                        FormOutcome::Continue
1013                    }
1014                }
1015                ct_event!(scroll up for x,y) => {
1016                    if self.area.contains((*x, *y).into()) {
1017                        if self.prev_page() {
1018                            FormOutcome::Page
1019                        } else {
1020                            FormOutcome::Continue
1021                        }
1022                    } else {
1023                        FormOutcome::Continue
1024                    }
1025                }
1026                ct_event!(mouse any for m)
1027                    if self.mouse.hover(&[self.prev_area, self.next_area], m) =>
1028                {
1029                    FormOutcome::Changed
1030                }
1031                _ => FormOutcome::Continue,
1032            }
1033        } else {
1034            FormOutcome::Continue
1035        }
1036    }
1037}