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 max_label = layout.min_label;
862        let mut max_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 + max_label + spacing + max_widget + layout.max_right_padding;
873
874        if nominal > column_width {
875            let mut reduce = nominal - column_width;
876
877            if spacing > reduce {
878                spacing -= reduce;
879                reduce = 0;
880            } else {
881                reduce -= spacing;
882                spacing = 0;
883            }
884            if max_label > 5 {
885                if max_label - 5 > reduce {
886                    max_label -= reduce;
887                    reduce = 0;
888                } else {
889                    reduce -= max_label - 5;
890                    max_label = 5;
891                }
892            }
893            if max_widget > 5 {
894                if max_widget - 5 > reduce {
895                    max_widget -= reduce;
896                    reduce = 0;
897                } else {
898                    reduce -= max_widget - 5;
899                    max_widget = 5;
900                }
901            }
902            if max_label > reduce {
903                max_label -= reduce;
904                reduce = 0;
905            } else {
906                reduce -= max_label;
907                max_label = 0;
908            }
909            if max_widget > reduce {
910                max_widget -= reduce;
911                // reduce = 0;
912            } else {
913                // reduce -= max_widget;
914                max_widget = 0;
915            }
916        }
917
918        (max_label, spacing, max_widget)
919    }
920
921    fn new<W>(layout: &LayoutForm<W>, page_size: Size) -> Self
922    where
923        W: Eq + Hash + Clone + Debug,
924    {
925        let (max_label, spacing, max_widget) = Self::adjusted_widths(layout, page_size);
926
927        let def = PageDef {
928            page_border: layout.page_border,
929            full_width: page_size.width,
930            flex: layout.flex,
931            max_left_padding: layout.max_left_padding,
932            max_right_padding: layout.max_right_padding,
933            max_label,
934            max_widget,
935            width: page_size.width,
936            height: page_size.height,
937            top: layout.page_border.top,
938            bottom: layout.page_border.bottom,
939            columns: layout.columns,
940            column_spacing: layout.column_spacing,
941            spacing,
942            line_spacing: layout.line_spacing,
943        };
944        let mut s = Page {
945            def: Rc::new(def),
946            page_no: 0,
947            page_start: 0,
948            page_end: page_size.height.saturating_sub(layout.page_border.bottom),
949            y: layout.page_border.top,
950            top_padding: 0,
951            bottom_padding: 0,
952            bottom_padding_break: 0,
953            effective_line_spacing: 0,
954            x_pos: Default::default(),
955        };
956        s.x_pos = XPositions::new(&s, 0, false);
957        s
958    }
959}
960
961// remove top/bottom border when squeezed.
962fn adjust_blocks<W>(layout: &mut LayoutForm<W>, page_height: u16)
963where
964    W: Eq + Hash + Clone + Debug,
965{
966    if page_height == u16::MAX {
967        return;
968    }
969
970    if page_height < 3 {
971        for block_def in layout.blocks.iter_mut() {
972            if let Some(block) = block_def.block.as_mut() {
973                let padding = block_padding2(block);
974                let borders = if padding.left > 0 {
975                    Borders::LEFT
976                } else {
977                    Borders::NONE
978                } | if padding.right > 0 {
979                    Borders::RIGHT
980                } else {
981                    Borders::NONE
982                };
983
984                *block = mem::take(block).borders(borders);
985                block_def.padding.top = 0;
986                block_def.padding.bottom = 0;
987            }
988        }
989    }
990}
991
992/// Calculate the layout for the given page size and padding.
993fn build_layout<W, const ENDLESS: bool>(
994    mut layout: LayoutForm<W>,
995    page_size: Size,
996) -> GenericLayout<W>
997where
998    W: Eq + Hash + Clone + Debug,
999{
1000    let mut gen_layout = GenericLayout::with_capacity(
1001        layout.widgets.len(), //
1002        layout.blocks.len() * 2,
1003    );
1004    gen_layout.set_page_size(page_size);
1005
1006    // clip Blocks if necessary
1007    adjust_blocks(&mut layout, page_size.height);
1008
1009    // indexes into gen_layout for any generated areas that need y adjustment.
1010    let mut stretch = Vec::with_capacity(layout.widgets.len());
1011    let mut blocks_out = Vec::with_capacity(layout.blocks.len());
1012
1013    let mut saved_page;
1014    let mut page = Page::new(&layout, page_size);
1015
1016    for (idx, widget) in layout.widgets.iter_mut().enumerate() {
1017        // safe point
1018        saved_page = page.clone();
1019
1020        let mut label_area;
1021        let mut widget_area;
1022        let mut gap;
1023
1024        (label_area, widget_area, gap) =
1025            next_widget(&mut page, &mut layout.blocks, widget, idx, false);
1026        next_blocks(&mut page, &mut layout.blocks, idx, &mut blocks_out);
1027
1028        // page overflow induces page-break
1029        if !ENDLESS && page.y.saturating_add(page.bottom_padding_break) > page.page_end {
1030            // reset to safe-point
1031            page = saved_page;
1032
1033            // page-break
1034            blocks_out.clear();
1035            page_break_blocks(&mut page, &mut layout.blocks, idx, &mut blocks_out);
1036            push_blocks(&mut blocks_out, &mut gen_layout);
1037            adjust_y_stretch(&page, &mut stretch, &mut gen_layout);
1038            page_break::<ENDLESS>(&mut page);
1039            assert!(stretch.is_empty());
1040
1041            // redo current widget
1042            (label_area, widget_area, gap) =
1043                next_widget(&mut page, &mut layout.blocks, widget, idx, true);
1044            next_blocks(&mut page, &mut layout.blocks, idx, &mut blocks_out);
1045        }
1046
1047        // remember stretch widget.
1048        if !ENDLESS && widget.widget.is_stretch_y() {
1049            stretch.push(gen_layout.widget_len());
1050        }
1051
1052        // add label + widget
1053        gen_layout.add(
1054            widget.id.clone(),
1055            widget_area,
1056            widget.label_str.take(),
1057            label_area,
1058        );
1059
1060        push_blocks(&mut blocks_out, &mut gen_layout);
1061
1062        let break_gap = !ENDLESS
1063            && page
1064                .y
1065                .saturating_add(gap)
1066                .saturating_add(page.bottom_padding_break)
1067                > page.page_end;
1068        let break_manual = layout.page_breaks.contains(&idx);
1069
1070        // page-break after widget
1071        if break_gap || break_manual {
1072            assert!(blocks_out.is_empty());
1073            page_break_blocks(&mut page, &mut layout.blocks, idx + 1, &mut blocks_out);
1074            push_blocks(&mut blocks_out, &mut gen_layout);
1075            if !ENDLESS {
1076                adjust_y_stretch(&page, &mut stretch, &mut gen_layout);
1077            }
1078            page_break::<ENDLESS>(&mut page);
1079            assert!(stretch.is_empty());
1080        }
1081
1082        if !break_gap {
1083            page.y += gap;
1084        }
1085
1086        drop_blocks(&mut layout.blocks, idx);
1087        assert!(blocks_out.is_empty());
1088    }
1089
1090    // modify layout to add y-stretch
1091    adjust_y_stretch(&page, &mut stretch, &mut gen_layout);
1092
1093    gen_layout.set_page_count(((page.page_no + page.def.columns) / page.def.columns) as usize);
1094    gen_layout
1095}
1096
1097// drop no longer used blocks. perf.
1098// there may be pathological cases, but otherwise this is fine.
1099fn drop_blocks(_block_def: &mut VecDeque<BlockDef>, _idx: usize) {
1100    // TODO: this. there is only pathological cases it seems.
1101    // loop {
1102    //     if let Some(block) = block_def.get(0) {
1103    //         if block.range.end < idx {
1104    //             block_def.pop_front();
1105    //         } else {
1106    //             break;
1107    //         }
1108    //     } else {
1109    //         break;
1110    //     }
1111    // }
1112}
1113
1114fn page_break_blocks(
1115    page: &mut Page,
1116    block_def: &mut VecDeque<BlockDef>,
1117    idx: usize,
1118    blocks_out: &mut Vec<BlockOut>,
1119) {
1120    // close and push containers
1121    // rev() ensures closing from outermost to innermost container.
1122    for block in block_def.iter_mut().rev() {
1123        if idx > block.range.start && idx < block.range.end {
1124            end_block(page, block);
1125            blocks_out.push(block.as_out());
1126
1127            // restart on next page
1128            block.range.start = idx;
1129        }
1130        // if block.range.start > idx {
1131        //     break;
1132        // }
1133    }
1134}
1135
1136// do a page-break
1137fn page_break<const ENDLESS: bool>(page: &mut Page) {
1138    // advance
1139    page.page_no += 1;
1140
1141    let column = page.page_no % page.def.columns;
1142    let mirror = (page.page_no / page.def.columns) % 2 == 1;
1143
1144    if !ENDLESS {
1145        page.page_start = (page.page_no / page.def.columns).saturating_mul(page.def.height);
1146        page.page_end = page
1147            .page_start
1148            .saturating_add(page.def.height.saturating_sub(page.def.bottom));
1149    }
1150
1151    page.x_pos = XPositions::new(page, column, mirror);
1152    page.y = page.page_start.saturating_add(page.def.top);
1153
1154    page.effective_line_spacing = 0;
1155    page.top_padding = 0;
1156    page.bottom_padding = 0;
1157    page.bottom_padding_break = 0;
1158}
1159
1160// add next widget
1161fn next_widget<W>(
1162    page: &mut Page,
1163    block_def: &mut VecDeque<BlockDef>,
1164    widget: &WidgetDef<W>,
1165    idx: usize,
1166    must_fit: bool,
1167) -> (Rect, Rect, u16)
1168where
1169    W: Eq + Hash + Clone + Debug,
1170{
1171    // line spacing
1172    page.y = page.y.saturating_add(page.effective_line_spacing);
1173
1174    page.effective_line_spacing = page.def.line_spacing;
1175    page.top_padding = 0;
1176    page.bottom_padding = 0;
1177    page.bottom_padding_break = 0;
1178
1179    // start container
1180    for block in block_def.iter_mut() {
1181        if block.range.start == idx {
1182            start_block(page, block);
1183        }
1184        if block.range.start <= idx {
1185            widget_padding(page, idx, block);
1186        }
1187    }
1188
1189    // get areas + advance
1190    let (label_area, widget_area, advance, gap) = areas_and_advance(page, widget, must_fit);
1191
1192    page.y = page.y.saturating_add(advance);
1193
1194    (label_area, widget_area, gap)
1195}
1196
1197// open the given container
1198fn start_block(page: &mut Page, block: &mut BlockDef) {
1199    // adjust block
1200    block.area.x = page.x_pos.container_left;
1201    block.area.width = page
1202        .x_pos
1203        .container_right
1204        .saturating_sub(page.x_pos.container_left);
1205    block.area.y = page.y;
1206
1207    // advance page
1208    page.y = page.y.saturating_add(block.padding.top);
1209    page.top_padding += block.padding.top;
1210    page.x_pos.container_left = page.x_pos.container_left.saturating_add(block.padding.left);
1211    page.x_pos.container_right = page
1212        .x_pos
1213        .container_right
1214        .saturating_sub(block.padding.right);
1215}
1216
1217fn widget_padding(page: &mut Page, idx: usize, block: &mut BlockDef) {
1218    if block.range.end > idx + 1 {
1219        page.bottom_padding_break += block.padding.bottom;
1220    } else if block.range.end == idx + 1 {
1221        page.bottom_padding += block.padding.bottom;
1222    }
1223}
1224
1225fn next_blocks(
1226    page: &mut Page,
1227    block_def: &mut VecDeque<BlockDef>,
1228    idx: usize,
1229    blocks_out: &mut Vec<BlockOut>,
1230) {
1231    // close and push containers
1232    // rev() ensures closing from outermost to innermost container.
1233    for block in block_def.iter_mut().rev() {
1234        if idx + 1 == block.range.end {
1235            end_block(page, block);
1236            blocks_out.push(block.as_out());
1237        }
1238        // if block.range.start > idx {
1239        //     break;
1240        // }
1241    }
1242}
1243
1244fn push_blocks<W: Eq + Hash + Clone>(
1245    blocks_out: &mut Vec<BlockOut>,
1246    gen_layout: &mut GenericLayout<W>,
1247) {
1248    while let Some(cc) = blocks_out.pop() {
1249        gen_layout.add_block(cc.area, cc.block);
1250    }
1251}
1252
1253// close the given container
1254fn end_block(page: &mut Page, block: &mut BlockDef) {
1255    // advance page
1256    page.y = page.y.saturating_add(block.padding.bottom);
1257    page.x_pos.container_left = page.x_pos.container_left.saturating_sub(block.padding.left);
1258    page.x_pos.container_right = page
1259        .x_pos
1260        .container_right
1261        .saturating_add(block.padding.right);
1262
1263    // adjust block
1264    block.area.height = page.y.saturating_sub(block.area.y);
1265}
1266
1267// calculate widget and label area.
1268// advance the page.y
1269fn areas_and_advance<W: Debug + Clone>(
1270    page: &Page,
1271    widget: &WidgetDef<W>,
1272    must_fit: bool,
1273) -> (Rect, Rect, u16, u16) {
1274    // [label]
1275    // [widget]
1276    // vs
1277    // [label] [widget]
1278    let stacked = matches!(
1279        widget.widget,
1280        FormWidget::Wide(_, _) | FormWidget::WideStretchX(_, _) | FormWidget::WideStretchXY(_, _)
1281    );
1282
1283    let mut label_height = match &widget.label {
1284        FormLabel::None => 0,
1285        FormLabel::Str(_) | FormLabel::String(_) => unreachable!(),
1286        FormLabel::Width(_) => 1,
1287        FormLabel::Size(_, h) => *h,
1288    };
1289
1290    let mut widget_height = match &widget.widget {
1291        FormWidget::None => 0,
1292        FormWidget::Width(_) => 1,
1293        FormWidget::Size(_, h) => *h,
1294        FormWidget::StretchY(_, h) => *h,
1295        FormWidget::Wide(_, h) => *h,
1296        FormWidget::StretchX(_, h) => *h,
1297        FormWidget::WideStretchX(_, h) => *h,
1298        FormWidget::StretchXY(_, h) => *h,
1299        FormWidget::WideStretchXY(_, h) => *h,
1300    };
1301
1302    let gap_height = widget.gap;
1303
1304    let stretch_width = page
1305        .x_pos
1306        .widget_right
1307        .saturating_sub(page.x_pos.widget_left);
1308    let total_stretch_width = page
1309        .x_pos
1310        .widget_right
1311        .saturating_sub(page.x_pos.label_left);
1312
1313    let max_height = if !must_fit {
1314        page.def
1315            .height
1316            .saturating_sub(page.def.top)
1317            .saturating_sub(page.def.bottom)
1318            .saturating_sub(page.top_padding)
1319            .saturating_sub(page.bottom_padding)
1320    } else {
1321        page.def
1322            .height
1323            .saturating_sub(page.y - page.page_start)
1324            .saturating_sub(page.def.bottom)
1325            .saturating_sub(page.bottom_padding_break)
1326    };
1327
1328    if stacked {
1329        if label_height + widget_height > max_height {
1330            label_height = max(1, max_height.saturating_sub(widget_height));
1331        }
1332        if label_height + widget_height > max_height {
1333            widget_height = max(1, max_height.saturating_sub(label_height));
1334        }
1335        if label_height + widget_height > max_height {
1336            label_height = 0;
1337        }
1338        if label_height + widget_height > max_height {
1339            widget_height = max_height;
1340        }
1341
1342        let mut label_area = match &widget.label {
1343            FormLabel::None => Rect::new(
1344                page.x_pos.label_left, //
1345                page.y,
1346                0,
1347                0,
1348            ),
1349            FormLabel::Str(_) | FormLabel::String(_) => unreachable!(),
1350            FormLabel::Width(_) => Rect::new(
1351                page.x_pos.label_left,
1352                page.y,
1353                page.x_pos.label_width,
1354                label_height,
1355            ),
1356            FormLabel::Size(_, _) => Rect::new(
1357                page.x_pos.label_left,
1358                page.y,
1359                page.x_pos.label_width,
1360                label_height,
1361            ),
1362        };
1363        match &widget.widget {
1364            FormWidget::Wide(_, _) => label_area.width = page.x_pos.total_width,
1365            FormWidget::WideStretchX(_, _) => label_area.width = total_stretch_width,
1366            FormWidget::WideStretchXY(_, _) => label_area.width = total_stretch_width,
1367            _ => {}
1368        }
1369
1370        let widget_area = match &widget.widget {
1371            FormWidget::None => unreachable!(),
1372            FormWidget::Width(_) => unreachable!(),
1373            FormWidget::Size(_, _) => unreachable!(),
1374            FormWidget::StretchY(_, _) => unreachable!(),
1375            FormWidget::Wide(w, _) => Rect::new(
1376                page.x_pos.label_left,
1377                page.y + label_height,
1378                min(*w, page.x_pos.total_width),
1379                widget_height,
1380            ),
1381            FormWidget::StretchX(_, _) => unreachable!(),
1382            FormWidget::WideStretchX(_, _) => Rect::new(
1383                page.x_pos.label_left,
1384                page.y + label_height,
1385                total_stretch_width,
1386                widget_height,
1387            ),
1388            FormWidget::StretchXY(_, _) => unreachable!(),
1389            FormWidget::WideStretchXY(_, _) => Rect::new(
1390                page.x_pos.label_left,
1391                page.y + label_height,
1392                total_stretch_width,
1393                widget_height,
1394            ),
1395        };
1396
1397        (
1398            label_area,
1399            widget_area,
1400            label_area.height + widget_area.height,
1401            gap_height,
1402        )
1403    } else {
1404        label_height = min(label_height, max_height);
1405        widget_height = min(widget_height, max_height);
1406        let height = max(label_height, widget_height);
1407
1408        let label_area = match &widget.label {
1409            FormLabel::None => Rect::new(
1410                page.x_pos.label_left, //
1411                page.y,
1412                0,
1413                0,
1414            ),
1415            FormLabel::Str(_) | FormLabel::String(_) => unreachable!(),
1416            FormLabel::Width(_) => Rect::new(
1417                page.x_pos.label_left,
1418                page.y,
1419                page.x_pos.label_width,
1420                height,
1421            ),
1422            FormLabel::Size(_, _) => Rect::new(
1423                page.x_pos.label_left,
1424                page.y,
1425                page.x_pos.label_width,
1426                height,
1427            ),
1428        };
1429
1430        let widget_area = match &widget.widget {
1431            FormWidget::None => Rect::default(),
1432            FormWidget::Width(w) => Rect::new(
1433                page.x_pos.widget_left,
1434                page.y,
1435                min(*w, page.x_pos.widget_width),
1436                height,
1437            ),
1438            FormWidget::Size(w, _) => Rect::new(
1439                page.x_pos.widget_left,
1440                page.y,
1441                min(*w, page.x_pos.widget_width),
1442                height,
1443            ),
1444            FormWidget::StretchY(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::Wide(_, _) => unreachable!(),
1451            FormWidget::StretchX(_, _) => Rect::new(
1452                page.x_pos.widget_left, //
1453                page.y,
1454                stretch_width,
1455                height,
1456            ),
1457            FormWidget::WideStretchX(_, _) => unreachable!(),
1458            FormWidget::StretchXY(_, _) => Rect::new(
1459                page.x_pos.widget_left, //
1460                page.y,
1461                stretch_width,
1462                height,
1463            ),
1464            FormWidget::WideStretchXY(_, _) => unreachable!(),
1465        };
1466
1467        (
1468            label_area,
1469            widget_area,
1470            max(label_area.height, widget_area.height),
1471            gap_height,
1472        )
1473    }
1474}
1475
1476// some stretching
1477// stretch_y contains the recorded widget indexes that need adjustment.
1478fn adjust_y_stretch<W: Eq + Hash + Clone>(
1479    page: &Page,
1480    stretch_y: &mut Vec<usize>,
1481    gen_layout: &mut GenericLayout<W>,
1482) {
1483    let mut remainder = page.page_end.saturating_sub(page.y);
1484    if remainder == 0 {
1485        stretch_y.clear();
1486        return;
1487    }
1488
1489    let mut n = stretch_y.len() as u16;
1490    for y_idx in stretch_y.drain(..) {
1491        // calculate stretch as a new fraction every time.
1492        // this makes a better distribution.
1493        let stretch = remainder / n;
1494        remainder -= stretch;
1495        n -= 1;
1496
1497        // stretch
1498        let mut area = gen_layout.widget(y_idx);
1499        let test_y = area.bottom();
1500        let test_x = page.x_pos.container_left;
1501
1502        area.height += stretch;
1503        gen_layout.set_widget(y_idx, area);
1504
1505        // shift everything after
1506        for idx in y_idx + 1..gen_layout.widget_len() {
1507            let mut area = gen_layout.widget(idx);
1508            if area.y >= test_y {
1509                area.y += stretch;
1510            }
1511            gen_layout.set_widget(idx, area);
1512
1513            let mut area = gen_layout.label(idx);
1514            if area.y >= test_y {
1515                area.y += stretch;
1516            }
1517            gen_layout.set_label(idx, area);
1518        }
1519
1520        // containers may be shifted or stretched.
1521        for idx in 0..gen_layout.block_len() {
1522            let mut area = gen_layout.block_area(idx);
1523            if area.x >= test_x && area.y >= test_y {
1524                area.y += stretch;
1525            }
1526            // may stretch the container
1527            if area.x >= test_x && area.y <= test_y && area.bottom() > test_y {
1528                area.height += stretch;
1529            }
1530            gen_layout.set_block_area(idx, area);
1531        }
1532    }
1533}