rat_widget/layout/
layout_form.rs

1use crate::layout::generic_layout::GenericLayout;
2use crate::util::{block_padding, block_padding2};
3use ratatui::layout::{Flex, Rect, Size};
4use ratatui::widgets::{Block, Borders, Padding};
5use std::borrow::Cow;
6use std::cmp::{max, min};
7use std::collections::VecDeque;
8use std::fmt::Debug;
9use std::hash::Hash;
10use std::mem;
11use std::ops::Range;
12use std::rc::Rc;
13
14/// Label constraints for [LayoutForm].
15///
16/// Any given widths and heights will be reduced if there is not enough space.
17#[derive(Debug, Default)]
18pub enum FormLabel {
19    /// No label, just the widget.
20    #[default]
21    None,
22    /// Label by example.
23    ///
24    /// Line breaks in the text will not work with `Form` or `Clipper`.
25    ///
26    /// Will create a label area with the max width of all labels and a height of 1.
27    /// The area will be top aligned with the widget.
28    Str(&'static str),
29    /// Label by example.
30    ///
31    /// Line breaks in the text will not work with `Form` or `Clipper`.
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    String(String),
36    /// Label by width.
37    ///
38    /// Will create a label area with the max width of all labels and a height of 1.
39    /// The area will be top aligned with the widget.
40    ///
41    ///  __unit: cols__
42    Width(u16),
43    /// Label by height+width.
44    ///
45    /// Will create a label area with the max width of all labels and a height of rows.
46    /// The area will be top aligned with the widget.
47    ///
48    ///  __unit: cols,rows__
49    Size(u16, u16),
50}
51
52/// Widget constraints for [LayoutForm].
53///
54/// Any given widths and heights will be reduced if there is not enough space.
55#[derive(Debug, Default)]
56pub enum FormWidget {
57    /// No widget, just a label.
58    #[default]
59    None,
60    /// Widget aligned with the label.
61    ///
62    /// Will create an area with the given width and height 1.
63    ///
64    /// __unit: cols__
65    Width(u16),
66    /// Widget aligned with the label.
67    ///
68    /// Will create an area with the given width and height.
69    ///
70    /// __unit: cols,rows__
71    Size(u16, u16),
72    /// Widget aligned with the label.
73    ///
74    /// The widget will start with the given number of rows.
75    /// If there is remaining vertical space left after a page-break this
76    /// widget will get it. If there are more than one such widget
77    /// the remainder will be evenly distributed.
78    ///
79    /// __unit: cols,rows__
80    StretchY(u16, u16),
81    /// Widget stacked below the label.
82    ///
83    /// Any label that is not FormLabel::None will be placed above
84    /// the widget.
85    ///
86    /// __unit: cols,rows__
87    Wide(u16, u16),
88    /// Widget filling the maximum width.
89    ///
90    /// __unit: cols,rows__
91    StretchX(u16, u16),
92    /// Widget stacked below the label, with the maximum width.
93    ///
94    /// __unit: cols,rows__
95    WideStretchX(u16, u16),
96    /// Stretch the widget to the maximum width and height.
97    ///
98    /// The widget will start with the given number of rows.
99    /// If there is remaining vertical space left after a page-break this
100    /// widget will get it. If there are more than one such widget
101    /// the remainder will be evenly distributed.
102    ///
103    /// __unit: cols,rows__
104    StretchXY(u16, u16),
105
106    /// Widget stacked below the label, with the maximum width and height.
107    ///
108    /// The widget will start with the given number of rows.
109    /// If there is remaining vertical space left after a page-break this
110    /// widget will get it. If there are more than one such widget
111    /// the remainder will be evenly distributed.
112    ///
113    /// __unit: rows__
114    WideStretchXY(u16, u16),
115}
116
117/// Create layouts for input widgets.
118///
119/// This takes the constraints for [FormLabel] and [FormWidget]
120/// and creates a multi-column layout for them.
121///
122/// There are two branches of the layout:
123///
124/// - paged: Fill a target area with the widgets and start
125///   a new page when the layout overflows. You can use [Form](crate::form::Form)
126///   to render a multipage and navigate between the pages.
127///
128/// - endless: Just stack the widgets. You can use [Clipper](crate::clipper::Clipper)
129///   to render such a layout and scroll through it.
130///
131/// ## Both variants
132///
133/// Can
134/// - add Blocks for multiple widgets. Can stack them too.
135/// - layout labels above or to the left of the widget.
136/// - add manual column/page breaks.
137/// - align widgets vertically.
138/// - define line/column spacing.
139/// - flex layout columns.
140///
141/// Can __not__
142/// - forcibly shrink-fit the widgets to a layout size.
143///
144/// ## Paged layout
145///
146/// Can
147/// - create n columns of widgets.
148/// - stretch some widgets vertically to fill a column.
149///
150/// ## Endless layout
151///
152/// Can
153/// - create n columns of widgets.
154///     - to-do: optimally fill all columns
155///     - add manual breaks for now.
156/// - __not__ usefully stretch the widgets vertically.
157///
158/// ```rust no_run
159/// # use ratatui::buffer::Buffer;
160/// # use ratatui::layout::{Flex, Rect};
161/// # use ratatui::text::Span;
162/// # use ratatui::widgets::{Padding, Widget, StatefulWidget};
163/// # use rat_focus::{FocusFlag, HasFocus};
164/// # use rat_text::text_input::{TextInput, TextInputState};
165/// # use rat_widget::layout::{GenericLayout, LayoutForm};
166/// #
167/// # struct State {
168/// #     layout: GenericLayout<usize>,
169/// #     text1: TextInputState,
170/// #     text2: TextInputState,
171/// #     text3: TextInputState,
172/// # }
173/// #
174/// # let mut state = State {layout: Default::default(),text1: Default::default(),text2: Default::default(),text3: Default::default()};
175/// # let area = Rect::default();
176/// # let mut buf = Buffer::empty(Rect::default());
177/// # let buf = &mut buf;
178///
179/// if state.layout.area_changed(area) {
180///     let mut ll = LayoutForm::new()
181///             .spacing(1)
182///             .line_spacing(1)
183///             .column_spacing(1)
184///             .padding(Padding::new(2,2,0,0))
185///             .flex(Flex::Start)
186///             .min_label(10);
187///
188///     use rat_widget::layout::{FormLabel as L, FormWidget as W};
189///
190///     // single row
191///     ll.widget(state.text1.id(), L::Str("Text 1"), W::Width(22));
192///     // stretch to form width. preferred width 15. 1 row high.
193///     ll.widget(state.text2.id(), L::Str("Text 2"), W::StretchX(15, 1));
194///     // stretch to the form-width and fill vertically.
195///     // preferred width is 15 3 rows high.
196///     ll.widget(state.text3.id(), L::Str("Text 3"), W::StretchXY(15, 3));
197///
198///     // calculate the layout and place it.
199///     state.layout = ll.build_paged(area.as_size());
200///  }
201///
202///  let ll = &state.layout;
203///
204///  // This is not really the intended use, but it works.
205///  // In reality, you would use Clipper or Form.
206///
207///  let lbl1 = ll.label_for(state.text1.id());
208///  Span::from(ll.label_str_for(state.text1.id())).render(lbl1, buf);
209///
210///  let txt1 = ll.widget_for(state.text1.id());
211///  TextInput::new()
212///     .render(txt1, buf, &mut state.text1);
213///
214///  let lbl2 = ll.label_for(state.text2.id());
215///  Span::from(ll.label_str_for(state.text2.id())).render(lbl2, buf);
216///
217///  let txt2 = ll.widget_for(state.text2.id());
218///  TextInput::new()
219///     .render(txt2, buf, &mut state.text2);
220///
221///  let lbl3 = ll.label_for(state.text3.id());
222///  Span::from(ll.label_str_for(state.text3.id())).render(lbl3, buf);
223///
224///  let txt3 = ll.widget_for(state.text3.id());
225///  TextInput::new()
226///     .render(txt3, buf, &mut state.text3);
227///
228/// ```
229///
230#[derive(Debug)]
231pub struct LayoutForm<W>
232where
233    W: Eq + Hash + Clone + Debug,
234{
235    /// Column spacing.
236    spacing: u16,
237    /// Line spacing.
238    line_spacing: u16,
239    /// Column spacing
240    column_spacing: u16,
241    /// Page border.
242    page_border: Padding,
243    /// Mirror the borders between even/odd pages.
244    mirror_border: bool,
245    /// Layout as columns.
246    columns: u16,
247    /// Flex
248    flex: Flex,
249    /// Areas
250    widgets: Vec<WidgetDef<W>>,
251    /// Containers/Blocks
252    blocks: VecDeque<BlockDef>,
253    /// Page breaks.
254    page_breaks: Vec<usize>,
255
256    /// maximum width
257    min_label: u16,
258    min_widget: u16,
259    min_widget_wide: u16,
260
261    /// maximum padding due to containers.
262    max_left_padding: u16,
263    max_right_padding: u16,
264
265    /// current left indent.
266    left_padding: u16,
267    /// current right indent.
268    right_padding: u16,
269}
270
271#[derive(Debug)]
272struct WidgetDef<W>
273where
274    W: Debug + Clone,
275{
276    // widget id
277    id: W,
278    // label constraint
279    label: FormLabel,
280    // label text
281    label_str: Option<Cow<'static, str>>,
282    // widget constraint
283    widget: FormWidget,
284}
285
286/// Tag for a group/block.
287#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
288pub struct BlockTag(usize);
289
290#[derive(Debug)]
291struct BlockDef {
292    id: BlockTag,
293    // block
294    block: Option<Block<'static>>,
295    // padding due to block
296    padding: Padding,
297    // under construction
298    constructing: bool,
299    // range into the widget vec
300    range: Range<usize>,
301    // calculated container area.
302    area: Rect,
303}
304
305#[derive(Debug)]
306struct BlockOut {
307    // block
308    block: Option<Block<'static>>,
309    // area
310    area: Rect,
311}
312
313// effective positions for layout construction.
314#[derive(Debug, Default, Clone, Copy)]
315struct XPositions {
316    // label position
317    label_left: u16,
318    // label width, max.
319    label_width: u16,
320    // widget position
321    widget_left: u16,
322    // widget width, max.
323    widget_width: u16,
324    // max widget position
325    widget_right: u16,
326    // left position for container blocks.
327    container_left: u16,
328    // right position for container blocks.
329    container_right: u16,
330    // total width label+spacing+widget
331    total_width: u16,
332}
333
334// Const part of the page.
335#[derive(Default, Debug, Clone)]
336struct PageDef {
337    // border
338    page_border: Padding,
339    // full width for all columns
340    full_width: u16,
341    // layout parameter flex
342    flex: Flex,
343    // max block padding
344    max_left_padding: u16,
345    // max block padding
346    max_right_padding: u16,
347    // max label
348    max_label: u16,
349    // max widget
350    max_widget: u16,
351
352    // page width
353    #[allow(dead_code)]
354    width: u16,
355    // page height
356    height: u16,
357    // top border
358    top: u16,
359    // bottom border
360    bottom: u16,
361    // columns
362    columns: u16,
363    // column spacing
364    column_spacing: u16,
365    // label/widget spacing
366    spacing: u16,
367    // line spacing
368    line_spacing: u16,
369}
370
371// Current page data
372#[derive(Default, Debug, Clone)]
373struct Page {
374    // page def consts
375    def: Rc<PageDef>,
376    // page number
377    page_no: u16,
378    // page start y
379    page_start: u16,
380    // page end y
381    page_end: u16,
382
383    // current y
384    y: u16,
385
386    // top padding, accumulated
387    top_padding: u16,
388    // bottom padding, accumulated
389    bottom_padding: u16,
390    // bottom padding in case of page-break, accumulated
391    bottom_padding_break: u16,
392    // current line spacing.
393    effective_line_spacing: u16,
394
395    // current page x-positions
396    x_pos: XPositions,
397}
398
399impl BlockDef {
400    fn as_out(&self) -> BlockOut {
401        BlockOut {
402            block: self.block.clone(),
403            area: self.area,
404        }
405    }
406}
407
408impl FormWidget {
409    #[inline(always)]
410    fn is_stretch_y(&self) -> bool {
411        match self {
412            FormWidget::None => false,
413            FormWidget::Width(_) => false,
414            FormWidget::Size(_, _) => false,
415            FormWidget::Wide(_, _) => false,
416            FormWidget::StretchX(_, _) => false,
417            FormWidget::WideStretchX(_, _) => false,
418            FormWidget::StretchXY(_, _) => true,
419            FormWidget::WideStretchXY(_, _) => true,
420            FormWidget::StretchY(_, _) => true,
421        }
422    }
423}
424
425impl<W> Default for LayoutForm<W>
426where
427    W: Eq + Clone + Hash + Debug,
428{
429    fn default() -> Self {
430        Self {
431            spacing: 1,
432            line_spacing: Default::default(),
433            column_spacing: 1,
434            page_border: Default::default(),
435            mirror_border: Default::default(),
436            columns: 1,
437            flex: Default::default(),
438            widgets: Default::default(),
439            page_breaks: Default::default(),
440            min_label: Default::default(),
441            min_widget: Default::default(),
442            min_widget_wide: Default::default(),
443            blocks: Default::default(),
444            max_left_padding: Default::default(),
445            max_right_padding: Default::default(),
446            left_padding: Default::default(),
447            right_padding: Default::default(),
448        }
449    }
450}
451
452impl<W> LayoutForm<W>
453where
454    W: Eq + Hash + Clone + Debug,
455{
456    pub fn new() -> Self {
457        Self::default()
458    }
459
460    /// Spacing between label and widget.
461    #[inline]
462    pub fn spacing(mut self, spacing: u16) -> Self {
463        self.spacing = spacing;
464        self
465    }
466
467    /// Empty lines between widgets.
468    #[inline]
469    pub fn line_spacing(mut self, spacing: u16) -> Self {
470        self.line_spacing = spacing;
471        self
472    }
473
474    /// Empty space between columns.
475    #[inline]
476    pub fn column_spacing(mut self, spacing: u16) -> Self {
477        self.column_spacing = spacing;
478        self
479    }
480
481    /// Page border.
482    pub fn padding(mut self, border: Padding) -> Self {
483        self.page_border = border;
484        self
485    }
486
487    /// Page border.
488    #[deprecated(since = "2.0.0", note = "use padding. is clearer.")]
489    pub fn border(mut self, border: Padding) -> Self {
490        self.page_border = border;
491        self
492    }
493
494    /// Mirror the border given to layout between even and odd pages.
495    /// The layout starts with page 0 which is even.
496    #[inline]
497    pub fn mirror_odd_border(mut self) -> Self {
498        self.mirror_border = true;
499        self
500    }
501
502    /// Layout as multiple columns.
503    pub fn columns(mut self, columns: u8) -> Self {
504        assert_ne!(columns, 0);
505        self.columns = columns as u16;
506        self
507    }
508
509    /// Flex.
510    #[inline]
511    pub fn flex(mut self, flex: Flex) -> Self {
512        self.flex = flex;
513        self
514    }
515
516    /// Set a reference label width
517    pub fn min_label(mut self, width: u16) -> Self {
518        self.min_label = width;
519        self
520    }
521
522    /// Set a reference widget width
523    pub fn min_widget(mut self, width: u16) -> Self {
524        self.min_widget = width;
525        self
526    }
527
528    /// Start a group/block.
529    ///
530    /// This will create a block that covers all widgets added
531    /// before calling `end()`.
532    ///
533    /// Groups/blocks can be stacked, but they cannot interleave.
534    /// An inner group/block must be closed before an outer one.
535    pub fn start(&mut self, block: Option<Block<'static>>) -> BlockTag {
536        let max_idx = self.widgets.len();
537        let padding = block_padding(&block);
538
539        let tag = BlockTag(self.blocks.len());
540        self.blocks.push_back(BlockDef {
541            id: tag,
542            block,
543            padding,
544            constructing: true,
545            range: max_idx..max_idx,
546            area: Rect::default(),
547        });
548
549        self.left_padding += padding.left;
550        self.right_padding += padding.right;
551
552        self.max_left_padding = max(self.max_left_padding, self.left_padding);
553        self.max_right_padding = max(self.max_right_padding, self.right_padding);
554
555        tag
556    }
557
558    /// Closes the group/block with the given tag.
559    ///
560    /// __Panic__
561    /// Groups must be closed in reverse start order, otherwise
562    /// this function will panic. It will also panic if there
563    /// is no open group for the given tag.
564    pub fn end(&mut self, tag: BlockTag) {
565        let max = self.widgets.len();
566        for cc in self.blocks.iter_mut().rev() {
567            if cc.id == tag && cc.constructing {
568                cc.range.end = max;
569                cc.constructing = false;
570
571                // might have been used by a widget.
572                self.left_padding -= cc.padding.left;
573                self.right_padding -= cc.padding.right;
574
575                return;
576            }
577            if cc.constructing {
578                panic!("Unclosed container {:?}", cc.id);
579            }
580        }
581
582        panic!("No open container.");
583    }
584
585    /// Closes all open groups/blocks.
586    pub fn end_all(&mut self) {
587        let max = self.widgets.len();
588        for cc in self.blocks.iter_mut().rev() {
589            if cc.constructing {
590                cc.range.end = max;
591                cc.constructing = false;
592
593                // might have been used by a widget.
594                self.left_padding -= cc.padding.left;
595                self.right_padding -= cc.padding.right;
596            }
597        }
598    }
599
600    /// Add a list of label + widget constraints.
601    pub fn widgets(&mut self, list: impl IntoIterator<Item = (W, FormLabel, FormWidget)>) {
602        for (k, l, w) in list {
603            self.widget(k, l, w);
604        }
605    }
606
607    /// Add label + widget constraint.
608    /// Key must be a unique identifier.
609    pub fn widget(&mut self, key: W, label: FormLabel, widget: FormWidget) {
610        // split label by sample
611        let (label, label_str) = match label {
612            FormLabel::Str(s) => {
613                let width = unicode_display_width::width(s) as u16;
614                (FormLabel::Width(width), Some(Cow::Borrowed(s)))
615            }
616            FormLabel::String(s) => {
617                let width = unicode_display_width::width(&s) as u16;
618                (FormLabel::Width(width), Some(Cow::Owned(s)))
619            }
620            FormLabel::Width(w) => (FormLabel::Width(w), None),
621            FormLabel::Size(w, h) => (FormLabel::Size(w, h), None),
622            FormLabel::None => (FormLabel::None, None),
623        };
624
625        let w = match &label {
626            FormLabel::None => 0,
627            FormLabel::Str(_) | FormLabel::String(_) => {
628                unreachable!()
629            }
630            FormLabel::Width(w) => *w,
631            FormLabel::Size(w, _) => *w,
632        };
633        self.min_label = max(self.min_label, w);
634
635        let (w, ww) = match &widget {
636            FormWidget::None => (0, 0),
637            FormWidget::Width(w) => (*w, 0),
638            FormWidget::Size(w, _) => (*w, 0),
639            FormWidget::StretchY(w, _) => (*w, 0),
640            FormWidget::Wide(w, _) => (0, *w),
641            FormWidget::StretchX(w, _) => (*w, 0),
642            FormWidget::WideStretchX(w, _) => (0, *w),
643            FormWidget::StretchXY(w, _) => (*w, 0),
644            FormWidget::WideStretchXY(w, _) => (0, *w),
645        };
646        self.min_widget = max(self.min_widget, w);
647        self.min_widget_wide = max(self.min_widget_wide, ww);
648
649        self.widgets.push(WidgetDef {
650            id: key,
651            label,
652            label_str,
653            widget,
654        });
655    }
656
657    /// Add a manual page break after the last widget.
658    ///
659    /// This will panic if the widget list is empty.
660    pub fn page_break(&mut self) {
661        self.page_breaks.push(self.widgets.len() - 1);
662    }
663
664    /// Add a manual column break after the last widget.
665    /// This is a synonym for [page_break].
666    ///
667    /// This will panic if the widget list is empty.
668    pub fn column_break(&mut self) {
669        self.page_breaks.push(self.widgets.len() - 1);
670    }
671
672    fn validate_containers(&self) {
673        for cc in self.blocks.iter() {
674            if cc.constructing {
675                panic!("Unclosed container {:?}", cc.id);
676            }
677        }
678    }
679
680    /// Calculate a layout without page-breaks using the given layout-width and padding.
681    #[inline(always)]
682    pub fn build_endless(self, width: u16) -> GenericLayout<W> {
683        self.validate_containers();
684        build_layout::<W, true>(self, Size::new(width, u16::MAX))
685    }
686
687    /// Calculate the layout for the given page size and padding.
688    #[inline(always)]
689    pub fn build_paged(self, page: Size) -> GenericLayout<W> {
690        self.validate_containers();
691        build_layout::<W, false>(self, page)
692    }
693}
694
695impl XPositions {
696    fn new(page: &Page, column: u16, mirror: bool) -> XPositions {
697        let border = if mirror {
698            Padding::new(
699                page.def.page_border.right,
700                page.def.page_border.left,
701                page.def.page_border.top,
702                page.def.page_border.bottom,
703            )
704        } else {
705            page.def.page_border
706        };
707
708        let layout_width = page
709            .def
710            .full_width
711            .saturating_sub(border.left)
712            .saturating_sub(border.right);
713        let n_col_spacers = page.def.columns.saturating_sub(1);
714        let column_width =
715            layout_width.saturating_sub(page.def.column_spacing * n_col_spacers) / page.def.columns;
716        let right_margin = page.def.full_width.saturating_sub(border.right);
717
718        let offset;
719        let label_left;
720        let widget_left;
721        let container_left;
722        let container_right;
723        let widget_right;
724
725        match page.def.flex {
726            Flex::Legacy => {
727                offset = border.left + (column_width + page.def.column_spacing) * column;
728                label_left = page.def.max_left_padding;
729                widget_left = label_left + page.def.max_label + page.def.spacing;
730                widget_right = column_width.saturating_sub(page.def.max_right_padding);
731
732                container_left = 0;
733                container_right = column_width;
734            }
735            Flex::Start => {
736                let single_width = page.def.max_left_padding
737                    + page.def.max_label
738                    + page.def.spacing
739                    + page.def.max_widget
740                    + page.def.max_right_padding
741                    + page.def.column_spacing;
742
743                offset = border.left + single_width * column;
744                label_left = page.def.max_left_padding;
745                widget_left = label_left + page.def.max_label + page.def.spacing;
746                widget_right = widget_left + page.def.max_widget;
747
748                container_left = 0;
749                container_right = widget_right + page.def.max_right_padding;
750            }
751            Flex::Center => {
752                let single_width = page.def.max_left_padding
753                    + page.def.max_label
754                    + page.def.spacing
755                    + page.def.max_widget
756                    + page.def.max_right_padding
757                    + page.def.column_spacing;
758                let rest = layout_width
759                    .saturating_sub(single_width * page.def.columns)
760                    .saturating_add(page.def.column_spacing);
761
762                offset = border.left + rest / 2 + single_width * column;
763                label_left = page.def.max_left_padding;
764                widget_left = label_left + page.def.max_label + page.def.spacing;
765                widget_right = widget_left + page.def.max_widget;
766
767                container_left = 0;
768                container_right = widget_right + page.def.max_right_padding;
769            }
770            Flex::End => {
771                let single_width = page.def.max_left_padding
772                    + page.def.max_label
773                    + page.def.spacing
774                    + page.def.max_widget
775                    + page.def.max_right_padding
776                    + page.def.column_spacing;
777
778                offset = right_margin
779                    .saturating_sub(single_width * (page.def.columns - column))
780                    .saturating_add(page.def.column_spacing);
781                label_left = page.def.max_left_padding;
782                widget_left = label_left + page.def.max_label + page.def.spacing;
783                widget_right = widget_left + page.def.max_widget;
784
785                container_left = 0;
786                container_right = widget_right + page.def.max_right_padding;
787            }
788            Flex::SpaceAround => {
789                let single_width = page.def.max_left_padding
790                    + page.def.max_label
791                    + page.def.spacing
792                    + page.def.max_widget
793                    + page.def.max_right_padding;
794                let rest = layout_width.saturating_sub(single_width * page.def.columns);
795                let spacing = rest / (page.def.columns + 1);
796
797                offset = border.left + spacing + (single_width + spacing) * column;
798                label_left = page.def.max_left_padding;
799                widget_left = label_left + page.def.max_label + page.def.spacing;
800                widget_right = widget_left + page.def.max_widget;
801
802                container_left = 0;
803                container_right = widget_right + page.def.max_right_padding;
804            }
805            Flex::SpaceBetween => {
806                let single_width = page.def.max_left_padding
807                    + page.def.max_label
808                    + page.def.max_widget
809                    + page.def.max_right_padding;
810                let rest = layout_width.saturating_sub(single_width * page.def.columns);
811                let spacing = if page.def.columns > 1 {
812                    rest / (page.def.columns - 1)
813                } else {
814                    0
815                };
816
817                offset = border.left + (single_width + spacing) * column;
818                label_left = page.def.max_left_padding;
819                widget_left = label_left + page.def.max_label + page.def.spacing;
820                widget_right = widget_left + page.def.max_widget;
821
822                container_left = 0;
823                container_right = widget_right + page.def.max_right_padding;
824            }
825        }
826
827        XPositions {
828            label_left: offset + label_left,
829            label_width: page.def.max_label,
830            widget_left: offset + widget_left,
831            widget_width: page.def.max_widget,
832            widget_right: offset + widget_right,
833            container_left: offset + container_left,
834            container_right: offset + container_right,
835            total_width: widget_right - label_left,
836        }
837    }
838}
839
840impl Page {
841    fn adjusted_widths<W>(layout: &LayoutForm<W>, page_size: Size) -> (u16, u16, u16)
842    where
843        W: Eq + Hash + Clone + Debug,
844    {
845        let layout_width = page_size
846            .width
847            .saturating_sub(layout.page_border.left)
848            .saturating_sub(layout.page_border.right);
849        let n_col_spacers = layout.columns.saturating_sub(1);
850        let column_width =
851            layout_width.saturating_sub(layout.column_spacing * n_col_spacers) / layout.columns;
852
853        let mut max_label = layout.min_label;
854        let mut max_widget = max(
855            layout.min_widget,
856            layout
857                .min_widget_wide
858                .saturating_sub(layout.min_label)
859                .saturating_sub(layout.spacing),
860        );
861        let mut spacing = layout.spacing;
862
863        let nominal =
864            layout.max_left_padding + max_label + spacing + max_widget + layout.max_right_padding;
865
866        if nominal > column_width {
867            let mut reduce = nominal - column_width;
868
869            if spacing > reduce {
870                spacing -= reduce;
871                reduce = 0;
872            } else {
873                reduce -= spacing;
874                spacing = 0;
875            }
876            if max_label > 5 {
877                if max_label - 5 > reduce {
878                    max_label -= reduce;
879                    reduce = 0;
880                } else {
881                    reduce -= max_label - 5;
882                    max_label = 5;
883                }
884            }
885            if max_widget > 5 {
886                if max_widget - 5 > reduce {
887                    max_widget -= reduce;
888                    reduce = 0;
889                } else {
890                    reduce -= max_widget - 5;
891                    max_widget = 5;
892                }
893            }
894            if max_label > reduce {
895                max_label -= reduce;
896                reduce = 0;
897            } else {
898                reduce -= max_label;
899                max_label = 0;
900            }
901            if max_widget > reduce {
902                max_widget -= reduce;
903                // reduce = 0;
904            } else {
905                // reduce -= max_widget;
906                max_widget = 0;
907            }
908        }
909
910        (max_label, spacing, max_widget)
911    }
912
913    fn new<W>(layout: &LayoutForm<W>, page_size: Size) -> Self
914    where
915        W: Eq + Hash + Clone + Debug,
916    {
917        let (max_label, spacing, max_widget) = Self::adjusted_widths(layout, page_size);
918
919        let def = PageDef {
920            page_border: layout.page_border,
921            full_width: page_size.width,
922            flex: layout.flex,
923            max_left_padding: layout.max_left_padding,
924            max_right_padding: layout.max_right_padding,
925            max_label,
926            max_widget,
927            width: page_size.width,
928            height: page_size.height,
929            top: layout.page_border.top,
930            bottom: layout.page_border.bottom,
931            columns: layout.columns,
932            column_spacing: layout.column_spacing,
933            spacing,
934            line_spacing: layout.line_spacing,
935        };
936        let mut s = Page {
937            def: Rc::new(def),
938            page_no: 0,
939            page_start: 0,
940            page_end: page_size.height.saturating_sub(layout.page_border.bottom),
941            y: layout.page_border.top,
942            top_padding: 0,
943            bottom_padding: 0,
944            bottom_padding_break: 0,
945            effective_line_spacing: 0,
946            x_pos: Default::default(),
947        };
948        s.x_pos = XPositions::new(&s, 0, false);
949        s
950    }
951}
952
953// remove top/bottom border when squeezed.
954fn adjust_blocks<W>(layout: &mut LayoutForm<W>, page_height: u16)
955where
956    W: Eq + Hash + Clone + Debug,
957{
958    if page_height == u16::MAX {
959        return;
960    }
961
962    if page_height < 3 {
963        for block_def in layout.blocks.iter_mut() {
964            if let Some(block) = block_def.block.as_mut() {
965                let padding = block_padding2(block);
966                let borders = if padding.left > 0 {
967                    Borders::LEFT
968                } else {
969                    Borders::NONE
970                } | if padding.right > 0 {
971                    Borders::RIGHT
972                } else {
973                    Borders::NONE
974                };
975
976                *block = mem::take(block).borders(borders);
977                block_def.padding.top = 0;
978                block_def.padding.bottom = 0;
979            }
980        }
981    }
982}
983
984/// Calculate the layout for the given page size and padding.
985fn build_layout<W, const ENDLESS: bool>(
986    mut layout: LayoutForm<W>,
987    page_size: Size,
988) -> GenericLayout<W>
989where
990    W: Eq + Hash + Clone + Debug,
991{
992    let mut gen_layout = GenericLayout::with_capacity(
993        layout.widgets.len(), //
994        layout.blocks.len() * 2,
995    );
996    gen_layout.set_page_size(page_size);
997
998    // clip Blocks if necessary
999    adjust_blocks(&mut layout, page_size.height);
1000
1001    // indexes into gen_layout for any generated areas that need y adjustment.
1002    let mut stretch = Vec::with_capacity(layout.widgets.len());
1003    let mut blocks_out = Vec::with_capacity(layout.blocks.len());
1004
1005    let mut saved_page;
1006    let mut page = Page::new(&layout, page_size);
1007
1008    for (idx, widget) in layout.widgets.iter_mut().enumerate() {
1009        // safe point
1010        saved_page = page.clone();
1011
1012        let mut label_area;
1013        let mut widget_area;
1014
1015        (label_area, widget_area) = next_widget(&mut page, &mut layout.blocks, widget, idx, false);
1016        next_blocks(&mut page, &mut layout.blocks, idx, &mut blocks_out);
1017
1018        // page overflow induces page-break
1019        if !ENDLESS && page.y.saturating_add(page.bottom_padding_break) > page.page_end {
1020            // reset to safe-point
1021            page = saved_page;
1022
1023            // page-break
1024            blocks_out.clear();
1025            page_break_blocks(&mut page, &mut layout.blocks, idx, &mut blocks_out);
1026            push_blocks(&mut blocks_out, &mut gen_layout);
1027            adjust_y_stretch(&page, &mut stretch, &mut gen_layout);
1028            page_break::<ENDLESS>(&mut page);
1029            assert!(stretch.is_empty());
1030
1031            // redo current widget
1032            (label_area, widget_area) =
1033                next_widget(&mut page, &mut layout.blocks, widget, idx, true);
1034            next_blocks(&mut page, &mut layout.blocks, idx, &mut blocks_out);
1035        }
1036
1037        // remember stretch widget.
1038        if !ENDLESS && widget.widget.is_stretch_y() {
1039            stretch.push(gen_layout.widget_len());
1040        }
1041
1042        // add label + widget
1043        gen_layout.add(
1044            widget.id.clone(),
1045            widget_area,
1046            widget.label_str.take(),
1047            label_area,
1048        );
1049
1050        push_blocks(&mut blocks_out, &mut gen_layout);
1051
1052        // page-break after widget
1053        if layout.page_breaks.contains(&idx) {
1054            assert!(blocks_out.is_empty());
1055            page_break_blocks(&mut page, &mut layout.blocks, idx + 1, &mut blocks_out);
1056            push_blocks(&mut blocks_out, &mut gen_layout);
1057            if !ENDLESS {
1058                adjust_y_stretch(&page, &mut stretch, &mut gen_layout);
1059            }
1060            page_break::<ENDLESS>(&mut page);
1061            assert!(stretch.is_empty());
1062        }
1063
1064        drop_blocks(&mut layout.blocks, idx);
1065        assert!(blocks_out.is_empty());
1066    }
1067
1068    // modify layout to add y-stretch
1069    adjust_y_stretch(&page, &mut stretch, &mut gen_layout);
1070
1071    gen_layout.set_page_count(((page.page_no + page.def.columns) / page.def.columns) as usize);
1072    gen_layout
1073}
1074
1075// drop no longer used blocks. perf.
1076// there may be pathological cases, but otherwise this is fine.
1077fn drop_blocks(_block_def: &mut VecDeque<BlockDef>, _idx: usize) {
1078    // TODO: this. there is only pathological cases it seems.
1079    // loop {
1080    //     if let Some(block) = block_def.get(0) {
1081    //         if block.range.end < idx {
1082    //             block_def.pop_front();
1083    //         } else {
1084    //             break;
1085    //         }
1086    //     } else {
1087    //         break;
1088    //     }
1089    // }
1090}
1091
1092fn page_break_blocks(
1093    page: &mut Page,
1094    block_def: &mut VecDeque<BlockDef>,
1095    idx: usize,
1096    blocks_out: &mut Vec<BlockOut>,
1097) {
1098    // close and push containers
1099    // rev() ensures closing from outermost to innermost container.
1100    for block in block_def.iter_mut().rev() {
1101        if idx > block.range.start && idx < block.range.end {
1102            end_block(page, block);
1103            blocks_out.push(block.as_out());
1104
1105            // restart on next page
1106            block.range.start = idx;
1107        }
1108        // if block.range.start > idx {
1109        //     break;
1110        // }
1111    }
1112}
1113
1114// do a page-break
1115fn page_break<const ENDLESS: bool>(page: &mut Page) {
1116    // advance
1117    page.page_no += 1;
1118
1119    let column = page.page_no % page.def.columns;
1120    let mirror = (page.page_no / page.def.columns) % 2 == 1;
1121
1122    if !ENDLESS {
1123        page.page_start = (page.page_no / page.def.columns).saturating_mul(page.def.height);
1124        page.page_end = page
1125            .page_start
1126            .saturating_add(page.def.height.saturating_sub(page.def.bottom));
1127    }
1128
1129    page.x_pos = XPositions::new(page, column, mirror);
1130    page.y = page.page_start.saturating_add(page.def.top);
1131
1132    page.effective_line_spacing = 0;
1133    page.top_padding = 0;
1134    page.bottom_padding = 0;
1135    page.bottom_padding_break = 0;
1136}
1137
1138// add next widget
1139fn next_widget<W>(
1140    page: &mut Page,
1141    block_def: &mut VecDeque<BlockDef>,
1142    widget: &WidgetDef<W>,
1143    idx: usize,
1144    must_fit: bool,
1145) -> (Rect, Rect)
1146where
1147    W: Eq + Hash + Clone + Debug,
1148{
1149    // line spacing
1150    page.y = page.y.saturating_add(page.effective_line_spacing);
1151
1152    page.effective_line_spacing = page.def.line_spacing;
1153    page.top_padding = 0;
1154    page.bottom_padding = 0;
1155    page.bottom_padding_break = 0;
1156
1157    // start container
1158    for block in block_def.iter_mut() {
1159        if block.range.start == idx {
1160            start_block(page, block);
1161        }
1162        if block.range.start <= idx {
1163            widget_padding(page, idx, block);
1164        }
1165        // if block.range.start > idx {
1166        //     break;
1167        // }
1168    }
1169
1170    // get areas + advance
1171    let (label_area, widget_area, advance) = areas_and_advance(page, widget, must_fit);
1172
1173    page.y = page.y.saturating_add(advance);
1174
1175    (label_area, widget_area)
1176}
1177
1178// open the given container
1179fn start_block(page: &mut Page, block: &mut BlockDef) {
1180    // adjust block
1181    block.area.x = page.x_pos.container_left;
1182    block.area.width = page
1183        .x_pos
1184        .container_right
1185        .saturating_sub(page.x_pos.container_left);
1186    block.area.y = page.y;
1187
1188    // advance page
1189    page.y = page.y.saturating_add(block.padding.top);
1190    page.top_padding += block.padding.top;
1191    page.x_pos.container_left = page.x_pos.container_left.saturating_add(block.padding.left);
1192    page.x_pos.container_right = page
1193        .x_pos
1194        .container_right
1195        .saturating_sub(block.padding.right);
1196}
1197
1198fn widget_padding(page: &mut Page, idx: usize, block: &mut BlockDef) {
1199    if block.range.end > idx + 1 {
1200        page.bottom_padding_break += block.padding.bottom;
1201    } else if block.range.end == idx + 1 {
1202        page.bottom_padding += block.padding.bottom;
1203    }
1204}
1205
1206fn next_blocks(
1207    page: &mut Page,
1208    block_def: &mut VecDeque<BlockDef>,
1209    idx: usize,
1210    blocks_out: &mut Vec<BlockOut>,
1211) {
1212    // close and push containers
1213    // rev() ensures closing from outermost to innermost container.
1214    for block in block_def.iter_mut().rev() {
1215        if idx + 1 == block.range.end {
1216            end_block(page, block);
1217            blocks_out.push(block.as_out());
1218        }
1219        // if block.range.start > idx {
1220        //     break;
1221        // }
1222    }
1223}
1224
1225fn push_blocks<W: Eq + Hash + Clone>(
1226    blocks_out: &mut Vec<BlockOut>,
1227    gen_layout: &mut GenericLayout<W>,
1228) {
1229    while let Some(cc) = blocks_out.pop() {
1230        gen_layout.add_block(cc.area, cc.block);
1231    }
1232}
1233
1234// close the given container
1235fn end_block(page: &mut Page, block: &mut BlockDef) {
1236    // advance page
1237    page.y = page.y.saturating_add(block.padding.bottom);
1238    page.x_pos.container_left = page.x_pos.container_left.saturating_sub(block.padding.left);
1239    page.x_pos.container_right = page
1240        .x_pos
1241        .container_right
1242        .saturating_add(block.padding.right);
1243
1244    // adjust block
1245    block.area.height = page.y.saturating_sub(block.area.y);
1246}
1247
1248// calculate widget and label area.
1249// advance the page.y
1250fn areas_and_advance<W: Debug + Clone>(
1251    page: &Page,
1252    widget: &WidgetDef<W>,
1253    must_fit: bool,
1254) -> (Rect, Rect, u16) {
1255    // [label]
1256    // [widget]
1257    // vs
1258    // [label] [widget]
1259    let stacked = matches!(
1260        widget.widget,
1261        FormWidget::Wide(_, _) | FormWidget::WideStretchX(_, _) | FormWidget::WideStretchXY(_, _)
1262    );
1263
1264    let mut label_height = match &widget.label {
1265        FormLabel::None => 0,
1266        FormLabel::Str(_) | FormLabel::String(_) => unreachable!(),
1267        FormLabel::Width(_) => 1,
1268        FormLabel::Size(_, h) => *h,
1269    };
1270
1271    let mut widget_height = match &widget.widget {
1272        FormWidget::None => 0,
1273        FormWidget::Width(_) => 1,
1274        FormWidget::Size(_, h) => *h,
1275        FormWidget::StretchY(_, h) => *h,
1276        FormWidget::Wide(_, h) => *h,
1277        FormWidget::StretchX(_, h) => *h,
1278        FormWidget::WideStretchX(_, h) => *h,
1279        FormWidget::StretchXY(_, h) => *h,
1280        FormWidget::WideStretchXY(_, h) => *h,
1281    };
1282
1283    let stretch_width = page
1284        .x_pos
1285        .widget_right
1286        .saturating_sub(page.x_pos.widget_left);
1287    let total_stretch_width = page
1288        .x_pos
1289        .widget_right
1290        .saturating_sub(page.x_pos.label_left);
1291
1292    let max_height = if !must_fit {
1293        page.def
1294            .height
1295            .saturating_sub(page.def.top)
1296            .saturating_sub(page.def.bottom)
1297            .saturating_sub(page.top_padding)
1298            .saturating_sub(page.bottom_padding)
1299    } else {
1300        page.def
1301            .height
1302            .saturating_sub(page.y - page.page_start)
1303            .saturating_sub(page.def.bottom)
1304            .saturating_sub(page.bottom_padding_break)
1305    };
1306
1307    if stacked {
1308        if label_height + widget_height > max_height {
1309            label_height = max(1, max_height.saturating_sub(widget_height));
1310        }
1311        if label_height + widget_height > max_height {
1312            widget_height = max(1, max_height.saturating_sub(label_height));
1313        }
1314        if label_height + widget_height > max_height {
1315            label_height = 0;
1316        }
1317        if label_height + widget_height > max_height {
1318            widget_height = max_height;
1319        }
1320
1321        let mut label_area = match &widget.label {
1322            FormLabel::None => Rect::new(
1323                page.x_pos.label_left, //
1324                page.y,
1325                0,
1326                0,
1327            ),
1328            FormLabel::Str(_) | FormLabel::String(_) => unreachable!(),
1329            FormLabel::Width(_) => Rect::new(
1330                page.x_pos.label_left,
1331                page.y,
1332                page.x_pos.label_width,
1333                label_height,
1334            ),
1335            FormLabel::Size(_, _) => Rect::new(
1336                page.x_pos.label_left,
1337                page.y,
1338                page.x_pos.label_width,
1339                label_height,
1340            ),
1341        };
1342        match &widget.widget {
1343            FormWidget::Wide(_, _) => label_area.width = page.x_pos.total_width,
1344            FormWidget::WideStretchX(_, _) => label_area.width = total_stretch_width,
1345            FormWidget::WideStretchXY(_, _) => label_area.width = total_stretch_width,
1346            _ => {}
1347        }
1348
1349        let widget_area = match &widget.widget {
1350            FormWidget::None => unreachable!(),
1351            FormWidget::Width(_) => unreachable!(),
1352            FormWidget::Size(_, _) => unreachable!(),
1353            FormWidget::StretchY(_, _) => unreachable!(),
1354            FormWidget::Wide(w, _) => Rect::new(
1355                page.x_pos.label_left,
1356                page.y + label_height,
1357                min(*w, page.x_pos.total_width),
1358                widget_height,
1359            ),
1360            FormWidget::StretchX(_, _) => unreachable!(),
1361            FormWidget::WideStretchX(_, _) => Rect::new(
1362                page.x_pos.label_left,
1363                page.y + label_height,
1364                total_stretch_width,
1365                widget_height,
1366            ),
1367            FormWidget::StretchXY(_, _) => unreachable!(),
1368            FormWidget::WideStretchXY(_, _) => Rect::new(
1369                page.x_pos.label_left,
1370                page.y + label_height,
1371                total_stretch_width,
1372                widget_height,
1373            ),
1374        };
1375
1376        (
1377            label_area,
1378            widget_area,
1379            label_area.height + widget_area.height,
1380        )
1381    } else {
1382        label_height = min(label_height, max_height);
1383        widget_height = min(widget_height, max_height);
1384        let height = max(label_height, widget_height);
1385
1386        let label_area = match &widget.label {
1387            FormLabel::None => Rect::new(
1388                page.x_pos.label_left, //
1389                page.y,
1390                0,
1391                0,
1392            ),
1393            FormLabel::Str(_) | FormLabel::String(_) => unreachable!(),
1394            FormLabel::Width(_) => Rect::new(
1395                page.x_pos.label_left,
1396                page.y,
1397                page.x_pos.label_width,
1398                height,
1399            ),
1400            FormLabel::Size(_, _) => Rect::new(
1401                page.x_pos.label_left,
1402                page.y,
1403                page.x_pos.label_width,
1404                height,
1405            ),
1406        };
1407
1408        let widget_area = match &widget.widget {
1409            FormWidget::None => Rect::default(),
1410            FormWidget::Width(w) => Rect::new(
1411                page.x_pos.widget_left,
1412                page.y,
1413                min(*w, page.x_pos.widget_width),
1414                height,
1415            ),
1416            FormWidget::Size(w, _) => Rect::new(
1417                page.x_pos.widget_left,
1418                page.y,
1419                min(*w, page.x_pos.widget_width),
1420                height,
1421            ),
1422            FormWidget::StretchY(w, _) => Rect::new(
1423                page.x_pos.widget_left,
1424                page.y,
1425                min(*w, page.x_pos.widget_width),
1426                height,
1427            ),
1428            FormWidget::Wide(_, _) => unreachable!(),
1429            FormWidget::StretchX(_, _) => Rect::new(
1430                page.x_pos.widget_left, //
1431                page.y,
1432                stretch_width,
1433                height,
1434            ),
1435            FormWidget::WideStretchX(_, _) => unreachable!(),
1436            FormWidget::StretchXY(_, _) => Rect::new(
1437                page.x_pos.widget_left, //
1438                page.y,
1439                stretch_width,
1440                height,
1441            ),
1442            FormWidget::WideStretchXY(_, _) => unreachable!(),
1443        };
1444
1445        (
1446            label_area,
1447            widget_area,
1448            max(label_area.height, widget_area.height),
1449        )
1450    }
1451}
1452
1453// some stretching
1454// stretch_y contains the recorded widget indexes that need adjustment.
1455fn adjust_y_stretch<W: Eq + Hash + Clone>(
1456    page: &Page,
1457    stretch_y: &mut Vec<usize>,
1458    gen_layout: &mut GenericLayout<W>,
1459) {
1460    let mut remainder = page.page_end.saturating_sub(page.y);
1461    if remainder == 0 {
1462        stretch_y.clear();
1463        return;
1464    }
1465
1466    let mut n = stretch_y.len() as u16;
1467    for y_idx in stretch_y.drain(..) {
1468        // calculate stretch as a new fraction every time.
1469        // this makes a better distribution.
1470        let stretch = remainder / n;
1471        remainder -= stretch;
1472        n -= 1;
1473
1474        // stretch
1475        let mut area = gen_layout.widget(y_idx);
1476        let test_y = area.bottom();
1477        let test_x = page.x_pos.container_left;
1478
1479        area.height += stretch;
1480        gen_layout.set_widget(y_idx, area);
1481
1482        // shift everything after
1483        for idx in y_idx + 1..gen_layout.widget_len() {
1484            let mut area = gen_layout.widget(idx);
1485            if area.y >= test_y {
1486                area.y += stretch;
1487            }
1488            gen_layout.set_widget(idx, area);
1489
1490            let mut area = gen_layout.label(idx);
1491            if area.y >= test_y {
1492                area.y += stretch;
1493            }
1494            gen_layout.set_label(idx, area);
1495        }
1496
1497        // containers may be shifted or stretched.
1498        for idx in 0..gen_layout.block_len() {
1499            let mut area = gen_layout.block_area(idx);
1500            if area.x >= test_x && area.y >= test_y {
1501                area.y += stretch;
1502            }
1503            // may stretch the container
1504            if area.x >= test_x && area.y <= test_y && area.bottom() > test_y {
1505                area.height += stretch;
1506            }
1507            gen_layout.set_block_area(idx, area);
1508        }
1509    }
1510}