use frus_core::{Alignment, Rect, Scene, Size};
use frus_layout::{Dimension, Style};
use crate::interaction::Status;
use crate::theme::Theme;
use crate::widget::{FillAxes, Widget};
pub struct LimitedBox<Msg = crate::callback::Callback> {
max: (Option<f32>, Option<f32>),
children: Vec<Box<dyn Widget<Msg>>>,
}
impl<Msg> LimitedBox<Msg> {
pub fn new(child: impl Widget<Msg> + 'static) -> Self {
Self {
max: (None, None),
children: vec![Box::new(child)],
}
}
pub fn max_width(mut self, max: f32) -> Self {
self.max.0 = Some(max.max(0.0));
self
}
pub fn max_height(mut self, max: f32) -> Self {
self.max.1 = Some(max.max(0.0));
self
}
}
impl<Msg: Clone> Widget<Msg> for LimitedBox<Msg> {
fn style(&self) -> Style {
Style::default()
}
fn children(&self) -> &[Box<dyn Widget<Msg>>] {
&self.children
}
fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
fn on_click(&self) -> Option<Msg> {
None
}
fn constraints_transform(&self) -> Option<ConstraintsTransform> {
let rule = |max: Option<f32>| match max {
Some(max) => AxisConstraint::Limited(max),
None => AxisConstraint::Loose,
};
Some(ConstraintsTransform {
width: rule(self.max.0),
height: rule(self.max.1),
alignment: Alignment::TOP_LEFT,
report: false,
})
}
fn debug_name(&self) -> &'static str {
"LimitedBox"
}
}
pub struct SizedBox<Msg = crate::callback::Callback> {
width: Dimension,
height: Dimension,
fills: FillAxes,
children: Vec<Box<dyn Widget<Msg>>>,
}
impl<Msg> SizedBox<Msg> {
pub fn empty() -> Self {
Self {
width: Dimension::Auto,
height: Dimension::Auto,
fills: FillAxes::NONE,
children: Vec::new(),
}
}
pub fn new(child: impl Widget<Msg> + 'static) -> Self {
let mut boxed = Self::empty();
boxed.children.push(Box::new(child));
boxed
}
pub fn square(side: f32, child: impl Widget<Msg> + 'static) -> Self {
Self::new(child).width(side).height(side)
}
pub fn expand(child: impl Widget<Msg> + 'static) -> Self {
let mut boxed = Self::new(child);
boxed.fills = FillAxes::BOTH;
boxed
}
pub fn shrink(child: impl Widget<Msg> + 'static) -> Self {
Self::new(child)
}
pub fn width(self, width: f32) -> Self {
self.width_dimension(Dimension::Length(width))
}
pub fn height(self, height: f32) -> Self {
self.height_dimension(Dimension::Length(height))
}
pub fn width_fraction(self, fraction: f32) -> Self {
self.width_dimension(Dimension::Percent(fraction))
}
pub fn height_fraction(self, fraction: f32) -> Self {
self.height_dimension(Dimension::Percent(fraction))
}
fn width_dimension(mut self, width: Dimension) -> Self {
self.width = width;
self
}
fn height_dimension(mut self, height: Dimension) -> Self {
self.height = height;
self
}
pub fn child(mut self, child: impl Widget<Msg> + 'static) -> Self {
self.children.clear();
self.children.push(Box::new(child));
self
}
}
impl<Msg: Clone> Widget<Msg> for SizedBox<Msg> {
fn style(&self) -> Style {
let base = Style {
width: self.width,
height: self.height,
..Default::default()
};
match self.fills == FillAxes::BOTH {
true => Style {
overlap: true,
align: frus_layout::Align::Stretch,
..base
},
false => base,
}
}
fn children(&self) -> &[Box<dyn Widget<Msg>>] {
&self.children
}
fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {
}
fn on_click(&self) -> Option<Msg> {
None
}
fn fill_axes(&self, _theme: &Theme) -> FillAxes {
self.fills
}
}
pub struct ConstrainedBox<Msg = crate::callback::Callback> {
min_width: Dimension,
max_width: Dimension,
min_height: Dimension,
max_height: Dimension,
children: Vec<Box<dyn Widget<Msg>>>,
}
impl<Msg> ConstrainedBox<Msg> {
pub fn new(child: impl Widget<Msg> + 'static) -> Self {
Self {
min_width: Dimension::Auto,
max_width: Dimension::Auto,
min_height: Dimension::Auto,
max_height: Dimension::Auto,
children: vec![Box::new(child)],
}
}
pub fn new_boxed(child: Box<dyn Widget<Msg>>) -> Self {
Self {
min_width: Dimension::Auto,
max_width: Dimension::Auto,
min_height: Dimension::Auto,
max_height: Dimension::Auto,
children: vec![child],
}
}
pub fn tight(width: f32, height: f32, child: impl Widget<Msg> + 'static) -> Self {
Self::new(child)
.min_width(width)
.max_width(width)
.min_height(height)
.max_height(height)
}
pub fn loose(width: f32, height: f32, child: impl Widget<Msg> + 'static) -> Self {
Self::new(child).max_width(width).max_height(height)
}
pub fn min_width(mut self, width: f32) -> Self {
self.min_width = Dimension::Length(width);
self
}
pub fn max_width(mut self, width: f32) -> Self {
self.max_width = Dimension::Length(width);
self
}
pub fn min_height(mut self, height: f32) -> Self {
self.min_height = Dimension::Length(height);
self
}
pub fn max_height(mut self, height: f32) -> Self {
self.max_height = Dimension::Length(height);
self
}
}
impl<Msg: Clone> Widget<Msg> for ConstrainedBox<Msg> {
fn style(&self) -> Style {
Style {
min_width: self.min_width,
max_width: self.max_width,
min_height: self.min_height,
max_height: self.max_height,
..Default::default()
}
}
fn children(&self) -> &[Box<dyn Widget<Msg>>] {
&self.children
}
fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
fn on_click(&self) -> Option<Msg> {
None
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum IntrinsicAxis {
Width,
Height,
}
pub struct Intrinsic<Msg = crate::callback::Callback> {
axis: IntrinsicAxis,
step: Option<f32>,
children: Vec<Box<dyn Widget<Msg>>>,
}
pub type IntrinsicWidth<Msg> = Intrinsic<Msg>;
pub type IntrinsicHeight<Msg> = Intrinsic<Msg>;
impl<Msg> Intrinsic<Msg> {
pub fn width(child: impl Widget<Msg> + 'static) -> Self {
Self {
axis: IntrinsicAxis::Width,
step: None,
children: vec![Box::new(child)],
}
}
pub fn height(child: impl Widget<Msg> + 'static) -> Self {
Self {
axis: IntrinsicAxis::Height,
step: None,
children: vec![Box::new(child)],
}
}
pub fn step(mut self, step: f32) -> Self {
self.step = (step > 0.0).then_some(step);
self
}
pub fn axis(&self) -> IntrinsicAxis {
self.axis
}
pub fn quantise(&self, extent: f32) -> f32 {
match self.step {
Some(step) => (extent / step).ceil() * step,
None => extent,
}
}
}
impl<Msg: Clone> Widget<Msg> for Intrinsic<Msg> {
fn style(&self) -> Style {
Style::default()
}
fn children(&self) -> &[Box<dyn Widget<Msg>>] {
&self.children
}
fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
fn on_click(&self) -> Option<Msg> {
None
}
fn intrinsic(&self) -> Option<(IntrinsicAxis, Option<f32>)> {
Some((self.axis, self.step))
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Overflow {
pub width: Option<f32>,
pub height: Option<f32>,
pub unconstrained: bool,
pub natural_height: bool,
pub alignment: Alignment,
}
pub struct OverflowBox<Msg = crate::callback::Callback> {
overflow: Overflow,
children: Vec<Box<dyn Widget<Msg>>>,
}
impl<Msg> OverflowBox<Msg> {
pub fn new(child: impl Widget<Msg> + 'static) -> Self {
Self {
overflow: Overflow {
width: None,
height: None,
unconstrained: false,
natural_height: false,
alignment: Alignment::CENTER,
},
children: vec![Box::new(child)],
}
}
pub fn unconstrained(child: impl Widget<Msg> + 'static) -> Self {
let mut boxed = Self::new(child);
boxed.overflow.unconstrained = true;
boxed
}
pub fn width(mut self, width: f32) -> Self {
self.overflow.width = Some(width);
self
}
pub fn height(mut self, height: f32) -> Self {
self.overflow.height = Some(height);
self
}
pub fn natural_height(mut self) -> Self {
self.overflow.natural_height = true;
self
}
pub fn alignment(mut self, alignment: Alignment) -> Self {
self.overflow.alignment = alignment;
self
}
}
impl<Msg: Clone> Widget<Msg> for OverflowBox<Msg> {
fn style(&self) -> Style {
Style {
width: Dimension::Percent(1.0),
height: Dimension::Percent(1.0),
..Default::default()
}
}
fn children(&self) -> &[Box<dyn Widget<Msg>>] {
&self.children
}
fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
fn on_click(&self) -> Option<Msg> {
None
}
fn overflow_box(&self) -> Option<Overflow> {
Some(self.overflow)
}
}
impl Overflow {
pub(crate) fn child_size(&self, own: Size) -> Size {
Size::new(
self.width.unwrap_or(own.width),
self.height.unwrap_or(own.height),
)
}
pub(crate) fn origin(&self, own: Rect, child: Size) -> (f32, f32) {
(
own.x + (own.width - child.width) * self.alignment.fraction_x(),
own.y + (own.height - child.height) * self.alignment.fraction_y(),
)
}
}
pub struct SizedOverflowBox<Msg = crate::callback::Callback> {
width: f32,
height: f32,
overflow: Overflow,
children: Vec<Box<dyn Widget<Msg>>>,
}
impl<Msg> SizedOverflowBox<Msg> {
pub fn new(width: f32, height: f32, child: impl Widget<Msg> + 'static) -> Self {
Self {
width,
height,
overflow: Overflow {
width: None,
height: None,
unconstrained: true,
natural_height: false,
alignment: Alignment::CENTER,
},
children: vec![Box::new(child)],
}
}
pub fn child_size(mut self, width: f32, height: f32) -> Self {
self.overflow.unconstrained = false;
self.overflow.width = Some(width);
self.overflow.height = Some(height);
self
}
pub fn alignment(mut self, alignment: Alignment) -> Self {
self.overflow.alignment = alignment;
self
}
}
impl<Msg: Clone> Widget<Msg> for SizedOverflowBox<Msg> {
fn style(&self) -> Style {
Style {
width: Dimension::Length(self.width),
height: Dimension::Length(self.height),
..Default::default()
}
}
fn children(&self) -> &[Box<dyn Widget<Msg>>] {
&self.children
}
fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
fn on_click(&self) -> Option<Msg> {
None
}
fn overflow_box(&self) -> Option<Overflow> {
Some(self.overflow)
}
fn debug_name(&self) -> &'static str {
"SizedOverflowBox"
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AxisConstraint {
AsGiven,
Unbounded,
Fixed(f32),
Loose,
Limited(f32),
}
#[derive(Clone, Copy, Debug)]
pub struct ConstraintsTransform {
pub width: AxisConstraint,
pub height: AxisConstraint,
pub alignment: Alignment,
pub report: bool,
}
impl ConstraintsTransform {
pub(crate) fn layout_hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
use std::hash::Hash;
for axis in [self.width, self.height] {
match axis {
AxisConstraint::AsGiven => 0u8.hash(hasher),
AxisConstraint::Unbounded => 1u8.hash(hasher),
AxisConstraint::Fixed(v) => {
2u8.hash(hasher);
v.to_bits().hash(hasher);
}
AxisConstraint::Loose => 3u8.hash(hasher),
AxisConstraint::Limited(v) => {
4u8.hash(hasher);
v.to_bits().hash(hasher);
}
}
}
self.alignment.x.to_bits().hash(hasher);
self.alignment.y.to_bits().hash(hasher);
}
pub fn new() -> Self {
Self {
width: AxisConstraint::AsGiven,
height: AxisConstraint::AsGiven,
alignment: Alignment::CENTER,
report: true,
}
}
pub fn width(mut self, width: AxisConstraint) -> Self {
self.width = width;
self
}
pub fn height(mut self, height: AxisConstraint) -> Self {
self.height = height;
self
}
pub fn alignment(mut self, alignment: Alignment) -> Self {
self.alignment = alignment;
self
}
pub fn report(mut self, report: bool) -> Self {
self.report = report;
self
}
}
impl Default for ConstraintsTransform {
fn default() -> Self {
Self::new()
}
}
impl AxisConstraint {
pub(crate) fn offered(self, offer: Option<f32>) -> (f32, bool, bool) {
match self {
AxisConstraint::Unbounded => (0.0, true, false),
AxisConstraint::Fixed(extent) => (extent.max(0.0), false, true),
AxisConstraint::AsGiven => match offer {
Some(extent) => (extent.max(0.0), false, true),
None => (0.0, true, false),
},
AxisConstraint::Loose => match offer {
Some(extent) => (extent.max(0.0), false, false),
None => (0.0, true, false),
},
AxisConstraint::Limited(most) => match offer {
Some(extent) => (extent.max(0.0), false, false),
None => (most.max(0.0), false, false),
},
}
}
pub(crate) fn at(self, own: f32) -> (f32, bool, bool) {
match self {
AxisConstraint::Unbounded => (0.0, true, false),
AxisConstraint::Fixed(extent) => (extent.max(0.0), false, true),
AxisConstraint::AsGiven => (own.max(0.0), false, true),
AxisConstraint::Loose | AxisConstraint::Limited(_) => (own.max(0.0), false, false),
}
}
}
pub struct ConstraintsTransformBox<Msg = crate::callback::Callback> {
transform: ConstraintsTransform,
children: Vec<Box<dyn Widget<Msg>>>,
}
impl<Msg> ConstraintsTransformBox<Msg> {
pub fn new(child: impl Widget<Msg> + 'static) -> Self {
Self {
transform: ConstraintsTransform::new(),
children: vec![Box::new(child)],
}
}
pub fn width(mut self, width: AxisConstraint) -> Self {
self.transform.width = width;
self
}
pub fn height(mut self, height: AxisConstraint) -> Self {
self.transform.height = height;
self
}
pub fn alignment(mut self, alignment: Alignment) -> Self {
self.transform.alignment = alignment;
self
}
pub fn report(mut self, report: bool) -> Self {
self.transform.report = report;
self
}
pub fn with(child: impl Widget<Msg> + 'static, transform: ConstraintsTransform) -> Self {
Self {
transform,
children: vec![Box::new(child)],
}
}
}
impl<Msg: Clone> Widget<Msg> for ConstraintsTransformBox<Msg> {
fn style(&self) -> Style {
Style::default()
}
fn children(&self) -> &[Box<dyn Widget<Msg>>] {
&self.children
}
fn paint(&self, _bounds: Rect, _status: Status, _theme: &Theme, _scene: &mut Scene) {}
fn on_click(&self) -> Option<Msg> {
None
}
fn constraints_transform(&self) -> Option<ConstraintsTransform> {
Some(self.transform)
}
fn debug_name(&self) -> &'static str {
"ConstraintsTransformBox"
}
}
pub struct UnconstrainedBox;
impl UnconstrainedBox {
#[allow(clippy::new_ret_no_self)]
pub fn new<Msg: Clone + 'static>(
child: impl Widget<Msg> + 'static,
) -> ConstraintsTransformBox<Msg> {
ConstraintsTransformBox::new(child)
.width(AxisConstraint::Unbounded)
.height(AxisConstraint::Unbounded)
}
pub fn axis<Msg: Clone + 'static>(
child: impl Widget<Msg> + 'static,
axis: crate::scroll::Axis,
) -> ConstraintsTransformBox<Msg> {
let freed = |free: bool| match free {
true => AxisConstraint::Unbounded,
false => AxisConstraint::AsGiven,
};
ConstraintsTransformBox::new(child)
.width(freed(axis.free_x()))
.height(freed(axis.free_y()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{build_ui, Container, Flex, InspectorNode, Runtime, Text};
use frus_core::{Color, Primitive};
const RED: Color = Color {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
fn filler() -> Container<()> {
Container::new().flex(1.0).color(RED)
}
fn red_box<W: Widget<()> + 'static>(root: W, available: Size) -> Rect {
let runtime = Runtime::default();
let ui = build_ui(&root, available, &runtime, &Theme::dark());
ui.scene()
.primitives()
.iter()
.find_map(|p| match p {
Primitive::Rect { rect, color, .. } if color.r > 0.5 && color.g < 0.5 => {
Some(*rect)
}
_ => None,
})
.expect("the red box")
}
#[test]
fn a_ceiling_stops_a_box_that_would_fill() {
let root = Flex::<()>::column()
.width(400.0)
.child(ConstrainedBox::new(filler().height(40.0)).max_width(120.0));
let rect = red_box(root, Size::new(400.0, 200.0));
assert!((rect.width - 120.0).abs() < 0.5, "capped: {rect:?}");
}
#[test]
fn a_floor_stops_a_box_that_would_hug() {
let root = Flex::<()>::row()
.width(400.0)
.height(60.0)
.child(ConstrainedBox::new(filler()).min_width(90.0))
.child(Container::new().width(10.0).height(10.0));
let rect = red_box(root, Size::new(400.0, 200.0));
assert!((rect.width - 90.0).abs() < 0.5, "floored: {rect:?}");
}
#[test]
fn a_tight_box_is_that_size_whatever_is_inside_it() {
let root = Flex::<()>::column()
.width(400.0)
.child(ConstrainedBox::tight(75.0, 33.0, filler()));
let rect = red_box(root, Size::new(400.0, 200.0));
assert!(
(rect.width - 75.0).abs() < 0.5 && (rect.height - 33.0).abs() < 0.5,
"tight: {rect:?}"
);
}
#[test]
fn a_sized_box_that_expands_takes_everything_on_offer() {
let root = Flex::<()>::column()
.width(400.0)
.height(200.0)
.child(SizedBox::expand(filler()));
let rect = red_box(root, Size::new(400.0, 200.0));
assert!(
(rect.width - 400.0).abs() < 0.5 && (rect.height - 200.0).abs() < 0.5,
"expanded: {rect:?}"
);
}
#[test]
fn a_sized_box_is_the_size_it_was_given() {
let root = Flex::<()>::column()
.width(400.0)
.child(SizedBox::square(64.0, filler()));
let rect = red_box(root, Size::new(400.0, 200.0));
assert!(
(rect.width - 64.0).abs() < 0.5 && (rect.height - 64.0).abs() < 0.5,
"square: {rect:?}"
);
}
#[test]
fn an_intrinsic_width_is_what_the_content_wants_not_what_the_column_offers() {
let root = Flex::<()>::column().width(400.0).child(Intrinsic::width(
Flex::<()>::column()
.child(Text::new("a short line"))
.child(Container::new().height(10.0).color(RED)),
));
let rect = red_box(root, Size::new(400.0, 200.0));
assert!(
rect.width > 1.0 && rect.width < 380.0,
"the content's width, not the column's: {rect:?}"
);
}
#[test]
fn an_intrinsic_width_rounds_up_to_its_step() {
let step = Intrinsic::<()>::width(Container::new()).step(20.0);
assert_eq!(step.quantise(1.0), 20.0);
assert_eq!(step.quantise(20.0), 20.0);
assert_eq!(step.quantise(21.0), 40.0);
let plain = Intrinsic::<()>::width(Container::new());
assert_eq!(plain.quantise(21.0), 21.0);
}
#[test]
fn an_overflowing_child_is_bigger_than_its_box_and_centred_on_it() {
let root = Flex::<()>::column().width(400.0).child(
SizedBox::empty()
.width(40.0)
.height(40.0)
.child(OverflowBox::new(filler()).width(100.0).height(100.0)),
);
let rect = red_box(root, Size::new(400.0, 200.0));
assert!(
(rect.width - 100.0).abs() < 0.5 && (rect.height - 100.0).abs() < 0.5,
"the child keeps its own size: {rect:?}"
);
assert!(
rect.x < -29.0 && rect.x > -31.0,
"centred, spilling: {rect:?}"
);
}
#[test]
fn an_overflow_box_can_anchor_the_spill_to_one_corner() {
let root = Flex::<()>::column().width(400.0).child(
SizedBox::empty().width(40.0).height(40.0).child(
OverflowBox::new(filler())
.width(100.0)
.height(100.0)
.alignment(Alignment::TOP_LEFT),
),
);
let rect = red_box(root, Size::new(400.0, 200.0));
assert!(
rect.x.abs() < 0.5 && rect.y.abs() < 0.5,
"anchored top-left, spilling right and down: {rect:?}"
);
}
#[test]
fn the_box_around_an_overflowing_child_does_not_grow() {
let root = Flex::<()>::column()
.width(400.0)
.child(
SizedBox::empty().width(40.0).height(40.0).child(
OverflowBox::new(Container::new().flex(1.0))
.width(100.0)
.height(100.0),
),
)
.child(Container::new().width(10.0).height(10.0).color(RED));
let rect = red_box(root, Size::new(400.0, 300.0));
assert!((rect.y - 40.0).abs() < 0.5, "the neighbour moved: {rect:?}");
}
#[test]
fn an_unconstrained_child_is_the_size_it_asks_for() {
let root = Flex::<()>::column().width(400.0).child(
SizedBox::empty()
.width(20.0)
.height(20.0)
.child(OverflowBox::unconstrained(
Container::new().width(90.0).height(30.0).color(RED),
)),
);
let rect = red_box(root, Size::new(400.0, 200.0));
assert!(
(rect.width - 90.0).abs() < 0.5 && (rect.height - 30.0).abs() < 0.5,
"unconstrained: {rect:?}"
);
}
fn inspected(root: &dyn Widget<()>, available: Size) -> (crate::Ui<()>, Vec<InspectorNode>) {
crate::build_ui_inspected(root, available, &Runtime::default(), &Theme::dark())
}
fn box_named(nodes: &[InspectorNode], name: &str) -> Rect {
nodes
.iter()
.find(|n| n.name == name)
.unwrap_or_else(|| panic!("no {name} in {}", crate::dump_tree(nodes)))
.rect
}
fn red(width: f32, height: f32) -> Container<()> {
Container::new().width(width).height(height).color(RED)
}
fn narrow_column(width: f32) -> Flex<()> {
Flex::<()>::column()
.width(width)
.align(frus_layout::Align::Start)
}
#[test]
fn a_child_that_fits_gives_the_unconstrained_box_its_own_size() {
let root = narrow_column(400.0).child(UnconstrainedBox::new(red(90.0, 30.0)));
let (ui, nodes) = inspected(&root, Size::new(400.0, 200.0));
let own = box_named(&nodes, "ConstraintsTransformBox");
assert!(
(own.width - 90.0).abs() < 0.5 && (own.height - 30.0).abs() < 0.5,
"the box is the child's size: {own:?}"
);
assert!(ui.overflows().is_empty(), "nothing ran past anything");
}
#[test]
fn an_unconstrained_child_that_does_not_fit_overflows_and_says_so() {
let root = narrow_column(100.0).child(UnconstrainedBox::new(red(300.0, 20.0)));
let (ui, nodes) = inspected(&root, Size::new(100.0, 200.0));
let child = red_box_in(&ui);
assert!(
(child.width - 300.0).abs() < 0.5,
"the child kept its size: {child:?}"
);
let own = box_named(&nodes, "ConstraintsTransformBox");
assert!(
(own.width - 100.0).abs() < 0.5,
"the box kept the room it was offered: {own:?}"
);
let spills = ui.overflows();
let mut sides: Vec<_> = spills.iter().map(|o| (o.side, o.amount)).collect();
sides.sort_by_key(|(side, _)| format!("{side:?}"));
assert_eq!(sides.len(), 2, "two edges: {spills:?}");
assert_eq!(sides[0].0, frus_layout::Side::Left);
assert_eq!(sides[1].0, frus_layout::Side::Right);
assert!(
sides.iter().all(|(_, amount)| (amount - 100.0).abs() < 0.5),
"a hundred either side: {spills:?}"
);
}
#[test]
fn freeing_one_axis_leaves_the_other_one_alone() {
let root = narrow_column(100.0).child(UnconstrainedBox::axis(
Container::new().height(200.0).color(RED),
crate::scroll::Axis::Vertical,
));
let (ui, _) = inspected(&root, Size::new(100.0, 80.0));
let child = red_box_in(&ui);
assert!(
(child.width - 100.0).abs() < 0.5,
"held to the column: {child:?}"
);
assert!(
(child.height - 200.0).abs() < 0.5,
"and as tall as it asked: {child:?}"
);
}
#[test]
fn a_transform_can_give_the_child_a_room_of_its_own() {
let root = narrow_column(200.0).child(
ConstraintsTransformBox::new(Container::new().height(20.0).color(RED))
.width(AxisConstraint::Fixed(100.0)),
);
let (ui, nodes) = inspected(&root, Size::new(200.0, 200.0));
let child = red_box_in(&ui);
assert!(
(child.width - 100.0).abs() < 0.5,
"the child was laid out in the room it was given: {child:?}"
);
assert!((box_named(&nodes, "ConstraintsTransformBox").width - 100.0).abs() < 0.5);
assert!(ui.overflows().is_empty(), "half of it fits in all of it");
}
#[test]
fn a_sized_overflow_box_reports_one_size_and_lays_its_child_out_at_another() {
let root = Flex::<()>::column()
.width(400.0)
.child(SizedOverflowBox::new(40.0, 40.0, red(100.0, 100.0)));
let (ui, nodes) = inspected(&root, Size::new(400.0, 300.0));
let own = box_named(&nodes, "SizedOverflowBox");
assert!(
(own.width - 40.0).abs() < 0.5 && (own.height - 40.0).abs() < 0.5,
"the box is the size it stated: {own:?}"
);
let child = red_box_in(&ui);
assert!(
(child.width - 100.0).abs() < 0.5,
"the child is not: {child:?}"
);
assert!(
(child.x + 30.0).abs() < 0.5 && (child.y + 30.0).abs() < 0.5,
"centred on the box it hangs out of: {child:?}"
);
}
fn red_box_in(ui: &crate::Ui<()>) -> Rect {
ui.scene()
.primitives()
.iter()
.find_map(|p| match p {
Primitive::Rect { rect, color, .. } if color.r > 0.5 && color.g < 0.5 => {
Some(*rect)
}
_ => None,
})
.expect("the red box")
}
fn expanding() -> Container<()> {
Container::new()
.color(RED)
.child(crate::Center::new(SizedBox::empty()))
}
#[test]
fn a_limited_box_caps_a_child_where_the_room_has_no_end() {
let root = crate::SingleChildScrollView::<()>::new()
.width(200.0)
.height(300.0)
.child(Flex::column().child(LimitedBox::new(expanding()).max_height(120.0)));
let rect = red_box(root, Size::new(200.0, 300.0));
assert!((rect.height - 120.0).abs() < 0.5, "{rect:?}");
assert!((rect.width - 200.0).abs() < 0.5, "{rect:?}");
}
#[test]
fn a_limited_box_leaves_a_bounded_room_alone() {
let rect = red_box(
LimitedBox::new(expanding()).max_height(120.0),
Size::new(200.0, 300.0),
);
assert!((rect.height - 300.0).abs() < 0.5, "{rect:?}");
}
#[test]
fn a_limited_box_does_not_grow_a_child_that_hugs() {
let root = crate::SingleChildScrollView::<()>::new()
.width(200.0)
.height(300.0)
.child(
Flex::column().child(
LimitedBox::new(Container::new().width(40.0).height(20.0).color(RED))
.max_height(120.0),
),
);
let rect = red_box(root, Size::new(200.0, 300.0));
assert!((rect.height - 20.0).abs() < 0.5, "{rect:?}");
assert!((rect.width - 40.0).abs() < 0.5, "{rect:?}");
}
#[test]
fn a_limited_box_caps_across_too() {
let root = crate::SingleChildScrollView::<()>::new()
.axis(crate::scroll::Axis::Horizontal)
.width(300.0)
.height(100.0)
.child(Flex::row().child(LimitedBox::new(expanding()).max_width(90.0)));
let rect = red_box(root, Size::new(300.0, 100.0));
assert!((rect.width - 90.0).abs() < 0.5, "{rect:?}");
}
#[test]
fn a_paragraph_wraps_at_the_limit() {
let words = "one two three four five six seven eight nine ten eleven twelve";
let root = crate::SingleChildScrollView::<()>::new()
.axis(crate::scroll::Axis::Horizontal)
.width(300.0)
.height(200.0)
.child(
Flex::row().child(
LimitedBox::new(Container::new().color(RED).child(Text::new(words)))
.max_width(120.0),
),
);
let rect = red_box(root, Size::new(300.0, 200.0));
assert!((rect.width - 120.0).abs() < 0.5, "{rect:?}");
assert!(rect.height > 50.0, "several lines: {rect:?}");
}
#[test]
fn a_sized_box_that_expands_is_as_wide_as_a_scroll() {
let root = crate::SingleChildScrollView::<()>::new()
.width(200.0)
.height(300.0)
.child(
Flex::column().child(SizedBox::expand(Container::new().color(RED)).height(50.0)),
);
let rect = red_box(root, Size::new(200.0, 300.0));
assert!((rect.width - 200.0).abs() < 0.5, "{rect:?}");
assert!((rect.height - 50.0).abs() < 0.5, "{rect:?}");
}
#[test]
fn a_sized_box_that_expands_fills_a_page_and_shares_a_line() {
let rect = red_box(SizedBox::expand(filler()), Size::new(200.0, 300.0));
assert!(
(rect.width - 200.0).abs() < 0.5 && (rect.height - 300.0).abs() < 0.5,
"{rect:?}"
);
let row = Flex::<()>::row()
.width(300.0)
.height(100.0)
.child(Text::new("label").no_wrap())
.child(SizedBox::expand(Container::new().width(30.0).color(RED)));
let rect = red_box(row, Size::new(300.0, 100.0));
assert!((rect.width - 30.0).abs() < 0.5, "{rect:?}");
assert!((rect.height - 100.0).abs() < 0.5, "{rect:?}");
assert!(rect.x > 20.0, "after the label: {rect:?}");
}
}