use std::ops::Add;
use crate::debug_state::DebugState;
use crate::kurbo::{common::FloatExt, Vec2};
use crate::widget::prelude::*;
use crate::{Data, KeyOrValue, Point, Rect, WidgetPod};
use tracing::{instrument, trace};
pub struct Flex<T> {
direction: Axis,
cross_alignment: CrossAxisAlignment,
main_alignment: MainAxisAlignment,
fill_major_axis: bool,
children: Vec<Child<T>>,
old_bc: BoxConstraints,
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct FlexParams {
flex: f64,
alignment: Option<CrossAxisAlignment>,
}
#[derive(Data, Debug, Clone, Copy, PartialEq, Eq)]
pub enum Axis {
Horizontal,
Vertical,
}
impl Axis {
pub fn cross(self) -> Axis {
match self {
Axis::Horizontal => Axis::Vertical,
Axis::Vertical => Axis::Horizontal,
}
}
pub fn major(self, coords: Size) -> f64 {
match self {
Axis::Horizontal => coords.width,
Axis::Vertical => coords.height,
}
}
pub fn minor(self, coords: Size) -> f64 {
self.cross().major(coords)
}
pub fn major_span(self, rect: Rect) -> (f64, f64) {
match self {
Axis::Horizontal => (rect.x0, rect.x1),
Axis::Vertical => (rect.y0, rect.y1),
}
}
pub fn minor_span(self, rect: Rect) -> (f64, f64) {
self.cross().major_span(rect)
}
pub fn major_pos(self, pos: Point) -> f64 {
match self {
Axis::Horizontal => pos.x,
Axis::Vertical => pos.y,
}
}
pub fn major_vec(self, vec: Vec2) -> f64 {
match self {
Axis::Horizontal => vec.x,
Axis::Vertical => vec.y,
}
}
pub fn minor_pos(self, pos: Point) -> f64 {
self.cross().major_pos(pos)
}
pub fn minor_vec(self, vec: Vec2) -> f64 {
self.cross().major_vec(vec)
}
pub fn pack(self, major: f64, minor: f64) -> (f64, f64) {
match self {
Axis::Horizontal => (major, minor),
Axis::Vertical => (minor, major),
}
}
pub(crate) fn constraints(
self,
bc: &BoxConstraints,
min_major: f64,
major: f64,
) -> BoxConstraints {
match self {
Axis::Horizontal => BoxConstraints::new(
Size::new(min_major, bc.min().height),
Size::new(major, bc.max().height),
),
Axis::Vertical => BoxConstraints::new(
Size::new(bc.min().width, min_major),
Size::new(bc.max().width, major),
),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Data)]
pub enum CrossAxisAlignment {
Start,
Center,
End,
Baseline,
Fill,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Data)]
pub enum MainAxisAlignment {
Start,
Center,
End,
SpaceBetween,
SpaceEvenly,
SpaceAround,
}
impl FlexParams {
pub fn new(flex: f64, alignment: impl Into<Option<CrossAxisAlignment>>) -> Self {
if flex <= 0.0 {
debug_panic!("Flex value should be > 0.0. Flex given was: {}", flex);
}
let flex = flex.max(0.0);
FlexParams {
flex,
alignment: alignment.into(),
}
}
}
impl<T: Data> Flex<T> {
pub fn for_axis(axis: Axis) -> Self {
Flex {
direction: axis,
children: Vec::new(),
cross_alignment: CrossAxisAlignment::Center,
main_alignment: MainAxisAlignment::Start,
fill_major_axis: false,
old_bc: BoxConstraints::tight(Size::ZERO),
}
}
pub fn row() -> Self {
Self::for_axis(Axis::Horizontal)
}
pub fn column() -> Self {
Self::for_axis(Axis::Vertical)
}
pub fn cross_axis_alignment(mut self, alignment: CrossAxisAlignment) -> Self {
self.cross_alignment = alignment;
self
}
pub fn main_axis_alignment(mut self, alignment: MainAxisAlignment) -> Self {
self.main_alignment = alignment;
self
}
pub fn must_fill_main_axis(mut self, fill: bool) -> Self {
self.fill_major_axis = fill;
self
}
pub fn with_child(mut self, child: impl Widget<T> + 'static) -> Self {
self.add_child(child);
self
}
pub fn with_flex_child(
mut self,
child: impl Widget<T> + 'static,
params: impl Into<FlexParams>,
) -> Self {
self.add_flex_child(child, params);
self
}
pub fn with_default_spacer(mut self) -> Self {
self.add_default_spacer();
self
}
pub fn with_spacer(mut self, len: impl Into<KeyOrValue<f64>>) -> Self {
self.add_spacer(len);
self
}
pub fn with_flex_spacer(mut self, flex: f64) -> Self {
self.add_flex_spacer(flex);
self
}
pub fn set_cross_axis_alignment(&mut self, alignment: CrossAxisAlignment) {
self.cross_alignment = alignment;
}
pub fn set_main_axis_alignment(&mut self, alignment: MainAxisAlignment) {
self.main_alignment = alignment;
}
pub fn set_must_fill_main_axis(&mut self, fill: bool) {
self.fill_major_axis = fill;
}
pub fn add_child(&mut self, child: impl Widget<T> + 'static) {
let child = Child::Fixed {
widget: WidgetPod::new(Box::new(child)),
alignment: None,
};
self.children.push(child);
}
pub fn add_flex_child(
&mut self,
child: impl Widget<T> + 'static,
params: impl Into<FlexParams>,
) {
let params = params.into();
let child = if params.flex > 0.0 {
Child::Flex {
widget: WidgetPod::new(Box::new(child)),
alignment: params.alignment,
flex: params.flex,
}
} else {
tracing::warn!("Flex value should be > 0.0. To add a non-flex child use the add_child or with_child methods.\nSee the docs for more information: https://docs.rs/druid/0.8.3/druid/widget/struct.Flex.html");
Child::Fixed {
widget: WidgetPod::new(Box::new(child)),
alignment: None,
}
};
self.children.push(child);
}
pub fn add_default_spacer(&mut self) {
let key = match self.direction {
Axis::Vertical => crate::theme::WIDGET_PADDING_VERTICAL,
Axis::Horizontal => crate::theme::WIDGET_PADDING_HORIZONTAL,
};
self.add_spacer(key);
}
pub fn add_spacer(&mut self, len: impl Into<KeyOrValue<f64>>) {
let mut value = len.into();
if let KeyOrValue::Concrete(ref mut len) = value {
if *len < 0.0 {
tracing::warn!("Provided spacer length was less than 0. Value was: {}", len);
}
*len = len.clamp(0.0, f64::MAX);
}
let new_child = Child::FixedSpacer(value, 0.0);
self.children.push(new_child);
}
pub fn add_flex_spacer(&mut self, flex: f64) {
let flex = if flex >= 0.0 {
flex
} else {
debug_assert!(
flex >= 0.0,
"flex value for space should be greater than equal to 0, received: {flex}"
);
tracing::warn!("Provided flex value was less than 0: {}", flex);
0.0
};
let new_child = Child::FlexedSpacer(flex, 0.0);
self.children.push(new_child);
}
}
impl<T: Data> Widget<T> for Flex<T> {
#[instrument(name = "Flex", level = "trace", skip(self, ctx, event, data, env))]
fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) {
child.event(ctx, event, data, env);
}
}
#[instrument(name = "Flex", level = "trace", skip(self, ctx, event, data, env))]
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) {
child.lifecycle(ctx, event, data, env);
}
}
#[instrument(name = "Flex", level = "trace", skip(self, ctx, _old_data, data, env))]
fn update(&mut self, ctx: &mut UpdateCtx, _old_data: &T, data: &T, env: &Env) {
for child in self.children.iter_mut() {
match child {
Child::Fixed { widget, .. } | Child::Flex { widget, .. } => {
widget.update(ctx, data, env)
}
Child::FixedSpacer(key_or_val, _) if ctx.env_key_changed(key_or_val) => {
ctx.request_layout()
}
_ => {}
}
}
}
#[instrument(name = "Flex", level = "trace", skip(self, ctx, bc, data, env))]
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size {
bc.debug_check("Flex");
let loosened_bc = bc.loosen();
let mut minor = self.direction.minor(bc.min());
let mut max_above_baseline = 0f64;
let mut max_below_baseline = 0f64;
let mut any_use_baseline = false;
let bc_changed = self.old_bc != *bc;
let mut any_changed = bc_changed;
self.old_bc = *bc;
let mut major_non_flex = 0.0;
let mut flex_sum = 0.0;
for child in &mut self.children {
match child {
Child::Fixed { widget, alignment } => {
let child_size = if bc_changed || widget.layout_requested() {
let alignment = alignment.unwrap_or(self.cross_alignment);
any_use_baseline |= alignment == CrossAxisAlignment::Baseline;
let old_size = widget.layout_rect().size();
let child_bc =
self.direction
.constraints(&loosened_bc, 0.0, std::f64::INFINITY);
let child_size = widget.layout(ctx, &child_bc, data, env);
if child_size.width.is_infinite() {
tracing::warn!("A non-Flex child has an infinite width.");
}
if child_size.height.is_infinite() {
tracing::warn!("A non-Flex child has an infinite height.");
}
if old_size != child_size {
any_changed = true;
}
child_size
} else {
widget.layout_rect().size()
};
let baseline_offset = widget.baseline_offset();
major_non_flex += self.direction.major(child_size).expand();
minor = minor.max(self.direction.minor(child_size).expand());
max_above_baseline =
max_above_baseline.max(child_size.height - baseline_offset);
max_below_baseline = max_below_baseline.max(baseline_offset);
}
Child::FixedSpacer(kv, calculated_siz) => {
*calculated_siz = kv.resolve(env);
if *calculated_siz < 0.0 {
tracing::warn!("Length provided to fixed spacer was less than 0");
}
*calculated_siz = calculated_siz.max(0.0);
major_non_flex += *calculated_siz;
}
Child::Flex { flex, .. } | Child::FlexedSpacer(flex, _) => flex_sum += *flex,
}
}
let total_major = self.direction.major(bc.max());
let remaining = (total_major - major_non_flex).max(0.0);
let mut remainder: f64 = 0.0;
let mut major_flex: f64 = 0.0;
let px_per_flex = remaining / flex_sum;
for child in &mut self.children {
match child {
Child::Flex {
widget,
flex,
alignment,
} => {
let child_size = if any_changed || widget.layout_requested() {
let alignment = alignment.unwrap_or(self.cross_alignment);
any_use_baseline |= alignment == CrossAxisAlignment::Baseline;
let desired_major = (*flex) * px_per_flex + remainder;
let actual_major = desired_major.round();
remainder = desired_major - actual_major;
let old_size = widget.layout_rect().size();
let child_bc = self.direction.constraints(&loosened_bc, 0.0, actual_major);
let child_size = widget.layout(ctx, &child_bc, data, env);
if old_size != child_size {
any_changed = true;
}
child_size
} else {
widget.layout_rect().size()
};
let baseline_offset = widget.baseline_offset();
major_flex += self.direction.major(child_size).expand();
minor = minor.max(self.direction.minor(child_size).expand());
max_above_baseline =
max_above_baseline.max(child_size.height - baseline_offset);
max_below_baseline = max_below_baseline.max(baseline_offset);
}
Child::FlexedSpacer(flex, calculated_size) => {
let desired_major = (*flex) * px_per_flex + remainder;
*calculated_size = desired_major.round();
remainder = desired_major - *calculated_size;
major_flex += *calculated_size;
}
_ => {}
}
}
let extra = if self.fill_major_axis {
(remaining - major_flex).max(0.0)
} else {
(self.direction.major(bc.min()) - (major_non_flex + major_flex)).max(0.0)
};
let mut spacing = Spacing::new(self.main_alignment, extra, self.children.len());
let minor_dim = match self.direction {
Axis::Horizontal if any_use_baseline => max_below_baseline + max_above_baseline,
_ => minor,
};
let extra_height = minor - minor_dim.min(minor);
let mut major = spacing.next().unwrap_or(0.);
let mut child_paint_rect = Rect::ZERO;
for child in &mut self.children {
match child {
Child::Fixed { widget, alignment }
| Child::Flex {
widget, alignment, ..
} => {
let child_size = widget.layout_rect().size();
let alignment = alignment.unwrap_or(self.cross_alignment);
let child_minor_offset = match alignment {
CrossAxisAlignment::Baseline
if matches!(self.direction, Axis::Horizontal) =>
{
let child_baseline = widget.baseline_offset();
let child_above_baseline = child_size.height - child_baseline;
extra_height + (max_above_baseline - child_above_baseline)
}
CrossAxisAlignment::Fill => {
let fill_size: Size = self
.direction
.pack(self.direction.major(child_size), minor_dim)
.into();
if widget.layout_rect().size() != fill_size {
let child_bc = BoxConstraints::tight(fill_size);
widget.layout(ctx, &child_bc, data, env);
}
0.0
}
_ => {
let extra_minor = minor_dim - self.direction.minor(child_size);
alignment.align(extra_minor)
}
};
let child_pos: Point = self.direction.pack(major, child_minor_offset).into();
widget.set_origin(ctx, child_pos);
child_paint_rect = child_paint_rect.union(widget.paint_rect());
major += self.direction.major(child_size).expand();
major += spacing.next().unwrap_or(0.);
}
Child::FlexedSpacer(_, calculated_size)
| Child::FixedSpacer(_, calculated_size) => {
major += *calculated_size;
}
}
}
if flex_sum > 0.0 && total_major.is_infinite() {
tracing::warn!("A child of Flex is flex, but Flex is unbounded.")
}
if flex_sum > 0.0 {
major = total_major;
}
let my_size: Size = self.direction.pack(major, minor_dim).into();
let my_size = if !self.fill_major_axis {
let max_major = self.direction.major(bc.max());
self.direction
.constraints(bc, 0.0, max_major)
.constrain(my_size)
} else {
bc.constrain(my_size)
};
let my_bounds = Rect::ZERO.with_size(my_size);
let insets = child_paint_rect - my_bounds;
ctx.set_paint_insets(insets);
let baseline_offset = match self.direction {
Axis::Horizontal => max_below_baseline,
Axis::Vertical => self
.children
.last()
.map(|last| {
let child = last.widget();
if let Some(widget) = child {
let child_bl = widget.baseline_offset();
let child_max_y = widget.layout_rect().max_y();
let extra_bottom_padding = my_size.height - child_max_y;
child_bl + extra_bottom_padding
} else {
0.0
}
})
.unwrap_or(0.0),
};
ctx.set_baseline_offset(baseline_offset);
trace!(
"Computed layout: size={}, baseline_offset={}",
my_size,
baseline_offset
);
my_size
}
#[instrument(name = "Flex", level = "trace", skip(self, ctx, data, env))]
fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) {
child.paint(ctx, data, env);
}
if env.get(Env::DEBUG_PAINT) && ctx.widget_state.baseline_offset != 0.0 {
let color = env.get_debug_color(ctx.widget_id().to_raw());
let my_baseline = ctx.size().height - ctx.widget_state.baseline_offset;
let line = crate::kurbo::Line::new((0.0, my_baseline), (ctx.size().width, my_baseline));
let stroke_style = crate::piet::StrokeStyle::new().dash_pattern(&[4.0, 4.0]);
ctx.stroke_styled(line, &color, 1.0, &stroke_style);
}
}
fn debug_state(&self, data: &T) -> DebugState {
let children_state = self
.children
.iter()
.filter_map(|child| {
let child_widget_pod = child.widget()?;
Some(child_widget_pod.widget().debug_state(data))
})
.collect();
DebugState {
display_name: self.short_type_name().to_string(),
children: children_state,
..Default::default()
}
}
fn compute_max_intrinsic(
&mut self,
axis: Axis,
ctx: &mut LayoutCtx,
bc: &BoxConstraints,
data: &T,
env: &Env,
) -> f64 {
if self.direction != axis {
let mut max_size_on_cross_axis: f64 = 0.;
let mut available_size_on_main_axis = self.direction.major(bc.max());
let mut total_flex = 0.;
for child in self.children.iter_mut() {
match child {
Child::Fixed { widget, .. } => {
let new_bc = bc
.unbound_max(axis)
.shrink_max_to(self.direction, available_size_on_main_axis);
let size_on_main_axis = widget.widget_mut().compute_max_intrinsic(
self.direction,
ctx,
&new_bc,
data,
env,
);
let new_bc = new_bc.shrink_max_to(self.direction, size_on_main_axis);
let size_on_cross_axis = widget
.widget_mut()
.compute_max_intrinsic(axis, ctx, &new_bc, data, env);
available_size_on_main_axis -= size_on_main_axis;
max_size_on_cross_axis = max_size_on_cross_axis.max(size_on_cross_axis);
}
Child::FixedSpacer(kv, _) => {
let mut s = kv.resolve(env);
if s < 0.0 {
tracing::warn!("Length provided to fixed spacer was less than 0");
s = 0.;
}
max_size_on_cross_axis = max_size_on_cross_axis.max(s);
}
Child::Flex { flex, .. } | Child::FlexedSpacer(flex, _) => total_flex += *flex,
}
}
let space_per_flex = available_size_on_main_axis / total_flex;
if space_per_flex > 0.0 {
for child in self.children.iter_mut() {
if let Child::Flex { widget, flex, .. } = child {
let main_axis_available_space = *flex * space_per_flex;
let new_bc = bc.shrink_max_to(axis, main_axis_available_space);
let size_on_cross_axis = widget
.widget_mut()
.compute_max_intrinsic(axis, ctx, &new_bc, data, env);
max_size_on_cross_axis = max_size_on_cross_axis.max(size_on_cross_axis);
}
}
}
max_size_on_cross_axis
} else {
let mut total: f64 = 0.;
let mut max_flex_fraction: f64 = 0.;
let mut total_flex = 0.;
for child in self.children.iter_mut() {
match child {
Child::Fixed { widget, .. } => {
let s = widget
.widget_mut()
.compute_max_intrinsic(axis, ctx, bc, data, env);
total = total.add(s);
}
Child::Flex { widget, flex, .. } => {
let s = widget
.widget_mut()
.compute_max_intrinsic(axis, ctx, bc, data, env);
let flex_fraction = s / *flex;
total_flex += *flex;
max_flex_fraction = max_flex_fraction.max(flex_fraction);
}
Child::FixedSpacer(kv, _) => {
let mut s = kv.resolve(env);
if s < 0.0 {
tracing::warn!("Length provided to fixed spacer was less than 0");
s = 0.;
}
total = total.add(s);
}
Child::FlexedSpacer(flex, _) => {
total_flex += *flex;
}
}
}
total + max_flex_fraction * total_flex
}
}
}
impl CrossAxisAlignment {
fn align(self, val: f64) -> f64 {
match self {
CrossAxisAlignment::Start => 0.0,
CrossAxisAlignment::Center | CrossAxisAlignment::Baseline => (val / 2.0).round(),
CrossAxisAlignment::End => val,
CrossAxisAlignment::Fill => 0.0,
}
}
}
struct Spacing {
alignment: MainAxisAlignment,
extra: f64,
n_children: usize,
index: usize,
equal_space: f64,
remainder: f64,
}
impl Spacing {
fn new(alignment: MainAxisAlignment, extra: f64, n_children: usize) -> Spacing {
let extra = if extra.is_finite() { extra } else { 0. };
let equal_space = if n_children > 0 {
match alignment {
MainAxisAlignment::Center => extra / 2.,
MainAxisAlignment::SpaceBetween => extra / (n_children - 1).max(1) as f64,
MainAxisAlignment::SpaceEvenly => extra / (n_children + 1) as f64,
MainAxisAlignment::SpaceAround => extra / (2 * n_children) as f64,
_ => 0.,
}
} else {
0.
};
Spacing {
alignment,
extra,
n_children,
index: 0,
equal_space,
remainder: 0.,
}
}
fn next_space(&mut self) -> f64 {
let desired_space = self.equal_space + self.remainder;
let actual_space = desired_space.round();
self.remainder = desired_space - actual_space;
actual_space
}
}
impl Iterator for Spacing {
type Item = f64;
fn next(&mut self) -> Option<f64> {
if self.index > self.n_children {
return None;
}
let result = {
if self.n_children == 0 {
self.extra
} else {
#[allow(clippy::match_bool)]
match self.alignment {
MainAxisAlignment::Start => match self.index == self.n_children {
true => self.extra,
false => 0.,
},
MainAxisAlignment::End => match self.index == 0 {
true => self.extra,
false => 0.,
},
MainAxisAlignment::Center => match self.index {
0 => self.next_space(),
i if i == self.n_children => self.next_space(),
_ => 0.,
},
MainAxisAlignment::SpaceBetween => match self.index {
0 => 0.,
i if i != self.n_children => self.next_space(),
_ => match self.n_children {
1 => self.next_space(),
_ => 0.,
},
},
MainAxisAlignment::SpaceEvenly => self.next_space(),
MainAxisAlignment::SpaceAround => {
if self.index == 0 || self.index == self.n_children {
self.next_space()
} else {
self.next_space() + self.next_space()
}
}
}
}
};
self.index += 1;
Some(result)
}
}
impl From<f64> for FlexParams {
fn from(flex: f64) -> FlexParams {
FlexParams::new(flex, None)
}
}
enum Child<T> {
Fixed {
widget: WidgetPod<T, Box<dyn Widget<T>>>,
alignment: Option<CrossAxisAlignment>,
},
Flex {
widget: WidgetPod<T, Box<dyn Widget<T>>>,
alignment: Option<CrossAxisAlignment>,
flex: f64,
},
FixedSpacer(KeyOrValue<f64>, f64),
FlexedSpacer(f64, f64),
}
impl<T> Child<T> {
fn widget_mut(&mut self) -> Option<&mut WidgetPod<T, Box<dyn Widget<T>>>> {
match self {
Child::Fixed { widget, .. } | Child::Flex { widget, .. } => Some(widget),
_ => None,
}
}
fn widget(&self) -> Option<&WidgetPod<T, Box<dyn Widget<T>>>> {
match self {
Child::Fixed { widget, .. } | Child::Flex { widget, .. } => Some(widget),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_log::test;
#[test]
#[allow(clippy::cognitive_complexity)]
fn test_main_axis_alignment_spacing() {
let vec = |a, e, n| -> Vec<f64> { Spacing::new(a, e, n).collect() };
let a = MainAxisAlignment::Start;
assert_eq!(vec(a, 10., 0), vec![10.]);
assert_eq!(vec(a, 10., 1), vec![0., 10.]);
assert_eq!(vec(a, 10., 2), vec![0., 0., 10.]);
assert_eq!(vec(a, 10., 3), vec![0., 0., 0., 10.]);
let a = MainAxisAlignment::End;
assert_eq!(vec(a, 10., 0), vec![10.]);
assert_eq!(vec(a, 10., 1), vec![10., 0.]);
assert_eq!(vec(a, 10., 2), vec![10., 0., 0.]);
assert_eq!(vec(a, 10., 3), vec![10., 0., 0., 0.]);
let a = MainAxisAlignment::Center;
assert_eq!(vec(a, 10., 0), vec![10.]);
assert_eq!(vec(a, 10., 1), vec![5., 5.]);
assert_eq!(vec(a, 10., 2), vec![5., 0., 5.]);
assert_eq!(vec(a, 10., 3), vec![5., 0., 0., 5.]);
assert_eq!(vec(a, 1., 0), vec![1.]);
assert_eq!(vec(a, 3., 1), vec![2., 1.]);
assert_eq!(vec(a, 5., 2), vec![3., 0., 2.]);
assert_eq!(vec(a, 17., 3), vec![9., 0., 0., 8.]);
let a = MainAxisAlignment::SpaceBetween;
assert_eq!(vec(a, 10., 0), vec![10.]);
assert_eq!(vec(a, 10., 1), vec![0., 10.]);
assert_eq!(vec(a, 10., 2), vec![0., 10., 0.]);
assert_eq!(vec(a, 10., 3), vec![0., 5., 5., 0.]);
assert_eq!(vec(a, 33., 5), vec![0., 8., 9., 8., 8., 0.]);
assert_eq!(vec(a, 34., 5), vec![0., 9., 8., 9., 8., 0.]);
assert_eq!(vec(a, 35., 5), vec![0., 9., 9., 8., 9., 0.]);
assert_eq!(vec(a, 36., 5), vec![0., 9., 9., 9., 9., 0.]);
assert_eq!(vec(a, 37., 5), vec![0., 9., 10., 9., 9., 0.]);
assert_eq!(vec(a, 38., 5), vec![0., 10., 9., 10., 9., 0.]);
assert_eq!(vec(a, 39., 5), vec![0., 10., 10., 9., 10., 0.]);
let a = MainAxisAlignment::SpaceEvenly;
assert_eq!(vec(a, 10., 0), vec![10.]);
assert_eq!(vec(a, 10., 1), vec![5., 5.]);
assert_eq!(vec(a, 10., 2), vec![3., 4., 3.]);
assert_eq!(vec(a, 10., 3), vec![3., 2., 3., 2.]);
assert_eq!(vec(a, 33., 5), vec![6., 5., 6., 5., 6., 5.]);
assert_eq!(vec(a, 34., 5), vec![6., 5., 6., 6., 5., 6.]);
assert_eq!(vec(a, 35., 5), vec![6., 6., 5., 6., 6., 6.]);
assert_eq!(vec(a, 36., 5), vec![6., 6., 6., 6., 6., 6.]);
assert_eq!(vec(a, 37., 5), vec![6., 6., 7., 6., 6., 6.]);
assert_eq!(vec(a, 38., 5), vec![6., 7., 6., 6., 7., 6.]);
assert_eq!(vec(a, 39., 5), vec![7., 6., 7., 6., 7., 6.]);
let a = MainAxisAlignment::SpaceAround;
assert_eq!(vec(a, 10., 0), vec![10.]);
assert_eq!(vec(a, 10., 1), vec![5., 5.]);
assert_eq!(vec(a, 10., 2), vec![3., 5., 2.]);
assert_eq!(vec(a, 10., 3), vec![2., 3., 3., 2.]);
assert_eq!(vec(a, 33., 5), vec![3., 7., 6., 7., 7., 3.]);
assert_eq!(vec(a, 34., 5), vec![3., 7., 7., 7., 7., 3.]);
assert_eq!(vec(a, 35., 5), vec![4., 7., 7., 7., 7., 3.]);
assert_eq!(vec(a, 36., 5), vec![4., 7., 7., 7., 7., 4.]);
assert_eq!(vec(a, 37., 5), vec![4., 7., 8., 7., 7., 4.]);
assert_eq!(vec(a, 38., 5), vec![4., 7., 8., 8., 7., 4.]);
assert_eq!(vec(a, 39., 5), vec![4., 8., 7., 8., 8., 4.]);
}
#[test]
#[should_panic]
fn test_invalid_flex_params() {
use float_cmp::assert_approx_eq;
let params = FlexParams::new(0.0, None);
assert_approx_eq!(f64, params.flex, 1.0, ulps = 2);
let params = FlexParams::new(-0.0, None);
assert_approx_eq!(f64, params.flex, 1.0, ulps = 2);
let params = FlexParams::new(-1.0, None);
assert_approx_eq!(f64, params.flex, 1.0, ulps = 2);
}
}