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 = usize>
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 = usize>
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 a stateful widget and its label, if any.
607    #[inline(always)]
608    pub fn render<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
609    where
610        FN: FnOnce() -> 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        widget.render(widget_area, self.buffer, state);
626        true
627    }
628
629    /// Render a stateful widget and its label.
630    ///
631    /// This expects a pair of StatefulWidgets, of which the first
632    /// will be rendered and the second will be returned.
633    ///
634    /// This is for rendering widgets that split in two parts,
635    /// the main widget and a popup. The popup must be rendered later
636    /// to be 'above' all other widgets. Use [render_pop] for this.
637    #[inline(always)]
638    #[allow(clippy::question_mark)]
639    pub fn render2<FN, WW, SS, R>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> Option<R>
640    where
641        FN: FnOnce() -> (WW, R),
642        WW: StatefulWidget<State = SS>,
643        SS: RelocatableState,
644    {
645        let Some(idx) = self.layout.try_index_of(widget) else {
646            return None;
647        };
648        if self.auto_label {
649            self.render_auto_label(idx);
650        }
651        let Some(widget_area) = self.locate_area(self.layout.widget(idx)) else {
652            state.relocate_hidden();
653            return None;
654        };
655        let (widget, remainder) = render_fn();
656        widget.render(widget_area, self.buffer, state);
657
658        Some(remainder)
659    }
660
661    /// Render an optional stateful widget and its label, if any.
662    #[inline(always)]
663    #[deprecated(since = "2.3.0", note = "use render_popup() for popups")]
664    pub fn render_opt<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
665    where
666        FN: FnOnce() -> Option<WW>,
667        WW: StatefulWidget<State = SS>,
668        SS: RelocatableState,
669    {
670        let Some(idx) = self.layout.try_index_of(widget) else {
671            return false;
672        };
673        if self.auto_label {
674            self.render_auto_label(idx);
675        }
676        let Some(widget_area) = self.locate_area(self.layout.widget(idx)) else {
677            state.relocate_hidden();
678            return false;
679        };
680        let widget = render_fn();
681        if let Some(widget) = widget {
682            widget.render(widget_area, self.buffer, state);
683            true
684        } else {
685            state.relocate_hidden();
686            false
687        }
688    }
689
690    /// Render an additional popup widget for the given main widget.
691    ///
692    /// Doesn't call relocate() at all.
693    #[inline(always)]
694    pub fn render_popup<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
695    where
696        FN: FnOnce() -> Option<WW>,
697        WW: StatefulWidget<State = SS>,
698        SS: RelocatableState,
699    {
700        let Some(idx) = self.layout.try_index_of(widget) else {
701            return false;
702        };
703        let Some(widget_area) = self.locate_area(self.layout.widget(idx)) else {
704            state.relocate_popup_hidden();
705            return false;
706        };
707        let widget = render_fn();
708        if let Some(widget) = widget {
709            widget.render(widget_area, self.buffer, state);
710            true
711        } else {
712            state.relocate_popup_hidden();
713            false
714        }
715    }
716
717    /// Get access to the buffer during rendering a page.
718    pub fn buffer(&mut self) -> &mut Buffer {
719        self.buffer
720    }
721
722    /// Render the label with the set style and alignment.
723    #[inline(always)]
724    fn render_auto_label(&mut self, idx: usize) -> bool {
725        let Some(label_area) = self.locate_area(self.layout.label(idx)) else {
726            return false;
727        };
728        let Some(label_str) = self.layout.try_label_str(idx) else {
729            return false;
730        };
731        let mut label = Line::from(label_str.as_ref());
732        if let Some(style) = self.label_style {
733            label = label.style(style)
734        };
735        if let Some(align) = self.label_alignment {
736            label = label.alignment(align);
737        }
738        label.render(label_area, self.buffer);
739
740        true
741    }
742
743    /// Get the area for the given widget.
744    pub fn locate_widget(&self, widget: W) -> Option<Rect> {
745        let Some(idx) = self.layout.try_index_of(widget) else {
746            return None;
747        };
748        self.locate_area(self.layout.widget(idx))
749    }
750
751    /// Get the area for the label of the given widget.
752    pub fn locate_label(&self, widget: W) -> Option<Rect> {
753        let Some(idx) = self.layout.try_index_of(widget) else {
754            return None;
755        };
756        self.locate_area(self.layout.label(idx))
757    }
758
759    /// This will clip the area to the page_area.
760    #[inline]
761    pub fn locate_area(&self, area: Rect) -> Option<Rect> {
762        // clip to page
763        let area = self.page_area.intersection(area);
764        if self.page_area.intersects(area) {
765            let located = Rect::new(
766                area.x - self.page_area.x + self.widget_area.x,
767                area.y - self.page_area.y + self.widget_area.y,
768                area.width,
769                area.height,
770            );
771            // clip to render area
772            let located = self.widget_area.intersection(located);
773            if self.widget_area.intersects(located) {
774                Some(located)
775            } else {
776                None
777            }
778        } else {
779            None
780        }
781    }
782}
783
784impl Default for FormStyle {
785    fn default() -> Self {
786        Self {
787            style: Default::default(),
788            label_style: None,
789            label_alignment: None,
790            navigation: None,
791            show_navigation: None,
792            title: None,
793            block: None,
794            next_page_mark: None,
795            prev_page_mark: None,
796            first_page_mark: None,
797            last_page_mark: None,
798            non_exhaustive: NonExhaustive,
799        }
800    }
801}
802
803impl<W> Default for FormState<W>
804where
805    W: Eq + Hash + Clone,
806{
807    fn default() -> Self {
808        Self {
809            layout: Default::default(),
810            area: Default::default(),
811            widget_area: Default::default(),
812            prev_area: Default::default(),
813            next_area: Default::default(),
814            page: 0,
815            container: Default::default(),
816            mouse: Default::default(),
817            non_exhaustive: NonExhaustive,
818        }
819    }
820}
821
822impl<W> HasFocus for FormState<W>
823where
824    W: Eq + Hash + Clone,
825{
826    fn build(&self, _builder: &mut FocusBuilder) {
827        // no content.
828    }
829
830    fn focus(&self) -> FocusFlag {
831        self.container.clone()
832    }
833
834    fn area(&self) -> Rect {
835        self.area
836    }
837}
838
839impl<W> FormState<W>
840where
841    W: Eq + Hash + Clone,
842{
843    pub fn new() -> Self {
844        Self::default()
845    }
846
847    pub fn named(name: &str) -> Self {
848        let mut z = Self::default();
849        z.container = z.container.with_name(name);
850        z
851    }
852
853    /// Clear the layout data and reset the page/page-count.
854    pub fn clear(&mut self) {
855        self.layout = Default::default();
856        self.page = 0;
857    }
858
859    /// Layout needs to change?
860    pub fn valid_layout(&self, size: Size) -> bool {
861        !self.layout.size_changed(size) && !self.layout.is_empty()
862    }
863
864    /// Set the layout.
865    pub fn set_layout(&mut self, layout: GenericLayout<W>) {
866        self.layout = Rc::new(layout);
867    }
868
869    /// Layout.
870    pub fn layout(&self) -> Rc<GenericLayout<W>> {
871        self.layout.clone()
872    }
873
874    /// Show the page for this widget.
875    /// If there is no widget for the given identifier, this
876    /// will set the page to 0.
877    pub fn show(&mut self, widget: W) {
878        let page = self.layout.page_of(widget).unwrap_or_default();
879        self.set_page(page);
880    }
881
882    /// Number of form pages.
883    pub fn page_count(&self) -> usize {
884        self.layout.page_count()
885    }
886
887    /// Returns the first widget for the given page.
888    pub fn first(&self, page: usize) -> Option<W> {
889        self.layout.first(page)
890    }
891
892    /// Calculates the page of the widget.
893    pub fn page_of(&self, widget: W) -> Option<usize> {
894        self.layout.page_of(widget)
895    }
896
897    /// Set the visible page.
898    pub fn set_page(&mut self, page: usize) -> bool {
899        let old_page = self.page;
900        self.page = min(page, self.page_count().saturating_sub(1));
901        old_page != self.page
902    }
903
904    /// Visible page
905    pub fn page(&self) -> usize {
906        self.page
907    }
908
909    /// Select next page. Keeps the page in bounds.
910    pub fn next_page(&mut self) -> bool {
911        let old_page = self.page;
912
913        if self.page + 1 == self.page_count() {
914            // don't change
915        } else if self.page + 1 > self.page_count() {
916            self.page = self.page_count().saturating_sub(1);
917        } else {
918            self.page += 1;
919        }
920
921        old_page != self.page
922    }
923
924    /// Select prev page.
925    pub fn prev_page(&mut self) -> bool {
926        if self.page >= 1 {
927            self.page -= 1;
928            true
929        } else if self.page > 0 {
930            self.page = 0;
931            true
932        } else {
933            false
934        }
935    }
936}
937
938impl FormState<usize> {
939    /// Focus the first widget on the active page.
940    /// This assumes the usize-key is a widget id.
941    pub fn focus_first(&self, focus: &Focus) -> bool {
942        if let Some(w) = self.first(self.page) {
943            focus.by_widget_id(w);
944            true
945        } else {
946            false
947        }
948    }
949
950    /// Show the page with the focused widget.
951    /// This assumes the usize-key is a widget id.
952    /// Does nothing if none of the widgets has the focus.
953    pub fn show_focused(&mut self, focus: &Focus) -> bool {
954        let Some(focused) = focus.focused() else {
955            return false;
956        };
957        let focused = focused.widget_id();
958        let page = self.layout.page_of(focused);
959        if let Some(page) = page {
960            self.set_page(page);
961            true
962        } else {
963            false
964        }
965    }
966}
967
968impl FormState<FocusFlag> {
969    /// Focus the first widget on the active page.
970    pub fn focus_first(&self, focus: &Focus) -> bool {
971        if let Some(w) = self.first(self.page) {
972            focus.focus(&w);
973            true
974        } else {
975            false
976        }
977    }
978
979    /// Show the page with the focused widget.
980    /// Does nothing if none of the widgets has the focus.
981    pub fn show_focused(&mut self, focus: &Focus) -> bool {
982        let Some(focused) = focus.focused() else {
983            return false;
984        };
985        let page = self.layout.page_of(focused);
986        if let Some(page) = page {
987            self.set_page(page);
988            true
989        } else {
990            false
991        }
992    }
993}
994
995impl<W> HandleEvent<crossterm::event::Event, Regular, FormOutcome> for FormState<W>
996where
997    W: Eq + Hash + Clone,
998{
999    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> FormOutcome {
1000        let r = if self.container.is_focused() && !self.layout.is_endless() {
1001            match event {
1002                ct_event!(keycode press ALT-PageUp) => {
1003                    if self.prev_page() {
1004                        FormOutcome::Page
1005                    } else {
1006                        FormOutcome::Continue
1007                    }
1008                }
1009                ct_event!(keycode press ALT-PageDown) => {
1010                    if self.next_page() {
1011                        FormOutcome::Page
1012                    } else {
1013                        FormOutcome::Continue
1014                    }
1015                }
1016                _ => FormOutcome::Continue,
1017            }
1018        } else {
1019            FormOutcome::Continue
1020        };
1021
1022        r.or_else(|| self.handle(event, MouseOnly))
1023    }
1024}
1025
1026impl<W> HandleEvent<crossterm::event::Event, MouseOnly, FormOutcome> for FormState<W>
1027where
1028    W: Eq + Hash + Clone,
1029{
1030    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> FormOutcome {
1031        if !self.layout.is_endless() {
1032            match event {
1033                ct_event!(mouse down Left for x,y) if self.prev_area.contains((*x, *y).into()) => {
1034                    if self.prev_page() {
1035                        FormOutcome::Page
1036                    } else {
1037                        FormOutcome::Unchanged
1038                    }
1039                }
1040                ct_event!(mouse down Left for x,y) if self.next_area.contains((*x, *y).into()) => {
1041                    if self.next_page() {
1042                        FormOutcome::Page
1043                    } else {
1044                        FormOutcome::Unchanged
1045                    }
1046                }
1047                ct_event!(scroll down for x,y) => {
1048                    if self.area.contains((*x, *y).into()) {
1049                        if self.next_page() {
1050                            FormOutcome::Page
1051                        } else {
1052                            FormOutcome::Continue
1053                        }
1054                    } else {
1055                        FormOutcome::Continue
1056                    }
1057                }
1058                ct_event!(scroll up for x,y) => {
1059                    if self.area.contains((*x, *y).into()) {
1060                        if self.prev_page() {
1061                            FormOutcome::Page
1062                        } else {
1063                            FormOutcome::Continue
1064                        }
1065                    } else {
1066                        FormOutcome::Continue
1067                    }
1068                }
1069                ct_event!(mouse any for m)
1070                    if self.mouse.hover(&[self.prev_area, self.next_area], m) =>
1071                {
1072                    FormOutcome::Changed
1073                }
1074                _ => FormOutcome::Continue,
1075            }
1076        } else {
1077            FormOutcome::Continue
1078        }
1079    }
1080}
1081
1082/// Handle all events.
1083/// Text events are only processed if focus is true.
1084/// Mouse events are processed if they are in range.
1085pub fn handle_events<W>(
1086    state: &mut FormState<W>,
1087    _focus: bool,
1088    event: &crossterm::event::Event,
1089) -> FormOutcome
1090where
1091    W: Eq + Clone + Hash,
1092{
1093    HandleEvent::handle(state, event, Regular)
1094}
1095
1096/// Handle only mouse-events.
1097pub fn handle_mouse_events<W>(
1098    state: &mut FormState<W>,
1099    event: &crossterm::event::Event,
1100) -> FormOutcome
1101where
1102    W: Eq + Clone + Hash,
1103{
1104    HandleEvent::handle(state, event, MouseOnly)
1105}