use super::{ChildInfo, Layout, LayoutContext};
use crate::compat::{Any, Vec};
use crate::core::{ObjectId, Rect, Size};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum WrapDirection {
#[default]
Horizontal,
Vertical,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum WrapAlignment {
#[default]
Start,
Center,
End,
SpaceBetween,
SpaceAround,
}
#[derive(Debug, Clone, Copy)]
struct WrapChild {
widget_id: ObjectId,
size: Size,
}
#[derive(Debug)]
pub struct WrapLayout {
direction: WrapDirection,
alignment: WrapAlignment,
spacing: i32,
run_spacing: Option<i32>,
padding: i32,
children: Vec<WrapChild>,
}
impl Default for WrapLayout {
fn default() -> Self {
Self::new(WrapDirection::Horizontal, WrapAlignment::Start, 8, 8)
}
}
struct Spacing {
gap: i32,
}
fn spacing_for(
alignment: WrapAlignment,
available: i32,
sizes_sum: i32,
item_count: usize,
base_gap: i32,
) -> Spacing {
if item_count < 2 {
return Spacing { gap: base_gap };
}
let used = sizes_sum + (item_count as i32 - 1) * base_gap;
let leftover = available - used;
match alignment {
WrapAlignment::SpaceBetween => {
Spacing { gap: base_gap + leftover / (item_count as i32 - 1) }
}
WrapAlignment::SpaceAround => Spacing { gap: base_gap + leftover / item_count as i32 },
_ => Spacing { gap: base_gap },
}
}
fn main_extent(size: Size, direction: WrapDirection) -> i32 {
match direction {
WrapDirection::Horizontal => size.width as i32,
WrapDirection::Vertical => size.height as i32,
}
}
fn cross_extent(size: Size, direction: WrapDirection) -> i32 {
match direction {
WrapDirection::Horizontal => size.height as i32,
WrapDirection::Vertical => size.width as i32,
}
}
pub(crate) fn break_lines(
sizes: &[Size],
direction: WrapDirection,
spacing: i32,
available: i32,
wrap: bool,
) -> Vec<Vec<usize>> {
let mut lines: Vec<Vec<usize>> = Vec::new();
let mut used = 0i32;
let mut count = 0usize;
for (index, size) in sizes.iter().enumerate() {
let candidate = main_extent(*size, direction);
let needs_wrap = wrap && count > 0 && used + spacing + candidate > available;
if needs_wrap {
lines.push(Vec::new());
used = 0;
count = 0;
}
if lines.is_empty() {
lines.push(Vec::new());
}
lines
.last_mut()
.expect("a line was just pushed above, so the list is non-empty")
.push(index);
used += candidate + if count > 0 { spacing } else { 0 };
count += 1;
}
lines
}
pub(crate) fn place_lines(
sizes: &[Size],
lines: &[Vec<usize>],
direction: WrapDirection,
alignment: WrapAlignment,
spacing: i32,
run_spacing: i32,
content: Rect,
) -> Vec<Rect> {
let mut rects = vec![Rect::new(content.x, content.y, 0, 0); sizes.len()];
if sizes.is_empty() {
return rects;
}
let horizontal = direction == WrapDirection::Horizontal;
let (available_main, available_cross) = if horizontal {
(content.width as i32, content.height as i32)
} else {
(content.height as i32, content.width as i32)
};
let line_cross: Vec<i32> = lines
.iter()
.map(|line| {
line.iter().map(|&index| cross_extent(sizes[index], direction)).max().unwrap_or(0)
})
.collect();
let used_cross: i32 =
line_cross.iter().sum::<i32>() + (lines.len() as i32 - 1).max(0) * run_spacing;
if horizontal {
let total_cross = used_cross.min(available_cross);
let mut cursor_cross = content.y
+ match alignment {
WrapAlignment::Start => 0,
WrapAlignment::Center => (available_cross - total_cross).max(0) / 2,
WrapAlignment::End => (available_cross - total_cross).max(0),
_ => 0,
};
for (line_index, line) in lines.iter().enumerate() {
let sizes_sum: i32 = line.iter().map(|&index| sizes[index].width as i32).sum::<i32>();
let line_total = sizes_sum + (line.len() as i32 - 1).max(0) * spacing;
let max_cross = line_cross[line_index];
let spacing = spacing_for(alignment, available_main, sizes_sum, line.len(), spacing);
let start_main = match alignment {
WrapAlignment::Start => content.x,
WrapAlignment::Center => content.x + (available_main - line_total).max(0) / 2,
WrapAlignment::End => content.x + (available_main - line_total).max(0),
WrapAlignment::SpaceBetween => content.x,
WrapAlignment::SpaceAround => {
let painted = sizes_sum + (line.len() as i32 - 1) * spacing.gap;
content.x + (available_main - painted).max(0) / 2
}
};
let mut cursor_main = start_main;
for (slot, &index) in line.iter().enumerate() {
if slot > 0 {
cursor_main += spacing.gap;
}
let size = sizes[index];
rects[index] = Rect::new(cursor_main, cursor_cross, size.width, size.height);
cursor_main += size.width as i32;
}
cursor_cross += max_cross + run_spacing;
}
} else {
let total_cross = used_cross;
let mut cursor_cross = content.x
+ match alignment {
WrapAlignment::Start => 0,
WrapAlignment::Center => (available_cross - total_cross).max(0) / 2,
WrapAlignment::End => (available_cross - total_cross).max(0),
_ => 0,
};
for (line_index, line) in lines.iter().enumerate() {
let sizes_sum: i32 = line.iter().map(|&index| sizes[index].height as i32).sum::<i32>();
let line_total = sizes_sum + (line.len() as i32 - 1).max(0) * spacing;
let max_cross = line_cross[line_index];
let spacing = spacing_for(alignment, available_main, sizes_sum, line.len(), spacing);
let start_main = match alignment {
WrapAlignment::Start => content.y,
WrapAlignment::Center => content.y + (available_main - line_total).max(0) / 2,
WrapAlignment::End => content.y + (available_main - line_total).max(0),
WrapAlignment::SpaceBetween => content.y,
WrapAlignment::SpaceAround => {
let painted = sizes_sum + (line.len() as i32 - 1) * spacing.gap;
content.y + (available_main - painted).max(0) / 2
}
};
let mut cursor_main = start_main;
for (slot, &index) in line.iter().enumerate() {
if slot > 0 {
cursor_main += spacing.gap;
}
let size = sizes[index];
rects[index] = Rect::new(cursor_cross, cursor_main, size.width, size.height);
cursor_main += size.height as i32;
}
cursor_cross += max_cross + run_spacing;
}
}
rects
}
impl WrapLayout {
pub fn new(
direction: WrapDirection,
alignment: WrapAlignment,
spacing: i32,
padding: i32,
) -> Self {
let padding = padding.max(0);
Self { direction, alignment, spacing, run_spacing: None, padding, children: Vec::new() }
}
pub fn set_run_spacing(&mut self, spacing: Option<i32>) {
self.run_spacing = spacing;
}
pub fn run_spacing(&self) -> i32 {
self.run_spacing.unwrap_or(self.spacing)
}
pub fn new_default() -> Self {
Self::new(WrapDirection::Horizontal, WrapAlignment::Start, 8, 8)
}
pub fn child_count(&self) -> usize {
self.children.len()
}
pub fn direction(&self) -> WrapDirection {
self.direction
}
pub fn alignment(&self) -> WrapAlignment {
self.alignment
}
pub fn spacing(&self) -> i32 {
self.spacing
}
pub fn padding(&self) -> i32 {
self.padding
}
pub fn set_child_size(&mut self, widget_id: ObjectId, size: Size) {
if let Some(child) = self.children.iter_mut().find(|c| c.widget_id == widget_id) {
child.size = size;
}
}
fn content_rect(&self, outer: Rect) -> Rect {
let pad = self.padding.max(0);
Rect::new(
outer.x.saturating_add(pad),
outer.y.saturating_add(pad),
outer.width.saturating_sub(2 * pad as u32),
outer.height.saturating_sub(2 * pad as u32),
)
}
fn compute_rects(&self, content: Rect) -> Vec<(ObjectId, Rect)> {
let sizes: Vec<Size> = self.children.iter().map(|child| child.size).collect();
let available_main = match self.direction {
WrapDirection::Horizontal => content.width,
WrapDirection::Vertical => content.height,
} as i32;
let lines = break_lines(&sizes, self.direction, self.spacing, available_main, true);
let rects = place_lines(
&sizes,
&lines,
self.direction,
self.alignment,
self.spacing,
self.run_spacing(),
content,
);
self.children.iter().zip(rects).map(|(child, rect)| (child.widget_id, rect)).collect()
}
}
impl Layout for WrapLayout {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn add_widget(&mut self, widget_id: ObjectId, _stretch: u32) {
self.children.push(WrapChild { widget_id, size: Size::new(0, 0) });
}
fn remove_widget(&mut self, widget_id: ObjectId) {
self.children.retain(|c| c.widget_id != widget_id);
}
fn child_ids(&self) -> Vec<ObjectId> {
self.children.iter().map(|c| c.widget_id).collect()
}
fn has_child(&self, id: ObjectId) -> bool {
self.children.iter().any(|c| c.widget_id == id)
}
fn clear(&mut self) {
self.children.clear();
}
fn update(&self, rect: Rect, widgets: &mut dyn FnMut(ObjectId, Rect)) {
let content = self.content_rect(rect);
if content.width == 0 || content.height == 0 {
return;
}
let results = self.compute_rects(content);
for (wid, child_rect) in results {
widgets(wid, child_rect);
}
}
fn arrange(&self, rect: Rect, children: &[ChildInfo], out: &mut dyn FnMut(ObjectId, Rect)) {
let sized: Vec<WrapChild> = self
.children
.iter()
.map(|child| WrapChild {
widget_id: child.widget_id,
size: match ChildInfo::find(children, child.widget_id) {
Some(info) => info.resolved_size(child.size),
None => child.size,
},
})
.collect();
let sized = WrapLayout {
direction: self.direction,
alignment: self.alignment,
spacing: self.spacing,
run_spacing: self.run_spacing,
padding: self.padding,
children: sized,
};
let content = sized.content_rect(rect);
if content.width == 0 || content.height == 0 {
return;
}
for (wid, child_rect) in sized.compute_rects(content) {
out(wid, child_rect);
}
}
fn update_with_context(
&self,
rect: Rect,
context: &LayoutContext,
widgets: &mut dyn FnMut(ObjectId, Rect),
) {
let scale = context.layout_scale.max(context.font_scale);
let scaled_padding = (self.padding.max(0) as f32 * scale).max(0.0) as i32;
let scaled = WrapLayout {
direction: self.direction,
alignment: self.alignment,
spacing: (self.spacing as f32 * scale) as i32,
run_spacing: self.run_spacing.map(|spacing| (spacing as f32 * scale) as i32),
padding: scaled_padding,
children: self.children.clone(),
};
let content = Rect::new(
rect.x.saturating_add(scaled_padding),
rect.y.saturating_add(scaled_padding),
rect.width.saturating_sub(2 * scaled_padding as u32),
rect.height.saturating_sub(2 * scaled_padding as u32),
);
if content.width == 0 || content.height == 0 {
return;
}
let results = scaled.compute_rects(content);
for (wid, child_rect) in results {
widgets(
wid,
crate::layout::types::grow_to_min_touch_size(child_rect, context.min_touch_size),
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compat::HashMap;
use crate::layout::Hints;
#[test]
fn wrap_layout_default_creates_empty() {
let layout = WrapLayout::default();
assert_eq!(layout.child_count(), 0);
}
#[test]
fn wrap_layout_add_and_remove() {
let mut layout = WrapLayout::default();
layout.add_widget(1, 0);
layout.add_widget(2, 0);
assert_eq!(layout.child_count(), 2);
assert!(layout.has_child(1));
layout.remove_widget(1);
assert_eq!(layout.child_count(), 1);
assert!(!layout.has_child(1));
assert!(layout.has_child(2));
}
#[test]
fn wrap_layout_child_ids() {
let mut layout = WrapLayout::default();
layout.add_widget(10, 0);
layout.add_widget(20, 0);
let ids = layout.child_ids();
assert_eq!(ids.len(), 2);
assert!(ids.contains(&10));
assert!(ids.contains(&20));
}
#[test]
fn wrap_layout_clear() {
let mut layout = WrapLayout::default();
layout.add_widget(1, 0);
layout.add_widget(2, 0);
assert_eq!(layout.child_count(), 2);
layout.clear();
assert_eq!(layout.child_count(), 0);
}
#[test]
fn wrap_layout_horizontal_single_row() {
let mut layout = WrapLayout::new(WrapDirection::Horizontal, WrapAlignment::Start, 0, 0);
layout.add_widget(1, 0);
layout.add_widget(2, 0);
layout.set_child_size(1, Size::new(40, 20));
layout.set_child_size(2, Size::new(40, 20));
let mut rects = HashMap::new();
layout.update(Rect::new(0, 0, 200, 50), &mut |id, rect| {
rects.insert(id, rect);
});
assert_eq!(rects.get(&1), Some(&Rect::new(0, 0, 40, 20)));
assert_eq!(rects.get(&2), Some(&Rect::new(40, 0, 40, 20)));
}
#[test]
fn wrap_layout_horizontal_wraps_to_next_row() {
let mut layout = WrapLayout::new(WrapDirection::Horizontal, WrapAlignment::Start, 4, 0);
layout.add_widget(1, 0);
layout.add_widget(2, 0);
layout.add_widget(3, 0);
layout.set_child_size(1, Size::new(60, 20));
layout.set_child_size(2, Size::new(60, 20));
layout.set_child_size(3, Size::new(60, 20));
let mut rects = HashMap::new();
layout.update(Rect::new(0, 0, 100, 100), &mut |id, rect| {
rects.insert(id, rect);
});
assert_eq!(rects.get(&1), Some(&Rect::new(0, 0, 60, 20)));
assert_eq!(rects.get(&2), Some(&Rect::new(0, 24, 60, 20)));
assert_eq!(rects.get(&3), Some(&Rect::new(0, 48, 60, 20)));
}
#[test]
fn wrap_layout_horizontal_two_per_row() {
let mut layout = WrapLayout::new(WrapDirection::Horizontal, WrapAlignment::Start, 0, 0);
layout.add_widget(1, 0);
layout.add_widget(2, 0);
layout.add_widget(3, 0);
layout.add_widget(4, 0);
layout.set_child_size(1, Size::new(40, 20));
layout.set_child_size(2, Size::new(40, 20));
layout.set_child_size(3, Size::new(40, 20));
layout.set_child_size(4, Size::new(40, 20));
let mut rects = HashMap::new();
layout.update(Rect::new(0, 0, 80, 100), &mut |id, rect| {
rects.insert(id, rect);
});
assert_eq!(rects.get(&1), Some(&Rect::new(0, 0, 40, 20)));
assert_eq!(rects.get(&2), Some(&Rect::new(40, 0, 40, 20)));
assert_eq!(rects.get(&3), Some(&Rect::new(0, 20, 40, 20)));
assert_eq!(rects.get(&4), Some(&Rect::new(40, 20, 40, 20)));
}
#[test]
fn wrap_layout_horizontal_center_alignment() {
let mut layout = WrapLayout::new(WrapDirection::Horizontal, WrapAlignment::Center, 0, 0);
layout.add_widget(1, 0);
layout.set_child_size(1, Size::new(40, 20));
let mut rects = HashMap::new();
layout.update(Rect::new(0, 0, 80, 50), &mut |id, rect| {
rects.insert(id, rect);
});
assert_eq!(rects.get(&1), Some(&Rect::new(20, 15, 40, 20)));
}
#[test]
fn wrap_layout_horizontal_end_alignment() {
let mut layout = WrapLayout::new(WrapDirection::Horizontal, WrapAlignment::End, 0, 0);
layout.add_widget(1, 0);
layout.set_child_size(1, Size::new(40, 20));
let mut rects = HashMap::new();
layout.update(Rect::new(0, 0, 80, 50), &mut |id, rect| {
rects.insert(id, rect);
});
assert_eq!(rects.get(&1), Some(&Rect::new(40, 30, 40, 20)));
}
#[test]
fn wrap_layout_padding_applied() {
let mut layout = WrapLayout::new(WrapDirection::Horizontal, WrapAlignment::Start, 0, 10);
layout.add_widget(1, 0);
layout.set_child_size(1, Size::new(40, 20));
let mut rects = HashMap::new();
layout.update(Rect::new(0, 0, 100, 50), &mut |id, rect| {
rects.insert(id, rect);
});
assert_eq!(rects.get(&1), Some(&Rect::new(10, 10, 40, 20)));
}
#[test]
fn wrap_layout_rejects_negative_padding() {
let mut layout = WrapLayout::new(WrapDirection::Horizontal, WrapAlignment::Start, 0, -1);
layout.add_widget(1, 0);
layout.set_child_size(1, Size::new(40, 20));
let mut rects = HashMap::new();
layout.update(Rect::new(0, 0, 100, 50), &mut |id, rect| {
rects.insert(id, rect);
});
assert_eq!(rects.get(&1), Some(&Rect::new(0, 0, 40, 20)));
}
#[test]
fn wrap_layout_vertical_single_column() {
let mut layout = WrapLayout::new(WrapDirection::Vertical, WrapAlignment::Start, 0, 0);
layout.add_widget(1, 0);
layout.add_widget(2, 0);
layout.set_child_size(1, Size::new(30, 40));
layout.set_child_size(2, Size::new(30, 40));
let mut rects = HashMap::new();
layout.update(Rect::new(0, 0, 100, 150), &mut |id, rect| {
rects.insert(id, rect);
});
assert_eq!(rects.get(&1), Some(&Rect::new(0, 0, 30, 40)));
assert_eq!(rects.get(&2), Some(&Rect::new(0, 40, 30, 40)));
}
#[test]
fn wrap_layout_vertical_wraps_to_next_column() {
let mut layout = WrapLayout::new(WrapDirection::Vertical, WrapAlignment::Start, 4, 0);
layout.add_widget(1, 0);
layout.add_widget(2, 0);
layout.add_widget(3, 0);
layout.set_child_size(1, Size::new(30, 50));
layout.set_child_size(2, Size::new(30, 50));
layout.set_child_size(3, Size::new(30, 50));
let mut rects = HashMap::new();
layout.update(Rect::new(0, 0, 200, 60), &mut |id, rect| {
rects.insert(id, rect);
});
assert_eq!(rects.get(&1), Some(&Rect::new(0, 0, 30, 50)));
assert_eq!(rects.get(&2), Some(&Rect::new(34, 0, 30, 50)));
assert_eq!(rects.get(&3), Some(&Rect::new(68, 0, 30, 50)));
}
#[test]
fn wrap_layout_update_with_context_scales_spacing() {
let mut layout = WrapLayout::new(WrapDirection::Horizontal, WrapAlignment::Start, 8, 4);
layout.add_widget(1, 0);
layout.add_widget(2, 0);
layout.set_child_size(1, Size::new(30, 20));
layout.set_child_size(2, Size::new(30, 20));
let context = LayoutContext { layout_scale: 2.0, ..LayoutContext::default() };
let mut rects = HashMap::new();
layout.update_with_context(Rect::new(0, 0, 200, 100), &context, &mut |id, rect| {
rects.insert(id, rect);
});
let min = context.min_touch_size;
let first = rects.get(&1).copied().expect("item 1 was laid out");
let second = rects.get(&2).copied().expect("item 2 was laid out");
assert_eq!(first.width, min.width.max(30));
assert_eq!(first.height, min.height.max(20));
assert_eq!(first.x, 8 - (first.width as i32 - 30) / 2);
assert_eq!(first.y, 8 - (first.height as i32 - 20) / 2);
assert_eq!(second.x, 54 - (second.width as i32 - 30) / 2);
}
#[test]
fn wrap_layout_empty_rect_yields_no_children() {
let mut layout = WrapLayout::default();
layout.add_widget(1, 0);
layout.set_child_size(1, Size::new(100, 100));
let mut called = false;
layout.update(Rect::new(0, 0, 0, 0), &mut |_id, _rect| {
called = true;
});
assert!(!called);
}
#[test]
fn space_around_leaves_a_half_gap_at_each_end() {
fn x_positions(alignment: WrapAlignment) -> Vec<i32> {
let mut layout = WrapLayout::new(WrapDirection::Horizontal, alignment, 0, 0);
for id in [1, 2, 3] {
layout.add_widget(id, 0);
layout.set_child_size(id, Size::new(20, 10));
}
layout
.compute_rects(layout.content_rect(Rect::new(0, 0, 160, 40)))
.into_iter()
.map(|(_, rect)| rect.x)
.collect()
}
let around = x_positions(WrapAlignment::SpaceAround);
let between = x_positions(WrapAlignment::SpaceBetween);
assert_eq!(between, vec![0, 70, 140], "SpaceBetween starts flush");
let pitch = around[1] - around[0];
assert_eq!(pitch, 53, "a whole slot between items");
assert_eq!(around[2] - around[1], pitch);
let lead = around[0];
let trail = 160 - (around[2] + 20);
assert_eq!(lead, trail, "the run is centred, not left-padded");
assert!(lead > 0, "SpaceAround leaves a gap before the first item");
assert_ne!(around, between, "SpaceAround and SpaceBetween are not the same alignment");
}
#[test]
fn spacing_alignments_never_overflow_the_line() {
for alignment in [WrapAlignment::SpaceBetween, WrapAlignment::SpaceAround] {
let mut layout = WrapLayout::new(WrapDirection::Horizontal, alignment, 10, 0);
for id in [1, 2, 3] {
layout.add_widget(id, 0);
layout.set_child_size(id, Size::new(30, 10));
}
let out = layout.compute_rects(layout.content_rect(Rect::new(0, 0, 160, 40)));
assert_eq!(
out.iter().map(|(_, r)| r.width).collect::<Vec<_>>(),
vec![30, 30, 30],
"the child sizes are what the test set"
);
assert_eq!(out.len(), 3, "160 must hold three 30px items plus two 10px gaps");
let last = out.last().expect("three items").1;
assert!(
last.x >= 0 && last.x + last.width as i32 <= 160,
"{alignment:?} ran off the line: last item at {}..{}",
last.x,
last.x + last.width as i32
);
}
}
#[test]
fn a_single_item_line_is_only_centred_not_pushed() {
for alignment in [WrapAlignment::SpaceBetween, WrapAlignment::SpaceAround] {
let mut layout = WrapLayout::new(WrapDirection::Horizontal, alignment, 10, 0);
layout.add_widget(1, 0);
layout.set_child_size(1, Size::new(30, 10));
let out = layout.compute_rects(layout.content_rect(Rect::new(0, 0, 160, 40)));
let x = out[0].1.x;
let expected = if alignment == WrapAlignment::SpaceAround { (160 - 30) / 2 } else { 0 };
assert_eq!(x, expected, "{alignment:?} placed a lone item at x={x}");
}
}
#[test]
fn space_around_is_symmetric_in_the_vertical_direction() {
let mut layout = WrapLayout::new(WrapDirection::Vertical, WrapAlignment::SpaceAround, 0, 0);
for id in [1, 2, 3] {
layout.add_widget(id, 0);
layout.set_child_size(id, Size::new(10, 20));
}
let out = layout.compute_rects(layout.content_rect(Rect::new(0, 0, 40, 160)));
assert_eq!(out[0].1.y, 17, "the run is centred vertically");
assert_eq!(160 - (out[2].1.y + 20), 17, "and symmetrically so");
}
#[test]
fn run_spacing_separates_rows_without_widening_the_items() {
let mut layout = WrapLayout::new(WrapDirection::Horizontal, WrapAlignment::Start, 0, 0);
for id in 1..=4 {
layout.add_widget(id, 0);
layout.set_child_size(id, Size::new(40, 10));
}
layout.set_run_spacing(Some(20));
assert_eq!(layout.run_spacing(), 20);
let mut placed = HashMap::new();
layout.update(Rect::new(0, 0, 100, 100), &mut |id, rect| {
placed.insert(id, rect);
});
assert_eq!(placed[&1].y, 0);
assert_eq!(placed[&2].y, 0);
assert_eq!(placed[&2].x, 40, "the in-row gap is still the item spacing (0)");
assert_eq!(placed[&3].y, 30, "10 px row height plus 20 px run spacing");
assert_eq!(placed[&4].y, 30);
}
#[test]
fn arrange_lays_children_out_from_their_hints() {
let mut layout = WrapLayout::new(WrapDirection::Horizontal, WrapAlignment::Start, 0, 0);
for id in 1..=3 {
layout.add_widget(id, 0);
}
let children: Vec<ChildInfo> =
(1..=3).map(|id| ChildInfo::new(id, Hints::fixed(40, 10))).collect();
let mut placed = HashMap::new();
layout.arrange(Rect::new(0, 0, 90, 100), &children, &mut |id, rect| {
placed.insert(id, rect);
});
assert_eq!(placed[&1], Rect::new(0, 0, 40, 10));
assert_eq!(placed[&2], Rect::new(40, 0, 40, 10));
assert_eq!(placed[&3], Rect::new(0, 10, 40, 10));
}
#[test]
fn arrange_falls_back_to_the_registered_size() {
let mut layout = WrapLayout::new(WrapDirection::Horizontal, WrapAlignment::Start, 0, 0);
layout.add_widget(1, 0);
layout.set_child_size(1, Size::new(30, 12));
let children = [ChildInfo::new(1, Hints::default())];
let mut placed = HashMap::new();
layout.arrange(Rect::new(0, 0, 100, 50), &children, &mut |id, rect| {
placed.insert(id, rect);
});
assert_eq!(placed[&1], Rect::new(0, 0, 30, 12));
}
}