pub mod absolute;
pub mod aspect_ratio;
pub mod box_layout;
pub mod center;
pub mod constraint;
pub mod declarative;
pub mod flex;
pub mod flow;
pub mod form;
pub mod grid;
pub mod hints;
pub mod inspector;
pub mod keyboard_aware;
pub mod splitter;
pub mod stack;
pub mod types;
pub mod uniform_grid;
pub mod wrap;
pub use crate::core::Orientation;
pub use absolute::*;
pub use aspect_ratio::*;
pub use box_layout::*;
pub use center::*;
pub use constraint::*;
pub use flex::*;
pub use flow::*;
pub use form::*;
pub use grid::*;
pub use hints::*;
pub use inspector::*;
pub use keyboard_aware::*;
pub use splitter::*;
pub use stack::*;
pub use types::*;
pub use uniform_grid::*;
pub use wrap::*;
#[cfg(test)]
mod tests {
use super::*;
use crate::compat::HashMap;
use crate::core::{Point, Rect, Size};
#[test]
fn box_layout_applies_constraints() {
let mut layout = BoxLayout::new(Orientation::Horizontal, 0, 0);
layout.add_widget(1, 1);
layout.add_widget(2, 1);
layout.set_constraints(1, LayoutConstraints::new(80, Some(80)));
layout.set_size_policy(1, SizePolicy::Fixed);
let mut rects = HashMap::new();
layout.update(Rect::new(0, 0, 200, 40), &mut |id, rect| {
rects.insert(id, rect);
});
assert_eq!(rects.get(&1).map(|rect| rect.width), Some(80));
}
#[test]
fn splitter_layout_distributes_space() {
let mut splitter = SplitterLayout::new(Orientation::Horizontal, 0);
splitter.add_widget(1, 1);
splitter.add_widget(2, 3);
let mut rects = HashMap::new();
splitter.update(Rect::new(0, 0, 400, 40), &mut |id, rect| {
rects.insert(id, rect);
});
let left = rects.get(&1).map(|rect| rect.width).unwrap_or(0);
let right = rects.get(&2).map(|rect| rect.width).unwrap_or(0);
assert!(right > left);
}
#[test]
fn layout_update_from_position_size_routes_through_rect_conversion() {
let mut layout = BoxLayout::new(Orientation::Horizontal, 0, 0);
layout.add_widget(42, 1);
let mut out = None;
layout.update_from_position_size(Point::new(9, 11), Size::new(30, 12), &mut |id, rect| {
if id == 42 {
out = Some(rect);
}
});
assert_eq!(out, Some(Rect::new(9, 11, 30, 12)));
}
#[test]
fn hbox_and_vbox_named_types_delegate_to_box_layout_contract() {
let mut hbox = BoxLayout::new(Orientation::Horizontal, 3, 2);
hbox.add_widget(1, 1);
hbox.add_spacer(1);
hbox.add_widget(2, 2);
assert_eq!(hbox.spacing(), 3);
assert_eq!(hbox.margin(), 2);
assert_eq!(hbox.item_count(), 3);
let mut rects = HashMap::new();
hbox.update(Rect::new(0, 0, 120, 20), &mut |id, rect| {
rects.insert(id, rect);
});
assert_eq!(rects.len(), 2);
let mut vbox = BoxLayout::new(Orientation::Vertical, 1, 0);
vbox.add_widget(10, 1);
vbox.add_widget(11, 1);
let mut out = HashMap::new();
vbox.update(Rect::new(0, 0, 20, 40), &mut |id, rect| {
out.insert(id, rect);
});
assert_eq!(out.len(), 2);
assert!(
out.get(&11).map(|r| r.y).unwrap_or_default()
> out.get(&10).map(|r| r.y).unwrap_or_default()
);
}
#[test]
fn box_layout_distribution_consumes_available_major_axis() {
let mut layout = BoxLayout::new(Orientation::Horizontal, 0, 0);
layout.add_widget(1, 1);
layout.add_widget(2, 1);
layout.add_widget(3, 1);
let mut widths = HashMap::new();
layout.update(Rect::new(0, 0, 100, 10), &mut |id, rect| {
widths.insert(id, rect.width);
});
let total: u32 = widths.values().copied().sum();
assert_eq!(total, 100);
}
#[test]
fn grid_and_stack_layouts_have_deterministic_placement() {
let mut grid = GridLayout::new(2, 2, 0, 0);
grid.set_widget(0, 0, 1);
grid.set_widget(1, 1, 2);
let mut grid_rects = HashMap::new();
grid.update(Rect::new(0, 0, 40, 20), &mut |id, rect| {
grid_rects.insert(id, rect);
});
assert_eq!(grid_rects.get(&1), Some(&Rect::new(0, 0, 20, 10)));
assert_eq!(grid_rects.get(&2), Some(&Rect::new(20, 10, 20, 10)));
let mut stack = StackLayout::new();
stack.add_widget(7, 0);
stack.add_widget(8, 0);
stack.set_current_index(1);
let mut shown = None;
stack.update(Rect::new(1, 2, 30, 40), &mut |id, rect| {
shown = Some((id, rect));
});
assert_eq!(shown, Some((8, Rect::new(1, 2, 30, 40))));
}
#[test]
fn box_layout_children_stay_inside_a_parent_that_cannot_pay_every_minimum() {
let mut layout = BoxLayout::new(Orientation::Horizontal, 0, 0);
layout.add_widget(1, 1);
layout.add_widget(2, 1);
for id in [1, 2] {
layout.set_constraints(id, LayoutConstraints::new(80, None));
layout.set_size_policy(id, SizePolicy::Fixed);
}
let mut rects = HashMap::new();
layout.update(Rect::new(0, 0, 100, 20), &mut |id, rect| {
rects.insert(id, rect);
});
let total: u32 = rects.values().map(|rect| rect.width).sum();
assert!(total <= 100, "the children must fit the parent, got {total}");
for (id, rect) in &rects {
assert!(
rect.x >= 0 && rect.x + rect.width as i32 <= 100,
"item {id} escapes: {rect:?}"
);
}
let first = rects.get(&1).map(|rect| rect.width).unwrap_or(0);
let second = rects.get(&2).map(|rect| rect.width).unwrap_or(0);
assert!(first.abs_diff(second) <= 1, "the shortfall must be shared evenly");
}
#[test]
fn grid_cells_stay_inside_a_parent_too_small_for_its_spacing() {
let mut grid = GridLayout::new(2, 2, 8, 2);
grid.set_widget(0, 0, 1);
grid.set_widget(0, 1, 2);
grid.set_widget(1, 0, 3);
grid.set_widget(1, 1, 4);
let mut rects = HashMap::new();
grid.update(Rect::new(0, 0, 8, 4), &mut |id, rect| {
rects.insert(id, rect);
});
assert_eq!(rects.len(), 4, "every cell is still addressed");
for (id, rect) in &rects {
assert!(
rect.x >= 0
&& rect.y >= 0
&& rect.x + rect.width as i32 <= 8
&& rect.y + rect.height as i32 <= 4,
"cell {id} escapes the 8x4 parent: {rect:?}"
);
}
}
#[test]
fn splitter_add_pane_matches_add_widget_and_normalises_on_release() {
let mut splitter = SplitterLayout::new(Orientation::Horizontal, 0);
splitter.add_pane(1, 1);
splitter.add_pane(2, 3);
assert_eq!(splitter.pane_count(), 2);
let weight_sum: f32 = splitter.ratios().iter().sum();
assert!((weight_sum - 4.0).abs() < 1e-6, "weights are relative: {weight_sum}");
splitter.normalize_ratios();
let fraction_sum: f32 = splitter.ratios().iter().sum();
assert!((fraction_sum - 1.0).abs() < 1e-6, "normalising yields fractions");
assert!((splitter.ratio(1).unwrap_or(0.0) - 0.75).abs() < 1e-6);
}
}