use super::hints::ChildInfo;
use super::types::LayoutContext;
use super::Layout;
use crate::compat::{Any, Box, Vec};
use crate::core::{ObjectId, Rect, Size};
use crate::widget::Widget;
use core::fmt;
struct FlowChild {
widget_id: ObjectId,
widget: Option<Box<dyn Widget>>,
default_size: Size,
}
impl FlowChild {
fn size_hint(&self) -> Size {
self.widget.as_ref().map(|w| w.size_hint()).unwrap_or(self.default_size)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FlowDirection {
#[default]
Horizontal,
Vertical,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FlowAlignment {
#[default]
Start,
Center,
End,
SpaceBetween,
SpaceAround,
}
#[derive(Debug, Clone, Copy)]
pub struct FlowLayoutConfig {
pub direction: FlowDirection,
pub alignment: FlowAlignment,
pub spacing: i32,
pub padding: i32,
pub wrap: bool,
}
impl Default for FlowLayoutConfig {
fn default() -> Self {
Self {
direction: FlowDirection::Horizontal,
alignment: FlowAlignment::Start,
spacing: 8,
padding: 8,
wrap: false,
}
}
}
pub struct FlowLayout {
config: FlowLayoutConfig,
children: Vec<FlowChild>,
}
impl fmt::Debug for FlowLayout {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FlowLayout")
.field("config", &self.config)
.field("children", &format_args!("{} children", self.children.len()))
.finish()
}
}
impl FlowLayout {
pub fn new() -> Self {
Self { config: FlowLayoutConfig::default(), children: Vec::new() }
}
pub fn with_config(mut config: FlowLayoutConfig) -> Self {
if config.padding < 0 {
log::warn!(
"FlowLayout: negative padding {} is undefined; treating it as 0",
config.padding
);
config.padding = 0;
}
Self { config, children: Vec::new() }
}
pub fn add_child(&mut self, child: Box<dyn Widget>) {
let widget_id = child.id();
let default_size = child.size_hint();
self.children.push(FlowChild { widget_id, widget: Some(child), default_size });
}
pub fn remove_child(&mut self, index: usize) -> Option<Box<dyn Widget>> {
if index < self.children.len() {
self.children.remove(index).widget
} else {
None
}
}
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.default_size = size;
}
}
pub fn clear_children(&mut self) {
self.children.clear();
}
pub fn child_count(&self) -> usize {
self.children.len()
}
pub fn layout(&self, available_rect: Rect) -> Vec<Rect> {
let sizes: Vec<Size> = self.children.iter().map(|child| child.size_hint()).collect();
self.layout_sized(available_rect, &sizes)
}
fn layout_sized(&self, available_rect: Rect, sizes: &[Size]) -> Vec<Rect> {
self.layout_sized_with(available_rect, sizes, self.config.spacing, self.config.padding)
}
fn layout_sized_with(
&self,
available_rect: Rect,
sizes: &[Size],
spacing: i32,
padding: i32,
) -> Vec<Rect> {
let padding = padding.max(0);
let content_rect = Rect::new(
available_rect.x.saturating_add(padding),
available_rect.y.saturating_add(padding),
available_rect.width.saturating_sub(2 * padding as u32),
available_rect.height.saturating_sub(2 * padding as u32),
);
let direction = match self.config.direction {
FlowDirection::Horizontal => crate::layout::WrapDirection::Horizontal,
FlowDirection::Vertical => crate::layout::WrapDirection::Vertical,
};
let available_main = match direction {
crate::layout::WrapDirection::Horizontal => content_rect.width,
crate::layout::WrapDirection::Vertical => content_rect.height,
} as i32;
let lines = crate::layout::wrap::break_lines(
sizes,
direction,
spacing,
available_main,
self.config.wrap,
);
crate::layout::wrap::place_lines(
sizes,
&lines,
direction,
self.wrap_alignment(),
spacing,
spacing,
content_rect,
)
}
fn wrap_alignment(&self) -> crate::layout::WrapAlignment {
match self.config.alignment {
FlowAlignment::Start => crate::layout::WrapAlignment::Start,
FlowAlignment::Center => crate::layout::WrapAlignment::Center,
FlowAlignment::End => crate::layout::WrapAlignment::End,
FlowAlignment::SpaceBetween => crate::layout::WrapAlignment::SpaceBetween,
FlowAlignment::SpaceAround => crate::layout::WrapAlignment::SpaceAround,
}
}
pub fn preferred_size(&self) -> Size {
let count = self.children.len() as i32;
let gap_total: i64 =
if count > 1 { (count - 1) as i64 * self.config.spacing as i64 } else { 0 };
let padding = self.config.padding.max(0) as i64;
let clamp_extent = |v: i64| -> u32 { v.clamp(0, u32::MAX as i64) as u32 };
match self.config.direction {
FlowDirection::Horizontal => {
let width: i64 =
self.children.iter().map(|c| c.size_hint().width as i64).sum::<i64>()
+ gap_total;
let height: i64 =
self.children.iter().map(|c| c.size_hint().height as i64).max().unwrap_or(0);
Size::new(clamp_extent(width + 2 * padding), clamp_extent(height + 2 * padding))
}
FlowDirection::Vertical => {
let height: i64 =
self.children.iter().map(|c| c.size_hint().height as i64).sum::<i64>()
+ gap_total;
let width: i64 =
self.children.iter().map(|c| c.size_hint().width as i64).max().unwrap_or(0);
Size::new(clamp_extent(width + 2 * padding), clamp_extent(height + 2 * padding))
}
}
}
}
crate::impl_default_via_new!(FlowLayout);
impl Layout for FlowLayout {
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) {
if !self.children.iter().any(|c| c.widget_id == widget_id) {
self.children.push(FlowChild {
widget_id,
widget: None,
default_size: Size::new(100, 100),
});
}
}
fn remove_widget(&mut self, widget_id: ObjectId) {
self.children.retain(|c| c.widget_id != widget_id);
}
fn update(&self, rect: Rect, widgets: &mut dyn FnMut(ObjectId, Rect)) {
let positions = self.layout(rect);
for (i, child_rect) in positions.iter().enumerate() {
if let Some(child) = self.children.get(i) {
widgets(child.widget_id, *child_rect);
}
}
}
fn arrange(&self, rect: Rect, children: &[ChildInfo], out: &mut dyn FnMut(ObjectId, Rect)) {
let sizes: Vec<Size> = self
.children
.iter()
.map(|child| {
let fallback = child.size_hint();
ChildInfo::find(children, child.widget_id)
.map_or(fallback, |info| info.resolved_size(fallback))
})
.collect();
for (index, child_rect) in self.layout_sized(rect, &sizes).into_iter().enumerate() {
if let Some(child) = self.children.get(index) {
out(child.widget_id, 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 spacing = (self.config.spacing as f32 * scale).round() as i32;
let padding = (self.config.padding.max(0) as f32 * scale).round() as i32;
let sizes: Vec<Size> = self.children.iter().map(|child| child.size_hint()).collect();
for (index, child_rect) in
self.layout_sized_with(rect, &sizes, spacing, padding).into_iter().enumerate()
{
if let Some(child) = self.children.get(index) {
widgets(child.widget_id, child_rect);
}
}
}
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.clear_children();
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TestWidget {
id: ObjectId,
size: Size,
}
impl TestWidget {
fn new(id: ObjectId, width: u32, height: u32) -> Self {
Self { id, size: Size::new(width, height) }
}
}
impl crate::event::EventHandler for TestWidget {
fn handle_event(&mut self, _event: &crate::event::Event) {}
}
impl Widget for TestWidget {
fn id(&self) -> ObjectId {
self.id
}
fn size_hint(&self) -> Size {
self.size
}
}
#[test]
fn test_empty_layout_returns_empty() {
let layout = FlowLayout::new();
let positions = layout.layout(Rect::new(0, 0, 300, 200));
assert!(positions.is_empty());
}
#[test]
fn test_empty_layout_center_alignment() {
let mut layout = FlowLayout::new();
layout.config.alignment = FlowAlignment::Center;
let positions = layout.layout(Rect::new(0, 0, 300, 200));
assert!(positions.is_empty());
}
#[test]
fn test_horizontal_positions_children_in_a_row() {
let mut layout = FlowLayout::new();
layout.config.direction = FlowDirection::Horizontal;
layout.config.spacing = 10;
layout.config.padding = 0;
layout.add_child(Box::new(TestWidget::new(1, 40, 20)));
layout.add_child(Box::new(TestWidget::new(2, 60, 30)));
let positions = layout.layout(Rect::new(0, 0, 300, 200));
assert_eq!(positions.len(), 2);
assert_eq!(positions[0], Rect::new(0, 0, 40, 20));
assert_eq!(positions[1], Rect::new(50, 0, 60, 30));
}
#[test]
fn test_vertical_positions_children_in_a_column() {
let mut layout = FlowLayout::new();
layout.config.direction = FlowDirection::Vertical;
layout.config.spacing = 5;
layout.config.padding = 0;
layout.add_child(Box::new(TestWidget::new(1, 40, 20)));
layout.add_child(Box::new(TestWidget::new(2, 30, 50)));
let positions = layout.layout(Rect::new(0, 0, 300, 200));
assert_eq!(positions.len(), 2);
assert_eq!(positions[0], Rect::new(0, 0, 40, 20));
assert_eq!(positions[1], Rect::new(0, 25, 30, 50));
}
#[test]
fn test_horizontal_wrap_to_next_row() {
let mut layout = FlowLayout::new();
layout.config.direction = FlowDirection::Horizontal;
layout.config.spacing = 0;
layout.config.padding = 0;
layout.config.wrap = true;
layout.add_child(Box::new(TestWidget::new(1, 60, 20)));
layout.add_child(Box::new(TestWidget::new(2, 60, 20)));
layout.add_child(Box::new(TestWidget::new(3, 60, 20)));
let positions = layout.layout(Rect::new(0, 0, 125, 100));
assert_eq!(positions.len(), 3);
assert_eq!(positions[0], Rect::new(0, 0, 60, 20)); assert_eq!(positions[1], Rect::new(60, 0, 60, 20)); assert_eq!(positions[2], Rect::new(0, 20, 60, 20)); }
#[test]
fn test_vertical_wrap_to_next_column() {
let mut layout = FlowLayout::new();
layout.config.direction = FlowDirection::Vertical;
layout.config.spacing = 0;
layout.config.padding = 0;
layout.config.wrap = true;
layout.add_child(Box::new(TestWidget::new(1, 30, 50)));
layout.add_child(Box::new(TestWidget::new(2, 30, 50)));
layout.add_child(Box::new(TestWidget::new(3, 30, 50)));
let positions = layout.layout(Rect::new(0, 0, 200, 105));
assert_eq!(positions.len(), 3);
assert_eq!(positions[0], Rect::new(0, 0, 30, 50)); assert_eq!(positions[1], Rect::new(0, 50, 30, 50)); assert_eq!(positions[2], Rect::new(30, 0, 30, 50)); }
#[test]
fn test_layout_honors_padding() {
let mut layout = FlowLayout::new();
layout.config.direction = FlowDirection::Horizontal;
layout.config.spacing = 0;
layout.config.padding = 10;
layout.add_child(Box::new(TestWidget::new(1, 40, 20)));
let positions = layout.layout(Rect::new(0, 0, 100, 50));
assert_eq!(positions.len(), 1);
assert_eq!(positions[0], Rect::new(10, 10, 40, 20));
}
#[test]
fn test_horizontal_preserves_child_taller_than_container() {
let mut layout = FlowLayout::new();
layout.config.direction = FlowDirection::Horizontal;
layout.config.spacing = 0;
layout.config.padding = 0;
layout.add_child(Box::new(TestWidget::new(1, 50, 30)));
let positions = layout.layout(Rect::new(0, 0, 200, 20));
assert_eq!(positions, vec![Rect::new(0, 0, 50, 30)]);
}
#[test]
fn test_horizontal_preserves_children_after_height_overflow() {
let mut layout = FlowLayout::new();
layout.config.direction = FlowDirection::Horizontal;
layout.config.spacing = 0;
layout.config.padding = 0;
layout.add_child(Box::new(TestWidget::new(1, 50, 20)));
layout.add_child(Box::new(TestWidget::new(2, 50, 30)));
let positions = layout.layout(Rect::new(0, 0, 200, 25));
assert_eq!(positions.len(), 2);
assert_eq!(positions[0], Rect::new(0, 0, 50, 20));
assert_eq!(positions[1], Rect::new(50, 0, 50, 30));
}
#[test]
fn test_add_widget_sets_default_size() {
let mut layout = FlowLayout::new();
layout.config.direction = FlowDirection::Horizontal;
layout.config.padding = 0;
layout.config.spacing = 0;
layout.add_widget(42, 0);
let positions = layout.layout(Rect::new(0, 0, 300, 200));
assert_eq!(positions.len(), 1);
assert_eq!(positions[0], Rect::new(0, 0, 100, 100));
}
#[test]
fn test_set_child_size_overrides_default() {
let mut layout = FlowLayout::new();
layout.config.direction = FlowDirection::Horizontal;
layout.config.padding = 0;
layout.config.spacing = 0;
layout.add_widget(42, 0);
layout.set_child_size(42, Size::new(50, 30));
let positions = layout.layout(Rect::new(0, 0, 300, 200));
assert_eq!(positions.len(), 1);
assert_eq!(positions[0], Rect::new(0, 0, 50, 30));
}
#[test]
fn test_preferred_size_with_children() {
let mut layout = FlowLayout::new();
layout.config.direction = FlowDirection::Horizontal;
layout.config.spacing = 10;
layout.config.padding = 0;
layout.add_child(Box::new(TestWidget::new(1, 40, 20)));
layout.add_child(Box::new(TestWidget::new(2, 60, 30)));
assert_eq!(layout.preferred_size(), Size::new(110, 30));
}
#[test]
fn wrapped_rows_are_aligned_per_line_not_against_the_whole_set() {
let mut layout = FlowLayout::new();
layout.config.direction = FlowDirection::Horizontal;
layout.config.alignment = FlowAlignment::End;
layout.config.spacing = 0;
layout.config.padding = 0;
layout.config.wrap = true;
for id in 1..=4 {
layout.add_child(Box::new(TestWidget::new(id, 40, 10)));
}
let positions = layout.layout(Rect::new(0, 0, 100, 200));
assert_eq!(positions.len(), 4);
for row in 0..2 {
let first = positions[row * 2];
let second = positions[row * 2 + 1];
assert_eq!(first.x, 20, "row {row} is not right-aligned: {first:?}");
assert_eq!(second.x, 60, "row {row} is not right-aligned: {second:?}");
assert_eq!(
second.x + second.width as i32,
100,
"row {row} does not reach the right edge"
);
}
assert_eq!(positions[0].y, positions[1].y, "the first row is one line");
assert_eq!(positions[2].y, positions[3].y, "the second row is one line");
assert!(positions[2].y > positions[0].y, "the second line is below the first");
}
#[test]
fn a_single_line_still_uses_the_whole_set_alignment() {
let mut layout = FlowLayout::new();
layout.config.direction = FlowDirection::Horizontal;
layout.config.alignment = FlowAlignment::Center;
layout.config.spacing = 10;
layout.config.padding = 0;
layout.add_child(Box::new(TestWidget::new(1, 40, 20)));
layout.add_child(Box::new(TestWidget::new(2, 40, 20)));
let positions = layout.layout(Rect::new(0, 0, 200, 100));
assert_eq!(positions[0].x, 55);
assert_eq!(positions[1].x, 105);
}
#[test]
fn space_around_is_symmetric_and_in_bounds() {
let mut layout = FlowLayout::new();
layout.config.direction = FlowDirection::Horizontal;
layout.config.alignment = FlowAlignment::SpaceAround;
layout.config.spacing = 0;
layout.config.padding = 0;
layout.add_child(Box::new(TestWidget::new(1, 10, 10)));
layout.add_child(Box::new(TestWidget::new(2, 10, 10)));
let positions = layout.layout(Rect::new(0, 0, 100, 40));
assert_eq!(positions[0], Rect::new(20, 0, 10, 10));
assert_eq!(positions[1], Rect::new(70, 0, 10, 10));
assert_eq!(positions[0].x, 100 - (positions[1].x + positions[1].width as i32));
}
#[test]
fn space_around_pays_the_base_gap_and_handles_remainders() {
let mut layout = FlowLayout::new();
layout.config.direction = FlowDirection::Horizontal;
layout.config.alignment = FlowAlignment::SpaceAround;
layout.config.spacing = 10;
layout.config.padding = 0;
for id in [1, 2, 3] {
layout.add_child(Box::new(TestWidget::new(id, 30, 10)));
}
let positions = layout.layout(Rect::new(0, 0, 160, 40));
assert_eq!(positions.len(), 3);
let lead = positions[0].x;
let trail = 160 - (positions[2].x + positions[2].width as i32);
assert_eq!(lead, trail, "the run is centred, not left-padded");
for (i, pos) in positions.iter().enumerate() {
assert!(
pos.x >= 0 && pos.x + pos.width as i32 <= 160,
"item {i} ran off the line: {pos:?}"
);
}
}
#[test]
fn space_around_single_item_and_vertical_are_symmetric() {
let mut horizontal = FlowLayout::new();
horizontal.config.direction = FlowDirection::Horizontal;
horizontal.config.alignment = FlowAlignment::SpaceAround;
horizontal.config.spacing = 0;
horizontal.config.padding = 0;
horizontal.add_child(Box::new(TestWidget::new(1, 10, 10)));
let positions = horizontal.layout(Rect::new(0, 0, 100, 40));
assert_eq!(positions[0].x, 45, "a lone item is centred");
let mut vertical = FlowLayout::new();
vertical.config.direction = FlowDirection::Vertical;
vertical.config.alignment = FlowAlignment::SpaceAround;
vertical.config.spacing = 0;
vertical.config.padding = 0;
vertical.add_child(Box::new(TestWidget::new(1, 10, 10)));
vertical.add_child(Box::new(TestWidget::new(2, 10, 10)));
let positions = vertical.layout(Rect::new(0, 0, 40, 100));
assert_eq!(positions[0], Rect::new(0, 20, 10, 10));
assert_eq!(positions[1], Rect::new(0, 70, 10, 10));
}
#[test]
fn minor_axis_centering_uses_the_lines_max_extent() {
let mut layout = FlowLayout::new();
layout.config.direction = FlowDirection::Horizontal;
layout.config.alignment = FlowAlignment::Center;
layout.config.spacing = 0;
layout.config.padding = 0;
layout.add_child(Box::new(TestWidget::new(1, 10, 10)));
layout.add_child(Box::new(TestWidget::new(2, 10, 10)));
let positions = layout.layout(Rect::new(0, 0, 100, 100));
assert_eq!(positions[0].y, 45);
assert_eq!(positions[1].y, 45);
}
#[test]
fn wrapped_minor_axis_centering_uses_real_line_heights() {
let mut layout = FlowLayout::new();
layout.config.direction = FlowDirection::Horizontal;
layout.config.alignment = FlowAlignment::Center;
layout.config.spacing = 0;
layout.config.padding = 0;
layout.config.wrap = true;
layout.add_child(Box::new(TestWidget::new(1, 40, 10)));
layout.add_child(Box::new(TestWidget::new(2, 40, 30)));
layout.add_child(Box::new(TestWidget::new(3, 40, 20)));
let positions = layout.layout(Rect::new(0, 0, 100, 100));
assert_eq!(positions[0].y, 25);
assert_eq!(positions[1].y, 25);
assert_eq!(positions[2].y, 55);
}
#[test]
fn negative_spacing_measures_and_places_consistently() {
let mut layout = FlowLayout::new();
layout.config.direction = FlowDirection::Horizontal;
layout.config.spacing = -1;
layout.config.padding = 0;
layout.add_child(Box::new(TestWidget::new(1, 10, 10)));
layout.add_child(Box::new(TestWidget::new(2, 10, 10)));
assert_eq!(layout.preferred_size(), Size::new(19, 10));
let positions = layout.layout(Rect::new(0, 0, 100, 40));
assert_eq!(positions[1].x, 9, "the second child overlaps by the negative spacing");
}
#[test]
fn negative_spacing_with_space_between_keeps_one_line() {
let mut layout = FlowLayout::with_config(FlowLayoutConfig {
direction: FlowDirection::Horizontal,
alignment: FlowAlignment::SpaceBetween,
spacing: -10,
wrap: false,
padding: 0,
});
layout.add_child(Box::new(TestWidget::new(1, 10, 10)));
layout.add_child(Box::new(TestWidget::new(2, 10, 10)));
let positions = layout.layout(Rect::new(0, 0, 100, 40));
assert_eq!(positions.len(), 2);
assert_eq!(positions[0].y, positions[1].y, "both children stay on one line");
assert_eq!(positions[0].x, 0);
assert_eq!(positions[1].x, 90, "SpaceBetween spreads the single line's free space");
}
#[test]
fn negative_spacing_with_space_between_keeps_one_column() {
let mut layout = FlowLayout::with_config(FlowLayoutConfig {
direction: FlowDirection::Vertical,
alignment: FlowAlignment::SpaceBetween,
spacing: -10,
wrap: false,
padding: 0,
});
layout.add_child(Box::new(TestWidget::new(1, 10, 10)));
layout.add_child(Box::new(TestWidget::new(2, 10, 10)));
let positions = layout.layout(Rect::new(0, 0, 40, 100));
assert_eq!(positions[0].x, positions[1].x, "both children stay in one column");
assert_eq!(positions[0].y, 0);
assert_eq!(positions[1].y, 90);
}
#[test]
fn wrapping_still_splits_lines_with_negative_spacing() {
let mut layout = FlowLayout::with_config(FlowLayoutConfig {
direction: FlowDirection::Horizontal,
alignment: FlowAlignment::Start,
spacing: -5,
wrap: true,
padding: 0,
});
for id in 1..=3 {
layout.add_child(Box::new(TestWidget::new(id, 10, 10)));
}
let positions = layout.layout(Rect::new(0, 0, 15, 100));
assert_eq!(positions[0].y, 0);
assert_eq!(positions[1].y, 0, "the second child still fits on the first line");
assert!(positions[2].y > 0, "the third child wraps to a new line");
}
#[test]
fn negative_padding_is_clamped_to_zero_at_construction() {
let layout = FlowLayout::with_config(FlowLayoutConfig {
direction: FlowDirection::Horizontal,
alignment: FlowAlignment::Start,
spacing: 0,
padding: -1,
wrap: false,
});
assert_eq!(layout.preferred_size(), Size::new(0, 0));
}
#[test]
fn a_directly_mutated_negative_padding_does_not_panic() {
let mut layout = FlowLayout::new();
layout.config.padding = -1;
layout.config.spacing = 0;
layout.add_child(Box::new(TestWidget::new(1, 10, 10)));
let positions = layout.layout(Rect::new(0, 0, 100, 50));
assert_eq!(positions[0], Rect::new(0, 0, 10, 10));
}
#[test]
fn a_huge_padding_saturates_instead_of_wrapping() {
let mut layout = FlowLayout::new();
layout.config.padding = i32::MAX;
layout.config.spacing = 0;
layout.add_child(Box::new(TestWidget::new(1, 10, 10)));
let size = layout.preferred_size();
assert!(size.width >= 10 && size.height >= 10);
}
#[test]
fn dyn_clear_removes_flow_children() {
let mut layout: Box<dyn Layout> = Box::new(FlowLayout::new());
layout.add_widget(1, 0);
assert!(layout.has_child(1));
layout.clear();
assert!(!layout.has_child(1));
assert!(layout.child_ids().is_empty());
layout.clear();
assert!(layout.child_ids().is_empty());
}
#[test]
fn arrange_lays_children_out_from_their_hints() {
let mut layout = FlowLayout::new();
layout.config.padding = 0;
layout.config.spacing = 0;
layout.config.wrap = true;
for id in 1..=3u64 {
layout.add_widget(id, 0);
}
let children: Vec<crate::layout::ChildInfo> = (1..=3u64)
.map(|id| crate::layout::ChildInfo::new(id, crate::layout::Hints::fixed(40, 10)))
.collect();
let mut placed = crate::compat::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 flow_and_wrap_agree_on_the_same_sizes() {
use crate::layout::{WrapAlignment, WrapDirection, WrapLayout};
let sizes = [(1u64, 40u32, 20u32), (2, 30, 35), (3, 55, 15), (4, 10, 10)];
let alignments = [
(FlowAlignment::Start, WrapAlignment::Start),
(FlowAlignment::Center, WrapAlignment::Center),
(FlowAlignment::End, WrapAlignment::End),
(FlowAlignment::SpaceBetween, WrapAlignment::SpaceBetween),
(FlowAlignment::SpaceAround, WrapAlignment::SpaceAround),
];
let rect = Rect::new(0, 0, 70, 80);
for (flow_alignment, wrap_alignment) in alignments {
for horizontal in [true, false] {
for spacing in [0i32, 5, -3] {
let mut flow = FlowLayout::new();
flow.config.direction = if horizontal {
FlowDirection::Horizontal
} else {
FlowDirection::Vertical
};
flow.config.alignment = flow_alignment;
flow.config.spacing = spacing;
flow.config.padding = 6;
flow.config.wrap = true;
let mut wrap = WrapLayout::new(
if horizontal {
WrapDirection::Horizontal
} else {
WrapDirection::Vertical
},
wrap_alignment,
spacing,
6,
);
for &(id, width, height) in &sizes {
flow.add_child(Box::new(TestWidget::new(id, width, height)));
wrap.add_widget(id, 0);
wrap.set_child_size(id, Size::new(width, height));
}
let mut flow_rects = crate::compat::HashMap::new();
for (index, child_rect) in flow.layout(rect).into_iter().enumerate() {
flow_rects.insert(sizes[index].0, child_rect);
}
let mut wrap_rects = crate::compat::HashMap::new();
wrap.update(rect, &mut |id, child_rect| {
wrap_rects.insert(id, child_rect);
});
for &(id, _, _) in &sizes {
assert_eq!(
flow_rects.get(&id),
wrap_rects.get(&id),
"{flow_alignment:?} horizontal={horizontal} spacing={spacing}: \
child {id} differs"
);
}
}
}
}
}
#[test]
fn update_with_context_scales_flow_spacing_and_padding() {
let mut layout = FlowLayout::new();
layout.config.padding = 8;
layout.config.spacing = 8;
layout.add_child(Box::new(TestWidget::new(1, 30, 20)));
layout.add_child(Box::new(TestWidget::new(2, 30, 20)));
let context = crate::layout::LayoutContext {
layout_scale: 2.0,
font_scale: 1.0,
min_touch_size: Size::new(0, 0),
};
let mut placed = crate::compat::HashMap::new();
layout.update_with_context(Rect::new(0, 0, 200, 100), &context, &mut |id, rect| {
placed.insert(id, rect);
});
assert_eq!(placed[&1], Rect::new(16, 16, 30, 20));
assert_eq!(placed[&2], Rect::new(16 + 30 + 16, 16, 30, 20));
}
}