use crate::{
layout::{LayoutInfo, Rect, Vec2},
Alignment, Layout, LayoutExt, Orientation,
};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Props {
pub expand: usize,
pub align: Alignment,
}
impl Default for Props {
fn default() -> Self {
Self::new()
}
}
impl Props {
pub const fn new() -> Self {
Self {
expand: 0,
align: Alignment::Expand,
}
}
}
pub trait FlexLayout: Layout {
fn props(&self) -> &Props;
}
pub fn layout_flexbox(
rect: Rect,
should_wrap: bool,
minimum: bool,
expand: bool,
row_gap: usize,
column_gap: usize,
orientation: Orientation,
items: &[impl FlexLayout],
) -> Vec<Rect> {
let mut wrap = if should_wrap {
wrap(rect.size, orientation, row_gap, column_gap, items, minimum)
} else {
vec![0]
};
let wrapped_len = wrap.len();
wrap.push(items.len());
let mut wrap_row_length = Vec::with_capacity(wrapped_len);
let mut sub_size_accumulate = 0;
for window in wrap.windows(2) {
let start = window[0];
let end = window[1];
let items = &items[start..end];
let sub_size: usize = items
.iter()
.map(|x| {
x.sub_for_prim(
rect.size.in_orientation(orientation),
orientation,
)
.minimum
})
.max()
.unwrap_or(1)
.min(
rect.size
.in_orientation(!orientation)
.saturating_sub(sub_size_accumulate),
)
.max(1);
sub_size_accumulate += sub_size;
wrap_row_length.push(sub_size);
}
if !minimum {
for (sub_size, window) in
wrap_row_length.iter_mut().zip(wrap.windows(2))
{
let start = window[0];
let end = window[1];
let items = &items[start..end];
sub_size_accumulate -= *sub_size;
let new_sub_size: usize = items
.iter()
.map(|x| {
x.sub_for_prim(
rect.size.in_orientation(orientation),
orientation,
)
.natural
})
.max()
.unwrap_or(1)
.min(
rect.size
.in_orientation(!orientation)
.saturating_sub(sub_size_accumulate),
)
.max(1);
sub_size_accumulate += new_sub_size;
*sub_size = new_sub_size;
}
}
if expand {
let rect_sub_size = rect.size.in_orientation(!orientation);
let free_space = rect_sub_size.saturating_sub(sub_size_accumulate);
let div_iter = divide_integer(free_space, wrapped_len);
for (wrap_row_item, n) in wrap_row_length.iter_mut().zip(div_iter) {
*wrap_row_item += n;
}
}
let mut vec = Vec::with_capacity(items.len());
let mut wrap_accumulate = 0;
let mut sub_size_accumulate = 0;
for (sub_size, window) in wrap_row_length.iter().zip(wrap.windows(2)) {
let start = window[0];
let end = window[1];
let items = &items[start..end];
let size = match orientation {
Orientation::Vertical => Vec2::new(*sub_size, rect.size.y),
Orientation::Horizontal => Vec2::new(rect.size.x, *sub_size),
};
sub_size_accumulate += sub_size;
let padding = match orientation {
Orientation::Vertical => row_gap,
Orientation::Horizontal => column_gap,
};
let layout = layout_flexbox_no_wrap(
size,
padding,
orientation,
items,
minimum,
expand,
);
for item_li in layout {
vec.push(match orientation {
Orientation::Vertical => Rect::new(
rect.start.x + wrap_accumulate,
rect.start.y + item_li.start,
*sub_size,
item_li.end - item_li.start,
),
Orientation::Horizontal => Rect::new(
rect.start.x + item_li.start,
rect.start.y + wrap_accumulate,
item_li.end - item_li.start,
*sub_size,
),
});
}
wrap_accumulate += sub_size;
wrap_accumulate += match orientation {
Orientation::Vertical => column_gap,
Orientation::Horizontal => row_gap,
};
}
vec
}
fn layout_flexbox_no_wrap(
size: Vec2,
padding: usize,
orientation: Orientation,
items: &[impl FlexLayout],
minimum: bool,
expand: bool,
) -> Vec<LayoutInfo> {
let prim_size = size
.in_orientation(orientation)
.saturating_sub(padding * (items.len().saturating_sub(1)));
let mut expand_total = 0;
for item in items {
expand_total += item.props().expand;
}
let mut vec = Vec::with_capacity(items.len());
let mut sum = 0;
for item in items {
let width =
item.sub_for_prim(size.in_orientation(!orientation), !orientation);
let width = width.minimum;
sum += width;
vec.push(width);
}
if !minimum {
for (old_width, item) in vec.iter_mut().zip(items) {
let width = item
.sub_for_prim(size.in_orientation(!orientation), !orientation);
let width = width.natural;
if sum + width - *old_width <= size.in_orientation(orientation) {
*old_width = width;
sum += width - *old_width;
}
}
}
if expand {
let sum = vec.iter().sum();
let expand_size = prim_size.saturating_sub(sum);
let mut to_add = divide_integer(expand_size, expand_total);
for (item, item_size) in items.iter().zip(vec.iter_mut()) {
let expand = item.props().expand;
let mut to_alloc = 0;
for _ in 0..expand {
let next = to_add.next().unwrap_or(0);
to_alloc += next;
}
*item_size += to_alloc;
}
}
let mut lis = Vec::with_capacity(vec.len());
let mut accumulated = 0;
for item_size in vec {
lis.push(LayoutInfo {
start: accumulated,
end: accumulated + item_size,
});
accumulated += item_size;
accumulated += padding;
}
lis
}
pub fn wrap(
size: Vec2,
orientation: Orientation,
row_gap: usize,
column_gap: usize,
items: &[impl FlexLayout],
minimum: bool,
) -> Vec<usize> {
let mut accumulated = 0;
let mut vec = vec![0];
let prim_size = size.in_orientation(orientation);
for (i, item) in items.iter().enumerate() {
let og_accumulated = accumulated;
let size =
item.sub_for_prim(size.in_orientation(!orientation), !orientation);
accumulated += if minimum { size.minimum } else { size.natural };
accumulated += match orientation {
Orientation::Vertical => row_gap,
Orientation::Horizontal => column_gap,
};
if accumulated > prim_size {
vec.push(i);
accumulated -= og_accumulated;
}
}
vec
}
pub(crate) fn divide_integer(
n: usize,
d: usize,
) -> impl Iterator<Item = usize> {
let step = n.checked_div(d).unwrap_or(0);
let rem_step = n.checked_rem(d).unwrap_or(0);
(0..d).scan(0, move |rem, _| {
let mut current = step;
*rem += rem_step;
if *rem >= d {
*rem -= d;
current += 1;
}
Some(current)
})
}