rat_widget/pager/
form.rs

1//! Widget that helps with rendering a layout using GenericForm.
2//!
3//!
4//! ```
5//! # use ratatui::buffer::Buffer;
6//! # use ratatui::layout::{Flex, Rect};
7//! # use ratatui::text::Span;
8//! # use ratatui::widgets::{Padding, Widget, StatefulWidget};
9//! # use rat_focus::{FocusFlag, HasFocus};
10//! # use rat_text::text_input::{TextInput, TextInputState};
11//! # use rat_widget::layout::{FormLabel, FormWidget, GenericLayout, LayoutForm};
12//! use rat_widget::pager::{Form, FormState};
13//! #
14//! # struct State {
15//! #     form: FormState<FocusFlag>,
16//! #     text1: TextInputState,
17//! #     text2: TextInputState,
18//! #     text3: TextInputState,
19//! # }
20//! #
21//! # let mut state = State {form: Default::default(),text1: Default::default(),text2: Default::default(),text3: Default::default()};
22//! # let area = Rect::default();
23//! # let mut buf = Buffer::empty(Rect::default());
24//! # let buf = &mut buf;
25//!
26//! if !state.form.valid_layout(area.as_size()) {
27//!     let mut form_layout = LayoutForm::new()
28//!             .spacing(1)
29//!             .flex(Flex::Legacy)
30//!             .line_spacing(1)
31//!             .min_label(10);
32//!
33//!     form_layout.widget(
34//!         state.text1.focus(),
35//!         FormLabel::Str("Text 1"),
36//!         // single row
37//!         FormWidget::Width(22)
38//!     );
39//!     form_layout.widget(
40//!         state.text2.focus(),
41//!         FormLabel::Str("Text 2"),
42//!         // stretch to the form-width, preferred with 15, 1 row high.
43//!         FormWidget::StretchX(15, 1)
44//!     );
45//!     form_layout.widget(
46//!         state.text3.focus(),
47//!         FormLabel::Str("Text 3"),
48//!         // stretch to the form-width and fill vertically.
49//!         // preferred width is 15 3 rows high.
50//!         FormWidget::StretchXY(15, 3)
51//!     );
52//!
53//!     // calculate the layout and set it.
54//!     state.form.set_layout(form_layout.paged(area.as_size(), Padding::default()));
55//!  }
56//!
57//!  let mut form = Form::new()
58//!     .into_buffer(area, buf, &mut state.form);
59//!
60//!  form.render(state.text1.focus(),
61//!     || TextInput::new(),
62//!     &mut state.text1
63//!  );
64//!  form.render(state.text2.focus(),
65//!     || TextInput::new(),
66//!     &mut state.text2
67//!  );
68//!  form.render(state.text3.focus(),
69//!     || TextInput::new(),
70//!     &mut state.text3
71//!  );
72//!
73//! ```
74
75use crate::_private::NonExhaustive;
76use crate::layout::GenericLayout;
77use crate::pager::{Pager, PagerBuffer, PagerStyle};
78use rat_reloc::RelocatableState;
79use ratatui::buffer::Buffer;
80use ratatui::layout::{Alignment, Rect, Size};
81use ratatui::prelude::BlockExt;
82use ratatui::style::Style;
83use ratatui::widgets::{Block, StatefulWidget, Widget};
84use std::borrow::Cow;
85use std::cell::{Ref, RefCell, RefMut};
86use std::hash::Hash;
87use std::rc::Rc;
88
89/// A widget that helps with rendering a Form.
90/// Uses a GenericLayout for its layout information.
91/// Does no scrolling/paging whatsoever and any widgets
92/// out of view are simply not rendered.
93#[derive(Debug, Clone)]
94pub struct Form<'a, W>
95where
96    W: Eq + Hash + Clone,
97{
98    layout: Option<GenericLayout<W>>,
99    pager: Pager<W>,
100    style: Style,
101    block: Option<Block<'a>>,
102    auto_label: bool,
103}
104
105/// Renders directly to the frame buffer.
106///
107/// * It maps your widget area from layout coordinates
108///   to screen coordinates before rendering.
109/// * It helps with cleanup of the widget state if your
110///   widget is currently invisible.
111#[derive(Debug)]
112pub struct FormBuffer<'a, W>
113where
114    W: Eq + Hash + Clone,
115{
116    pager: PagerBuffer<'a, W>,
117    auto_label: bool,
118}
119
120/// Widget state.
121#[derive(Debug, Clone)]
122pub struct FormState<W>
123where
124    W: Eq + Hash + Clone,
125{
126    /// Page layout
127    /// __read+write__ might be overwritten from widget.
128    pub layout: Rc<RefCell<GenericLayout<W>>>,
129
130    /// Only construct with `..Default::default()`.
131    pub non_exhaustive: NonExhaustive,
132}
133
134impl<W> Default for Form<'_, W>
135where
136    W: Eq + Hash + Clone,
137{
138    fn default() -> Self {
139        Self {
140            layout: Default::default(),
141            pager: Default::default(),
142            style: Default::default(),
143            block: Default::default(),
144            auto_label: true,
145        }
146    }
147}
148
149impl<'a, W> Form<'a, W>
150where
151    W: Eq + Hash + Clone,
152{
153    /// New SinglePage.
154    pub fn new() -> Self {
155        Self::default()
156    }
157
158    /// Set the layout. If no layout is set here the layout is
159    /// taken from the state.
160    pub fn layout(mut self, layout: GenericLayout<W>) -> Self {
161        self.layout = Some(layout);
162        self
163    }
164
165    /// Base style.
166    pub fn style(mut self, style: Style) -> Self {
167        self.pager = self.pager.style(style);
168        self.style = style;
169        self.block = self.block.map(|v| v.style(style));
170        self
171    }
172
173    /// Render the label automatically when rendering the widget.
174    ///
175    /// Default: true
176    pub fn auto_label(mut self, auto: bool) -> Self {
177        self.auto_label = auto;
178        self
179    }
180
181    /// Style for auto-labels.
182    pub fn label_style(mut self, style: Style) -> Self {
183        self.pager = self.pager.label_style(style);
184        self
185    }
186
187    /// Alignment for auto-labels.
188    pub fn label_alignment(mut self, alignment: Alignment) -> Self {
189        self.pager = self.pager.label_alignment(alignment);
190        self
191    }
192
193    /// Block
194    pub fn block(mut self, block: Block<'a>) -> Self {
195        self.block = Some(block.style(self.style));
196        self
197    }
198
199    /// Set all styles.
200    pub fn styles(mut self, styles: PagerStyle) -> Self {
201        self.pager = self.pager.styles(styles.clone());
202        self.style = styles.style;
203        self.block = self.block.map(|v| v.style(styles.style));
204        self
205    }
206
207    /// Calculate the layout page size.
208    pub fn layout_size(&self, area: Rect) -> Size {
209        self.block.inner_if_some(area).as_size()
210    }
211
212    // Calculate the view area for all columns.
213    pub fn inner(&self, area: Rect) -> Rect {
214        self.block.inner_if_some(area)
215    }
216
217    /// Render the page navigation and create the FormBuffer
218    /// that will do the actual rendering.
219    pub fn into_buffer(
220        self,
221        area: Rect,
222        buf: &'a mut Buffer,
223        state: &'a mut FormState<W>,
224    ) -> FormBuffer<'a, W> {
225        // render border
226        self.block.render(area, buf);
227        // set layout
228        if let Some(layout) = self.layout {
229            state.layout = Rc::new(RefCell::new(layout));
230        }
231
232        FormBuffer {
233            pager: self
234                .pager //
235                .layout(state.layout.clone())
236                .page(0)
237                .into_buffer(area, Rc::new(RefCell::new(buf))),
238            auto_label: self.auto_label,
239        }
240    }
241}
242
243impl<'a, W> FormBuffer<'a, W>
244where
245    W: Eq + Hash + Clone,
246{
247    /// Is the given area visible?
248    pub fn is_visible(&self, widget: W) -> bool {
249        if let Some(idx) = self.pager.widget_idx(widget) {
250            self.pager.is_visible(idx)
251        } else {
252            false
253        }
254    }
255
256    /// Render all blocks for the current page.
257    pub fn render_block(&mut self) {
258        self.pager.render_block()
259    }
260
261    /// Render a manual label.
262    #[inline(always)]
263    pub fn render_label<FN>(&mut self, widget: W, render_fn: FN) -> bool
264    where
265        FN: FnOnce(&Cow<'static, str>, Rect, &mut Buffer),
266    {
267        let Some(idx) = self.pager.widget_idx(widget) else {
268            return false;
269        };
270        self.pager.render_label(idx, render_fn)
271    }
272
273    /// Render a stateless widget and its label, if any.
274    #[inline(always)]
275    pub fn render_widget<FN, WW>(&mut self, widget: W, render_fn: FN) -> bool
276    where
277        FN: FnOnce() -> WW,
278        WW: Widget,
279    {
280        let Some(idx) = self.pager.widget_idx(widget) else {
281            return false;
282        };
283        if self.auto_label {
284            self.pager.render_auto_label(idx);
285        }
286        self.pager.render_widget(idx, render_fn)
287    }
288
289    /// Render an optional stateful widget and its label, if any.
290    #[inline(always)]
291    pub fn render_opt<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
292    where
293        FN: FnOnce() -> Option<WW>,
294        WW: StatefulWidget<State = SS>,
295        SS: RelocatableState,
296    {
297        let Some(idx) = self.pager.widget_idx(widget) else {
298            return false;
299        };
300        if self.auto_label {
301            self.pager.render_auto_label(idx);
302        }
303        if !self.pager.render_opt(idx, render_fn, state) {
304            self.hidden(state);
305            false
306        } else {
307            true
308        }
309    }
310
311    /// Render a stateful widget and its label, if any.
312    #[inline(always)]
313    pub fn render<FN, WW, SS>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> bool
314    where
315        FN: FnOnce() -> WW,
316        WW: StatefulWidget<State = SS>,
317        SS: RelocatableState,
318    {
319        let Some(idx) = self.pager.widget_idx(widget) else {
320            return false;
321        };
322        if self.auto_label {
323            self.pager.render_auto_label(idx);
324        }
325        if self.pager.render(idx, render_fn, state) {
326            true
327        } else {
328            self.hidden(state);
329            false
330        }
331    }
332
333    /// Render a stateful widget and its label, if any.
334    /// The closure can return a second value, which will be forwarded
335    /// if the widget is visible.
336    #[inline(always)]
337    #[allow(clippy::question_mark)]
338    pub fn render2<FN, WW, SS, R>(&mut self, widget: W, render_fn: FN, state: &mut SS) -> Option<R>
339    where
340        FN: FnOnce() -> (WW, R),
341        WW: StatefulWidget<State = SS>,
342        SS: RelocatableState,
343    {
344        let Some(idx) = self.pager.widget_idx(widget) else {
345            return None;
346        };
347        if self.auto_label {
348            self.pager.render_auto_label(idx);
349        }
350        if let Some(remainder) = self.pager.render2(idx, render_fn, state) {
351            Some(remainder)
352        } else {
353            self.hidden(state);
354            None
355        }
356    }
357
358    /// Calculate the necessary shift from view to screen.
359    /// This does nothing as pager always places the widgets
360    /// in screen coordinates.
361    ///
362    /// Just to keep the api in sync with [Clipper](crate::clipper::Clipper).
363    pub fn shift(&self) -> (i16, i16) {
364        (0, 0)
365    }
366
367    /// Relocate the widget area to screen coordinates.
368    /// Returns None if the widget is not visible.
369    /// This clips the area to page_area.
370    #[allow(clippy::question_mark)]
371    pub fn locate_widget(&self, widget: W) -> Option<Rect> {
372        let Some(idx) = self.pager.widget_idx(widget) else {
373            return None;
374        };
375        self.pager.locate_widget(idx)
376    }
377
378    /// Relocate the label area to screen coordinates.
379    /// Returns None if the widget is not visible.
380    /// This clips the area to page_area.
381    #[allow(clippy::question_mark)]
382    pub fn locate_label(&self, widget: W) -> Option<Rect> {
383        let Some(idx) = self.pager.widget_idx(widget) else {
384            return None;
385        };
386        self.pager.locate_label(idx)
387    }
388
389    /// Relocate an area from layout coordinates to screen coordinates.
390    /// A result None indicates that the area is invisible.
391    ///
392    /// This will clip the area to the page_area.
393    pub fn locate_area(&self, area: Rect) -> Option<Rect> {
394        self.pager.locate_area(area)
395    }
396
397    /// Does nothing for pager.
398    /// Just to keep the api in sync with [Clipper](crate::clipper::Clipper).
399    pub fn relocate<S>(&self, _state: &mut S)
400    where
401        S: RelocatableState,
402    {
403    }
404
405    /// Clear the areas in the widget-state.
406    /// This is called by render_xx whenever a widget is invisible.
407    pub fn hidden<S>(&self, state: &mut S)
408    where
409        S: RelocatableState,
410    {
411        state.relocate((0, 0), Rect::default())
412    }
413
414    /// Get access to the buffer during rendering a page.
415    pub fn buffer<'b>(&'b mut self) -> RefMut<'b, &'a mut Buffer> {
416        self.pager.buffer()
417    }
418}
419
420impl<W> Default for FormState<W>
421where
422    W: Eq + Hash + Clone,
423{
424    fn default() -> Self {
425        Self {
426            layout: Default::default(),
427            non_exhaustive: NonExhaustive,
428        }
429    }
430}
431
432impl<W> FormState<W>
433where
434    W: Eq + Hash + Clone,
435{
436    pub fn new() -> Self {
437        Self::default()
438    }
439
440    /// Clear the layout data and reset the page/page-count.
441    pub fn clear(&mut self) {
442        self.layout.borrow_mut().clear();
443    }
444
445    /// Layout needs to change?
446    pub fn valid_layout(&self, size: Size) -> bool {
447        let layout = self.layout.borrow();
448        !layout.size_changed(size) && !layout.is_empty()
449    }
450
451    /// Set the layout.
452    pub fn set_layout(&mut self, layout: GenericLayout<W>) {
453        self.layout = Rc::new(RefCell::new(layout));
454    }
455
456    /// Layout.
457    pub fn layout(&self) -> Ref<'_, GenericLayout<W>> {
458        self.layout.borrow()
459    }
460}