rat_widget/layout/
layout_form.rs

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