use std::any::TypeId;
use accesskit::{Node, Role};
use masonry_core::core::HasProperty;
use tracing::{Span, trace_span};
use vello::Scene;
use vello::kurbo::{Affine, Line, Point, Size, Stroke};
use crate::core::{
AccessCtx, Axis, BoxConstraints, ChildrenIds, LayoutCtx, NewWidget, NoAction, PaintCtx,
PropertiesMut, PropertiesRef, RegisterCtx, UpdateCtx, Widget, WidgetId, WidgetMut, WidgetPod,
};
use crate::properties::types::Length;
use crate::properties::types::{CrossAxisAlignment, MainAxisAlignment};
use crate::properties::{Background, BorderColor, BorderWidth, CornerRadius, Padding};
use crate::theme::DEFAULT_GAP;
use crate::util::{debug_panic, fill, include_screenshot, stroke};
#[doc = include_screenshot!("flex_col_main_axis_spaceAround.png", "Flex column with multiple labels.")]
pub struct Flex {
direction: Axis,
cross_alignment: CrossAxisAlignment,
main_alignment: MainAxisAlignment,
fill_major_axis: bool,
children: Vec<Child>,
gap: Length,
}
#[derive(Default, Debug, Copy, Clone, PartialEq)]
pub struct FlexParams {
flex: Option<f64>,
alignment: Option<CrossAxisAlignment>,
}
enum Child {
Fixed {
widget: WidgetPod<dyn Widget>,
alignment: Option<CrossAxisAlignment>,
},
Flex {
widget: WidgetPod<dyn Widget>,
alignment: Option<CrossAxisAlignment>,
flex: f64,
},
FixedSpacer(Length, f64),
FlexedSpacer(f64, f64),
}
impl Flex {
pub fn for_axis(axis: Axis) -> Self {
Self {
direction: axis,
children: Vec::new(),
cross_alignment: CrossAxisAlignment::Center,
main_alignment: MainAxisAlignment::Start,
fill_major_axis: false,
gap: DEFAULT_GAP,
}
}
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_gap(mut self, gap: Length) -> Self {
self.gap = gap;
self
}
pub fn with_child(mut self, child: NewWidget<impl Widget + ?Sized>) -> Self {
let child = Child::Fixed {
widget: child.erased().to_pod(),
alignment: None,
};
self.children.push(child);
self
}
pub fn with_flex_child(
mut self,
child: NewWidget<impl Widget + ?Sized>,
params: impl Into<FlexParams>,
) -> Self {
let child = child.erased().to_pod();
let child = new_flex_child(params.into(), child);
self.children.push(child);
self
}
pub fn with_spacer(mut self, len: Length) -> Self {
let new_child = Child::FixedSpacer(len, 0.0);
self.children.push(new_child);
self
}
pub fn with_flex_spacer(mut self, flex: f64) -> Self {
let flex = if flex >= 0.0 {
flex
} else {
debug_panic!("add_spacer called with negative length: {}", flex);
0.0
};
let new_child = Child::FlexedSpacer(flex, 0.0);
self.children.push(new_child);
self
}
pub fn len(&self) -> usize {
self.children.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl Flex {
pub fn set_direction(this: &mut WidgetMut<'_, Self>, direction: Axis) {
this.widget.direction = direction;
this.ctx.request_layout();
}
pub fn set_cross_axis_alignment(this: &mut WidgetMut<'_, Self>, alignment: CrossAxisAlignment) {
this.widget.cross_alignment = alignment;
this.ctx.request_layout();
}
pub fn set_main_axis_alignment(this: &mut WidgetMut<'_, Self>, alignment: MainAxisAlignment) {
this.widget.main_alignment = alignment;
this.ctx.request_layout();
}
pub fn set_must_fill_main_axis(this: &mut WidgetMut<'_, Self>, fill: bool) {
this.widget.fill_major_axis = fill;
this.ctx.request_layout();
}
pub fn set_gap(this: &mut WidgetMut<'_, Self>, gap: Length) {
this.widget.gap = gap;
this.ctx.request_layout();
}
pub fn add_child(this: &mut WidgetMut<'_, Self>, child: NewWidget<impl Widget + ?Sized>) {
let child = Child::Fixed {
widget: child.erased().to_pod(),
alignment: None,
};
this.widget.children.push(child);
this.ctx.children_changed();
}
pub fn add_flex_child(
this: &mut WidgetMut<'_, Self>,
child: NewWidget<impl Widget + ?Sized>,
params: impl Into<FlexParams>,
) {
let child = child.erased().to_pod();
let child = new_flex_child(params.into(), child);
this.widget.children.push(child);
this.ctx.children_changed();
}
pub fn add_spacer(this: &mut WidgetMut<'_, Self>, len: Length) {
let new_child = Child::FixedSpacer(len, 0.0);
this.widget.children.push(new_child);
this.ctx.request_layout();
}
pub fn add_flex_spacer(this: &mut WidgetMut<'_, Self>, flex: f64) {
let flex = if flex >= 0.0 {
flex
} else {
debug_panic!("add_spacer called with negative length: {}", flex);
0.0
};
let new_child = Child::FlexedSpacer(flex, 0.0);
this.widget.children.push(new_child);
this.ctx.request_layout();
}
pub fn insert_child(
this: &mut WidgetMut<'_, Self>,
idx: usize,
child: NewWidget<impl Widget + ?Sized>,
) {
let child = Child::Fixed {
widget: child.erased().to_pod(),
alignment: None,
};
this.widget.children.insert(idx, child);
this.ctx.children_changed();
}
pub fn insert_flex_child(
this: &mut WidgetMut<'_, Self>,
idx: usize,
child: NewWidget<impl Widget + ?Sized>,
params: impl Into<FlexParams>,
) {
let child = child.erased().to_pod();
let child = new_flex_child(params.into(), child);
this.widget.children.insert(idx, child);
this.ctx.children_changed();
}
pub fn insert_spacer(this: &mut WidgetMut<'_, Self>, idx: usize, len: Length) {
let new_child = Child::FixedSpacer(len, 0.0);
this.widget.children.insert(idx, new_child);
this.ctx.request_layout();
}
pub fn insert_flex_spacer(this: &mut WidgetMut<'_, Self>, idx: usize, flex: f64) {
let flex = if flex >= 0.0 {
flex
} else {
debug_panic!("add_spacer called with negative length: {}", flex);
0.0
};
let new_child = Child::FlexedSpacer(flex, 0.0);
this.widget.children.insert(idx, new_child);
this.ctx.request_layout();
}
pub fn remove_child(this: &mut WidgetMut<'_, Self>, idx: usize) {
let child = this.widget.children.remove(idx);
if let Child::Fixed { widget, .. } | Child::Flex { widget, .. } = child {
this.ctx.remove_child(widget);
}
this.ctx.request_layout();
}
pub fn child_mut<'t>(
this: &'t mut WidgetMut<'_, Self>,
idx: usize,
) -> Option<WidgetMut<'t, dyn Widget>> {
let child = match &mut this.widget.children[idx] {
Child::Fixed { widget, .. } | Child::Flex { widget, .. } => widget,
Child::FixedSpacer(..) => return None,
Child::FlexedSpacer(..) => return None,
};
Some(this.ctx.get_mut(child))
}
pub fn update_child_flex_params(
this: &mut WidgetMut<'_, Self>,
idx: usize,
params: impl Into<FlexParams>,
) {
let child = &mut this.widget.children[idx];
let child_val = std::mem::replace(child, Child::FixedSpacer(Length::ZERO, 0.0));
let widget = match child_val {
Child::Fixed { widget, .. } | Child::Flex { widget, .. } => widget,
_ => {
panic!("Can't update flex parameters of a spacer element");
}
};
let new_child = new_flex_child(params.into(), widget);
*child = new_child;
this.ctx.children_changed();
}
pub fn update_spacer_flex(this: &mut WidgetMut<'_, Self>, idx: usize, flex: f64) {
let child = &mut this.widget.children[idx];
match *child {
Child::FixedSpacer(_, _) | Child::FlexedSpacer(_, _) => {
*child = Child::FlexedSpacer(flex, 0.0);
}
_ => {
panic!("Can't update spacer parameters of a non-spacer element");
}
};
this.ctx.children_changed();
}
pub fn update_spacer_fixed(this: &mut WidgetMut<'_, Self>, idx: usize, len: Length) {
let child = &mut this.widget.children[idx];
match *child {
Child::FixedSpacer(_, _) | Child::FlexedSpacer(_, _) => {
*child = Child::FixedSpacer(len, 0.0);
}
_ => {
panic!("Can't update spacer parameters of a non-spacer element");
}
};
this.ctx.children_changed();
}
pub fn clear(this: &mut WidgetMut<'_, Self>) {
if !this.widget.children.is_empty() {
this.ctx.request_layout();
for child in this.widget.children.drain(..) {
if let Child::Fixed { widget, .. } | Child::Flex { widget, .. } = child {
this.ctx.remove_child(widget);
}
}
}
}
}
impl FlexParams {
pub fn new(
flex: impl Into<Option<f64>>,
alignment: impl Into<Option<CrossAxisAlignment>>,
) -> Self {
let flex = match flex.into() {
Some(flex) if flex <= 0.0 => {
debug_panic!("Flex value should be > 0.0. Flex given was: {}", flex);
Some(0.0)
}
other => other,
};
Self {
flex,
alignment: alignment.into(),
}
}
}
impl From<f64> for FlexParams {
fn from(flex: f64) -> Self {
Self::new(flex, None)
}
}
impl From<CrossAxisAlignment> for FlexParams {
fn from(alignment: CrossAxisAlignment) -> Self {
Self::new(None, alignment)
}
}
impl Child {
fn widget_mut(&mut self) -> Option<&mut WidgetPod<dyn Widget>> {
match self {
Self::Fixed { widget, .. } | Self::Flex { widget, .. } => Some(widget),
_ => None,
}
}
fn widget(&self) -> Option<&WidgetPod<dyn Widget>> {
match self {
Self::Fixed { widget, .. } | Self::Flex { widget, .. } => Some(widget),
_ => None,
}
}
}
fn new_flex_child(params: FlexParams, child: WidgetPod<dyn Widget>) -> Child {
if let Some(flex) = params.flex {
if flex.is_normal() && flex > 0.0 {
Child::Flex {
widget: child,
alignment: params.alignment,
flex,
}
} else {
tracing::warn!(
"Flex value should be > 0.0 (was {flex}). See the docs for masonry::widgets::Flex for more information"
);
Child::Fixed {
widget: child,
alignment: params.alignment,
}
}
} else {
Child::Fixed {
widget: child,
alignment: params.alignment,
}
}
}
fn get_spacing(alignment: MainAxisAlignment, extra: f64, child_count: usize) -> (f64, f64) {
let space_before;
let space_between;
match alignment {
_ if child_count == 0 => {
space_before = 0.;
space_between = 0.;
}
MainAxisAlignment::Start => {
space_before = 0.;
space_between = 0.;
}
MainAxisAlignment::End => {
space_before = extra;
space_between = 0.;
}
MainAxisAlignment::Center => {
space_before = extra / 2.;
space_between = 0.;
}
MainAxisAlignment::SpaceBetween => {
let equal_space = extra / (child_count - 1).max(1) as f64;
space_before = 0.;
space_between = equal_space;
}
MainAxisAlignment::SpaceEvenly => {
let equal_space = extra / (child_count + 1) as f64;
space_before = equal_space;
space_between = equal_space;
}
MainAxisAlignment::SpaceAround => {
let equal_space = extra / (2 * child_count) as f64;
space_before = equal_space;
space_between = equal_space * 2.;
}
}
(space_before, space_between)
}
impl HasProperty<Background> for Flex {}
impl HasProperty<BorderColor> for Flex {}
impl HasProperty<BorderWidth> for Flex {}
impl HasProperty<CornerRadius> for Flex {}
impl HasProperty<Padding> for Flex {}
impl Widget for Flex {
type Action = NoAction;
fn accepts_pointer_interaction(&self) -> bool {
false
}
fn register_children(&mut self, ctx: &mut RegisterCtx<'_>) {
for child in self.children.iter_mut().filter_map(|x| x.widget_mut()) {
ctx.register_child(child);
}
}
fn property_changed(&mut self, ctx: &mut UpdateCtx<'_>, property_type: TypeId) {
Background::prop_changed(ctx, property_type);
BorderColor::prop_changed(ctx, property_type);
BorderWidth::prop_changed(ctx, property_type);
CornerRadius::prop_changed(ctx, property_type);
Padding::prop_changed(ctx, property_type);
}
fn layout(
&mut self,
ctx: &mut LayoutCtx<'_>,
props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
let border = props.get::<BorderWidth>();
let padding = props.get::<Padding>();
let bc = *bc;
let bc = border.layout_down(bc);
let bc = padding.layout_down(bc);
let loosened_bc = bc.loosen();
const MIN_FLEX_SUM: f64 = 0.0001;
let gap_count = self.children.len().saturating_sub(1);
let bc_major_min = self.direction.major(bc.min());
let bc_major_max = self.direction.major(bc.max());
let mut minor = self.direction.minor(bc.min());
let mut major_non_flex = gap_count as f64 * self.gap.get();
let mut major_flex: f64 = 0.0;
let mut flex_sum = MIN_FLEX_SUM;
let mut max_above_baseline = 0_f64;
let mut max_below_baseline = 0_f64;
for child in &mut self.children {
match child {
Child::Fixed { widget, .. } => {
let child_size = {
let child_size = ctx.run_layout(widget, &loosened_bc);
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.");
}
child_size
};
let baseline_offset = ctx.child_baseline_offset(widget);
major_non_flex += self.direction.major(child_size);
minor = minor.max(self.direction.minor(child_size));
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_size) => {
*calculated_size = kv.get();
if *calculated_size < 0.0 {
tracing::warn!("Length provided to fixed spacer was less than 0");
}
*calculated_size = calculated_size.max(0.0);
major_non_flex += *calculated_size;
}
Child::Flex { flex, .. } | Child::FlexedSpacer(flex, _) => flex_sum += *flex,
}
}
let remaining_major = (bc_major_max - major_non_flex).max(0.0);
let px_per_flex = remaining_major / flex_sum;
for child in &mut self.children {
match child {
Child::Flex { widget, flex, .. } => {
let child_size = {
let desired_major = (*flex) * px_per_flex;
let child_bc = self.direction.constraints(&loosened_bc, 0.0, desired_major);
ctx.run_layout(widget, &child_bc)
};
let baseline_offset = ctx.child_baseline_offset(widget);
major_flex += self.direction.major(child_size);
minor = minor.max(self.direction.minor(child_size));
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;
*calculated_size = desired_major;
major_flex += *calculated_size;
}
_ => {}
}
}
let extra_length = if self.fill_major_axis {
(remaining_major - major_flex).max(0.0)
} else {
(self.direction.major(bc.min()) - (major_non_flex + major_flex)).max(0.0)
};
let widget_count = self
.children
.iter()
.filter(|child| child.widget().is_some())
.count();
let (space_before, space_between) =
get_spacing(self.main_alignment, extra_length, widget_count);
let mut major = space_before;
let mut previous_was_widget = false;
for child in &mut self.children {
match child {
Child::Fixed { widget, alignment }
| Child::Flex {
widget, alignment, ..
} => {
if previous_was_widget {
major += space_between;
}
let child_size = ctx.child_size(widget);
let alignment = alignment.unwrap_or(self.cross_alignment);
let child_minor_offset = match alignment {
CrossAxisAlignment::Baseline if self.direction == Axis::Horizontal => {
let max_height = max_below_baseline + max_above_baseline;
let extra_height = (minor - max_height).max(0.);
let child_baseline = ctx.child_baseline_offset(widget);
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)
.into();
let child_bc = BoxConstraints::tight(fill_size);
ctx.run_layout(widget, &child_bc);
0.0
}
_ => {
let extra_minor = minor - self.direction.minor(child_size);
alignment.align(extra_minor)
}
};
let child_pos: Point = self.direction.pack(major, child_minor_offset).into();
let child_pos = border.place_down(child_pos);
let child_pos = padding.place_down(child_pos);
ctx.place_child(widget, child_pos);
major += self.direction.major(child_size);
major += self.gap.get();
previous_was_widget = true;
}
Child::FlexedSpacer(_, calculated_size)
| Child::FixedSpacer(_, calculated_size) => {
major += *calculated_size;
major += self.gap.get();
previous_was_widget = false;
}
}
}
if flex_sum > MIN_FLEX_SUM && bc_major_max.is_infinite() {
tracing::warn!("A child of Flex is flex, but Flex is unbounded.");
}
let final_major = if flex_sum > MIN_FLEX_SUM || self.fill_major_axis {
bc_major_max.max(major_non_flex)
} else {
bc_major_min.max(major_non_flex)
};
let my_size: Size = self.direction.pack(final_major, minor).into();
let baseline = 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 = ctx.child_baseline_offset(widget);
let child_max_y = ctx.child_layout_rect(widget).max_y();
let extra_bottom_padding = my_size.height - child_max_y;
child_bl + extra_bottom_padding
} else {
0.0
}
})
.unwrap_or(0.0),
};
let (my_size, baseline) = padding.layout_up(my_size, baseline);
let (my_size, baseline) = border.layout_up(my_size, baseline);
ctx.set_baseline_offset(baseline);
my_size
}
fn paint(&mut self, ctx: &mut PaintCtx<'_>, props: &PropertiesRef<'_>, scene: &mut Scene) {
let border_width = props.get::<BorderWidth>();
let border_radius = props.get::<CornerRadius>();
let bg = props.get::<Background>();
let border_color = props.get::<BorderColor>();
let bg_rect = border_width.bg_rect(ctx.size(), border_radius);
let border_rect = border_width.border_rect(ctx.size(), border_radius);
let brush = bg.get_peniko_brush_for_rect(bg_rect.rect());
fill(scene, &bg_rect, &brush);
stroke(scene, &border_rect, border_color.color, border_width.width);
if ctx.debug_paint_enabled() && ctx.baseline_offset() != 0.0 {
let color = ctx.debug_color();
let my_baseline = ctx.size().height - ctx.baseline_offset();
let line = Line::new((0.0, my_baseline), (ctx.size().width, my_baseline));
let stroke_style = Stroke::new(1.0).with_dashes(0., [4.0, 4.0]);
scene.stroke(&stroke_style, Affine::IDENTITY, color, None, &line);
}
}
fn accessibility_role(&self) -> Role {
Role::GenericContainer
}
fn accessibility(
&mut self,
_ctx: &mut AccessCtx<'_>,
_props: &PropertiesRef<'_>,
_node: &mut Node,
) {
}
fn children_ids(&self) -> ChildrenIds {
self.children
.iter()
.filter_map(|child| child.widget())
.map(|widget_pod| widget_pod.id())
.collect()
}
fn make_trace_span(&self, id: WidgetId) -> Span {
trace_span!("Flex", id = id.trace())
}
}
#[cfg(test)]
mod tests {
use masonry_testing::assert_debug_panics;
use super::*;
use crate::properties::types::AsUnit;
use crate::testing::{TestHarness, assert_render_snapshot};
use crate::theme::{ACCENT_COLOR, default_property_set};
use crate::widgets::Label;
#[test]
fn test_main_axis_alignment_spacing() {
let apply_align = |align, extra, child_count| {
let (space_before, space_between) = get_spacing(align, extra, child_count);
let space_after =
extra - space_before - space_between * child_count.saturating_sub(1) as f64;
(space_before, space_between, space_after)
};
let align = MainAxisAlignment::Start;
let (before, _, after) = apply_align(align, 10., 1);
assert_eq!(before, 0.);
assert_eq!(after, 10.);
let (before, between, after) = apply_align(align, 10., 2);
assert_eq!(before, 0.);
assert_eq!(between, 0.);
assert_eq!(after, 10.);
let align = MainAxisAlignment::End;
let (before, _, after) = apply_align(align, 10., 1);
assert_eq!(before, 10.);
assert_eq!(after, 0.);
let (before, between, after) = apply_align(align, 10., 2);
assert_eq!(before, 10.);
assert_eq!(between, 0.);
assert_eq!(after, 0.);
let align = MainAxisAlignment::Center;
let (before, _, after) = apply_align(align, 10., 1);
assert_eq!(before, 5.);
assert_eq!(after, 5.);
let (before, between, after) = apply_align(align, 10., 3);
assert_eq!(before, 5.);
assert_eq!(between, 0.);
assert_eq!(after, 5.);
let (before, between, after) = apply_align(align, 5., 2);
assert_eq!(before, 2.5);
assert_eq!(between, 0.);
assert_eq!(after, 2.5);
let align = MainAxisAlignment::SpaceBetween;
let (before, _, after) = apply_align(align, 10., 1);
assert_eq!(before, 0.);
assert_eq!(after, 10.);
let (before, between, after) = apply_align(align, 10., 2);
assert_eq!(before, 0.);
assert_eq!(between, 10.);
assert_eq!(after, 0.);
let (before, between, after) = apply_align(align, 30., 5);
assert_eq!(before, 0.);
assert_eq!(between, 7.5);
assert_eq!(after, 0.);
let align = MainAxisAlignment::SpaceEvenly;
let (before, _, after) = apply_align(align, 10., 1);
assert_eq!(before, 5.);
assert_eq!(after, 5.);
let (before, between, after) = apply_align(align, 10., 3);
assert_eq!(before, 2.5);
assert_eq!(between, 2.5);
assert_eq!(after, 2.5);
let align = MainAxisAlignment::SpaceAround;
let (before, _, after) = apply_align(align, 10., 1);
assert_eq!(before, 5.);
assert_eq!(after, 5.);
let (before, between, after) = apply_align(align, 10., 2);
assert_eq!(before, 2.5);
assert_eq!(between, 5.);
assert_eq!(after, 2.5);
let (before, between, after) = apply_align(align, 35., 5);
assert_eq!(before, 3.5);
assert_eq!(between, 7.);
assert_eq!(after, 3.5);
}
#[test]
fn invalid_flex_params() {
assert_debug_panics!(FlexParams::new(0.0, None), "Flex value should be > 0.0");
assert_debug_panics!(FlexParams::new(-0.0, None), "Flex value should be > 0.0");
assert_debug_panics!(FlexParams::new(-1.0, None), "Flex value should be > 0.0");
}
#[test]
fn flex_row_fixed_size_only() {
let widget = NewWidget::new_with_props(
Flex::row()
.with_child(Label::new("hello").with_auto_id())
.with_child(Label::new("world").with_auto_id())
.with_child(Label::new("foo").with_auto_id())
.with_child(Label::new("bar").with_auto_id()),
(BorderWidth::all(2.0), BorderColor::new(ACCENT_COLOR)).into(),
);
let window_size = Size::new(200.0, 150.0);
let mut harness =
TestHarness::create_with_size(default_property_set(), widget, window_size);
harness.edit_root_widget(|mut flex| {
Flex::set_main_axis_alignment(&mut flex, MainAxisAlignment::Start);
});
assert_render_snapshot!(harness, "flex_row_fixed_children_start");
harness.edit_root_widget(|mut flex| {
Flex::set_main_axis_alignment(&mut flex, MainAxisAlignment::Center);
});
assert_render_snapshot!(harness, "flex_row_fixed_children_center");
harness.edit_root_widget(|mut flex| {
Flex::set_main_axis_alignment(&mut flex, MainAxisAlignment::End);
});
assert_render_snapshot!(harness, "flex_row_fixed_children_end");
harness.edit_root_widget(|mut flex| {
Flex::set_main_axis_alignment(&mut flex, MainAxisAlignment::SpaceBetween);
});
assert_render_snapshot!(harness, "flex_row_fixed_children_spaceBetween");
harness.edit_root_widget(|mut flex| {
Flex::set_main_axis_alignment(&mut flex, MainAxisAlignment::SpaceEvenly);
});
assert_render_snapshot!(harness, "flex_row_fixed_children_spaceEvenly");
harness.edit_root_widget(|mut flex| {
Flex::set_main_axis_alignment(&mut flex, MainAxisAlignment::SpaceAround);
});
assert_render_snapshot!(harness, "flex_row_fixed_children_spaceAround");
}
#[test]
fn flex_row_cross_axis_snapshots() {
let widget = NewWidget::new_with_props(
Flex::row()
.with_child(Label::new("hello").with_auto_id())
.with_flex_child(Label::new("world").with_auto_id(), 1.0)
.with_child(Label::new("foo").with_auto_id())
.with_flex_child(
Label::new("bar").with_auto_id(),
FlexParams::new(2.0, CrossAxisAlignment::Start),
),
(BorderWidth::all(2.0), BorderColor::new(ACCENT_COLOR)).into(),
);
let window_size = Size::new(200.0, 150.0);
let mut harness =
TestHarness::create_with_size(default_property_set(), widget, window_size);
harness.edit_root_widget(|mut flex| {
Flex::set_cross_axis_alignment(&mut flex, CrossAxisAlignment::Start);
});
assert_render_snapshot!(harness, "flex_row_cross_axis_start");
harness.edit_root_widget(|mut flex| {
Flex::set_cross_axis_alignment(&mut flex, CrossAxisAlignment::Center);
});
assert_render_snapshot!(harness, "flex_row_cross_axis_center");
harness.edit_root_widget(|mut flex| {
Flex::set_cross_axis_alignment(&mut flex, CrossAxisAlignment::End);
});
assert_render_snapshot!(harness, "flex_row_cross_axis_end");
harness.edit_root_widget(|mut flex| {
Flex::set_cross_axis_alignment(&mut flex, CrossAxisAlignment::Baseline);
});
assert_render_snapshot!(harness, "flex_row_cross_axis_baseline");
harness.edit_root_widget(|mut flex| {
Flex::set_cross_axis_alignment(&mut flex, CrossAxisAlignment::Fill);
});
assert_render_snapshot!(harness, "flex_row_cross_axis_fill");
}
#[test]
fn flex_row_main_axis_snapshots() {
let widget = NewWidget::new_with_props(
Flex::row()
.with_child(Label::new("hello").with_auto_id())
.with_flex_child(Label::new("world").with_auto_id(), 1.0)
.with_child(Label::new("foo").with_auto_id())
.with_flex_child(
Label::new("bar").with_auto_id(),
FlexParams::new(2.0, CrossAxisAlignment::Start),
),
(BorderWidth::all(2.0), BorderColor::new(ACCENT_COLOR)).into(),
);
let window_size = Size::new(200.0, 150.0);
let mut harness =
TestHarness::create_with_size(default_property_set(), widget, window_size);
harness.edit_root_widget(|mut flex| {
Flex::set_main_axis_alignment(&mut flex, MainAxisAlignment::Start);
});
assert_render_snapshot!(harness, "flex_row_main_axis_start");
harness.edit_root_widget(|mut flex| {
Flex::set_main_axis_alignment(&mut flex, MainAxisAlignment::Center);
});
assert_render_snapshot!(harness, "flex_row_main_axis_center");
harness.edit_root_widget(|mut flex| {
Flex::set_main_axis_alignment(&mut flex, MainAxisAlignment::End);
});
assert_render_snapshot!(harness, "flex_row_main_axis_end");
harness.edit_root_widget(|mut flex| {
Flex::set_main_axis_alignment(&mut flex, MainAxisAlignment::SpaceBetween);
});
assert_render_snapshot!(harness, "flex_row_main_axis_spaceBetween");
harness.edit_root_widget(|mut flex| {
Flex::set_main_axis_alignment(&mut flex, MainAxisAlignment::SpaceEvenly);
});
assert_render_snapshot!(harness, "flex_row_main_axis_spaceEvenly");
harness.edit_root_widget(|mut flex| {
Flex::set_main_axis_alignment(&mut flex, MainAxisAlignment::SpaceAround);
});
assert_render_snapshot!(harness, "flex_row_main_axis_spaceAround");
harness.edit_root_widget(|mut flex| {
Flex::set_must_fill_main_axis(&mut flex, true);
});
assert_render_snapshot!(harness, "flex_row_fill_main_axis");
}
#[test]
fn flex_col_cross_axis_snapshots() {
let widget = NewWidget::new_with_props(
Flex::column()
.with_child(Label::new("hello").with_auto_id())
.with_flex_child(Label::new("world").with_auto_id(), 1.0)
.with_child(Label::new("foo").with_auto_id())
.with_flex_child(
Label::new("bar").with_auto_id(),
FlexParams::new(2.0, CrossAxisAlignment::Start),
),
(BorderWidth::all(2.0), BorderColor::new(ACCENT_COLOR)).into(),
);
let window_size = Size::new(200.0, 150.0);
let mut harness =
TestHarness::create_with_size(default_property_set(), widget, window_size);
harness.edit_root_widget(|mut flex| {
Flex::set_cross_axis_alignment(&mut flex, CrossAxisAlignment::Start);
});
assert_render_snapshot!(harness, "flex_col_cross_axis_start");
harness.edit_root_widget(|mut flex| {
Flex::set_cross_axis_alignment(&mut flex, CrossAxisAlignment::Center);
});
assert_render_snapshot!(harness, "flex_col_cross_axis_center");
harness.edit_root_widget(|mut flex| {
Flex::set_cross_axis_alignment(&mut flex, CrossAxisAlignment::End);
});
assert_render_snapshot!(harness, "flex_col_cross_axis_end");
harness.edit_root_widget(|mut flex| {
Flex::set_cross_axis_alignment(&mut flex, CrossAxisAlignment::Baseline);
});
assert_render_snapshot!(harness, "flex_col_cross_axis_baseline");
harness.edit_root_widget(|mut flex| {
Flex::set_cross_axis_alignment(&mut flex, CrossAxisAlignment::Fill);
});
assert_render_snapshot!(harness, "flex_col_cross_axis_fill");
}
#[test]
fn flex_col_main_axis_snapshots() {
let widget = NewWidget::new_with_props(
Flex::column()
.with_child(Label::new("hello").with_auto_id())
.with_flex_child(Label::new("world").with_auto_id(), 1.0)
.with_child(Label::new("foo").with_auto_id())
.with_flex_child(
Label::new("bar").with_auto_id(),
FlexParams::new(2.0, CrossAxisAlignment::Start),
),
(BorderWidth::all(2.0), BorderColor::new(ACCENT_COLOR)).into(),
);
let window_size = Size::new(200.0, 150.0);
let mut harness =
TestHarness::create_with_size(default_property_set(), widget, window_size);
harness.edit_root_widget(|mut flex| {
Flex::set_main_axis_alignment(&mut flex, MainAxisAlignment::Start);
});
assert_render_snapshot!(harness, "flex_col_main_axis_start");
harness.edit_root_widget(|mut flex| {
Flex::set_main_axis_alignment(&mut flex, MainAxisAlignment::Center);
});
assert_render_snapshot!(harness, "flex_col_main_axis_center");
harness.edit_root_widget(|mut flex| {
Flex::set_main_axis_alignment(&mut flex, MainAxisAlignment::End);
});
assert_render_snapshot!(harness, "flex_col_main_axis_end");
harness.edit_root_widget(|mut flex| {
Flex::set_main_axis_alignment(&mut flex, MainAxisAlignment::SpaceBetween);
});
assert_render_snapshot!(harness, "flex_col_main_axis_spaceBetween");
harness.edit_root_widget(|mut flex| {
Flex::set_main_axis_alignment(&mut flex, MainAxisAlignment::SpaceEvenly);
});
assert_render_snapshot!(harness, "flex_col_main_axis_spaceEvenly");
harness.edit_root_widget(|mut flex| {
Flex::set_main_axis_alignment(&mut flex, MainAxisAlignment::SpaceAround);
});
assert_render_snapshot!(harness, "flex_col_main_axis_spaceAround");
harness.edit_root_widget(|mut flex| {
Flex::set_must_fill_main_axis(&mut flex, true);
});
assert_render_snapshot!(harness, "flex_col_fill_main_axis");
}
#[test]
fn edit_flex_container() {
let image_1 = {
let widget = Flex::column()
.with_child(Label::new("a").with_auto_id())
.with_child(Label::new("b").with_auto_id())
.with_child(Label::new("c").with_auto_id())
.with_child(Label::new("d").with_auto_id())
.with_auto_id();
let window_size = Size::new(200.0, 150.0);
let mut harness =
TestHarness::create_with_size(default_property_set(), widget, window_size);
harness.edit_root_widget(|mut flex| {
Flex::remove_child(&mut flex, 1);
Flex::add_child(&mut flex, Label::new("x").with_auto_id());
Flex::add_flex_child(&mut flex, Label::new("y").with_auto_id(), 2.0);
Flex::add_spacer(&mut flex, 5.px());
Flex::add_flex_spacer(&mut flex, 1.0);
Flex::insert_child(&mut flex, 2, Label::new("i").with_auto_id());
Flex::insert_flex_child(&mut flex, 2, Label::new("j").with_auto_id(), 2.0);
Flex::insert_spacer(&mut flex, 2, 5.px());
Flex::insert_flex_spacer(&mut flex, 2, 1.0);
});
harness.render()
};
let image_2 = {
let widget = Flex::column()
.with_child(Label::new("a").with_auto_id())
.with_child(Label::new("c").with_auto_id())
.with_flex_spacer(1.0)
.with_spacer(5.px())
.with_flex_child(Label::new("j").with_auto_id(), 2.0)
.with_child(Label::new("i").with_auto_id())
.with_child(Label::new("d").with_auto_id())
.with_child(Label::new("x").with_auto_id())
.with_flex_child(Label::new("y").with_auto_id(), 2.0)
.with_spacer(5.px())
.with_flex_spacer(1.0)
.with_auto_id();
let window_size = Size::new(200.0, 150.0);
let mut harness =
TestHarness::create_with_size(default_property_set(), widget, window_size);
harness.render()
};
assert!(image_1 == image_2);
}
#[test]
fn get_flex_child() {
let widget = Flex::column()
.with_child(Label::new("hello").with_auto_id())
.with_child(Label::new("world").with_auto_id())
.with_spacer(1.px())
.with_auto_id();
let window_size = Size::new(200.0, 150.0);
let mut harness =
TestHarness::create_with_size(default_property_set(), widget, window_size);
harness.edit_root_widget(|mut flex| {
let mut child = Flex::child_mut(&mut flex, 1).unwrap();
assert_eq!(
child
.try_downcast::<Label>()
.unwrap()
.widget
.text()
.to_string(),
"world"
);
drop(child);
assert!(Flex::child_mut(&mut flex, 2).is_none());
});
}
#[test]
fn divide_by_zero() {
let widget = Flex::column().with_flex_spacer(0.0).with_auto_id();
let window_size = Size::new(200.0, 150.0);
let mut harness =
TestHarness::create_with_size(default_property_set(), widget, window_size);
harness.render();
}
}