use crate::layout::generic_layout::GenericLayout;
use crate::util::{block_padding, block_padding2};
use ratatui_core::layout::{Flex, Rect, Size};
use ratatui_widgets::block::{Block, Padding};
use ratatui_widgets::borders::Borders;
use std::borrow::Cow;
use std::cmp::{max, min};
use std::collections::VecDeque;
use std::fmt::Debug;
use std::hash::Hash;
use std::mem;
use std::ops::Range;
use std::rc::Rc;
#[derive(Debug, Default)]
pub enum FormLabel {
#[default]
None,
Str(&'static str),
String(String),
Width(u16),
Size(u16, u16),
}
#[derive(Debug, Default)]
pub enum FormWidget {
#[default]
None,
Width(u16),
Size(u16, u16),
StretchY(u16, u16),
Wide(u16, u16),
StretchX(u16, u16),
WideStretchX(u16, u16),
StretchXY(u16, u16),
WideStretchXY(u16, u16),
}
#[derive(Debug)]
pub struct LayoutForm<W = usize>
where
W: Eq + Hash + Clone + Debug,
{
spacing: u16,
line_spacing: u16,
column_spacing: u16,
page_border: Padding,
mirror_border: bool,
columns: u16,
flex: Flex,
widgets: Vec<WidgetDef<W>>,
blocks: VecDeque<BlockDef>,
page_breaks: Vec<usize>,
min_label: u16,
min_widget: u16,
min_widget_wide: u16,
max_left_padding: u16,
max_right_padding: u16,
left_padding: u16,
right_padding: u16,
}
#[derive(Debug)]
struct WidgetDef<W>
where
W: Debug + Clone,
{
id: W,
label: FormLabel,
label_str: Option<Cow<'static, str>>,
widget: FormWidget,
gap: u16,
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)]
pub struct BlockTag(usize);
#[derive(Debug)]
struct BlockDef {
id: BlockTag,
block: Option<Block<'static>>,
padding: Padding,
constructing: bool,
range: Range<usize>,
area: Rect,
}
#[derive(Debug)]
struct BlockOut {
block: Option<Block<'static>>,
area: Rect,
}
#[derive(Debug, Default, Clone, Copy)]
struct XPositions {
label_left: u16,
label_width: u16,
widget_left: u16,
widget_width: u16,
widget_right: u16,
container_left: u16,
container_right: u16,
total_width: u16,
}
#[derive(Default, Debug, Clone)]
struct PageDef {
page_border: Padding,
full_width: u16,
flex: Flex,
max_left_padding: u16,
max_right_padding: u16,
max_label: u16,
max_widget: u16,
#[allow(dead_code)]
width: u16,
height: u16,
top: u16,
bottom: u16,
columns: u16,
column_spacing: u16,
spacing: u16,
line_spacing: u16,
}
#[derive(Default, Debug, Clone)]
struct Page {
def: Rc<PageDef>,
page_no: u16,
page_start: u16,
page_end: u16,
y: u16,
top_padding: u16,
bottom_padding: u16,
bottom_padding_break: u16,
effective_line_spacing: u16,
x_pos: XPositions,
}
impl BlockDef {
fn as_out(&self) -> BlockOut {
BlockOut {
block: self.block.clone(),
area: self.area,
}
}
}
impl FormWidget {
#[inline(always)]
fn is_stretch_y(&self) -> bool {
match self {
FormWidget::None => false,
FormWidget::Width(_) => false,
FormWidget::Size(_, _) => false,
FormWidget::Wide(_, _) => false,
FormWidget::StretchX(_, _) => false,
FormWidget::WideStretchX(_, _) => false,
FormWidget::StretchXY(_, _) => true,
FormWidget::WideStretchXY(_, _) => true,
FormWidget::StretchY(_, _) => true,
}
}
}
impl<W> Default for LayoutForm<W>
where
W: Eq + Clone + Hash + Debug,
{
fn default() -> Self {
Self {
spacing: 1,
line_spacing: Default::default(),
column_spacing: 1,
page_border: Default::default(),
mirror_border: Default::default(),
columns: 1,
flex: Default::default(),
widgets: Default::default(),
page_breaks: Default::default(),
min_label: Default::default(),
min_widget: Default::default(),
min_widget_wide: Default::default(),
blocks: Default::default(),
max_left_padding: Default::default(),
max_right_padding: Default::default(),
left_padding: Default::default(),
right_padding: Default::default(),
}
}
}
impl<W> LayoutForm<W>
where
W: Eq + Hash + Clone + Debug,
{
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn spacing(mut self, spacing: u16) -> Self {
self.spacing = spacing;
self
}
#[inline]
pub fn line_spacing(mut self, spacing: u16) -> Self {
self.line_spacing = spacing;
self
}
#[inline]
pub fn column_spacing(mut self, spacing: u16) -> Self {
self.column_spacing = spacing;
self
}
pub fn padding(mut self, border: Padding) -> Self {
self.page_border = border;
self
}
#[deprecated(since = "2.0.0", note = "use padding. is clearer.")]
pub fn border(mut self, border: Padding) -> Self {
self.page_border = border;
self
}
#[inline]
pub fn mirror_odd_border(mut self) -> Self {
self.mirror_border = true;
self
}
pub fn columns(mut self, columns: u8) -> Self {
assert_ne!(columns, 0);
self.columns = columns as u16;
self
}
#[inline]
pub fn flex(mut self, flex: Flex) -> Self {
self.flex = flex;
self
}
pub fn min_label(mut self, width: u16) -> Self {
self.min_label = width;
self
}
pub fn min_widget(mut self, width: u16) -> Self {
self.min_widget = width;
self
}
pub fn start(&mut self, block: Option<Block<'static>>) -> BlockTag {
let max_idx = self.widgets.len();
let padding = block_padding(&block);
let tag = BlockTag(self.blocks.len());
self.blocks.push_back(BlockDef {
id: tag,
block,
padding,
constructing: true,
range: max_idx..max_idx,
area: Rect::default(),
});
self.left_padding += padding.left;
self.right_padding += padding.right;
self.max_left_padding = max(self.max_left_padding, self.left_padding);
self.max_right_padding = max(self.max_right_padding, self.right_padding);
tag
}
pub fn end(&mut self, tag: BlockTag) {
let max = self.widgets.len();
for cc in self.blocks.iter_mut().rev() {
if cc.id == tag && cc.constructing {
cc.range.end = max;
cc.constructing = false;
self.left_padding -= cc.padding.left;
self.right_padding -= cc.padding.right;
return;
}
if cc.constructing {
panic!("Unclosed container {:?}", cc.id);
}
}
panic!("No open container.");
}
pub fn end_all(&mut self) {
let max = self.widgets.len();
for cc in self.blocks.iter_mut().rev() {
if cc.constructing {
cc.range.end = max;
cc.constructing = false;
self.left_padding -= cc.padding.left;
self.right_padding -= cc.padding.right;
}
}
}
pub fn widgets(&mut self, list: impl IntoIterator<Item = (W, FormLabel, FormWidget)>) {
for (k, l, w) in list {
self.widget(k, l, w);
}
}
pub fn widget(&mut self, key: W, label: FormLabel, widget: FormWidget) {
let (label, label_str) = match label {
FormLabel::Str(s) => {
let width = unicode_display_width::width(s) as u16;
(FormLabel::Width(width), Some(Cow::Borrowed(s)))
}
FormLabel::String(s) => {
let width = unicode_display_width::width(&s) as u16;
(FormLabel::Width(width), Some(Cow::Owned(s)))
}
FormLabel::Width(w) => (FormLabel::Width(w), None),
FormLabel::Size(w, h) => (FormLabel::Size(w, h), None),
FormLabel::None => (FormLabel::None, None),
};
let w = match &label {
FormLabel::None => 0,
FormLabel::Str(_) | FormLabel::String(_) => {
unreachable!()
}
FormLabel::Width(w) => *w,
FormLabel::Size(w, _) => *w,
};
self.min_label = max(self.min_label, w);
let (w, ww) = match &widget {
FormWidget::None => (0, 0),
FormWidget::Width(w) => (*w, 0),
FormWidget::Size(w, _) => (*w, 0),
FormWidget::StretchY(w, _) => (*w, 0),
FormWidget::Wide(w, _) => (0, *w),
FormWidget::StretchX(w, _) => (*w, 0),
FormWidget::WideStretchX(w, _) => (0, *w),
FormWidget::StretchXY(w, _) => (*w, 0),
FormWidget::WideStretchXY(w, _) => (0, *w),
};
self.min_widget = max(self.min_widget, w);
self.min_widget_wide = max(self.min_widget_wide, ww);
self.widgets.push(WidgetDef {
id: key,
label,
label_str,
widget,
gap: 0,
});
}
pub fn gap(&mut self, n: u16) {
self.widgets.last_mut().expect("widget").gap = n;
}
pub fn page_break(&mut self) {
self.page_breaks.push(self.widgets.len() - 1);
}
pub fn column_break(&mut self) {
self.page_breaks.push(self.widgets.len() - 1);
}
fn validate_containers(&self) {
for cc in self.blocks.iter() {
if cc.constructing {
panic!("Unclosed container {:?}", cc.id);
}
}
}
#[inline(always)]
pub fn build_endless(self, width: u16) -> GenericLayout<W> {
self.validate_containers();
build_layout::<W, true>(self, Size::new(width, u16::MAX))
}
#[inline(always)]
pub fn build_paged(self, page: Size) -> GenericLayout<W> {
self.validate_containers();
build_layout::<W, false>(self, page)
}
}
impl XPositions {
fn new(page: &Page, column: u16, mirror: bool) -> XPositions {
let border = if mirror {
Padding::new(
page.def.page_border.right,
page.def.page_border.left,
page.def.page_border.top,
page.def.page_border.bottom,
)
} else {
page.def.page_border
};
let layout_width = page
.def
.full_width
.saturating_sub(border.left)
.saturating_sub(border.right);
let n_col_spacers = page.def.columns.saturating_sub(1);
let column_width =
layout_width.saturating_sub(page.def.column_spacing * n_col_spacers) / page.def.columns;
let right_margin = page.def.full_width.saturating_sub(border.right);
let offset;
let label_left;
let widget_left;
let container_left;
let container_right;
let widget_right;
match page.def.flex {
Flex::Legacy => {
offset = border.left + (column_width + page.def.column_spacing) * column;
label_left = page.def.max_left_padding;
widget_left = label_left + page.def.max_label + page.def.spacing;
widget_right = column_width.saturating_sub(page.def.max_right_padding);
container_left = 0;
container_right = column_width;
}
Flex::Start => {
let single_width = page.def.max_left_padding
+ page.def.max_label
+ page.def.spacing
+ page.def.max_widget
+ page.def.max_right_padding
+ page.def.column_spacing;
offset = border.left + single_width * column;
label_left = page.def.max_left_padding;
widget_left = label_left + page.def.max_label + page.def.spacing;
widget_right = widget_left + page.def.max_widget;
container_left = 0;
container_right = widget_right + page.def.max_right_padding;
}
Flex::Center => {
let single_width = page.def.max_left_padding
+ page.def.max_label
+ page.def.spacing
+ page.def.max_widget
+ page.def.max_right_padding
+ page.def.column_spacing;
let rest = layout_width
.saturating_sub(single_width * page.def.columns)
.saturating_add(page.def.column_spacing);
offset = border.left + rest / 2 + single_width * column;
label_left = page.def.max_left_padding;
widget_left = label_left + page.def.max_label + page.def.spacing;
widget_right = widget_left + page.def.max_widget;
container_left = 0;
container_right = widget_right + page.def.max_right_padding;
}
Flex::End => {
let single_width = page.def.max_left_padding
+ page.def.max_label
+ page.def.spacing
+ page.def.max_widget
+ page.def.max_right_padding
+ page.def.column_spacing;
offset = right_margin
.saturating_sub(single_width * (page.def.columns - column))
.saturating_add(page.def.column_spacing);
label_left = page.def.max_left_padding;
widget_left = label_left + page.def.max_label + page.def.spacing;
widget_right = widget_left + page.def.max_widget;
container_left = 0;
container_right = widget_right + page.def.max_right_padding;
}
Flex::SpaceAround | Flex::SpaceEvenly => {
let single_width = page.def.max_left_padding
+ page.def.max_label
+ page.def.spacing
+ page.def.max_widget
+ page.def.max_right_padding;
let rest = layout_width.saturating_sub(single_width * page.def.columns);
let spacing = rest / (page.def.columns + 1);
offset = border.left + spacing + (single_width + spacing) * column;
label_left = page.def.max_left_padding;
widget_left = label_left + page.def.max_label + page.def.spacing;
widget_right = widget_left + page.def.max_widget;
container_left = 0;
container_right = widget_right + page.def.max_right_padding;
}
Flex::SpaceBetween => {
let single_width = page.def.max_left_padding
+ page.def.max_label
+ page.def.max_widget
+ page.def.max_right_padding;
let rest = layout_width.saturating_sub(single_width * page.def.columns);
let spacing = if page.def.columns > 1 {
rest / (page.def.columns - 1)
} else {
0
};
offset = border.left + (single_width + spacing) * column;
label_left = page.def.max_left_padding;
widget_left = label_left + page.def.max_label + page.def.spacing;
widget_right = widget_left + page.def.max_widget;
container_left = 0;
container_right = widget_right + page.def.max_right_padding;
}
}
XPositions {
label_left: offset + label_left,
label_width: page.def.max_label,
widget_left: offset + widget_left,
widget_width: page.def.max_widget,
widget_right: offset + widget_right,
container_left: offset + container_left,
container_right: offset + container_right,
total_width: widget_right - label_left,
}
}
}
impl Page {
fn adjusted_widths<W>(layout: &LayoutForm<W>, page_size: Size) -> (u16, u16, u16)
where
W: Eq + Hash + Clone + Debug,
{
let layout_width = page_size
.width
.saturating_sub(layout.page_border.left)
.saturating_sub(layout.page_border.right);
let n_col_spacers = layout.columns.saturating_sub(1);
let column_width =
layout_width.saturating_sub(layout.column_spacing * n_col_spacers) / layout.columns;
let mut min_label = layout.min_label;
let mut min_widget = max(
layout.min_widget,
layout
.min_widget_wide
.saturating_sub(layout.min_label)
.saturating_sub(layout.spacing),
);
let mut spacing = layout.spacing;
let nominal =
layout.max_left_padding + min_label + spacing + min_widget + layout.max_right_padding;
if nominal > column_width {
let mut reduce = nominal - column_width;
if min_label > 5 && min_label - 5 > reduce {
if spacing.saturating_sub(1) > reduce {
spacing -= reduce;
reduce = 0;
} else {
reduce -= spacing.saturating_sub(1);
spacing = if spacing > 0 { 1 } else { 0 };
}
} else {
if spacing > reduce {
spacing -= reduce;
reduce = 0;
} else {
reduce -= spacing;
spacing = 0;
}
}
if min_label > 5 {
if min_label - 5 > reduce {
min_label -= reduce;
reduce = 0;
} else {
reduce -= min_label - 5;
min_label = 5;
}
}
if min_widget > 5 {
if min_widget - 5 > reduce {
min_widget -= reduce;
reduce = 0;
} else {
reduce -= min_widget - 5;
min_widget = 5;
}
}
if min_label > reduce {
min_label -= reduce;
reduce = 0;
} else {
reduce -= min_label;
min_label = 0;
}
if min_widget > reduce {
min_widget -= reduce;
} else {
min_widget = 0;
}
}
(min_label, spacing, min_widget)
}
fn new<W>(layout: &LayoutForm<W>, page_size: Size) -> Self
where
W: Eq + Hash + Clone + Debug,
{
let (max_label, spacing, max_widget) = Self::adjusted_widths(layout, page_size);
let def = PageDef {
page_border: layout.page_border,
full_width: page_size.width,
flex: layout.flex,
max_left_padding: layout.max_left_padding,
max_right_padding: layout.max_right_padding,
max_label,
max_widget,
width: page_size.width,
height: page_size.height,
top: layout.page_border.top,
bottom: layout.page_border.bottom,
columns: layout.columns,
column_spacing: layout.column_spacing,
spacing,
line_spacing: layout.line_spacing,
};
let mut s = Page {
def: Rc::new(def),
page_no: 0,
page_start: 0,
page_end: page_size.height.saturating_sub(layout.page_border.bottom),
y: layout.page_border.top,
top_padding: 0,
bottom_padding: 0,
bottom_padding_break: 0,
effective_line_spacing: 0,
x_pos: Default::default(),
};
s.x_pos = XPositions::new(&s, 0, false);
s
}
}
fn adjust_blocks<W>(layout: &mut LayoutForm<W>, page_height: u16)
where
W: Eq + Hash + Clone + Debug,
{
if page_height == u16::MAX {
return;
}
if page_height < 3 {
for block_def in layout.blocks.iter_mut() {
if let Some(block) = block_def.block.as_mut() {
let padding = block_padding2(block);
let borders = if padding.left > 0 {
Borders::LEFT
} else {
Borders::NONE
} | if padding.right > 0 {
Borders::RIGHT
} else {
Borders::NONE
};
*block = mem::take(block).borders(borders);
block_def.padding.top = 0;
block_def.padding.bottom = 0;
}
}
}
}
fn build_layout<W, const ENDLESS: bool>(
mut layout: LayoutForm<W>,
page_size: Size,
) -> GenericLayout<W>
where
W: Eq + Hash + Clone + Debug,
{
let mut gen_layout = GenericLayout::with_capacity(
layout.widgets.len(), layout.blocks.len() * 2,
);
gen_layout.set_page_size(page_size);
adjust_blocks(&mut layout, page_size.height);
let mut stretch = Vec::with_capacity(layout.widgets.len());
let mut blocks_out = Vec::with_capacity(layout.blocks.len());
let mut saved_page;
let mut page = Page::new(&layout, page_size);
for (idx, widget) in layout.widgets.iter_mut().enumerate() {
saved_page = page.clone();
let mut label_area;
let mut widget_area;
let mut gap;
(label_area, widget_area, gap) =
next_widget(&mut page, &mut layout.blocks, widget, idx, false);
next_blocks(&mut page, &mut layout.blocks, idx, &mut blocks_out);
if !ENDLESS && page.y.saturating_add(page.bottom_padding_break) > page.page_end {
page = saved_page;
blocks_out.clear();
page_break_blocks(&mut page, &mut layout.blocks, idx, &mut blocks_out);
push_blocks(&mut blocks_out, &mut gen_layout);
adjust_y_stretch(&page, &mut stretch, &mut gen_layout);
page_break::<ENDLESS>(&mut page);
assert!(stretch.is_empty());
(label_area, widget_area, gap) =
next_widget(&mut page, &mut layout.blocks, widget, idx, true);
next_blocks(&mut page, &mut layout.blocks, idx, &mut blocks_out);
}
if !ENDLESS && widget.widget.is_stretch_y() {
stretch.push(gen_layout.widget_len());
}
gen_layout.add(
widget.id.clone(),
widget_area,
widget.label_str.take(),
label_area,
);
push_blocks(&mut blocks_out, &mut gen_layout);
let break_gap = !ENDLESS
&& page
.y
.saturating_add(gap)
.saturating_add(page.bottom_padding_break)
> page.page_end;
let break_manual = layout.page_breaks.contains(&idx);
if break_gap || break_manual {
assert!(blocks_out.is_empty());
page_break_blocks(&mut page, &mut layout.blocks, idx + 1, &mut blocks_out);
push_blocks(&mut blocks_out, &mut gen_layout);
if !ENDLESS {
adjust_y_stretch(&page, &mut stretch, &mut gen_layout);
}
page_break::<ENDLESS>(&mut page);
assert!(stretch.is_empty());
}
if !break_gap {
page.y += gap;
}
drop_blocks(&mut layout.blocks, idx);
assert!(blocks_out.is_empty());
}
adjust_y_stretch(&page, &mut stretch, &mut gen_layout);
gen_layout.set_page_count(((page.page_no + page.def.columns) / page.def.columns) as usize);
gen_layout
}
fn drop_blocks(_block_def: &mut VecDeque<BlockDef>, _idx: usize) {
}
fn page_break_blocks(
page: &mut Page,
block_def: &mut VecDeque<BlockDef>,
idx: usize,
blocks_out: &mut Vec<BlockOut>,
) {
for block in block_def.iter_mut().rev() {
if idx > block.range.start && idx < block.range.end {
end_block(page, block);
blocks_out.push(block.as_out());
block.range.start = idx;
}
}
}
fn page_break<const ENDLESS: bool>(page: &mut Page) {
page.page_no += 1;
let column = page.page_no % page.def.columns;
let mirror = (page.page_no / page.def.columns) % 2 == 1;
if !ENDLESS {
page.page_start = (page.page_no / page.def.columns).saturating_mul(page.def.height);
page.page_end = page
.page_start
.saturating_add(page.def.height.saturating_sub(page.def.bottom));
}
page.x_pos = XPositions::new(page, column, mirror);
page.y = page.page_start.saturating_add(page.def.top);
page.effective_line_spacing = 0;
page.top_padding = 0;
page.bottom_padding = 0;
page.bottom_padding_break = 0;
}
fn next_widget<W>(
page: &mut Page,
block_def: &mut VecDeque<BlockDef>,
widget: &WidgetDef<W>,
idx: usize,
must_fit: bool,
) -> (Rect, Rect, u16)
where
W: Eq + Hash + Clone + Debug,
{
page.y = page.y.saturating_add(page.effective_line_spacing);
page.effective_line_spacing = page.def.line_spacing;
page.top_padding = 0;
page.bottom_padding = 0;
page.bottom_padding_break = 0;
for block in block_def.iter_mut() {
if block.range.start == idx {
start_block(page, block);
}
if block.range.start <= idx {
widget_padding(page, idx, block);
}
}
let (label_area, widget_area, advance, gap) = areas_and_advance(page, widget, must_fit);
page.y = page.y.saturating_add(advance);
(label_area, widget_area, gap)
}
fn start_block(page: &mut Page, block: &mut BlockDef) {
block.area.x = page.x_pos.container_left;
block.area.width = page
.x_pos
.container_right
.saturating_sub(page.x_pos.container_left);
block.area.y = page.y;
page.y = page.y.saturating_add(block.padding.top);
page.top_padding += block.padding.top;
page.x_pos.container_left = page.x_pos.container_left.saturating_add(block.padding.left);
page.x_pos.container_right = page
.x_pos
.container_right
.saturating_sub(block.padding.right);
}
fn widget_padding(page: &mut Page, idx: usize, block: &mut BlockDef) {
if block.range.end > idx + 1 {
page.bottom_padding_break += block.padding.bottom;
} else if block.range.end == idx + 1 {
page.bottom_padding += block.padding.bottom;
}
}
fn next_blocks(
page: &mut Page,
block_def: &mut VecDeque<BlockDef>,
idx: usize,
blocks_out: &mut Vec<BlockOut>,
) {
for block in block_def.iter_mut().rev() {
if idx + 1 == block.range.end {
end_block(page, block);
blocks_out.push(block.as_out());
}
}
}
fn push_blocks<W: Eq + Hash + Clone>(
blocks_out: &mut Vec<BlockOut>,
gen_layout: &mut GenericLayout<W>,
) {
while let Some(cc) = blocks_out.pop() {
gen_layout.add_block(cc.area, cc.block);
}
}
fn end_block(page: &mut Page, block: &mut BlockDef) {
page.y = page.y.saturating_add(block.padding.bottom);
page.x_pos.container_left = page.x_pos.container_left.saturating_sub(block.padding.left);
page.x_pos.container_right = page
.x_pos
.container_right
.saturating_add(block.padding.right);
block.area.height = page.y.saturating_sub(block.area.y);
}
fn areas_and_advance<W: Debug + Clone>(
page: &Page,
widget: &WidgetDef<W>,
must_fit: bool,
) -> (Rect, Rect, u16, u16) {
let stacked = matches!(
widget.widget,
FormWidget::Wide(_, _) | FormWidget::WideStretchX(_, _) | FormWidget::WideStretchXY(_, _)
);
let mut label_height = match &widget.label {
FormLabel::None => 0,
FormLabel::Str(_) | FormLabel::String(_) => unreachable!(),
FormLabel::Width(_) => 1,
FormLabel::Size(_, h) => *h,
};
let mut widget_height = match &widget.widget {
FormWidget::None => 0,
FormWidget::Width(_) => 1,
FormWidget::Size(_, h) => *h,
FormWidget::StretchY(_, h) => *h,
FormWidget::Wide(_, h) => *h,
FormWidget::StretchX(_, h) => *h,
FormWidget::WideStretchX(_, h) => *h,
FormWidget::StretchXY(_, h) => *h,
FormWidget::WideStretchXY(_, h) => *h,
};
let gap_height = widget.gap;
let stretch_width = page
.x_pos
.widget_right
.saturating_sub(page.x_pos.widget_left);
let total_stretch_width = page
.x_pos
.widget_right
.saturating_sub(page.x_pos.label_left);
let max_height = if !must_fit {
page.def
.height
.saturating_sub(page.def.top)
.saturating_sub(page.def.bottom)
.saturating_sub(page.top_padding)
.saturating_sub(page.bottom_padding)
} else {
page.def
.height
.saturating_sub(page.y - page.page_start)
.saturating_sub(page.def.bottom)
.saturating_sub(page.bottom_padding_break)
};
if stacked {
if label_height + widget_height > max_height {
label_height = max(1, max_height.saturating_sub(widget_height));
}
if label_height + widget_height > max_height {
widget_height = max(1, max_height.saturating_sub(label_height));
}
if label_height + widget_height > max_height {
label_height = 0;
}
if label_height + widget_height > max_height {
widget_height = max_height;
}
let mut label_area = match &widget.label {
FormLabel::None => Rect::new(
page.x_pos.label_left, page.y,
0,
0,
),
FormLabel::Str(_) | FormLabel::String(_) => unreachable!(),
FormLabel::Width(_) => Rect::new(
page.x_pos.label_left,
page.y,
page.x_pos.label_width,
label_height,
),
FormLabel::Size(_, _) => Rect::new(
page.x_pos.label_left,
page.y,
page.x_pos.label_width,
label_height,
),
};
match &widget.widget {
FormWidget::Wide(_, _) => label_area.width = page.x_pos.total_width,
FormWidget::WideStretchX(_, _) => label_area.width = total_stretch_width,
FormWidget::WideStretchXY(_, _) => label_area.width = total_stretch_width,
_ => {}
}
let widget_area = match &widget.widget {
FormWidget::None => unreachable!(),
FormWidget::Width(_) => unreachable!(),
FormWidget::Size(_, _) => unreachable!(),
FormWidget::StretchY(_, _) => unreachable!(),
FormWidget::Wide(w, _) => Rect::new(
page.x_pos.label_left,
page.y + label_height,
min(*w, page.x_pos.total_width),
widget_height,
),
FormWidget::StretchX(_, _) => unreachable!(),
FormWidget::WideStretchX(_, _) => Rect::new(
page.x_pos.label_left,
page.y + label_height,
total_stretch_width,
widget_height,
),
FormWidget::StretchXY(_, _) => unreachable!(),
FormWidget::WideStretchXY(_, _) => Rect::new(
page.x_pos.label_left,
page.y + label_height,
total_stretch_width,
widget_height,
),
};
(
label_area,
widget_area,
label_area.height + widget_area.height,
gap_height,
)
} else {
label_height = min(label_height, max_height);
widget_height = min(widget_height, max_height);
let height = max(label_height, widget_height);
let label_area = match &widget.label {
FormLabel::None => Rect::new(
page.x_pos.label_left, page.y,
0,
0,
),
FormLabel::Str(_) | FormLabel::String(_) => unreachable!(),
FormLabel::Width(_) => Rect::new(
page.x_pos.label_left,
page.y,
page.x_pos.label_width,
height,
),
FormLabel::Size(_, _) => Rect::new(
page.x_pos.label_left,
page.y,
page.x_pos.label_width,
height,
),
};
let widget_area = match &widget.widget {
FormWidget::None => Rect::default(),
FormWidget::Width(w) => Rect::new(
page.x_pos.widget_left,
page.y,
min(*w, page.x_pos.widget_width),
height,
),
FormWidget::Size(w, _) => Rect::new(
page.x_pos.widget_left,
page.y,
min(*w, page.x_pos.widget_width),
height,
),
FormWidget::StretchY(w, _) => Rect::new(
page.x_pos.widget_left,
page.y,
min(*w, page.x_pos.widget_width),
height,
),
FormWidget::Wide(_, _) => unreachable!(),
FormWidget::StretchX(_, _) => Rect::new(
page.x_pos.widget_left, page.y,
stretch_width,
height,
),
FormWidget::WideStretchX(_, _) => unreachable!(),
FormWidget::StretchXY(_, _) => Rect::new(
page.x_pos.widget_left, page.y,
stretch_width,
height,
),
FormWidget::WideStretchXY(_, _) => unreachable!(),
};
(
label_area,
widget_area,
max(label_area.height, widget_area.height),
gap_height,
)
}
}
fn adjust_y_stretch<W: Eq + Hash + Clone>(
page: &Page,
stretch_y: &mut Vec<usize>,
gen_layout: &mut GenericLayout<W>,
) {
let mut remainder = page.page_end.saturating_sub(page.y);
if remainder == 0 {
stretch_y.clear();
return;
}
let mut n = stretch_y.len() as u16;
for y_idx in stretch_y.drain(..) {
let stretch = remainder / n;
remainder -= stretch;
n -= 1;
let mut area = gen_layout.widget(y_idx);
let test_y = area.bottom();
let test_x = page.x_pos.container_left;
area.height += stretch;
gen_layout.set_widget(y_idx, area);
for idx in y_idx + 1..gen_layout.widget_len() {
let mut area = gen_layout.widget(idx);
if area.y >= test_y {
area.y += stretch;
}
gen_layout.set_widget(idx, area);
let mut area = gen_layout.label(idx);
if area.y >= test_y {
area.y += stretch;
}
gen_layout.set_label(idx, area);
}
for idx in 0..gen_layout.block_len() {
let mut area = gen_layout.block_area(idx);
if area.x >= test_x && area.y >= test_y {
area.y += stretch;
}
if area.x >= test_x && area.y <= test_y && area.bottom() > test_y {
area.height += stretch;
}
gen_layout.set_block_area(idx, area);
}
}
}