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