use figures::units::{Px, UPx};
use figures::{IntoSigned, IntoUnsigned, Point, Rect, Round, ScreenScale, Size, Zero};
use intentional::Cast;
use crate::context::{AsEventContext, GraphicsContext, LayoutContext, Trackable};
use crate::styles::components::{IntrinsicPadding, LayoutOrder, VerticalAlignment};
use crate::styles::{FlexibleDimension, HorizontalOrder, VerticalAlign};
use crate::value::{IntoValue, Value};
use crate::widget::{MountedChildren, Widget, WidgetList};
use crate::ConstraintLimit;
#[derive(Debug)]
pub struct Wrap {
pub children: Value<WidgetList>,
pub align: Value<WrapAlign>,
pub spacing: Value<Size<FlexibleDimension>>,
mounted: MountedChildren,
}
impl Wrap {
#[must_use]
pub fn new(children: impl IntoValue<WidgetList>) -> Self {
Self {
children: children.into_value(),
align: Value::default(),
spacing: Value::Constant(Size::squared(FlexibleDimension::Auto)),
mounted: MountedChildren::default(),
}
}
#[must_use]
pub fn spacing(mut self, spacing: impl IntoValue<Size<FlexibleDimension>>) -> Self {
self.spacing = spacing.into_value();
self
}
#[must_use]
pub fn align(mut self, align: impl IntoValue<WrapAlign>) -> Self {
self.align = align.into_value();
self
}
fn horizontal_alignment(
align: WrapAlign,
order: HorizontalOrder,
remaining: Px,
row_children_len: usize,
) -> (Px, Px) {
match (align, order) {
(WrapAlign::Start, HorizontalOrder::LeftToRight)
| (WrapAlign::End, HorizontalOrder::RightToLeft) => (Px::ZERO, Px::ZERO),
(WrapAlign::End, HorizontalOrder::LeftToRight)
| (WrapAlign::Start, HorizontalOrder::RightToLeft) => (remaining, Px::ZERO),
(WrapAlign::Center, _) => (remaining / 2, Px::ZERO),
(WrapAlign::SpaceBetween, _) => {
if row_children_len > 1 {
(Px::ZERO, remaining / (row_children_len - 1).cast::<i32>())
} else {
(Px::ZERO, Px::ZERO)
}
}
(WrapAlign::SpaceEvenly, _) => {
let spacing = remaining / row_children_len.cast::<i32>();
(spacing / 2, spacing)
}
(WrapAlign::SpaceAround, _) => {
let spacing = remaining / (row_children_len + 1).cast::<i32>();
(spacing, spacing)
}
}
}
}
impl Widget for Wrap {
fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_>) {
for child in self.mounted.children() {
context.for_other(child).redraw();
}
}
#[allow(clippy::too_many_lines)]
fn layout(
&mut self,
available_space: Size<ConstraintLimit>,
context: &mut LayoutContext<'_, '_, '_, '_>,
) -> Size<UPx> {
struct RowChild {
index: usize,
x: Px,
size: Size<Px>,
}
let order = context.get(&LayoutOrder).horizontal;
self.children.invalidate_when_changed(context);
let align = self.align.get_tracking_invalidate(context);
let vertical_align = context.get(&VerticalAlignment);
let spacing = self
.spacing
.get_tracking_invalidate(context)
.map(|dimension| match dimension {
FlexibleDimension::Auto => context.get(&IntrinsicPadding),
FlexibleDimension::Dimension(dimension) => dimension,
})
.into_px(context.gfx.scale())
.round();
self.mounted
.synchronize_with(&self.children, &mut context.as_event_context());
let mut y = Px::ZERO;
let mut row_children = Vec::new();
let mut index = 0;
let width = available_space.width.max().into_signed();
let child_constraints =
available_space.map(|limit| ConstraintLimit::SizeToFit(limit.max()));
while index < self.mounted.children().len() {
if y != Px::ZERO {
y += spacing.height;
}
let mut x = Px::ZERO;
let mut max_height = Px::ZERO;
while let Some(child) = self.mounted.children().get(index) {
let child_size = context
.for_other(child)
.layout(child_constraints)
.into_signed();
max_height = max_height.max(child_size.height);
let child_x = if x.is_zero() {
x
} else {
x.saturating_add(spacing.width)
};
let after_child = child_x.saturating_add(child_size.width);
if x > 0 && after_child > width {
break;
}
row_children.push(RowChild {
index,
x: child_x,
size: child_size,
});
x = after_child;
index += 1;
}
let remaining = (width - x).max(Px::ZERO);
let (x, space_between) = if remaining > 0 {
Self::horizontal_alignment(align, order, remaining, row_children.len())
} else {
(Px::ZERO, Px::ZERO)
};
let mut additional_x = x;
for (child_index, child) in row_children.drain(..).enumerate() {
if child_index > 0 {
additional_x += space_between;
}
let child_x = additional_x + child.x;
let child_y = y + match vertical_align {
VerticalAlign::Top => Px::ZERO,
VerticalAlign::Center => (max_height - child.size.height) / 2,
VerticalAlign::Bottom => max_height - child.size.height,
};
context.set_child_layout(
&self.mounted.children()[child.index],
Rect::new(Point::new(child_x, child_y), child.size),
);
}
y += max_height;
}
Size::new(width, y).into_unsigned()
}
}
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
pub enum WrapAlign {
#[default]
Start,
End,
Center,
SpaceBetween,
SpaceEvenly,
SpaceAround,
}