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