rat_widget/layout/
layout_form.rs

1use crate::layout::generic_layout::GenericLayout;
2use crate::util::block_padding;
3use ratatui::layout::{Flex, Rect, Size};
4use ratatui::widgets::{Block, Padding};
5use std::borrow::Cow;
6use std::cmp::{max, min};
7use std::fmt::Debug;
8use std::hash::Hash;
9use std::ops::Range;
10
11/// Label constraints.
12///
13/// Any given widths and heights will be reduced if there is not enough space.
14#[derive(Debug, Default)]
15pub enum FormLabel {
16    /// No label, just the widget.
17    #[default]
18    None,
19    /// Label by example.
20    /// Line breaks in the text don't work.
21    ///
22    /// Will create a label area with the max width of all labels and a height of 1.
23    /// The area will be top aligned with the widget.
24    Str(&'static str),
25    /// Label by example.
26    /// Line breaks in the text don't work.
27    ///
28    /// Will create a label area with the max width of all labels and a height of 1.
29    /// The area will be top aligned with the widget.
30    String(String),
31    /// Label by width.
32    ///
33    /// Will create a label area with the max width of all labels and a height of 1.
34    /// The area will be top aligned with the widget.
35    ///
36    ///  __unit: cols__
37    Width(u16),
38    /// Label by height+width.
39    ///
40    /// Will create a label area with the max width of all labels and a height of rows.
41    /// The area will be top aligned with the widget.
42    ///
43    ///  __unit: cols,rows__
44    Size(u16, u16),
45}
46
47/// Widget constraints.
48///
49/// Any given widths and heights will be reduced if there is not enough space.
50#[derive(Debug, Default)]
51pub enum FormWidget {
52    /// No widget, just a label.
53    #[default]
54    None,
55    /// Widget aligned with the label.
56    ///
57    /// Will create an area with the given width and height 1.
58    /// The area will be top aligned with the label.
59    ///
60    /// __unit: cols__
61    Width(u16),
62    /// Widget aligned with the label.
63    ///
64    /// Will create an area with the given width and height.
65    /// The area will be top aligned with the label.
66    ///
67    /// __unit: cols,rows__
68    Size(u16, u16),
69    /// Widget aligned with the label.
70    ///
71    /// The widget will start with the given number of rows.
72    /// If there is remaining vertical space left after a page-break this
73    /// widget will get it. If there are more than one such widget
74    /// the remainder will be evenly distributed.
75    ///
76    /// Will create an area with the given width and height.
77    /// The area will be top aligned with the label.
78    ///
79    /// __unit: cols,rows__
80    StretchY(u16, u16),
81    /// Fill the total width of labels+widget.
82    /// Any label that is not FormLabel::None will be placed above
83    /// the widget.
84    ///
85    /// Will create an area with the full width of labels + widgets
86    /// and the given height.
87    ///
88    /// __unit: cols,rows__
89    Wide(u16, u16),
90
91    /// Stretch the widget to the maximum extent horizontally.
92    ///
93    /// Will create an area with the full width of the given area,
94    /// still respecting labels, borders and blocks.
95    ///
96    /// __unit: cols,rows__
97    StretchX(u16, u16),
98
99    /// Stretch the widget to the maximum extend horizontally,
100    /// including the label. (rows).
101    ///
102    /// Will create an area with the full width of the given area,
103    /// still respecting borders and blocks.
104    ///
105    /// __unit: cols,rows__
106    WideStretchX(u16, u16),
107
108    /// Stretch the widget to the maximum extent horizontally and vertically.
109    ///
110    /// The widget will start with the given number of rows.
111    /// If there is remaining vertical space left after a page-break this
112    /// widget will get it. If there are more than one such widget
113    /// the remainder will be evenly distributed.
114    ///
115    /// __unit: cols,rows__
116    StretchXY(u16, u16),
117
118    /// Stretch the widget to the maximum extent horizontally and vertically,
119    /// including the label.
120    ///
121    /// The widget will start with the given number of rows.
122    /// If there is remaining vertical space left after a page-break this
123    /// widget will get it. If there are more than one such widget
124    /// the remainder will be evenly distributed.
125    ///
126    /// __unit: rows__
127    WideStretchXY(u16, u16),
128}
129
130/// Create a layout with a single column of label+widget.
131///
132/// There are a number of possible constraints that influence
133/// the exact layout: [FormLabel] and [FormWidget].
134///
135/// * This layout can page break the form, if there is not enough
136///   space on one page. This can be used with [SinglePager](crate::pager::SinglePager)
137///   and friends.
138///
139/// * Or it can generate an endless layout that will be used
140///   with scrolling logic like [Clipper](crate::clipper::Clipper).
141///
142/// * There is currently no functionality to shrink-fit the layout
143///   to a given page size.
144///
145/// The widgets can be grouped together and a [Block] can be set
146/// to highlight this grouping. Groups can cascade. Groups will
147/// be correctly broken by the page break logic. There is no
148/// special handling for orphans and widows.
149///
150/// Other features:
151/// * Spacing/Line spacing.
152/// * Supports Flex.
153/// * Manual page breaks.
154///
155/// ```rust no_run
156/// # use ratatui::buffer::Buffer;
157/// # use ratatui::layout::{Flex, Rect};
158/// # use ratatui::text::Span;
159/// # use ratatui::widgets::{Padding, Widget, StatefulWidget};
160/// # use rat_focus::{FocusFlag, HasFocus};
161/// # use rat_text::text_input::{TextInput, TextInputState};
162/// # use rat_widget::layout::{FormLabel, FormWidget, GenericLayout, LayoutForm};
163/// #
164/// # struct State {
165/// #     layout: GenericLayout<FocusFlag>,
166/// #     text1: TextInputState,
167/// #     text2: TextInputState,
168/// #     text3: TextInputState,
169/// # }
170/// #
171/// # let mut state = State {layout: Default::default(),text1: Default::default(),text2: Default::default(),text3: Default::default()};
172/// # let area = Rect::default();
173/// # let mut buf = Buffer::empty(Rect::default());
174/// # let buf = &mut buf;
175///
176/// if state.layout.area_changed(area) {
177///     let mut form_layout = LayoutForm::new()
178///             .spacing(1)
179///             .flex(Flex::Legacy)
180///             .line_spacing(1)
181///             .min_label(10);
182///
183///     form_layout.widget(
184///         state.text1.focus(),
185///         FormLabel::Str("Text 1"),
186///         // single row
187///         FormWidget::Width(22)
188///     );
189///     form_layout.widget(
190///         state.text2.focus(),
191///         FormLabel::Str("Text 2"),
192///         // stretch to the form-width, preferred with 15, 1 row high.
193///         FormWidget::StretchX(15, 1)
194///     );
195///     form_layout.widget(
196///         state.text3.focus(),
197///         FormLabel::Str("Text 3"),
198///         // stretch to the form-width and fill vertically.
199///         // preferred width is 15 3 rows high.
200///         FormWidget::StretchXY(15, 3)
201///     );
202///
203///     // calculate the layout and place it.
204///     state.layout = form_layout.paged(area.as_size(), Padding::default())
205///         .place(area.as_position());
206///  }
207///
208///  let layout = &state.layout;
209///
210///  // this is not really the intended use, but it works.
211///  // in reality, you would use [Clipper], [SinglePager],
212///  // [DualPager] or [Form].
213///
214///  let lbl1= layout.label_for(state.text1.focus());
215///  Span::from(layout.label_str_for(state.text1.focus()))
216///     .render(lbl1, buf);
217///  let txt1 = layout.widget_for(state.text1.focus());
218///  TextInput::new()
219///     .render(txt1, buf, &mut state.text1);
220///
221///  let lbl2 = layout.label_for(state.text2.focus());
222///  Span::from(layout.label_str_for(state.text2.focus()))
223///     .render(lbl2, buf);
224///  let txt2 = layout.widget_for(state.text2.focus());
225///  TextInput::new()
226///     .render(txt2, buf, &mut state.text2);
227///
228///  let lbl3 = layout.label_for(state.text3.focus());
229///  Span::from(layout.label_str_for(state.text3.focus()))
230///     .render(lbl3, buf);
231///  let txt3 = layout.widget_for(state.text3.focus());
232///  TextInput::new()
233///     .render(txt3, buf, &mut state.text3);
234///
235/// ```
236///
237#[derive(Debug)]
238pub struct LayoutForm<W>
239where
240    W: Eq + Hash + Clone + Debug,
241{
242    /// Column spacing.
243    spacing: u16,
244    /// Line spacing.
245    line_spacing: u16,
246    /// Mirror the borders between even/odd pages.
247    mirror: bool,
248    /// Flex
249    flex: Flex,
250    /// Areas
251    widgets: Vec<WidgetDef<W>>,
252    /// Containers/Blocks
253    blocks: Vec<BlockDef>,
254    /// Page breaks.
255    page_breaks: Vec<usize>,
256
257    /// maximum width
258    max_label: u16,
259    max_widget: u16,
260
261    /// maximum padding due to containers.
262    max_left_padding: u16,
263    max_right_padding: u16,
264
265    /// container padding, accumulated.
266    /// current active top-padding. valid for 1 widget.
267    c_top: u16,
268    /// current active bottom-padding.
269    /// valid for every contained widget to calculate a page-break.
270    c_bottom: u16,
271    /// current left indent.
272    c_left: u16,
273    /// current right indent.
274    c_right: u16,
275}
276
277#[derive(Debug)]
278struct WidgetDef<W>
279where
280    W: Debug + Clone,
281{
282    // widget id
283    id: W,
284    // label constraint
285    label: FormLabel,
286    // label text
287    label_str: Option<Cow<'static, str>>,
288    // widget constraint
289    widget: FormWidget,
290    // effective top border due to container padding.
291    top_border: u16,
292    // effective bottom border due to container padding.
293    bottom_border: u16,
294    // optional bottom border. all containers that
295    // do not end exactly at this widget contribute.
296    opt_bottom_border: u16,
297}
298
299/// Tag for a group/block.
300#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
301pub struct BlockTag(usize);
302
303#[derive(Debug)]
304struct BlockDef {
305    id: BlockTag,
306    // block
307    block: Option<Block<'static>>,
308    // padding due to block
309    padding: Padding,
310    // under construction
311    constructing: bool,
312    // range into the widget vec
313    range: Range<usize>,
314    // calculated container area.
315    area: Rect,
316}
317
318#[derive(Debug)]
319struct BlockOut {
320    // block
321    block: Option<Block<'static>>,
322    // area
323    area: Rect,
324}
325
326// effective positions for layout construction.
327#[derive(Debug, Default, Clone, Copy)]
328struct Positions {
329    // label position
330    label_x: u16,
331    // label width, max.
332    label_width: u16,
333    // widget position
334    widget_x: u16,
335    // widget width, max.
336    widget_width: u16,
337    // left position for container blocks.
338    container_left: u16,
339    // right position for container blocks.
340    container_right: u16,
341    // total width label+spacing+widget
342    total_width: u16,
343}
344
345// Current page data
346#[derive(Debug, Default, Clone, Copy)]
347struct Page {
348    // page width
349    #[allow(dead_code)]
350    width: u16,
351    // page height
352    height: u16,
353    // top border
354    top: u16,
355    // bottom border
356    bottom: u16,
357    // maximum widget + label height
358    max_height: u16,
359
360    // page number
361    page_no: u16,
362    // page start y
363    y_page: u16,
364    // current y
365    y: u16,
366
367    // current line spacing.
368    // set to zero on page-break and restored after the first widget.
369    line_spacing: u16,
370
371    // container left pos
372    container_left: u16,
373    // container right pos
374    container_right: u16,
375}
376
377impl BlockDef {
378    fn as_out(&self) -> BlockOut {
379        BlockOut {
380            block: self.block.clone(),
381            area: self.area,
382        }
383    }
384}
385
386impl FormWidget {
387    #[inline(always)]
388    fn is_stretch_y(&self) -> bool {
389        match self {
390            FormWidget::None => false,
391            FormWidget::Width(_) => false,
392            FormWidget::Size(_, _) => false,
393            FormWidget::Wide(_, _) => false,
394            FormWidget::StretchX(_, _) => false,
395            FormWidget::WideStretchX(_, _) => false,
396            FormWidget::StretchXY(_, _) => true,
397            FormWidget::WideStretchXY(_, _) => true,
398            FormWidget::StretchY(_, _) => true,
399        }
400    }
401}
402
403impl<W> Default for LayoutForm<W>
404where
405    W: Eq + Clone + Hash + Debug,
406{
407    fn default() -> Self {
408        Self {
409            spacing: 1,
410            line_spacing: Default::default(),
411            mirror: Default::default(),
412            flex: Default::default(),
413            widgets: Default::default(),
414            page_breaks: Default::default(),
415            max_label: Default::default(),
416            max_widget: Default::default(),
417            blocks: Default::default(),
418            max_left_padding: Default::default(),
419            max_right_padding: Default::default(),
420            c_top: Default::default(),
421            c_bottom: Default::default(),
422            c_left: Default::default(),
423            c_right: Default::default(),
424        }
425    }
426}
427
428impl<W> LayoutForm<W>
429where
430    W: Eq + Hash + Clone + Debug,
431{
432    pub fn new() -> Self {
433        Self::default()
434    }
435
436    /// Spacing between label and widget.
437    #[inline]
438    pub fn spacing(mut self, spacing: u16) -> Self {
439        self.spacing = spacing;
440        self
441    }
442
443    /// Empty lines between widgets.
444    #[inline]
445    pub fn line_spacing(mut self, spacing: u16) -> Self {
446        self.line_spacing = spacing;
447        self
448    }
449
450    /// Mirror the border given to layout between even and odd pages.
451    /// The layout starts with page 0 which is even.
452    #[inline]
453    pub fn mirror_odd_border(mut self) -> Self {
454        self.mirror = true;
455        self
456    }
457
458    /// Flex.
459    #[inline]
460    pub fn flex(mut self, flex: Flex) -> Self {
461        self.flex = flex;
462        self
463    }
464
465    /// Set a reference label width
466    pub fn min_label(mut self, width: u16) -> Self {
467        self.max_label = width;
468        self
469    }
470
471    /// Set a reference widget width
472    pub fn min_widget(mut self, width: u16) -> Self {
473        self.max_widget = width;
474        self
475    }
476
477    /// Start a group/block.
478    ///
479    /// This will create a block that covers all widgets added
480    /// before calling `end()`.
481    ///
482    /// Groups/blocks can be stacked, but they cannot interleave.
483    /// An inner group/block must be closed before an outer one.
484    pub fn start(&mut self, block: Option<Block<'static>>) -> BlockTag {
485        let max_idx = self.widgets.len();
486        let padding = block_padding(&block);
487
488        let tag = BlockTag(self.blocks.len());
489        self.blocks.push(BlockDef {
490            id: tag,
491            block,
492            padding,
493            constructing: true,
494            range: max_idx..max_idx,
495            area: Rect::default(),
496        });
497
498        self.c_top += padding.top;
499        self.c_bottom += padding.bottom;
500        self.c_left += padding.left;
501        self.c_right += padding.right;
502
503        self.max_left_padding = max(self.max_left_padding, self.c_left);
504        self.max_right_padding = max(self.max_right_padding, self.c_right);
505
506        tag
507    }
508
509    /// Closes the group/block with the given tag.
510    ///
511    /// __Panic__
512    /// Groups must be closed in reverse start order, otherwise
513    /// this function will panic. It will also panic if there
514    /// is no open group for the given tag.
515    pub fn end(&mut self, tag: BlockTag) {
516        let max = self.widgets.len();
517        for cc in self.blocks.iter_mut().rev() {
518            if cc.id == tag && cc.constructing {
519                cc.range.end = max;
520                cc.constructing = false;
521
522                // might have been used by a widget.
523                if self.c_top > 0 {
524                    self.c_top -= cc.padding.top;
525                }
526                self.c_bottom -= cc.padding.bottom;
527                self.c_left -= cc.padding.left;
528                self.c_right -= cc.padding.right;
529
530                if let Some(last) = self.widgets.last_mut() {
531                    last.opt_bottom_border -= cc.padding.bottom;
532                }
533
534                return;
535            }
536            if cc.constructing {
537                panic!("Unclosed container {:?}", cc.id);
538            }
539        }
540
541        panic!("No open container.");
542    }
543
544    fn validate_containers(&self) {
545        for cc in self.blocks.iter() {
546            if cc.constructing {
547                panic!("Unclosed container {:?}", cc.id);
548            }
549        }
550    }
551
552    /// Add label + widget constraint.
553    /// Key must be a unique identifier.
554    pub fn widget(&mut self, key: W, label: FormLabel, widget: FormWidget) {
555        // split label by sample
556        let (label, label_str) = match label {
557            FormLabel::Str(s) => {
558                let width = unicode_display_width::width(s) as u16;
559                (FormLabel::Width(width), Some(Cow::Borrowed(s)))
560            }
561            FormLabel::String(s) => {
562                let width = unicode_display_width::width(&s) as u16;
563                (FormLabel::Width(width), Some(Cow::Owned(s)))
564            }
565            FormLabel::Width(w) => (FormLabel::Width(w), None),
566            FormLabel::Size(w, h) => (FormLabel::Size(w, h), None),
567            FormLabel::None => (FormLabel::None, None),
568        };
569
570        let w = match &label {
571            FormLabel::None => 0,
572            FormLabel::Str(_) | FormLabel::String(_) => {
573                unreachable!()
574            }
575            FormLabel::Width(w) => *w,
576            FormLabel::Size(w, _) => *w,
577        };
578        self.max_label = max(self.max_label, w);
579
580        let w = match &widget {
581            FormWidget::None => 0,
582            FormWidget::Width(w) => *w,
583            FormWidget::Size(w, _) => *w,
584            FormWidget::StretchY(w, _) => *w,
585            FormWidget::Wide(w, _) => *w,
586            FormWidget::StretchX(w, _) => *w,
587            FormWidget::WideStretchX(w, _) => *w,
588            FormWidget::StretchXY(w, _) => *w,
589            FormWidget::WideStretchXY(w, _) => *w,
590        };
591        self.max_widget = max(self.max_widget, w);
592
593        self.widgets.push(WidgetDef {
594            id: key,
595            label,
596            label_str,
597            widget,
598            top_border: self.c_top,
599            bottom_border: self.c_bottom,
600            opt_bottom_border: self.c_bottom,
601        });
602
603        // top padding is only used once.
604        // bottom padding may apply for every contained widget
605        // in case of page-break.
606        self.c_top = 0;
607    }
608
609    /// Add a manual page break after the last widget.
610    ///
611    /// This will panic if the widget list is empty.
612    pub fn page_break(&mut self) {
613        self.page_breaks.push(self.widgets.len() - 1);
614    }
615
616    // Adjust widths to the available sapce.
617    fn adjust_widths(&mut self, page_width: u16, border: Padding) {
618        // cut excess
619        let page_width = page_width.saturating_sub(
620            border.left + self.max_left_padding + self.max_right_padding + border.right,
621        );
622        if self.max_label + self.spacing + self.max_widget > page_width {
623            let mut reduce = self.max_label + self.spacing + self.max_widget - page_width;
624
625            if self.spacing > reduce {
626                self.spacing -= reduce;
627                reduce = 0;
628            } else {
629                reduce -= self.spacing;
630                self.spacing = 0;
631            }
632            if self.max_label > 5 {
633                if self.max_label - 5 > reduce {
634                    self.max_label -= reduce;
635                    reduce = 0;
636                } else {
637                    reduce -= self.max_label - 5;
638                    self.max_label = 5;
639                }
640            }
641            if self.max_widget > 5 {
642                if self.max_widget - 5 > reduce {
643                    self.max_widget -= reduce;
644                    reduce = 0;
645                } else {
646                    reduce -= self.max_widget - 5;
647                    self.max_widget = 5;
648                }
649            }
650            if self.max_label > reduce {
651                self.max_label -= reduce;
652                reduce = 0;
653            } else {
654                reduce -= self.max_label;
655                self.max_label = 0;
656            }
657            if self.max_widget > reduce {
658                self.max_widget -= reduce;
659                // reduce = 0;
660            } else {
661                // reduce -= max_widget;
662                self.max_widget = 0;
663            }
664        }
665    }
666
667    // Find horizontal positions for label and widget.
668    fn find_pos(&self, layout_width: u16, border: Padding) -> Positions {
669        let label_x;
670        let widget_x;
671        let container_left;
672        let container_right;
673        let total_width;
674
675        match self.flex {
676            Flex::Legacy => {
677                label_x = border.left + self.max_left_padding;
678                widget_x = label_x + self.max_label + self.spacing;
679
680                container_left = label_x.saturating_sub(self.max_left_padding);
681                container_right = layout_width.saturating_sub(border.right);
682
683                total_width = self.max_label + self.spacing + self.max_widget;
684            }
685            Flex::Start => {
686                label_x = border.left + self.max_left_padding;
687                widget_x = label_x + self.max_label + self.spacing;
688
689                container_left = label_x.saturating_sub(self.max_left_padding);
690                container_right = widget_x + self.max_widget + self.max_right_padding;
691
692                total_width = self.max_label + self.spacing + self.max_widget;
693            }
694            Flex::Center => {
695                let rest = layout_width.saturating_sub(
696                    border.left
697                        + self.max_left_padding
698                        + self.max_label
699                        + self.spacing
700                        + self.max_widget
701                        + self.max_right_padding
702                        + border.right,
703                );
704                label_x = border.left + self.max_left_padding + rest / 2;
705                widget_x = label_x + self.max_label + self.spacing;
706
707                container_left = label_x.saturating_sub(self.max_left_padding);
708                container_right = widget_x + self.max_widget + self.max_right_padding;
709
710                total_width = self.max_label + self.spacing + self.max_widget;
711            }
712            Flex::End => {
713                widget_x = layout_width
714                    .saturating_sub(border.right + self.max_right_padding + self.max_widget);
715                label_x = widget_x.saturating_sub(self.spacing + self.max_label);
716
717                container_left = label_x.saturating_sub(self.max_left_padding);
718                container_right = layout_width.saturating_sub(border.right);
719
720                total_width = self.max_label + self.spacing + self.max_widget;
721            }
722            Flex::SpaceAround => {
723                let rest = layout_width.saturating_sub(
724                    border.left
725                        + self.max_left_padding
726                        + self.max_label
727                        + self.max_widget
728                        + self.max_right_padding
729                        + border.right,
730                );
731                let spacing = rest / 3;
732
733                label_x = border.left + self.max_left_padding + spacing;
734                widget_x = label_x + self.max_label + spacing;
735
736                container_left = border.left;
737                container_right = layout_width.saturating_sub(border.right);
738
739                total_width = self.max_label + spacing + self.max_widget;
740            }
741            Flex::SpaceBetween => {
742                label_x = border.left + self.max_left_padding;
743                widget_x = layout_width
744                    .saturating_sub(border.right + self.max_right_padding + self.max_widget);
745
746                container_left = label_x.saturating_sub(self.max_left_padding);
747                container_right = layout_width.saturating_sub(border.right);
748
749                total_width = layout_width.saturating_sub(
750                    border.left + self.max_left_padding + border.right + self.max_right_padding,
751                );
752            }
753        }
754
755        Positions {
756            container_left,
757            label_x,
758            label_width: self.max_label,
759            widget_x,
760            widget_width: self.max_widget,
761            container_right,
762            total_width,
763        }
764    }
765
766    /// Calculate a layout without page-breaks using the given layout-width and padding.
767    #[inline(always)]
768    pub fn endless(self, width: u16, border: Padding) -> GenericLayout<W> {
769        self._layout::<true>(Size::new(width, u16::MAX), border)
770    }
771
772    /// Calculate the layout for the given page size and padding.
773    #[inline(always)]
774    pub fn paged(self, page: Size, border: Padding) -> GenericLayout<W> {
775        self._layout::<false>(page, border)
776    }
777
778    /// Calculate the layout for the given page size and padding.
779    fn _layout<const ENDLESS: bool>(mut self, page: Size, border: Padding) -> GenericLayout<W> {
780        self.validate_containers();
781        self.adjust_widths(page.width, border);
782        let pos_even = self.find_pos(page.width, border);
783        let pos_odd = if self.mirror {
784            self.find_pos(
785                page.width,
786                Padding::new(border.right, border.left, border.top, border.bottom),
787            )
788        } else {
789            pos_even
790        };
791
792        let mut gen_layout = GenericLayout::with_capacity(self.widgets.len(), self.blocks.len());
793        gen_layout.set_page_size(page);
794
795        let mut tmp = Vec::new();
796
797        let mut pos = &pos_even;
798        let mut page_bak;
799        let mut page = Page {
800            width: page.width,
801            height: page.height,
802            top: border.top,
803            bottom: border.bottom,
804            max_height: page.height.saturating_sub(border.top + border.bottom),
805
806            page_no: 0,
807            y_page: 0,
808            y: border.top,
809
810            line_spacing: 0,
811
812            container_left: pos.container_left,
813            container_right: pos.container_right,
814        };
815        // indexes into gen_layout for any generated areas that need y adjustment.
816        let mut stretch_y = Vec::new();
817
818        for (idx, widget) in self.widgets.into_iter().enumerate() {
819            // safe point
820            page_bak = page;
821
822            // line spacing
823            page.next_widget(self.line_spacing);
824            // start container
825            for cc in self.blocks.iter_mut() {
826                if cc.range.start == idx {
827                    page.start_container(cc);
828                }
829            }
830            // get areas + advance
831            let (mut label_area, mut widget_area) = page.widget_area(&widget, pos);
832            // end and push containers
833            for cc in self.blocks.iter_mut().rev() {
834                if idx + 1 == cc.range.end {
835                    page.end_container(cc);
836                    tmp.push(cc.as_out());
837                }
838            }
839
840            let break_overflow = if ENDLESS {
841                false
842            } else {
843                page.y.saturating_add(widget.opt_bottom_border)
844                    >= page
845                        .y_page
846                        .saturating_add(page.height.saturating_sub(page.bottom))
847            };
848            let break_manual = if ENDLESS {
849                false
850            } else {
851                self.page_breaks.contains(&idx)
852            };
853
854            // page overflow induces page-break
855            if break_overflow {
856                // reset safe-point
857                page = page_bak;
858                // any container areas are invalid
859                tmp.clear();
860
861                // close and push containers
862                // rev() ensures closing from innermost to outermost container.
863                for cc in self.blocks.iter_mut().rev() {
864                    if idx > cc.range.start && idx < cc.range.end {
865                        page.end_container(cc);
866                        tmp.push(cc.as_out());
867                        // restart on next page
868                        cc.range.start = idx;
869                    }
870                }
871                // pop reverts the ordering for render
872                while let Some(cc) = tmp.pop() {
873                    gen_layout.add_block(cc.area, cc.block);
874                }
875
876                // modify layout to add y-stretch
877                Self::adjust_y_stretch(&page, &mut stretch_y, &mut gen_layout);
878
879                // advance
880                pos = page.next_page(&pos_even, &pos_odd);
881
882                // redo current widget
883
884                // line spacing
885                page.next_widget(self.line_spacing);
886                // start container
887                for cc in self.blocks.iter_mut() {
888                    if idx == cc.range.start {
889                        page.start_container(cc);
890                    }
891                }
892                // get areas + advance
893                (label_area, widget_area) = page.widget_area(&widget, pos);
894                // end and push containers
895                // rev() ensures closing from innermost to outermost container.
896                for cc in self.blocks.iter_mut().rev() {
897                    if idx + 1 == cc.range.end {
898                        page.end_container(cc);
899                        tmp.push(cc.as_out());
900                    }
901                }
902            }
903
904            // remember stretch widget.
905            if widget.widget.is_stretch_y() && !ENDLESS {
906                stretch_y.push(gen_layout.widget_len());
907            }
908            // add label + widget
909            gen_layout.add(widget.id.clone(), widget_area, widget.label_str, label_area);
910            // pop reverts the ordering for render
911            while let Some(cc) = tmp.pop() {
912                gen_layout.add_block(cc.area, cc.block);
913            }
914
915            if break_manual {
916                // page-break after widget
917
918                // close and push containers
919                // rev() ensures closing from innermost to outermost container.
920                for cc in self.blocks.iter_mut().rev() {
921                    if idx + 1 > cc.range.start && idx + 1 < cc.range.end {
922                        page.end_container(cc);
923                        tmp.push(cc.as_out());
924                        // restart on next page
925                        cc.range.start = idx + 1;
926                    }
927                }
928                // pop reverts the ordering for render
929                while let Some(cc) = tmp.pop() {
930                    gen_layout.add_block(cc.area, cc.block);
931                }
932
933                // modify layout to add y-stretch
934                Self::adjust_y_stretch(&page, &mut stretch_y, &mut gen_layout);
935
936                // advance
937                pos = page.next_page(&pos_even, &pos_odd);
938            }
939        }
940
941        // modify layout to add y-stretch
942        Self::adjust_y_stretch(&page, &mut stretch_y, &mut gen_layout);
943
944        gen_layout.set_page_count((page.page_no + 1) as usize);
945
946        gen_layout
947    }
948
949    // some stretching
950    // stretch_y contains the recorded widget indexes that need adjustment.
951    fn adjust_y_stretch(
952        page: &Page,
953        stretch_y: &mut Vec<usize>,
954        gen_layout: &mut GenericLayout<W>,
955    ) {
956        let bottom_y = page
957            .y_page
958            .saturating_add(page.height.saturating_sub(page.bottom));
959
960        let mut remainder = bottom_y.saturating_sub(page.y);
961        if remainder == 0 {
962            return;
963        }
964        let mut n = stretch_y.len() as u16;
965
966        for y_idx in stretch_y.drain(..) {
967            // calculate stretch as a new fraction every time.
968            // this makes a better distribution.
969            let stretch = remainder / n;
970            remainder -= stretch;
971            n -= 1;
972
973            // stretch
974            let mut area = gen_layout.widget(y_idx);
975            let test_y = area.bottom();
976            area.height += stretch;
977            gen_layout.set_widget(y_idx, area);
978
979            // shift everything after
980            for idx in y_idx + 1..gen_layout.widget_len() {
981                let mut area = gen_layout.widget(idx);
982                if area.y >= test_y {
983                    area.y += stretch;
984                }
985                gen_layout.set_widget(idx, area);
986
987                let mut area = gen_layout.label(idx);
988                if area.y >= test_y {
989                    area.y += stretch;
990                }
991                gen_layout.set_label(idx, area);
992            }
993
994            // containers may be shifted or stretched.
995            for idx in 0..gen_layout.block_len() {
996                let mut area = gen_layout.block_area(idx);
997                if area.y >= test_y {
998                    area.y += stretch;
999                }
1000                // may stretch the container
1001                if area.y <= test_y && area.bottom() > test_y {
1002                    area.height += stretch;
1003                }
1004                gen_layout.set_block_area(idx, area);
1005            }
1006        }
1007    }
1008}
1009
1010impl Page {
1011    fn widget_area<W: Debug + Clone>(
1012        &mut self,
1013        widget: &WidgetDef<W>,
1014        pos: &Positions,
1015    ) -> (Rect, Rect) {
1016        let stacked = matches!(
1017            widget.widget,
1018            FormWidget::Wide(_, _)
1019                | FormWidget::WideStretchX(_, _)
1020                | FormWidget::WideStretchXY(_, _)
1021        );
1022
1023        let mut label_height = match &widget.label {
1024            FormLabel::None => 0,
1025            FormLabel::Str(_) | FormLabel::String(_) => unreachable!(),
1026            FormLabel::Width(_) => 1,
1027            FormLabel::Size(_, h) => *h,
1028        };
1029
1030        let mut widget_height = match &widget.widget {
1031            FormWidget::None => 0,
1032            FormWidget::Width(_) => 1,
1033            FormWidget::Size(_, h) => *h,
1034            FormWidget::StretchY(_, h) => *h,
1035            FormWidget::Wide(_, h) => *h,
1036            FormWidget::StretchX(_, h) => *h,
1037            FormWidget::WideStretchX(_, h) => *h,
1038            FormWidget::StretchXY(_, h) => *h,
1039            FormWidget::WideStretchXY(_, h) => *h,
1040        };
1041
1042        let stretch_width = self.container_right.saturating_sub(pos.widget_x);
1043        let total_stretch_width = self.container_right.saturating_sub(pos.label_x);
1044
1045        if stacked {
1046            let max_height = self
1047                .max_height
1048                .saturating_sub(widget.top_border + widget.bottom_border);
1049            if label_height + widget_height > max_height {
1050                label_height = min(1, max_height.saturating_sub(widget_height));
1051            }
1052            if label_height + widget_height > max_height {
1053                widget_height = min(1, max_height.saturating_sub(label_height));
1054            }
1055            if label_height + widget_height > max_height {
1056                label_height = 0;
1057            }
1058            if label_height + widget_height > max_height {
1059                widget_height = max_height;
1060            }
1061
1062            let mut label_area = match &widget.label {
1063                FormLabel::None => Rect::default(),
1064                FormLabel::Str(_) | FormLabel::String(_) => {
1065                    unreachable!()
1066                }
1067                FormLabel::Width(_) => {
1068                    Rect::new(pos.label_x, self.y, pos.label_width, label_height)
1069                }
1070                FormLabel::Size(_, _) => {
1071                    Rect::new(pos.label_x, self.y, pos.label_width, label_height)
1072                }
1073            };
1074            match &widget.widget {
1075                FormWidget::Wide(_, _) => label_area.width = pos.total_width,
1076                FormWidget::WideStretchX(_, _) => label_area.width = total_stretch_width,
1077                FormWidget::WideStretchXY(_, _) => label_area.width = total_stretch_width,
1078                _ => {}
1079            }
1080
1081            self.y = self.y.saturating_add(label_area.height);
1082
1083            let widget_area = match &widget.widget {
1084                FormWidget::None => Rect::default(),
1085                FormWidget::Width(w) => Rect::new(
1086                    pos.widget_x,
1087                    self.y,
1088                    min(*w, pos.widget_width),
1089                    widget_height,
1090                ),
1091                FormWidget::Size(w, _) => Rect::new(
1092                    pos.widget_x,
1093                    self.y,
1094                    min(*w, pos.widget_width),
1095                    widget_height,
1096                ),
1097                FormWidget::StretchY(w, _) => Rect::new(
1098                    pos.widget_x,
1099                    self.y,
1100                    min(*w, pos.widget_width),
1101                    widget_height,
1102                ),
1103                FormWidget::Wide(_, _) => {
1104                    Rect::new(pos.label_x, self.y, pos.total_width, widget_height)
1105                }
1106                FormWidget::StretchX(_, _) => {
1107                    Rect::new(pos.widget_x, self.y, stretch_width, widget_height)
1108                }
1109                FormWidget::WideStretchX(_, _) => {
1110                    Rect::new(pos.label_x, self.y, total_stretch_width, widget_height)
1111                }
1112                FormWidget::StretchXY(_, _) => {
1113                    Rect::new(pos.widget_x, self.y, stretch_width, widget_height)
1114                }
1115                FormWidget::WideStretchXY(_, _) => {
1116                    Rect::new(pos.label_x, self.y, total_stretch_width, widget_height)
1117                }
1118            };
1119
1120            self.y = self.y.saturating_add(widget_area.height);
1121
1122            (label_area, widget_area)
1123        } else {
1124            let max_height = self
1125                .max_height
1126                .saturating_sub(widget.top_border + widget.bottom_border);
1127            label_height = min(label_height, max_height);
1128            widget_height = min(widget_height, max_height);
1129
1130            let label_area = match &widget.label {
1131                FormLabel::None => Rect::default(),
1132                FormLabel::Str(_) | FormLabel::String(_) => {
1133                    unreachable!()
1134                }
1135                FormLabel::Width(_) => {
1136                    Rect::new(pos.label_x, self.y, pos.label_width, label_height)
1137                }
1138                FormLabel::Size(_, _) => {
1139                    Rect::new(pos.label_x, self.y, pos.label_width, label_height)
1140                }
1141            };
1142
1143            let widget_area = match &widget.widget {
1144                FormWidget::None => Rect::default(),
1145                FormWidget::Width(w) => Rect::new(
1146                    pos.widget_x,
1147                    self.y,
1148                    min(*w, pos.widget_width),
1149                    widget_height,
1150                ),
1151                FormWidget::Size(w, _) => Rect::new(
1152                    pos.widget_x,
1153                    self.y,
1154                    min(*w, pos.widget_width),
1155                    widget_height,
1156                ),
1157                FormWidget::StretchY(w, _) => Rect::new(
1158                    pos.widget_x,
1159                    self.y,
1160                    min(*w, pos.widget_width),
1161                    widget_height,
1162                ),
1163                FormWidget::Wide(_, _) => {
1164                    unreachable!()
1165                }
1166                FormWidget::StretchX(_, _) => {
1167                    Rect::new(pos.widget_x, self.y, stretch_width, widget_height)
1168                }
1169                FormWidget::WideStretchX(_, _) => {
1170                    unreachable!()
1171                }
1172                FormWidget::StretchXY(_, _) => {
1173                    Rect::new(pos.widget_x, self.y, stretch_width, widget_height)
1174                }
1175                FormWidget::WideStretchXY(_, _) => {
1176                    unreachable!()
1177                }
1178            };
1179
1180            self.y = self
1181                .y
1182                .saturating_add(max(label_area.height, widget_area.height));
1183
1184            (label_area, widget_area)
1185        }
1186    }
1187
1188    // advance to next page
1189    fn next_page<'a>(&mut self, pos_even: &'a Positions, pos_odd: &'a Positions) -> &'a Positions {
1190        self.page_no += 1;
1191        self.y_page = self.page_no.saturating_mul(self.height);
1192        self.y = self.y_page.saturating_add(self.top);
1193        self.line_spacing = 0;
1194
1195        if self.page_no % 2 == 0 {
1196            pos_even
1197        } else {
1198            pos_odd
1199        }
1200    }
1201
1202    // advance to next widget
1203    fn next_widget(&mut self, adjust_spacing: u16) {
1204        self.y = self.y.saturating_add(self.line_spacing);
1205        self.line_spacing = adjust_spacing;
1206    }
1207
1208    // close the given container
1209    fn end_container(&mut self, cc: &mut BlockDef) {
1210        self.y = self.y.saturating_add(cc.padding.bottom);
1211        self.container_left = self.container_left.saturating_sub(cc.padding.left);
1212        self.container_right = self.container_right.saturating_add(cc.padding.right);
1213
1214        cc.area.height = self.y.saturating_sub(cc.area.y);
1215    }
1216
1217    // open the given container
1218    fn start_container(&mut self, cc: &mut BlockDef) {
1219        cc.area.x = self.container_left;
1220        cc.area.width = self.container_right.saturating_sub(self.container_left);
1221        cc.area.y = self.y;
1222
1223        self.y = self.y.saturating_add(cc.padding.top);
1224        self.container_left = self.container_left.saturating_add(cc.padding.left);
1225        self.container_right = self.container_right.saturating_sub(cc.padding.right);
1226    }
1227}