use super::context::{MeasureCx, PaintCx};
use super::place::{Placed, with_spill};
use super::wrap::{self, Piece};
use super::{Align, LayoutProps, Length, Node, Widget};
use crate::geometry::{Rect, Size, clamp_u16};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Axis {
Row,
Column,
Stack,
}
pub(crate) struct Flex<Msg> {
axis: Axis,
children: Vec<Node<Msg>>,
wrap: bool,
line_gap: u16,
}
impl<Msg> Flex<Msg> {
pub(crate) fn new(axis: Axis, children: Vec<Node<Msg>>) -> Self {
Self { axis, children, wrap: false, line_gap: 0 }
}
pub(crate) fn set_wrap(&mut self, wrap: bool) {
self.wrap = wrap;
}
pub(crate) fn set_line_gap(&mut self, rows: u16) {
self.line_gap = rows;
}
}
fn split(axis: Axis, size: Size) -> (u16, u16) {
match axis {
Axis::Row => (size.width, size.height),
Axis::Column | Axis::Stack => (size.height, size.width),
}
}
fn join(axis: Axis, main: u16, cross: u16) -> Size {
match axis {
Axis::Row => Size::new(main, cross),
Axis::Column | Axis::Stack => Size::new(cross, main),
}
}
fn lengths(axis: Axis, layout: LayoutProps) -> (Length, Length) {
match axis {
Axis::Row => (layout.width, layout.height),
Axis::Column | Axis::Stack => (layout.height, layout.width),
}
}
fn offset(align: Align, free: u16) -> u16 {
match align {
Align::Start => 0,
Align::Center => free / 2,
Align::End => free,
}
}
fn gaps(count: usize, gap: u16) -> u16 {
gap.saturating_mul(clamp_u16(i32::try_from(count).unwrap_or(i32::MAX) - 1))
}
fn is_fill<Msg>(axis: Axis, child: &Node<Msg>) -> bool {
matches!(lengths(axis, child.layout).0, Length::Fill(_))
}
impl<Msg: 'static> Flex<Msg> {
fn cross_available(cross: Length, available: u16) -> u16 {
match cross {
Length::Cells(cells) => cells.min(available),
Length::Auto | Length::Fill(_) => available,
}
}
fn main_sizes(
&self,
children: &[&Node<Msg>],
measure: &mut dyn FnMut(&Node<Msg>, Size) -> Size,
main: u16,
cross: u16,
gap: u16,
) -> Vec<u16> {
let mut sizes = vec![0u16; children.len()];
let mut used = gaps(children.len(), gap);
let mut weights = 0u32;
for (i, child) in children.iter().enumerate() {
let (main_len, cross_len) = lengths(self.axis, child.layout);
match main_len {
Length::Cells(cells) => sizes[i] = cells,
Length::Auto => {
let available = join(self.axis, main.saturating_sub(used), Self::cross_available(cross_len, cross));
sizes[i] = split(self.axis, measure(child, available)).0;
}
Length::Fill(weight) => weights += u32::from(weight.max(1)),
}
if !matches!(main_len, Length::Fill(_)) {
used = used.saturating_add(sizes[i]);
}
}
let remaining = u32::from(main.saturating_sub(used));
let mut given = 0u32;
let mut last_fill = None;
for (i, child) in children.iter().enumerate() {
if let (Length::Fill(weight), _) = lengths(self.axis, child.layout)
&& let Some(share) = (remaining * u32::from(weight.max(1))).checked_div(weights)
{
sizes[i] = u16::try_from(share).unwrap_or(u16::MAX);
given += share;
last_fill = Some(i);
}
}
if let Some(i) = last_fill {
let rest = u16::try_from(remaining - given).unwrap_or(0);
sizes[i] = sizes[i].saturating_add(rest);
}
sizes
}
fn measure_run(&self, cx: &mut MeasureCx<'_>, children: &[&Node<Msg>], available: Size, gap: u16) -> (u16, u16) {
let (main_avail, cross_avail) = split(self.axis, available);
let gaps = gaps(children.len(), gap);
let mut main_total = gaps;
let mut cross_max = 0u16;
let mut remaining = main_avail.saturating_sub(gaps);
let sized = children.iter().filter(|child| !is_fill(self.axis, child));
let filling = children.iter().filter(|child| is_fill(self.axis, child));
for (fill, child) in sized.map(|child| (false, child)).chain(filling.map(|child| (true, child))) {
let (main_len, cross_len) = lengths(self.axis, child.layout);
let main_room = match main_len {
Length::Cells(cells) => cells.min(remaining),
Length::Auto | Length::Fill(_) => remaining,
};
let child_avail = join(self.axis, main_room, Self::cross_available(cross_len, cross_avail));
let measured = split(self.axis, cx.measure_child(child, child_avail));
let main_size = match main_len {
Length::Cells(cells) => cells.min(remaining),
Length::Auto | Length::Fill(_) => measured.0,
};
let cross_size = match cross_len {
Length::Cells(cells) => cells.min(cross_avail),
Length::Auto | Length::Fill(_) => measured.1,
};
main_total = main_total.saturating_add(main_size);
if !fill {
remaining = remaining.saturating_sub(main_size);
}
cross_max = cross_max.max(cross_size);
}
(main_total.min(main_avail), cross_max)
}
fn paint_run(&self, cx: &mut PaintCx<'_>, children: &[&Node<Msg>], area: Rect, layout: LayoutProps) {
let (main, cross) = split(self.axis, area.size());
let sizes = {
let mut measure = |node: &Node<Msg>, available: Size| cx.measure_child(node, available);
self.main_sizes(children, &mut measure, main, cross, layout.gap)
};
let total = sizes.iter().fold(gaps(children.len(), layout.gap), |sum, size| sum.saturating_add(*size));
let mut position = i32::from(offset(layout.justify, main.saturating_sub(total)));
for (child, main_size) in children.iter().zip(sizes) {
let (_, cross_len) = lengths(self.axis, child.layout);
let cross_size = match cross_len {
Length::Fill(_) => cross,
Length::Cells(cells) => cells.min(cross),
Length::Auto => {
let available = join(self.axis, main_size, cross);
split(self.axis, cx.measure_child(child, available)).1
}
};
let cross_offset = i32::from(offset(layout.align, cross - cross_size));
let rect = match self.axis {
Axis::Row => Rect::new(area.x + position, area.y + cross_offset, main_size, cross_size),
Axis::Column | Axis::Stack => {
Rect::new(area.x + cross_offset, area.y + position, cross_size, main_size)
}
};
cx.paint_child(child, rect);
position += i32::from(main_size) + i32::from(layout.gap);
}
}
fn lines(
&self,
measure: &mut dyn FnMut(&Node<Msg>, Size) -> Size,
available: Size,
gap: u16,
) -> Option<Vec<Vec<&Node<Msg>>>> {
if !self.wrap || self.axis != Axis::Row {
return None;
}
let pieces: Vec<Piece> = self
.children
.iter()
.map(|child| {
let width = match child.layout.width {
Length::Cells(cells) => cells,
Length::Auto | Length::Fill(_) => {
let height = Self::cross_available(child.layout.height, available.height);
measure(child, Size::new(available.width, height)).width
}
};
Piece { width: width.min(available.width), spacer: width == 0 && is_fill(self.axis, child) }
})
.collect();
let lines = wrap::break_lines(&pieces, available.width, gap);
(lines.len() > 1)
.then(|| lines.into_iter().map(|line| line.into_iter().map(|i| &self.children[i]).collect()).collect())
}
}
impl<Msg: 'static> Widget<Msg> for Flex<Msg> {
fn measure(&self, cx: &mut MeasureCx<'_>, available: Size) -> Size {
let layout = cx.layout();
if self.axis == Axis::Stack {
let mut size = Size::default();
for child in &self.children {
let child_size = match Placed::of(child) {
Some(rect) => Size::new(
clamp_u16(rect.right().max(0)).min(available.width),
clamp_u16(rect.bottom().max(0)).min(available.height),
),
None => cx.measure_child(child, available),
};
size = Size::new(size.width.max(child_size.width), size.height.max(child_size.height));
}
return size;
}
let lines = {
let mut measure = |node: &Node<Msg>, available: Size| cx.measure_child(node, available);
self.lines(&mut measure, available, layout.gap)
};
if let Some(lines) = lines {
let (mut width, mut height) = (0u16, 0u16);
for (i, line) in lines.iter().enumerate() {
let (line_width, line_height) = self.measure_run(cx, line, available, layout.gap);
width = width.max(line_width);
let before = if i == 0 { 0 } else { self.line_gap };
height = height.saturating_add(before).saturating_add(line_height);
}
return Size::new(width, height.min(available.height));
}
let children: Vec<&Node<Msg>> = self.children.iter().collect();
let (main, cross) = self.measure_run(cx, &children, available, layout.gap);
join(self.axis, main, cross)
}
fn paint(&self, cx: &mut PaintCx<'_>, area: Rect) {
let layout = cx.layout();
if self.axis == Axis::Stack {
for child in &self.children {
if let Some(placed) = Placed::of(child) {
let rect = Rect::new(area.x + placed.x, area.y + placed.y, placed.width, placed.height);
cx.paint_child_spilling(child, rect, with_spill(rect));
continue;
}
let measured = cx.measure_child(child, area.size());
let width = match child.layout.width {
Length::Fill(_) => area.width,
Length::Cells(cells) => cells.min(area.width),
Length::Auto => measured.width,
};
let height = match child.layout.height {
Length::Fill(_) => area.height,
Length::Cells(cells) => cells.min(area.height),
Length::Auto => measured.height,
};
let rect = Rect::new(
area.x + i32::from(offset(layout.align, area.width - width)),
area.y + i32::from(offset(layout.justify, area.height - height)),
width,
height,
);
cx.paint_child(child, rect);
}
return;
}
let lines = {
let mut measure = |node: &Node<Msg>, available: Size| cx.measure_child(node, available);
self.lines(&mut measure, area.size(), layout.gap)
};
if let Some(lines) = lines {
let mut top = area.y;
for line in &lines {
let room = clamp_u16(area.bottom() - top);
let height = {
let mut measure_cx = MeasureCx::for_frame(cx.env, &mut cx.frame.measures);
self.measure_run(&mut measure_cx, line, Size::new(area.width, room), layout.gap).1
};
self.paint_run(cx, line, Rect::new(area.x, top, area.width, height.min(room)), layout);
top += i32::from(height) + i32::from(self.line_gap);
}
return;
}
let children: Vec<&Node<Msg>> = self.children.iter().collect();
self.paint_run(cx, &children, area, layout);
}
fn children(&self) -> &[Node<Msg>] {
&self.children
}
fn children_mut(&mut self) -> &mut [Node<Msg>] {
&mut self.children
}
}