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 = usize>
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    // extra gap after this widget
285    gap: u16,
286}
287
288/// Tag for a group/block.
289#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
290pub struct BlockTag(usize);
291
292#[derive(Debug)]
293struct BlockDef {
294    id: BlockTag,
295    // block
296    block: Option<Block<'static>>,
297    // padding due to block
298    padding: Padding,
299    // under construction
300    constructing: bool,
301    // range into the widget vec
302    range: Range<usize>,
303    // calculated container area.
304    area: Rect,
305}
306
307#[derive(Debug)]
308struct BlockOut {
309    // block
310    block: Option<Block<'static>>,
311    // area
312    area: Rect,
313}
314
315// effective positions for layout construction.
316#[derive(Debug, Default, Clone, Copy)]
317struct XPositions {
318    // label position
319    label_left: u16,
320    // label width, max.
321    label_width: u16,
322    // widget position
323    widget_left: u16,
324    // widget width, max.
325    widget_width: u16,
326    // max widget position
327    widget_right: u16,
328    // left position for container blocks.
329    container_left: u16,
330    // right position for container blocks.
331    container_right: u16,
332    // total width label+spacing+widget
333    total_width: u16,
334}
335
336// Const part of the page.
337#[derive(Default, Debug, Clone)]
338struct PageDef {
339    // border
340    page_border: Padding,
341    // full width for all columns
342    full_width: u16,
343    // layout parameter flex
344    flex: Flex,
345    // max block padding
346    max_left_padding: u16,
347    // max block padding
348    max_right_padding: u16,
349    // max label
350    max_label: u16,
351    // max widget
352    max_widget: u16,
353
354    // page width
355    #[allow(dead_code)]
356    width: u16,
357    // page height
358    height: u16,
359    // top border
360    top: u16,
361    // bottom border
362    bottom: u16,
363    // columns
364    columns: u16,
365    // column spacing
366    column_spacing: u16,
367    // label/widget spacing
368    spacing: u16,
369    // line spacing
370    line_spacing: u16,
371}
372
373// Current page data
374#[derive(Default, Debug, Clone)]
375struct Page {
376    // page def consts
377    def: Rc<PageDef>,
378    // page number
379    page_no: u16,
380    // page start y
381    page_start: u16,
382    // page end y
383    page_end: u16,
384
385    // current y
386    y: u16,
387
388    // top padding, accumulated
389    top_padding: u16,
390    // bottom padding, accumulated
391    bottom_padding: u16,
392    // bottom padding in case of page-break, accumulated
393    bottom_padding_break: u16,
394    // current line spacing.
395    effective_line_spacing: u16,
396
397    // current page x-positions
398    x_pos: XPositions,
399}
400
401impl BlockDef {
402    fn as_out(&self) -> BlockOut {
403        BlockOut {
404            block: self.block.clone(),
405            area: self.area,
406        }
407    }
408}
409
410impl FormWidget {
411    #[inline(always)]
412    fn is_stretch_y(&self) -> bool {
413        match self {
414            FormWidget::None => false,
415            FormWidget::Width(_) => false,
416            FormWidget::Size(_, _) => false,
417            FormWidget::Wide(_, _) => false,
418            FormWidget::StretchX(_, _) => false,
419            FormWidget::WideStretchX(_, _) => false,
420            FormWidget::StretchXY(_, _) => true,
421            FormWidget::WideStretchXY(_, _) => true,
422            FormWidget::StretchY(_, _) => true,
423        }
424    }
425}
426
427impl<W> Default for LayoutForm<W>
428where
429    W: Eq + Clone + Hash + Debug,
430{
431    fn default() -> Self {
432        Self {
433            spacing: 1,
434            line_spacing: Default::default(),
435            column_spacing: 1,
436            page_border: Default::default(),
437            mirror_border: Default::default(),
438            columns: 1,
439            flex: Default::default(),
440            widgets: Default::default(),
441            page_breaks: Default::default(),
442            min_label: Default::default(),
443            min_widget: Default::default(),
444            min_widget_wide: Default::default(),
445            blocks: Default::default(),
446            max_left_padding: Default::default(),
447            max_right_padding: Default::default(),
448            left_padding: Default::default(),
449            right_padding: Default::default(),
450        }
451    }
452}
453
454impl<W> LayoutForm<W>
455where
456    W: Eq + Hash + Clone + Debug,
457{
458    pub fn new() -> Self {
459        Self::default()
460    }
461
462    /// Spacing between label and widget.
463    #[inline]
464    pub fn spacing(mut self, spacing: u16) -> Self {
465        self.spacing = spacing;
466        self
467    }
468
469    /// Empty lines between widgets.
470    #[inline]
471    pub fn line_spacing(mut self, spacing: u16) -> Self {
472        self.line_spacing = spacing;
473        self
474    }
475
476    /// Empty space between columns.
477    #[inline]
478    pub fn column_spacing(mut self, spacing: u16) -> Self {
479        self.column_spacing = spacing;
480        self
481    }
482
483    /// Page border.
484    pub fn padding(mut self, border: Padding) -> Self {
485        self.page_border = border;
486        self
487    }
488
489    /// Page border.
490    #[deprecated(since = "2.0.0", note = "use padding. is clearer.")]
491    pub fn border(mut self, border: Padding) -> Self {
492        self.page_border = border;
493        self
494    }
495
496    /// Mirror the border given to layout between even and odd pages.
497    /// The layout starts with page 0 which is even.
498    #[inline]
499    pub fn mirror_odd_border(mut self) -> Self {
500        self.mirror_border = true;
501        self
502    }
503
504    /// Layout as multiple columns.
505    pub fn columns(mut self, columns: u8) -> Self {
506        assert_ne!(columns, 0);
507        self.columns = columns as u16;
508        self
509    }
510
511    /// Flex.
512    #[inline]
513    pub fn flex(mut self, flex: Flex) -> Self {
514        self.flex = flex;
515        self
516    }
517
518    /// Set a reference label width
519    pub fn min_label(mut self, width: u16) -> Self {
520        self.min_label = width;
521        self
522    }
523
524    /// Set a reference widget width
525    pub fn min_widget(mut self, width: u16) -> Self {
526        self.min_widget = width;
527        self
528    }
529
530    /// Start a group/block.
531    ///
532    /// This will create a block that covers all widgets added
533    /// before calling `end()`.
534    ///
535    /// Groups/blocks can be stacked, but they cannot interleave.
536    /// An inner group/block must be closed before an outer one.
537    pub fn start(&mut self, block: Option<Block<'static>>) -> BlockTag {
538        let max_idx = self.widgets.len();
539        let padding = block_padding(&block);
540
541        let tag = BlockTag(self.blocks.len());
542        self.blocks.push_back(BlockDef {
543            id: tag,
544            block,
545            padding,
546            constructing: true,
547            range: max_idx..max_idx,
548            area: Rect::default(),
549        });
550
551        self.left_padding += padding.left;
552        self.right_padding += padding.right;
553
554        self.max_left_padding = max(self.max_left_padding, self.left_padding);
555        self.max_right_padding = max(self.max_right_padding, self.right_padding);
556
557        tag
558    }
559
560    /// Closes the group/block with the given tag.
561    ///
562    /// __Panic__
563    /// Groups must be closed in reverse start order, otherwise
564    /// this function will panic. It will also panic if there
565    /// is no open group for the given tag.
566    pub fn end(&mut self, tag: BlockTag) {
567        let max = self.widgets.len();
568        for cc in self.blocks.iter_mut().rev() {
569            if cc.id == tag && cc.constructing {
570                cc.range.end = max;
571                cc.constructing = false;
572
573                // might have been used by a widget.
574                self.left_padding -= cc.padding.left;
575                self.right_padding -= cc.padding.right;
576
577                return;
578            }
579            if cc.constructing {
580                panic!("Unclosed container {:?}", cc.id);
581            }
582        }
583
584        panic!("No open container.");
585    }
586
587    /// Closes all open groups/blocks.
588    pub fn end_all(&mut self) {
589        let max = self.widgets.len();
590        for cc in self.blocks.iter_mut().rev() {
591            if cc.constructing {
592                cc.range.end = max;
593                cc.constructing = false;
594
595                // might have been used by a widget.
596                self.left_padding -= cc.padding.left;
597                self.right_padding -= cc.padding.right;
598            }
599        }
600    }
601
602    /// Add a list of label + widget constraints.
603    pub fn widgets(&mut self, list: impl IntoIterator<Item = (W, FormLabel, FormWidget)>) {
604        for (k, l, w) in list {
605            self.widget(k, l, w);
606        }
607    }
608
609    /// Add label + widget constraint.
610    /// Key must be a unique identifier.
611    pub fn widget(&mut self, key: W, label: FormLabel, widget: FormWidget) {
612        // split label by sample
613        let (label, label_str) = match label {
614            FormLabel::Str(s) => {
615                let width = unicode_display_width::width(s) as u16;
616                (FormLabel::Width(width), Some(Cow::Borrowed(s)))
617            }
618            FormLabel::String(s) => {
619                let width = unicode_display_width::width(&s) as u16;
620                (FormLabel::Width(width), Some(Cow::Owned(s)))
621            }
622            FormLabel::Width(w) => (FormLabel::Width(w), None),
623            FormLabel::Size(w, h) => (FormLabel::Size(w, h), None),
624            FormLabel::None => (FormLabel::None, None),
625        };
626
627        let w = match &label {
628            FormLabel::None => 0,
629            FormLabel::Str(_) | FormLabel::String(_) => {
630                unreachable!()
631            }
632            FormLabel::Width(w) => *w,
633            FormLabel::Size(w, _) => *w,
634        };
635        self.min_label = max(self.min_label, w);
636
637        let (w, ww) = match &widget {
638            FormWidget::None => (0, 0),
639            FormWidget::Width(w) => (*w, 0),
640            FormWidget::Size(w, _) => (*w, 0),
641            FormWidget::StretchY(w, _) => (*w, 0),
642            FormWidget::Wide(w, _) => (0, *w),
643            FormWidget::StretchX(w, _) => (*w, 0),
644            FormWidget::WideStretchX(w, _) => (0, *w),
645            FormWidget::StretchXY(w, _) => (*w, 0),
646            FormWidget::WideStretchXY(w, _) => (0, *w),
647        };
648        self.min_widget = max(self.min_widget, w);
649        self.min_widget_wide = max(self.min_widget_wide, ww);
650
651        self.widgets.push(WidgetDef {
652            id: key,
653            label,
654            label_str,
655            widget,
656            gap: 0,
657        });
658    }
659
660    /// Add a gap in the layout.
661    pub fn gap(&mut self, n: u16) {
662        self.widgets.last_mut().expect("widget").gap = n;
663    }
664
665    /// Add a manual page break after the last widget.
666    ///
667    /// This will panic if the widget list is empty.
668    pub fn page_break(&mut self) {
669        self.page_breaks.push(self.widgets.len() - 1);
670    }
671
672    /// Add a manual column break after the last widget.
673    /// This is a synonym for [page_break].
674    ///
675    /// This will panic if the widget list is empty.
676    pub fn column_break(&mut self) {
677        self.page_breaks.push(self.widgets.len() - 1);
678    }
679
680    fn validate_containers(&self) {
681        for cc in self.blocks.iter() {
682            if cc.constructing {
683                panic!("Unclosed container {:?}", cc.id);
684            }
685        }
686    }
687
688    /// Calculate a layout without page-breaks using the given layout-width and padding.
689    #[inline(always)]
690    pub fn build_endless(self, width: u16) -> GenericLayout<W> {
691        self.validate_containers();
692        build_layout::<W, true>(self, Size::new(width, u16::MAX))
693    }
694
695    /// Calculate the layout for the given page size and padding.
696    #[inline(always)]
697    pub fn build_paged(self, page: Size) -> GenericLayout<W> {
698        self.validate_containers();
699        build_layout::<W, false>(self, page)
700    }
701}
702
703impl XPositions {
704    fn new(page: &Page, column: u16, mirror: bool) -> XPositions {
705        let border = if mirror {
706            Padding::new(
707                page.def.page_border.right,
708                page.def.page_border.left,
709                page.def.page_border.top,
710                page.def.page_border.bottom,
711            )
712        } else {
713            page.def.page_border
714        };
715
716        let layout_width = page
717            .def
718            .full_width
719            .saturating_sub(border.left)
720            .saturating_sub(border.right);
721        let n_col_spacers = page.def.columns.saturating_sub(1);
722        let column_width =
723            layout_width.saturating_sub(page.def.column_spacing * n_col_spacers) / page.def.columns;
724        let right_margin = page.def.full_width.saturating_sub(border.right);
725
726        let offset;
727        let label_left;
728        let widget_left;
729        let container_left;
730        let container_right;
731        let widget_right;
732
733        match page.def.flex {
734            Flex::Legacy => {
735                offset = border.left + (column_width + page.def.column_spacing) * column;
736                label_left = page.def.max_left_padding;
737                widget_left = label_left + page.def.max_label + page.def.spacing;
738                widget_right = column_width.saturating_sub(page.def.max_right_padding);
739
740                container_left = 0;
741                container_right = column_width;
742            }
743            Flex::Start => {
744                let single_width = page.def.max_left_padding
745                    + page.def.max_label
746                    + page.def.spacing
747                    + page.def.max_widget
748                    + page.def.max_right_padding
749                    + page.def.column_spacing;
750
751                offset = border.left + single_width * column;
752                label_left = page.def.max_left_padding;
753                widget_left = label_left + page.def.max_label + page.def.spacing;
754                widget_right = widget_left + page.def.max_widget;
755
756                container_left = 0;
757                container_right = widget_right + page.def.max_right_padding;
758            }
759            Flex::Center => {
760                let single_width = page.def.max_left_padding
761                    + page.def.max_label
762                    + page.def.spacing
763                    + page.def.max_widget
764                    + page.def.max_right_padding
765                    + page.def.column_spacing;
766                let rest = layout_width
767                    .saturating_sub(single_width * page.def.columns)
768                    .saturating_add(page.def.column_spacing);
769
770                offset = border.left + rest / 2 + single_width * column;
771                label_left = page.def.max_left_padding;
772                widget_left = label_left + page.def.max_label + page.def.spacing;
773                widget_right = widget_left + page.def.max_widget;
774
775                container_left = 0;
776                container_right = widget_right + page.def.max_right_padding;
777            }
778            Flex::End => {
779                let single_width = page.def.max_left_padding
780                    + page.def.max_label
781                    + page.def.spacing
782                    + page.def.max_widget
783                    + page.def.max_right_padding
784                    + page.def.column_spacing;
785
786                offset = right_margin
787                    .saturating_sub(single_width * (page.def.columns - column))
788                    .saturating_add(page.def.column_spacing);
789                label_left = page.def.max_left_padding;
790                widget_left = label_left + page.def.max_label + page.def.spacing;
791                widget_right = widget_left + page.def.max_widget;
792
793                container_left = 0;
794                container_right = widget_right + page.def.max_right_padding;
795            }
796            Flex::SpaceAround => {
797                let single_width = page.def.max_left_padding
798                    + page.def.max_label
799                    + page.def.spacing
800                    + page.def.max_widget
801                    + page.def.max_right_padding;
802                let rest = layout_width.saturating_sub(single_width * page.def.columns);
803                let spacing = rest / (page.def.columns + 1);
804
805                offset = border.left + spacing + (single_width + spacing) * column;
806                label_left = page.def.max_left_padding;
807                widget_left = label_left + page.def.max_label + page.def.spacing;
808                widget_right = widget_left + page.def.max_widget;
809
810                container_left = 0;
811                container_right = widget_right + page.def.max_right_padding;
812            }
813            Flex::SpaceBetween => {
814                let single_width = page.def.max_left_padding
815                    + page.def.max_label
816                    + page.def.max_widget
817                    + page.def.max_right_padding;
818                let rest = layout_width.saturating_sub(single_width * page.def.columns);
819                let spacing = if page.def.columns > 1 {
820                    rest / (page.def.columns - 1)
821                } else {
822                    0
823                };
824
825                offset = border.left + (single_width + spacing) * column;
826                label_left = page.def.max_left_padding;
827                widget_left = label_left + page.def.max_label + page.def.spacing;
828                widget_right = widget_left + page.def.max_widget;
829
830                container_left = 0;
831                container_right = widget_right + page.def.max_right_padding;
832            }
833        }
834
835        XPositions {
836            label_left: offset + label_left,
837            label_width: page.def.max_label,
838            widget_left: offset + widget_left,
839            widget_width: page.def.max_widget,
840            widget_right: offset + widget_right,
841            container_left: offset + container_left,
842            container_right: offset + container_right,
843            total_width: widget_right - label_left,
844        }
845    }
846}
847
848impl Page {
849    fn adjusted_widths<W>(layout: &LayoutForm<W>, page_size: Size) -> (u16, u16, u16)
850    where
851        W: Eq + Hash + Clone + Debug,
852    {
853        let layout_width = page_size
854            .width
855            .saturating_sub(layout.page_border.left)
856            .saturating_sub(layout.page_border.right);
857        let n_col_spacers = layout.columns.saturating_sub(1);
858        let column_width =
859            layout_width.saturating_sub(layout.column_spacing * n_col_spacers) / layout.columns;
860
861        let mut min_label = layout.min_label;
862        let mut min_widget = max(
863            layout.min_widget,
864            layout
865                .min_widget_wide
866                .saturating_sub(layout.min_label)
867                .saturating_sub(layout.spacing),
868        );
869        let mut spacing = layout.spacing;
870
871        let nominal =
872            layout.max_left_padding + min_label + spacing + min_widget + layout.max_right_padding;
873
874        if nominal > column_width {
875            let mut reduce = nominal - column_width;
876
877            if min_label > 5 && min_label - 5 > reduce {
878                // keep a minimum spacing of 1 as long as there
879                // is more than 5 char of label
880                if spacing.saturating_sub(1) > reduce {
881                    spacing -= reduce;
882                    reduce = 0;
883                } else {
884                    reduce -= spacing.saturating_sub(1);
885                    spacing = if spacing > 0 { 1 } else { 0 };
886                }
887            } else {
888                if spacing > reduce {
889                    spacing -= reduce;
890                    reduce = 0;
891                } else {
892                    reduce -= spacing;
893                    spacing = 0;
894                }
895            }
896            if min_label > 5 {
897                if min_label - 5 > reduce {
898                    min_label -= reduce;
899                    reduce = 0;
900                } else {
901                    reduce -= min_label - 5;
902                    min_label = 5;
903                }
904            }
905            if min_widget > 5 {
906                if min_widget - 5 > reduce {
907                    min_widget -= reduce;
908                    reduce = 0;
909                } else {
910                    reduce -= min_widget - 5;
911                    min_widget = 5;
912                }
913            }
914            if min_label > reduce {
915                min_label -= reduce;
916                reduce = 0;
917            } else {
918                reduce -= min_label;
919                min_label = 0;
920            }
921            if min_widget > reduce {
922                min_widget -= reduce;
923                // reduce = 0;
924            } else {
925                // reduce -= max_widget;
926                min_widget = 0;
927            }
928        }
929
930        (min_label, spacing, min_widget)
931    }
932
933    fn new<W>(layout: &LayoutForm<W>, page_size: Size) -> Self
934    where
935        W: Eq + Hash + Clone + Debug,
936    {
937        let (max_label, spacing, max_widget) = Self::adjusted_widths(layout, page_size);
938
939        let def = PageDef {
940            page_border: layout.page_border,
941            full_width: page_size.width,
942            flex: layout.flex,
943            max_left_padding: layout.max_left_padding,
944            max_right_padding: layout.max_right_padding,
945            max_label,
946            max_widget,
947            width: page_size.width,
948            height: page_size.height,
949            top: layout.page_border.top,
950            bottom: layout.page_border.bottom,
951            columns: layout.columns,
952            column_spacing: layout.column_spacing,
953            spacing,
954            line_spacing: layout.line_spacing,
955        };
956        let mut s = Page {
957            def: Rc::new(def),
958            page_no: 0,
959            page_start: 0,
960            page_end: page_size.height.saturating_sub(layout.page_border.bottom),
961            y: layout.page_border.top,
962            top_padding: 0,
963            bottom_padding: 0,
964            bottom_padding_break: 0,
965            effective_line_spacing: 0,
966            x_pos: Default::default(),
967        };
968        s.x_pos = XPositions::new(&s, 0, false);
969        s
970    }
971}
972
973// remove top/bottom border when squeezed.
974fn adjust_blocks<W>(layout: &mut LayoutForm<W>, page_height: u16)
975where
976    W: Eq + Hash + Clone + Debug,
977{
978    if page_height == u16::MAX {
979        return;
980    }
981
982    if page_height < 3 {
983        for block_def in layout.blocks.iter_mut() {
984            if let Some(block) = block_def.block.as_mut() {
985                let padding = block_padding2(block);
986                let borders = if padding.left > 0 {
987                    Borders::LEFT
988                } else {
989                    Borders::NONE
990                } | if padding.right > 0 {
991                    Borders::RIGHT
992                } else {
993                    Borders::NONE
994                };
995
996                *block = mem::take(block).borders(borders);
997                block_def.padding.top = 0;
998                block_def.padding.bottom = 0;
999            }
1000        }
1001    }
1002}
1003
1004/// Calculate the layout for the given page size and padding.
1005fn build_layout<W, const ENDLESS: bool>(
1006    mut layout: LayoutForm<W>,
1007    page_size: Size,
1008) -> GenericLayout<W>
1009where
1010    W: Eq + Hash + Clone + Debug,
1011{
1012    let mut gen_layout = GenericLayout::with_capacity(
1013        layout.widgets.len(), //
1014        layout.blocks.len() * 2,
1015    );
1016    gen_layout.set_page_size(page_size);
1017
1018    // clip Blocks if necessary
1019    adjust_blocks(&mut layout, page_size.height);
1020
1021    // indexes into gen_layout for any generated areas that need y adjustment.
1022    let mut stretch = Vec::with_capacity(layout.widgets.len());
1023    let mut blocks_out = Vec::with_capacity(layout.blocks.len());
1024
1025    let mut saved_page;
1026    let mut page = Page::new(&layout, page_size);
1027
1028    for (idx, widget) in layout.widgets.iter_mut().enumerate() {
1029        // safe point
1030        saved_page = page.clone();
1031
1032        let mut label_area;
1033        let mut widget_area;
1034        let mut gap;
1035
1036        (label_area, widget_area, gap) =
1037            next_widget(&mut page, &mut layout.blocks, widget, idx, false);
1038        next_blocks(&mut page, &mut layout.blocks, idx, &mut blocks_out);
1039
1040        // page overflow induces page-break
1041        if !ENDLESS && page.y.saturating_add(page.bottom_padding_break) > page.page_end {
1042            // reset to safe-point
1043            page = saved_page;
1044
1045            // page-break
1046            blocks_out.clear();
1047            page_break_blocks(&mut page, &mut layout.blocks, idx, &mut blocks_out);
1048            push_blocks(&mut blocks_out, &mut gen_layout);
1049            adjust_y_stretch(&page, &mut stretch, &mut gen_layout);
1050            page_break::<ENDLESS>(&mut page);
1051            assert!(stretch.is_empty());
1052
1053            // redo current widget
1054            (label_area, widget_area, gap) =
1055                next_widget(&mut page, &mut layout.blocks, widget, idx, true);
1056            next_blocks(&mut page, &mut layout.blocks, idx, &mut blocks_out);
1057        }
1058
1059        // remember stretch widget.
1060        if !ENDLESS && widget.widget.is_stretch_y() {
1061            stretch.push(gen_layout.widget_len());
1062        }
1063
1064        // add label + widget
1065        gen_layout.add(
1066            widget.id.clone(),
1067            widget_area,
1068            widget.label_str.take(),
1069            label_area,
1070        );
1071
1072        push_blocks(&mut blocks_out, &mut gen_layout);
1073
1074        let break_gap = !ENDLESS
1075            && page
1076                .y
1077                .saturating_add(gap)
1078                .saturating_add(page.bottom_padding_break)
1079                > page.page_end;
1080        let break_manual = layout.page_breaks.contains(&idx);
1081
1082        // page-break after widget
1083        if break_gap || break_manual {
1084            assert!(blocks_out.is_empty());
1085            page_break_blocks(&mut page, &mut layout.blocks, idx + 1, &mut blocks_out);
1086            push_blocks(&mut blocks_out, &mut gen_layout);
1087            if !ENDLESS {
1088                adjust_y_stretch(&page, &mut stretch, &mut gen_layout);
1089            }
1090            page_break::<ENDLESS>(&mut page);
1091            assert!(stretch.is_empty());
1092        }
1093
1094        if !break_gap {
1095            page.y += gap;
1096        }
1097
1098        drop_blocks(&mut layout.blocks, idx);
1099        assert!(blocks_out.is_empty());
1100    }
1101
1102    // modify layout to add y-stretch
1103    adjust_y_stretch(&page, &mut stretch, &mut gen_layout);
1104
1105    gen_layout.set_page_count(((page.page_no + page.def.columns) / page.def.columns) as usize);
1106    gen_layout
1107}
1108
1109// drop no longer used blocks. perf.
1110// there may be pathological cases, but otherwise this is fine.
1111fn drop_blocks(_block_def: &mut VecDeque<BlockDef>, _idx: usize) {
1112    // TODO: this. there is only pathological cases it seems.
1113    // loop {
1114    //     if let Some(block) = block_def.get(0) {
1115    //         if block.range.end < idx {
1116    //             block_def.pop_front();
1117    //         } else {
1118    //             break;
1119    //         }
1120    //     } else {
1121    //         break;
1122    //     }
1123    // }
1124}
1125
1126fn page_break_blocks(
1127    page: &mut Page,
1128    block_def: &mut VecDeque<BlockDef>,
1129    idx: usize,
1130    blocks_out: &mut Vec<BlockOut>,
1131) {
1132    // close and push containers
1133    // rev() ensures closing from outermost to innermost container.
1134    for block in block_def.iter_mut().rev() {
1135        if idx > block.range.start && idx < block.range.end {
1136            end_block(page, block);
1137            blocks_out.push(block.as_out());
1138
1139            // restart on next page
1140            block.range.start = idx;
1141        }
1142        // if block.range.start > idx {
1143        //     break;
1144        // }
1145    }
1146}
1147
1148// do a page-break
1149fn page_break<const ENDLESS: bool>(page: &mut Page) {
1150    // advance
1151    page.page_no += 1;
1152
1153    let column = page.page_no % page.def.columns;
1154    let mirror = (page.page_no / page.def.columns) % 2 == 1;
1155
1156    if !ENDLESS {
1157        page.page_start = (page.page_no / page.def.columns).saturating_mul(page.def.height);
1158        page.page_end = page
1159            .page_start
1160            .saturating_add(page.def.height.saturating_sub(page.def.bottom));
1161    }
1162
1163    page.x_pos = XPositions::new(page, column, mirror);
1164    page.y = page.page_start.saturating_add(page.def.top);
1165
1166    page.effective_line_spacing = 0;
1167    page.top_padding = 0;
1168    page.bottom_padding = 0;
1169    page.bottom_padding_break = 0;
1170}
1171
1172// add next widget
1173fn next_widget<W>(
1174    page: &mut Page,
1175    block_def: &mut VecDeque<BlockDef>,
1176    widget: &WidgetDef<W>,
1177    idx: usize,
1178    must_fit: bool,
1179) -> (Rect, Rect, u16)
1180where
1181    W: Eq + Hash + Clone + Debug,
1182{
1183    // line spacing
1184    page.y = page.y.saturating_add(page.effective_line_spacing);
1185
1186    page.effective_line_spacing = page.def.line_spacing;
1187    page.top_padding = 0;
1188    page.bottom_padding = 0;
1189    page.bottom_padding_break = 0;
1190
1191    // start container
1192    for block in block_def.iter_mut() {
1193        if block.range.start == idx {
1194            start_block(page, block);
1195        }
1196        if block.range.start <= idx {
1197            widget_padding(page, idx, block);
1198        }
1199    }
1200
1201    // get areas + advance
1202    let (label_area, widget_area, advance, gap) = areas_and_advance(page, widget, must_fit);
1203
1204    page.y = page.y.saturating_add(advance);
1205
1206    (label_area, widget_area, gap)
1207}
1208
1209// open the given container
1210fn start_block(page: &mut Page, block: &mut BlockDef) {
1211    // adjust block
1212    block.area.x = page.x_pos.container_left;
1213    block.area.width = page
1214        .x_pos
1215        .container_right
1216        .saturating_sub(page.x_pos.container_left);
1217    block.area.y = page.y;
1218
1219    // advance page
1220    page.y = page.y.saturating_add(block.padding.top);
1221    page.top_padding += block.padding.top;
1222    page.x_pos.container_left = page.x_pos.container_left.saturating_add(block.padding.left);
1223    page.x_pos.container_right = page
1224        .x_pos
1225        .container_right
1226        .saturating_sub(block.padding.right);
1227}
1228
1229fn widget_padding(page: &mut Page, idx: usize, block: &mut BlockDef) {
1230    if block.range.end > idx + 1 {
1231        page.bottom_padding_break += block.padding.bottom;
1232    } else if block.range.end == idx + 1 {
1233        page.bottom_padding += block.padding.bottom;
1234    }
1235}
1236
1237fn next_blocks(
1238    page: &mut Page,
1239    block_def: &mut VecDeque<BlockDef>,
1240    idx: usize,
1241    blocks_out: &mut Vec<BlockOut>,
1242) {
1243    // close and push containers
1244    // rev() ensures closing from outermost to innermost container.
1245    for block in block_def.iter_mut().rev() {
1246        if idx + 1 == block.range.end {
1247            end_block(page, block);
1248            blocks_out.push(block.as_out());
1249        }
1250        // if block.range.start > idx {
1251        //     break;
1252        // }
1253    }
1254}
1255
1256fn push_blocks<W: Eq + Hash + Clone>(
1257    blocks_out: &mut Vec<BlockOut>,
1258    gen_layout: &mut GenericLayout<W>,
1259) {
1260    while let Some(cc) = blocks_out.pop() {
1261        gen_layout.add_block(cc.area, cc.block);
1262    }
1263}
1264
1265// close the given container
1266fn end_block(page: &mut Page, block: &mut BlockDef) {
1267    // advance page
1268    page.y = page.y.saturating_add(block.padding.bottom);
1269    page.x_pos.container_left = page.x_pos.container_left.saturating_sub(block.padding.left);
1270    page.x_pos.container_right = page
1271        .x_pos
1272        .container_right
1273        .saturating_add(block.padding.right);
1274
1275    // adjust block
1276    block.area.height = page.y.saturating_sub(block.area.y);
1277}
1278
1279// calculate widget and label area.
1280// advance the page.y
1281fn areas_and_advance<W: Debug + Clone>(
1282    page: &Page,
1283    widget: &WidgetDef<W>,
1284    must_fit: bool,
1285) -> (Rect, Rect, u16, u16) {
1286    // [label]
1287    // [widget]
1288    // vs
1289    // [label] [widget]
1290    let stacked = matches!(
1291        widget.widget,
1292        FormWidget::Wide(_, _) | FormWidget::WideStretchX(_, _) | FormWidget::WideStretchXY(_, _)
1293    );
1294
1295    let mut label_height = match &widget.label {
1296        FormLabel::None => 0,
1297        FormLabel::Str(_) | FormLabel::String(_) => unreachable!(),
1298        FormLabel::Width(_) => 1,
1299        FormLabel::Size(_, h) => *h,
1300    };
1301
1302    let mut widget_height = match &widget.widget {
1303        FormWidget::None => 0,
1304        FormWidget::Width(_) => 1,
1305        FormWidget::Size(_, h) => *h,
1306        FormWidget::StretchY(_, h) => *h,
1307        FormWidget::Wide(_, h) => *h,
1308        FormWidget::StretchX(_, h) => *h,
1309        FormWidget::WideStretchX(_, h) => *h,
1310        FormWidget::StretchXY(_, h) => *h,
1311        FormWidget::WideStretchXY(_, h) => *h,
1312    };
1313
1314    let gap_height = widget.gap;
1315
1316    let stretch_width = page
1317        .x_pos
1318        .widget_right
1319        .saturating_sub(page.x_pos.widget_left);
1320    let total_stretch_width = page
1321        .x_pos
1322        .widget_right
1323        .saturating_sub(page.x_pos.label_left);
1324
1325    let max_height = if !must_fit {
1326        page.def
1327            .height
1328            .saturating_sub(page.def.top)
1329            .saturating_sub(page.def.bottom)
1330            .saturating_sub(page.top_padding)
1331            .saturating_sub(page.bottom_padding)
1332    } else {
1333        page.def
1334            .height
1335            .saturating_sub(page.y - page.page_start)
1336            .saturating_sub(page.def.bottom)
1337            .saturating_sub(page.bottom_padding_break)
1338    };
1339
1340    if stacked {
1341        if label_height + widget_height > max_height {
1342            label_height = max(1, max_height.saturating_sub(widget_height));
1343        }
1344        if label_height + widget_height > max_height {
1345            widget_height = max(1, max_height.saturating_sub(label_height));
1346        }
1347        if label_height + widget_height > max_height {
1348            label_height = 0;
1349        }
1350        if label_height + widget_height > max_height {
1351            widget_height = max_height;
1352        }
1353
1354        let mut label_area = match &widget.label {
1355            FormLabel::None => Rect::new(
1356                page.x_pos.label_left, //
1357                page.y,
1358                0,
1359                0,
1360            ),
1361            FormLabel::Str(_) | FormLabel::String(_) => unreachable!(),
1362            FormLabel::Width(_) => Rect::new(
1363                page.x_pos.label_left,
1364                page.y,
1365                page.x_pos.label_width,
1366                label_height,
1367            ),
1368            FormLabel::Size(_, _) => Rect::new(
1369                page.x_pos.label_left,
1370                page.y,
1371                page.x_pos.label_width,
1372                label_height,
1373            ),
1374        };
1375        match &widget.widget {
1376            FormWidget::Wide(_, _) => label_area.width = page.x_pos.total_width,
1377            FormWidget::WideStretchX(_, _) => label_area.width = total_stretch_width,
1378            FormWidget::WideStretchXY(_, _) => label_area.width = total_stretch_width,
1379            _ => {}
1380        }
1381
1382        let widget_area = match &widget.widget {
1383            FormWidget::None => unreachable!(),
1384            FormWidget::Width(_) => unreachable!(),
1385            FormWidget::Size(_, _) => unreachable!(),
1386            FormWidget::StretchY(_, _) => unreachable!(),
1387            FormWidget::Wide(w, _) => Rect::new(
1388                page.x_pos.label_left,
1389                page.y + label_height,
1390                min(*w, page.x_pos.total_width),
1391                widget_height,
1392            ),
1393            FormWidget::StretchX(_, _) => unreachable!(),
1394            FormWidget::WideStretchX(_, _) => Rect::new(
1395                page.x_pos.label_left,
1396                page.y + label_height,
1397                total_stretch_width,
1398                widget_height,
1399            ),
1400            FormWidget::StretchXY(_, _) => unreachable!(),
1401            FormWidget::WideStretchXY(_, _) => Rect::new(
1402                page.x_pos.label_left,
1403                page.y + label_height,
1404                total_stretch_width,
1405                widget_height,
1406            ),
1407        };
1408
1409        (
1410            label_area,
1411            widget_area,
1412            label_area.height + widget_area.height,
1413            gap_height,
1414        )
1415    } else {
1416        label_height = min(label_height, max_height);
1417        widget_height = min(widget_height, max_height);
1418        let height = max(label_height, widget_height);
1419
1420        let label_area = match &widget.label {
1421            FormLabel::None => Rect::new(
1422                page.x_pos.label_left, //
1423                page.y,
1424                0,
1425                0,
1426            ),
1427            FormLabel::Str(_) | FormLabel::String(_) => unreachable!(),
1428            FormLabel::Width(_) => Rect::new(
1429                page.x_pos.label_left,
1430                page.y,
1431                page.x_pos.label_width,
1432                height,
1433            ),
1434            FormLabel::Size(_, _) => Rect::new(
1435                page.x_pos.label_left,
1436                page.y,
1437                page.x_pos.label_width,
1438                height,
1439            ),
1440        };
1441
1442        let widget_area = match &widget.widget {
1443            FormWidget::None => Rect::default(),
1444            FormWidget::Width(w) => Rect::new(
1445                page.x_pos.widget_left,
1446                page.y,
1447                min(*w, page.x_pos.widget_width),
1448                height,
1449            ),
1450            FormWidget::Size(w, _) => Rect::new(
1451                page.x_pos.widget_left,
1452                page.y,
1453                min(*w, page.x_pos.widget_width),
1454                height,
1455            ),
1456            FormWidget::StretchY(w, _) => Rect::new(
1457                page.x_pos.widget_left,
1458                page.y,
1459                min(*w, page.x_pos.widget_width),
1460                height,
1461            ),
1462            FormWidget::Wide(_, _) => unreachable!(),
1463            FormWidget::StretchX(_, _) => Rect::new(
1464                page.x_pos.widget_left, //
1465                page.y,
1466                stretch_width,
1467                height,
1468            ),
1469            FormWidget::WideStretchX(_, _) => unreachable!(),
1470            FormWidget::StretchXY(_, _) => Rect::new(
1471                page.x_pos.widget_left, //
1472                page.y,
1473                stretch_width,
1474                height,
1475            ),
1476            FormWidget::WideStretchXY(_, _) => unreachable!(),
1477        };
1478
1479        (
1480            label_area,
1481            widget_area,
1482            max(label_area.height, widget_area.height),
1483            gap_height,
1484        )
1485    }
1486}
1487
1488// some stretching
1489// stretch_y contains the recorded widget indexes that need adjustment.
1490fn adjust_y_stretch<W: Eq + Hash + Clone>(
1491    page: &Page,
1492    stretch_y: &mut Vec<usize>,
1493    gen_layout: &mut GenericLayout<W>,
1494) {
1495    let mut remainder = page.page_end.saturating_sub(page.y);
1496    if remainder == 0 {
1497        stretch_y.clear();
1498        return;
1499    }
1500
1501    let mut n = stretch_y.len() as u16;
1502    for y_idx in stretch_y.drain(..) {
1503        // calculate stretch as a new fraction every time.
1504        // this makes a better distribution.
1505        let stretch = remainder / n;
1506        remainder -= stretch;
1507        n -= 1;
1508
1509        // stretch
1510        let mut area = gen_layout.widget(y_idx);
1511        let test_y = area.bottom();
1512        let test_x = page.x_pos.container_left;
1513
1514        area.height += stretch;
1515        gen_layout.set_widget(y_idx, area);
1516
1517        // shift everything after
1518        for idx in y_idx + 1..gen_layout.widget_len() {
1519            let mut area = gen_layout.widget(idx);
1520            if area.y >= test_y {
1521                area.y += stretch;
1522            }
1523            gen_layout.set_widget(idx, area);
1524
1525            let mut area = gen_layout.label(idx);
1526            if area.y >= test_y {
1527                area.y += stretch;
1528            }
1529            gen_layout.set_label(idx, area);
1530        }
1531
1532        // containers may be shifted or stretched.
1533        for idx in 0..gen_layout.block_len() {
1534            let mut area = gen_layout.block_area(idx);
1535            if area.x >= test_x && area.y >= test_y {
1536                area.y += stretch;
1537            }
1538            // may stretch the container
1539            if area.x >= test_x && area.y <= test_y && area.bottom() > test_y {
1540                area.height += stretch;
1541            }
1542            gen_layout.set_block_area(idx, area);
1543        }
1544    }
1545}