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