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