use std::any::{Any, TypeId};
use std::fmt::{self, Debug};
use std::ops::Add;
use inset_embedder::{Clip, Offset, Size, TextBaseline, TextDirection};
use inset_foundation::{App, Handle, PRECISION_ERROR_TOLERANCE};
use inset_painting::{Axis, VerticalDirection};
use crate::box_::{
AnyRenderBox, BoxConstraints, BoxHitTestResult, BoxParentData, ContainerBoxParentData,
DryLayoutFailure, RenderBox, RenderBoxContainerDefaultsMixin, RenderBoxData,
};
use crate::layer::{ClipRectLayer, LayerHandle};
use crate::layout_helper::ChildLayoutHelper;
use crate::object::{
AnyRenderObject, ContainerParentData, ContainerParentDataMixin, ContainerRenderObjectData,
ContainerRenderObjectMixin, ParentData, RenderHandle, RenderObject, RenderObjectData,
};
use crate::painting_context::PaintingContext;
use crate::pipeline_owner::PipelineOwner;
#[derive(Clone, Copy)]
struct AxisSize(Size);
impl AxisSize {
fn new(main_axis_extent: f64, cross_axis_extent: f64) -> AxisSize {
AxisSize(Size::new(main_axis_extent, cross_axis_extent))
}
fn from_size(size: Size, direction: Axis) -> AxisSize {
AxisSize(AxisSize::convert(size, direction))
}
fn convert(size: Size, direction: Axis) -> Size {
match direction {
Axis::Horizontal => size,
Axis::Vertical => size.flipped(),
}
}
fn main_axis_extent(self) -> f64 {
self.0.width()
}
fn cross_axis_extent(self) -> f64 {
self.0.height()
}
fn to_size(self, direction: Axis) -> Size {
AxisSize::convert(self.0, direction)
}
fn apply_constraints(self, constraints: BoxConstraints, direction: Axis) -> AxisSize {
let effective_constraints = match direction {
Axis::Horizontal => constraints,
Axis::Vertical => constraints.flipped(),
};
AxisSize(effective_constraints.constrain(self.0))
}
}
impl Add for AxisSize {
type Output = AxisSize;
fn add(self, other: AxisSize) -> AxisSize {
AxisSize(Size::new(
self.0.width() + other.0.width(),
self.0.height().max(other.0.height()),
))
}
}
type ChildSizingFunction = fn(AnyRenderBox, &mut App, f64) -> f64;
pub struct LayoutSizes {
axis_size: AxisSize,
main_axis_free_space: f64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FlexFit {
Tight,
Loose,
}
#[derive(Debug)]
pub struct FlexParentData {
box_parent_data: BoxParentData,
container_parent_data: ContainerParentData<AnyRenderBox>,
pub flex: Option<i32>,
pub fit: Option<FlexFit>,
}
impl FlexParentData {
pub const fn new() -> FlexParentData {
FlexParentData {
box_parent_data: BoxParentData::new(),
container_parent_data: ContainerParentData::new(),
flex: None,
fit: None,
}
}
}
impl Default for FlexParentData {
fn default() -> FlexParentData {
FlexParentData::new()
}
}
impl ParentData for FlexParentData {
fn detach(&mut self) {
ContainerParentDataMixin::detach(self);
}
fn provide(&self, id: TypeId) -> Option<&dyn Any> {
if id == TypeId::of::<FlexParentData>() {
return Some(self);
}
self.box_parent_data.provide(id)
}
fn provide_mut(&mut self, id: TypeId) -> Option<&mut dyn Any> {
if id == TypeId::of::<FlexParentData>() {
return Some(self);
}
self.box_parent_data.provide_mut(id)
}
}
impl ContainerParentDataMixin for FlexParentData {
type ChildType = AnyRenderBox;
fn container_parent_data(&self) -> &ContainerParentData<AnyRenderBox> {
&self.container_parent_data
}
fn container_parent_data_mut(&mut self) -> &mut ContainerParentData<AnyRenderBox> {
&mut self.container_parent_data
}
}
impl ContainerBoxParentData for FlexParentData {
fn box_parent_data(&self) -> &BoxParentData {
&self.box_parent_data
}
fn box_parent_data_mut(&mut self) -> &mut BoxParentData {
&mut self.box_parent_data
}
}
impl fmt::Display for FlexParentData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}; flex={:?}; fit={:?}",
self.box_parent_data, self.flex, self.fit
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MainAxisSize {
Min,
Max,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MainAxisAlignment {
Start,
End,
Center,
SpaceBetween,
SpaceAround,
SpaceEvenly,
}
impl MainAxisAlignment {
fn distribute_space(
self,
free_space: f64,
item_count: usize,
flipped: bool,
spacing: f64,
) -> (f64, f64) {
let items = item_count as f64;
match self {
MainAxisAlignment::Start => {
if flipped {
(free_space, spacing)
} else {
(0.0, spacing)
}
}
MainAxisAlignment::End => {
MainAxisAlignment::Start.distribute_space(free_space, item_count, !flipped, spacing)
}
MainAxisAlignment::SpaceBetween if item_count < 2 => {
MainAxisAlignment::Start.distribute_space(free_space, item_count, flipped, spacing)
}
MainAxisAlignment::SpaceAround if item_count == 0 => {
MainAxisAlignment::Start.distribute_space(free_space, item_count, flipped, spacing)
}
MainAxisAlignment::Center => (free_space / 2.0, spacing),
MainAxisAlignment::SpaceBetween => (0.0, free_space / (items - 1.0) + spacing),
MainAxisAlignment::SpaceAround => {
(free_space / items / 2.0, free_space / items + spacing)
}
MainAxisAlignment::SpaceEvenly => (
free_space / (items + 1.0),
free_space / (items + 1.0) + spacing,
),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CrossAxisAlignment {
Start,
End,
Center,
Stretch,
Baseline,
}
impl CrossAxisAlignment {
fn get_child_cross_axis_offset(self, free_space: f64, flipped: bool) -> f64 {
match self {
CrossAxisAlignment::Stretch | CrossAxisAlignment::Baseline => 0.0,
CrossAxisAlignment::Start => {
if flipped {
free_space
} else {
0.0
}
}
CrossAxisAlignment::Center => free_space / 2.0,
CrossAxisAlignment::End => {
CrossAxisAlignment::Start.get_child_cross_axis_offset(free_space, !flipped)
}
}
}
}
pub struct RenderFlexData {
direction: Axis,
main_axis_alignment: MainAxisAlignment,
main_axis_size: MainAxisSize,
cross_axis_alignment: CrossAxisAlignment,
text_direction: Option<TextDirection>,
vertical_direction: VerticalDirection,
text_baseline: Option<TextBaseline>,
clip_behavior: Clip,
spacing: f64,
overflow: f64,
clip_rect_layer: LayerHandle<Handle<ClipRectLayer>>,
}
impl RenderFlexData {
pub fn new() -> RenderFlexData {
RenderFlexData {
direction: Axis::Horizontal,
main_axis_alignment: MainAxisAlignment::Start,
main_axis_size: MainAxisSize::Max,
cross_axis_alignment: CrossAxisAlignment::Center,
text_direction: None,
vertical_direction: VerticalDirection::Down,
text_baseline: None,
clip_behavior: Clip::None,
spacing: 0.0,
overflow: 0.0,
clip_rect_layer: LayerHandle::new(),
}
}
}
impl Default for RenderFlexData {
fn default() -> RenderFlexData {
RenderFlexData::new()
}
}
impl RenderFlexData {
pub fn direction(mut self, direction: Axis) -> RenderFlexData {
self.direction = direction;
self
}
pub fn main_axis_alignment(mut self, main_axis_alignment: MainAxisAlignment) -> RenderFlexData {
self.main_axis_alignment = main_axis_alignment;
self
}
pub fn main_axis_size(mut self, main_axis_size: MainAxisSize) -> RenderFlexData {
self.main_axis_size = main_axis_size;
self
}
pub fn cross_axis_alignment(
mut self,
cross_axis_alignment: CrossAxisAlignment,
) -> RenderFlexData {
self.cross_axis_alignment = cross_axis_alignment;
self
}
pub fn text_direction(mut self, text_direction: Option<TextDirection>) -> RenderFlexData {
self.text_direction = text_direction;
self
}
pub fn vertical_direction(mut self, vertical_direction: VerticalDirection) -> RenderFlexData {
self.vertical_direction = vertical_direction;
self
}
pub fn text_baseline(mut self, text_baseline: Option<TextBaseline>) -> RenderFlexData {
self.text_baseline = text_baseline;
self
}
pub fn clip_behavior(mut self, clip_behavior: Clip) -> RenderFlexData {
self.clip_behavior = clip_behavior;
self
}
pub fn spacing(mut self, spacing: f64) -> RenderFlexData {
self.spacing = spacing;
self
}
}
pub trait RenderFlexMixin:
RenderBox
+ ContainerRenderObjectMixin<ChildType = AnyRenderBox, ParentDataType = FlexParentData>
+ RenderBoxContainerDefaultsMixin
{
fn flex_data(self: RenderHandle<Self>, app: &App) -> &RenderFlexData;
fn flex_data_mut(self: RenderHandle<Self>, app: &mut App) -> &mut RenderFlexData;
fn direction(self: RenderHandle<Self>, app: &App) -> Axis {
self.flex_data(app).direction
}
fn set_direction(self: RenderHandle<Self>, app: &mut App, value: Axis) {
if self.flex_data(app).direction != value {
self.flex_data_mut(app).direction = value;
self.mark_needs_layout(app);
}
}
fn main_axis_alignment(self: RenderHandle<Self>, app: &App) -> MainAxisAlignment {
self.flex_data(app).main_axis_alignment
}
fn set_main_axis_alignment(self: RenderHandle<Self>, app: &mut App, value: MainAxisAlignment) {
if self.flex_data(app).main_axis_alignment != value {
self.flex_data_mut(app).main_axis_alignment = value;
self.mark_needs_layout(app);
}
}
fn main_axis_size(self: RenderHandle<Self>, app: &App) -> MainAxisSize {
self.flex_data(app).main_axis_size
}
fn set_main_axis_size(self: RenderHandle<Self>, app: &mut App, value: MainAxisSize) {
if self.flex_data(app).main_axis_size != value {
self.flex_data_mut(app).main_axis_size = value;
self.mark_needs_layout(app);
}
}
fn cross_axis_alignment(self: RenderHandle<Self>, app: &App) -> CrossAxisAlignment {
self.flex_data(app).cross_axis_alignment
}
fn set_cross_axis_alignment(
self: RenderHandle<Self>,
app: &mut App,
value: CrossAxisAlignment,
) {
if self.flex_data(app).cross_axis_alignment != value {
self.flex_data_mut(app).cross_axis_alignment = value;
self.mark_needs_layout(app);
}
}
fn text_direction(self: RenderHandle<Self>, app: &App) -> Option<TextDirection> {
self.flex_data(app).text_direction
}
fn set_text_direction(self: RenderHandle<Self>, app: &mut App, value: Option<TextDirection>) {
if self.flex_data(app).text_direction != value {
self.flex_data_mut(app).text_direction = value;
self.mark_needs_layout(app);
}
}
fn vertical_direction(self: RenderHandle<Self>, app: &App) -> VerticalDirection {
self.flex_data(app).vertical_direction
}
fn set_vertical_direction(self: RenderHandle<Self>, app: &mut App, value: VerticalDirection) {
if self.flex_data(app).vertical_direction != value {
self.flex_data_mut(app).vertical_direction = value;
self.mark_needs_layout(app);
}
}
fn text_baseline(self: RenderHandle<Self>, app: &App) -> Option<TextBaseline> {
self.flex_data(app).text_baseline
}
fn set_text_baseline(self: RenderHandle<Self>, app: &mut App, value: Option<TextBaseline>) {
debug_assert!(
self.flex_data(app).cross_axis_alignment != CrossAxisAlignment::Baseline
|| value.is_some()
);
if self.flex_data(app).text_baseline != value {
self.flex_data_mut(app).text_baseline = value;
self.mark_needs_layout(app);
}
}
fn has_overflow(self: RenderHandle<Self>, app: &App) -> bool {
self.flex_data(app).overflow > PRECISION_ERROR_TOLERANCE
}
fn clip_behavior(self: RenderHandle<Self>, app: &App) -> Clip {
self.flex_data(app).clip_behavior
}
fn set_clip_behavior(self: RenderHandle<Self>, app: &mut App, value: Clip) {
if value != self.flex_data(app).clip_behavior {
self.flex_data_mut(app).clip_behavior = value;
self.mark_needs_paint(app);
}
}
fn spacing(self: RenderHandle<Self>, app: &App) -> f64 {
self.flex_data(app).spacing
}
fn set_spacing(self: RenderHandle<Self>, app: &mut App, value: f64) {
debug_assert!(value >= 0.0);
if self.flex_data(app).spacing == value {
return;
}
self.flex_data_mut(app).spacing = value;
self.mark_needs_layout(app);
}
fn debug_has_necessary_directions(self: RenderHandle<Self>, app: &App) -> bool {
let direction = self.direction(app);
if self.first_child(app).is_some() && self.last_child(app) != self.first_child(app) {
if direction == Axis::Horizontal {
debug_assert!(
self.text_direction(app).is_some(),
"Horizontal RenderFlex with multiple children has a null textDirection, so \
the layout order is undefined."
);
}
}
let main_axis_alignment = self.main_axis_alignment(app);
if (main_axis_alignment == MainAxisAlignment::Start
|| main_axis_alignment == MainAxisAlignment::End)
&& direction == Axis::Horizontal
{
debug_assert!(
self.text_direction(app).is_some(),
"Horizontal RenderFlex with {main_axis_alignment:?} has a null textDirection, so \
the alignment cannot be resolved."
);
}
let cross_axis_alignment = self.cross_axis_alignment(app);
if (cross_axis_alignment == CrossAxisAlignment::Start
|| cross_axis_alignment == CrossAxisAlignment::End)
&& direction == Axis::Vertical
{
debug_assert!(
self.text_direction(app).is_some(),
"Vertical RenderFlex with {cross_axis_alignment:?} has a null textDirection, so \
the alignment cannot be resolved."
);
}
true
}
fn get_cross_size(self: RenderHandle<Self>, app: &App, size: Size) -> f64 {
match self.direction(app) {
Axis::Horizontal => size.height(),
Axis::Vertical => size.width(),
}
}
fn get_main_size(self: RenderHandle<Self>, app: &App, size: Size) -> f64 {
match self.direction(app) {
Axis::Horizontal => size.width(),
Axis::Vertical => size.height(),
}
}
fn flip_main_axis(self: RenderHandle<Self>, app: &App) -> bool {
self.first_child(app).is_some()
&& match self.direction(app) {
Axis::Horizontal => self.text_direction(app) == Some(TextDirection::Rtl),
Axis::Vertical => self.vertical_direction(app) == VerticalDirection::Up,
}
}
fn flip_cross_axis(self: RenderHandle<Self>, app: &App) -> bool {
self.first_child(app).is_some()
&& match self.direction(app) {
Axis::Vertical => self.text_direction(app) == Some(TextDirection::Rtl),
Axis::Horizontal => self.vertical_direction(app) == VerticalDirection::Up,
}
}
fn constraints_for_non_flex_child(
self: RenderHandle<Self>,
app: &App,
constraints: BoxConstraints,
) -> BoxConstraints {
let fill_cross_axis = self.cross_axis_alignment(app) == CrossAxisAlignment::Stretch;
match self.direction(app) {
Axis::Horizontal => {
if fill_cross_axis {
BoxConstraints::tight_for(None, Some(constraints.max_height))
} else {
BoxConstraints::new().max_height(constraints.max_height)
}
}
Axis::Vertical => {
if fill_cross_axis {
BoxConstraints::tight_for(Some(constraints.max_width), None)
} else {
BoxConstraints::new().max_width(constraints.max_width)
}
}
}
}
fn constraints_for_flex_child(
self: RenderHandle<Self>,
app: &App,
child: AnyRenderBox,
constraints: BoxConstraints,
max_child_extent: f64,
) -> BoxConstraints {
debug_assert!(get_flex(app, child) > 0);
debug_assert!(max_child_extent >= 0.0);
let min_child_extent = match get_fit(app, child) {
FlexFit::Tight => max_child_extent,
FlexFit::Loose => 0.0,
};
let fill_cross_axis = self.cross_axis_alignment(app) == CrossAxisAlignment::Stretch;
match self.direction(app) {
Axis::Horizontal => BoxConstraints {
min_width: min_child_extent,
max_width: max_child_extent,
min_height: if fill_cross_axis {
constraints.max_height
} else {
0.0
},
max_height: constraints.max_height,
},
Axis::Vertical => BoxConstraints {
min_width: if fill_cross_axis {
constraints.max_width
} else {
0.0
},
max_width: constraints.max_width,
min_height: min_child_extent,
max_height: max_child_extent,
},
}
}
fn get_intrinsic_size(
self: RenderHandle<Self>,
app: &mut App,
sizing_direction: Axis,
extent: f64,
child_size: ChildSizingFunction,
) -> f64 {
let direction = self.direction(app);
if direction == sizing_direction {
let mut total_flex = 0.0;
let mut inflexible_space = self.spacing(app) * (self.child_count(app) as f64 - 1.0);
let mut max_flex_fraction_so_far: f64 = 0.0;
let mut child = self.first_child(app);
while let Some(current) = child {
let flex = get_flex(app, current);
total_flex += f64::from(flex);
if flex > 0 {
let flex_fraction = child_size(current, app, extent) / f64::from(flex);
max_flex_fraction_so_far = max_flex_fraction_so_far.max(flex_fraction);
} else {
inflexible_space += child_size(current, app, extent);
}
child = self.child_after(app, current);
}
return max_flex_fraction_so_far * total_flex + inflexible_space;
}
let is_horizontal = direction == Axis::Horizontal;
let layout_child = |app: &mut App, child: AnyRenderBox, constraints: BoxConstraints| {
let main_axis_size_from_constraints = if is_horizontal {
constraints.max_width
} else {
constraints.max_height
};
debug_assert_eq!(
get_flex(app, child) != 0 && extent.is_finite(),
main_axis_size_from_constraints.is_finite()
);
let max_main_axis_size = if main_axis_size_from_constraints.is_finite() {
main_axis_size_from_constraints
} else if is_horizontal {
child.get_max_intrinsic_width(app, f64::INFINITY)
} else {
child.get_max_intrinsic_height(app, f64::INFINITY)
};
let cross_size = child_size(child, app, max_main_axis_size);
if is_horizontal {
Size::new(max_main_axis_size, cross_size)
} else {
Size::new(cross_size, max_main_axis_size)
}
};
let constraints = if is_horizontal {
BoxConstraints::new().max_width(extent)
} else {
BoxConstraints::new().max_height(extent)
};
self.compute_sizes(app, constraints, layout_child)
.axis_size
.cross_axis_extent()
}
fn debug_check_constraints(
self: RenderHandle<Self>,
app: &App,
constraints: BoxConstraints,
) -> Option<String> {
let direction = self.direction(app);
let max_main_size = match direction {
Axis::Horizontal => constraints.max_width,
Axis::Vertical => constraints.max_height,
};
let can_flex = max_main_size < f64::INFINITY;
let mut child = self.first_child(app);
while let Some(current) = child {
if get_flex(app, current) > 0
&& !can_flex
&& (self.main_axis_size(app) == MainAxisSize::Max
|| get_fit(app, current) == FlexFit::Tight)
{
let (identity, axis, dimension) = match direction {
Axis::Horizontal => ("row", "horizontal", "width"),
Axis::Vertical => ("column", "vertical", "height"),
};
return Some(format!(
"RenderFlex children have non-zero flex but incoming {dimension} constraints \
are unbounded.\n\
When a {identity} is in a parent that does not provide a finite {dimension} \
constraint, for example if it is in a {axis} scrollable, it will try to \
shrink-wrap its children along the {axis} axis. Setting a flex on a child \
(e.g. using Expanded) indicates that the child is to expand to fill the \
remaining space in the {axis} direction.\n\
These two directives are mutually exclusive. If a parent is to shrink-wrap \
its child, the child cannot simultaneously expand to fit its parent.\n\
Consider setting mainAxisSize to MainAxisSize.min and using FlexFit.loose \
fits for the flexible children (using Flexible rather than Expanded). This \
will allow the flexible children to size themselves to less than the \
infinite remaining space they would otherwise be forced to take, and then \
will cause the RenderFlex to shrink-wrap the children rather than expanding \
to fit the maximum constraints provided by the parent."
));
}
child = self.child_after(app, current);
}
None
}
fn compute_sizes(
self: RenderHandle<Self>,
app: &mut App,
constraints: BoxConstraints,
mut layout_child: impl FnMut(&mut App, AnyRenderBox, BoxConstraints) -> Size,
) -> LayoutSizes {
debug_assert!(self.debug_has_necessary_directions(app));
let direction = self.direction(app);
let max_main_size = self.get_main_size(app, constraints.biggest());
let can_flex = max_main_size.is_finite();
let non_flex_child_constraints = self.constraints_for_non_flex_child(app, constraints);
let mut total_flex = 0;
let mut first_flex_child: Option<AnyRenderBox> = None;
let mut accumulated_size = AxisSize::new(
self.spacing(app) * (self.child_count(app) as f64 - 1.0),
0.0,
);
let mut child = self.first_child(app);
while let Some(current) = child {
let flex = get_flex(app, current);
if can_flex && flex > 0 {
total_flex += flex;
first_flex_child.get_or_insert(current);
} else {
let child_size = layout_child(app, current, non_flex_child_constraints);
accumulated_size = accumulated_size + AxisSize::from_size(child_size, direction);
}
child = self.child_after(app, current);
}
debug_assert_eq!(total_flex == 0, first_flex_child.is_none());
debug_assert!(first_flex_child.is_none() || can_flex);
let flex_space = (max_main_size - accumulated_size.main_axis_extent()).max(0.0);
let space_per_flex = flex_space / total_flex as f64;
let mut child = first_flex_child;
while let Some(current) = child {
if total_flex == 0 {
break;
}
let flex = get_flex(app, current);
if flex != 0 {
total_flex -= flex;
debug_assert!(space_per_flex.is_finite());
let max_child_extent = space_per_flex * f64::from(flex);
debug_assert!(
get_fit(app, current) == FlexFit::Loose || max_child_extent < f64::INFINITY
);
let child_constraints =
self.constraints_for_flex_child(app, current, constraints, max_child_extent);
let child_size = layout_child(app, current, child_constraints);
accumulated_size = accumulated_size + AxisSize::from_size(child_size, direction);
}
child = self.child_after(app, current);
}
debug_assert_eq!(total_flex, 0);
let ideal_main_size = match self.main_axis_size(app) {
MainAxisSize::Max if max_main_size.is_finite() => max_main_size,
MainAxisSize::Max | MainAxisSize::Min => accumulated_size.main_axis_extent(),
};
let constrained_size = AxisSize::new(ideal_main_size, accumulated_size.cross_axis_extent())
.apply_constraints(constraints, direction);
LayoutSizes {
axis_size: constrained_size,
main_axis_free_space: constrained_size.main_axis_extent()
- accumulated_size.main_axis_extent(),
}
}
fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
let constraints = self.constraints(app);
if cfg!(debug_assertions)
&& let Some(error) = self.debug_check_constraints(app, constraints)
{
panic!("{error}");
}
let sizes = self.compute_sizes(app, constraints, ChildLayoutHelper::layout_child);
let direction = self.direction(app);
let cross_axis_extent = sizes.axis_size.cross_axis_extent();
self.set_size(app, sizes.axis_size.to_size(direction));
self.flex_data_mut(app).overflow = (-sizes.main_axis_free_space).max(0.0);
let remaining_space = sizes.main_axis_free_space.max(0.0);
let flip_main_axis = self.flip_main_axis(app);
let flip_cross_axis = self.flip_cross_axis(app);
let cross_axis_alignment = self.cross_axis_alignment(app);
let (leading_space, between_space) = self.main_axis_alignment(app).distribute_space(
remaining_space,
self.child_count(app),
flip_main_axis,
self.spacing(app),
);
let top_left_child = if flip_main_axis {
self.last_child(app)
} else {
self.first_child(app)
};
let mut child_main_position = leading_space;
let mut child = top_left_child;
while let Some(current) = child {
let child_size = current.size(app);
let free_cross_space = cross_axis_extent - self.get_cross_size(app, child_size);
let child_cross_position = if cross_axis_alignment == CrossAxisAlignment::Baseline
&& direction == Axis::Horizontal
{
CrossAxisAlignment::Start.get_child_cross_axis_offset(free_cross_space, false)
} else {
cross_axis_alignment.get_child_cross_axis_offset(free_cross_space, flip_cross_axis)
};
let offset = match direction {
Axis::Horizontal => Offset::new(child_main_position, child_cross_position),
Axis::Vertical => Offset::new(child_cross_position, child_main_position),
};
current
.parent_data_of_mut::<FlexParentData>(app)
.set_offset(offset);
child_main_position += self.get_main_size(app, child_size) + between_space;
child = if flip_main_axis {
self.child_before(app, current)
} else {
self.child_after(app, current)
};
}
}
fn paint(
self: RenderHandle<Self>,
app: &mut App,
context: &mut PaintingContext,
offset: Offset,
) {
if !self.has_overflow(app) {
self.default_paint(app, context, offset);
return;
}
let size = self.size(app);
if size.is_empty() {
return;
}
let clip_behavior = self.clip_behavior(app);
let old = self.flex_data(app).clip_rect_layer.layer();
let layer = context.push_clip_rect(
app,
self.as_object().needs_compositing(app),
offset,
Offset::ZERO & size,
|app, context, offset| self.default_paint(app, context, offset),
clip_behavior,
old,
);
LayerHandle::set_layer(
app,
|app| &mut self.flex_data_mut(app).clip_rect_layer,
layer,
);
}
fn dispose(self: RenderHandle<Self>, app: &mut App) {
LayerHandle::set_layer(
app,
|app| &mut self.flex_data_mut(app).clip_rect_layer,
None,
);
crate::object::RenderObjectBase::dispose(self, app);
}
fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
if !child.parent_data_is::<FlexParentData>(app) {
child.set_parent_data(app, FlexParentData::new());
}
}
fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
self.get_intrinsic_size(
app,
Axis::Horizontal,
height,
AnyRenderBox::get_min_intrinsic_width,
)
}
fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
self.get_intrinsic_size(
app,
Axis::Horizontal,
height,
AnyRenderBox::get_max_intrinsic_width,
)
}
fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
self.get_intrinsic_size(
app,
Axis::Vertical,
width,
AnyRenderBox::get_min_intrinsic_height,
)
}
fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
self.get_intrinsic_size(
app,
Axis::Vertical,
width,
AnyRenderBox::get_max_intrinsic_height,
)
}
fn compute_dry_layout(
self: RenderHandle<Self>,
app: &mut App,
constraints: BoxConstraints,
) -> Size {
if cfg!(debug_assertions)
&& let Some(error) = self.debug_check_constraints(app, constraints)
{
self.debug_cannot_compute_dry_layout(DryLayoutFailure::Error(&error));
return Size::ZERO;
}
let direction = self.direction(app);
self.compute_sizes(app, constraints, ChildLayoutHelper::dry_layout_child)
.axis_size
.to_size(direction)
}
fn hit_test_children(
self: RenderHandle<Self>,
app: &mut App,
result: &mut BoxHitTestResult<'_>,
position: Offset,
) -> bool {
self.default_hit_test_children(app, result, position)
}
}
fn get_flex(app: &App, child: AnyRenderBox) -> i32 {
child
.as_object()
.parent_data_of::<FlexParentData>(app)
.flex
.unwrap_or(0)
}
fn get_fit(app: &App, child: AnyRenderBox) -> FlexFit {
child
.as_object()
.parent_data_of::<FlexParentData>(app)
.fit
.unwrap_or(FlexFit::Tight)
}
pub struct RenderFlex {
render_object: RenderObjectData,
render_box: RenderBoxData,
container: ContainerRenderObjectData<AnyRenderBox>,
flex: RenderFlexData,
}
impl RenderFlex {
pub fn new(app: &mut App) -> RenderHandle<RenderFlex> {
RenderHandle::new_box(
app,
RenderFlex {
render_object: RenderObjectData::new(),
render_box: RenderBoxData::new(),
container: ContainerRenderObjectData::new(),
flex: RenderFlexData::new(),
},
)
}
pub fn direction(self: RenderHandle<Self>, app: &App) -> Axis {
RenderFlexMixin::direction(self, app)
}
pub fn set_direction(self: RenderHandle<Self>, app: &mut App, value: Axis) {
RenderFlexMixin::set_direction(self, app, value)
}
pub fn main_axis_alignment(self: RenderHandle<Self>, app: &App) -> MainAxisAlignment {
RenderFlexMixin::main_axis_alignment(self, app)
}
pub fn set_main_axis_alignment(
self: RenderHandle<Self>,
app: &mut App,
value: MainAxisAlignment,
) {
RenderFlexMixin::set_main_axis_alignment(self, app, value)
}
pub fn main_axis_size(self: RenderHandle<Self>, app: &App) -> MainAxisSize {
RenderFlexMixin::main_axis_size(self, app)
}
pub fn set_main_axis_size(self: RenderHandle<Self>, app: &mut App, value: MainAxisSize) {
RenderFlexMixin::set_main_axis_size(self, app, value)
}
pub fn cross_axis_alignment(self: RenderHandle<Self>, app: &App) -> CrossAxisAlignment {
RenderFlexMixin::cross_axis_alignment(self, app)
}
pub fn set_cross_axis_alignment(
self: RenderHandle<Self>,
app: &mut App,
value: CrossAxisAlignment,
) {
RenderFlexMixin::set_cross_axis_alignment(self, app, value)
}
pub fn text_direction(self: RenderHandle<Self>, app: &App) -> Option<TextDirection> {
RenderFlexMixin::text_direction(self, app)
}
pub fn set_text_direction(
self: RenderHandle<Self>,
app: &mut App,
value: Option<TextDirection>,
) {
RenderFlexMixin::set_text_direction(self, app, value)
}
pub fn vertical_direction(self: RenderHandle<Self>, app: &App) -> VerticalDirection {
RenderFlexMixin::vertical_direction(self, app)
}
pub fn set_vertical_direction(
self: RenderHandle<Self>,
app: &mut App,
value: VerticalDirection,
) {
RenderFlexMixin::set_vertical_direction(self, app, value)
}
pub fn text_baseline(self: RenderHandle<Self>, app: &App) -> Option<TextBaseline> {
RenderFlexMixin::text_baseline(self, app)
}
pub fn set_text_baseline(self: RenderHandle<Self>, app: &mut App, value: Option<TextBaseline>) {
RenderFlexMixin::set_text_baseline(self, app, value)
}
pub fn clip_behavior(self: RenderHandle<Self>, app: &App) -> Clip {
RenderFlexMixin::clip_behavior(self, app)
}
pub fn set_clip_behavior(self: RenderHandle<Self>, app: &mut App, value: Clip) {
RenderFlexMixin::set_clip_behavior(self, app, value)
}
pub fn spacing(self: RenderHandle<Self>, app: &App) -> f64 {
RenderFlexMixin::spacing(self, app)
}
pub fn set_spacing(self: RenderHandle<Self>, app: &mut App, value: f64) {
RenderFlexMixin::set_spacing(self, app, value)
}
}
impl ContainerRenderObjectMixin for RenderFlex {
type ChildType = AnyRenderBox;
type ParentDataType = FlexParentData;
fn container_data(
self: RenderHandle<Self>,
app: &App,
) -> &ContainerRenderObjectData<AnyRenderBox> {
&self.get(app).container
}
fn container_data_mut(
self: RenderHandle<Self>,
app: &mut App,
) -> &mut ContainerRenderObjectData<AnyRenderBox> {
&mut self.get_mut(app).container
}
}
impl RenderBoxContainerDefaultsMixin for RenderFlex {}
impl RenderFlexMixin for RenderFlex {
fn flex_data(self: RenderHandle<Self>, app: &App) -> &RenderFlexData {
&self.get(app).flex
}
fn flex_data_mut(self: RenderHandle<Self>, app: &mut App) -> &mut RenderFlexData {
&mut self.get_mut(app).flex
}
}
impl RenderObject for RenderFlex {
crate::render_object_accessors!();
fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
RenderFlexMixin::perform_layout(self, app)
}
fn visit_children(
self: RenderHandle<Self>,
app: &App,
visitor: &mut dyn FnMut(AnyRenderObject),
) {
ContainerRenderObjectMixin::visit_children(self, app, visitor)
}
fn did_attach(self: RenderHandle<Self>, app: &mut App, owner: Handle<PipelineOwner>) {
ContainerRenderObjectMixin::did_attach(self, app, owner)
}
fn did_detach(self: RenderHandle<Self>, app: &mut App) {
ContainerRenderObjectMixin::did_detach(self, app)
}
fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
ContainerRenderObjectMixin::redepth_children(self, app)
}
fn paint(
self: RenderHandle<Self>,
app: &mut App,
context: &mut PaintingContext,
offset: Offset,
) {
RenderFlexMixin::paint(self, app, context, offset)
}
fn dispose(self: RenderHandle<Self>, app: &mut App) {
RenderFlexMixin::dispose(self, app)
}
}
impl RenderBox for RenderFlex {
crate::render_box_accessors!();
fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
RenderFlexMixin::setup_parent_data(self, app, child)
}
fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
RenderFlexMixin::compute_min_intrinsic_width(self, app, height)
}
fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
RenderFlexMixin::compute_max_intrinsic_width(self, app, height)
}
fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
RenderFlexMixin::compute_min_intrinsic_height(self, app, width)
}
fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
RenderFlexMixin::compute_max_intrinsic_height(self, app, width)
}
fn compute_dry_layout(
self: RenderHandle<Self>,
app: &mut App,
constraints: BoxConstraints,
) -> Size {
RenderFlexMixin::compute_dry_layout(self, app, constraints)
}
fn hit_test_children(
self: RenderHandle<Self>,
app: &mut App,
result: &mut BoxHitTestResult<'_>,
position: Offset,
) -> bool {
RenderFlexMixin::hit_test_children(self, app, result, position)
}
}
#[cfg(test)]
mod tests {
use inset_foundation::AppCell;
use std::any::Any;
use inset_foundation::Handle;
use inset_gestures::{HitTestEntry, HitTestResult};
use super::*;
use crate::box_::BoxHitTestEntry;
use crate::layer::{ContainerLayer, ErasedLayer, OffsetLayer, PictureLayer};
use crate::pipeline_owner::PipelineOwner;
use crate::proxy_box::RenderRepaintBoundary;
use inset_embedder::valo::Op;
struct SizedTestBox {
render_object: RenderObjectData,
render_box: RenderBoxData,
preferred_size: Size,
}
impl RenderObject for SizedTestBox {
crate::render_object_accessors!();
fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
let preferred_size = self.get(app).preferred_size;
let size = self.constraints(app).constrain(preferred_size);
self.set_size(app, size);
}
}
impl RenderBox for SizedTestBox {
crate::render_box_accessors!();
fn hit_test_self(self: RenderHandle<Self>, _app: &App, _position: Offset) -> bool {
true
}
}
fn sized_box(app: &mut App, size: Size) -> AnyRenderBox {
RenderHandle::new_box(
app,
SizedTestBox {
render_object: RenderObjectData::new(),
render_box: RenderBoxData::new(),
preferred_size: size,
},
)
.as_box()
}
fn row(app: &mut App, children: Vec<AnyRenderBox>) -> RenderHandle<RenderFlex> {
let flex = RenderFlex::new(app);
flex.set_text_direction(app, Some(TextDirection::Ltr));
flex.add_all(app, Some(children));
flex
}
fn offset_of(app: &App, child: AnyRenderBox) -> Offset {
child.box_parent_data(app).offset
}
fn schedule_root_paint(app: &mut App, node: AnyRenderObject) {
let root = OffsetLayer::new(app, Offset::ZERO);
root.as_layer().attach(app, node.id());
node.schedule_initial_paint(app, root.as_container_layer());
}
fn first_frame(
app: &mut App,
root: AnyRenderBox,
constraints: BoxConstraints,
) -> Handle<PipelineOwner> {
let owner = PipelineOwner::new(app, None);
owner.set_root_node(app, Some(root.as_object()));
root.layout(app, constraints, false);
schedule_root_paint(app, root.as_object());
owner.flush_compositing_bits(app);
owner.flush_paint(app);
owner
}
fn picture_has_clip(app: &App, boundary: AnyRenderObject) -> bool {
let layer = boundary.debug_layer(app).expect("painted");
layer
.depth_first_iterate_children(app)
.into_iter()
.filter_map(|child| {
app.handle::<PictureLayer>(child.id())
.and_then(|picture| picture.picture(app).map(|p| p.ops().to_vec()))
})
.flatten()
.any(|op| matches!(op, Op::ClipPath { .. }))
}
fn hit_boxes(result: &HitTestResult) -> Vec<AnyRenderBox> {
result
.path()
.iter()
.filter_map(|entry: &HitTestEntry| {
let target: &dyn Any = entry.target();
target
.downcast_ref::<BoxHitTestEntry>()
.map(BoxHitTestEntry::target)
})
.collect()
}
#[test]
fn child_list_insert_move_and_remove() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let first = sized_box(&mut app, Size::new(10.0, 10.0));
let middle = sized_box(&mut app, Size::new(10.0, 10.0));
let last = sized_box(&mut app, Size::new(10.0, 10.0));
let flex = RenderFlex::new(&mut app);
flex.add(&mut app, first);
flex.insert(&mut app, last, Some(first));
flex.insert(&mut app, middle, Some(first));
assert_eq!(flex.child_count(&app), 3);
assert_eq!(flex.children_as_list(&app), vec![first, middle, last]);
assert_eq!(flex.first_child(&app), Some(first));
assert_eq!(flex.last_child(&app), Some(last));
assert_eq!(flex.child_after(&app, first), Some(middle));
assert_eq!(flex.child_before(&app, last), Some(middle));
let mut visited = Vec::new();
flex.as_object()
.visit_children(&app, &mut |child| visited.push(child));
assert_eq!(
visited,
vec![first.as_object(), middle.as_object(), last.as_object()]
);
flex.move_child(&mut app, first, Some(last));
assert_eq!(flex.children_as_list(&app), vec![middle, last, first]);
flex.move_child(&mut app, first, None);
assert_eq!(flex.children_as_list(&app), vec![first, middle, last]);
flex.remove(&mut app, middle);
assert_eq!(flex.children_as_list(&app), vec![first, last]);
assert_eq!(flex.child_count(&app), 2);
assert!(middle.as_object().parent(&app).is_none());
flex.remove_all(&mut app);
assert_eq!(flex.child_count(&app), 0);
assert!(flex.first_child(&app).is_none());
assert!(flex.last_child(&app).is_none());
assert!(first.as_object().parent(&app).is_none());
assert!(last.as_object().parent(&app).is_none());
}
struct IntrinsicTestBox {
render_object: RenderObjectData,
render_box: RenderBoxData,
intrinsic: Size,
}
impl RenderObject for IntrinsicTestBox {
crate::render_object_accessors!();
fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
let intrinsic = self.get(app).intrinsic;
let size = self.constraints(app).constrain(intrinsic);
self.set_size(app, size);
}
}
impl RenderBox for IntrinsicTestBox {
crate::render_box_accessors!();
fn compute_min_intrinsic_width(
self: RenderHandle<Self>,
app: &mut App,
_height: f64,
) -> f64 {
self.get(app).intrinsic.width()
}
fn compute_max_intrinsic_width(
self: RenderHandle<Self>,
app: &mut App,
_height: f64,
) -> f64 {
self.get(app).intrinsic.width()
}
fn compute_min_intrinsic_height(
self: RenderHandle<Self>,
app: &mut App,
_width: f64,
) -> f64 {
self.get(app).intrinsic.height()
}
fn compute_max_intrinsic_height(
self: RenderHandle<Self>,
app: &mut App,
_width: f64,
) -> f64 {
self.get(app).intrinsic.height()
}
fn compute_dry_layout(
self: RenderHandle<Self>,
app: &mut App,
constraints: BoxConstraints,
) -> Size {
constraints.constrain(self.get(app).intrinsic)
}
}
fn intrinsic_box(app: &mut App, intrinsic: Size) -> AnyRenderBox {
RenderHandle::new_box(
app,
IntrinsicTestBox {
render_object: RenderObjectData::new(),
render_box: RenderBoxData::new(),
intrinsic,
},
)
.as_box()
}
#[test]
fn row_intrinsics_add_the_inflexible_space_to_the_largest_flex_fraction() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let fixed = intrinsic_box(&mut app, Size::new(50.0, 20.0));
let flexible = intrinsic_box(&mut app, Size::new(30.0, 10.0));
let flex = row(&mut app, vec![fixed, flexible]);
flexible.parent_data_of_mut::<FlexParentData>(&mut app).flex = Some(1);
let row = flex.as_box();
assert_eq!(row.get_max_intrinsic_width(&mut app, f64::INFINITY), 80.0);
assert_eq!(row.get_min_intrinsic_width(&mut app, f64::INFINITY), 80.0);
assert_eq!(row.get_max_intrinsic_height(&mut app, 100.0), 20.0);
}
#[test]
fn row_intrinsics_include_the_spacing() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let first = intrinsic_box(&mut app, Size::new(50.0, 20.0));
let second = intrinsic_box(&mut app, Size::new(30.0, 10.0));
let flex = row(&mut app, vec![first, second]);
flex.set_spacing(&mut app, 10.0);
assert_eq!(
flex.as_box()
.get_max_intrinsic_width(&mut app, f64::INFINITY),
90.0
);
}
#[test]
fn row_dry_layout_follows_the_main_axis_size() {
for (main_axis_size, expected) in [
(MainAxisSize::Max, Size::new(200.0, 20.0)),
(MainAxisSize::Min, Size::new(80.0, 20.0)),
] {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let fixed = intrinsic_box(&mut app, Size::new(50.0, 20.0));
let flexible = intrinsic_box(&mut app, Size::new(30.0, 10.0));
let flex = row(&mut app, vec![fixed, flexible]);
flex.set_main_axis_size(&mut app, main_axis_size);
{
let parent_data = flexible.parent_data_of_mut::<FlexParentData>(&mut app);
parent_data.flex = Some(1);
parent_data.fit = Some(FlexFit::Loose);
}
let constraints = BoxConstraints::new().max_width(200.0).max_height(100.0);
let dry = flex.as_box().get_dry_layout(&mut app, constraints);
flex.layout(&mut app, constraints, false);
assert_eq!(dry, expected);
assert_eq!(dry, flex.size(&app), "the dry layout is the real layout");
}
}
#[test]
fn row_lays_out_fixed_and_flexible_children_with_spacing() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let fixed = sized_box(&mut app, Size::new(50.0, 30.0));
let flexible = sized_box(&mut app, Size::new(20.0, 10.0));
let flex = row(&mut app, vec![fixed, flexible]);
flexible.parent_data_of_mut::<FlexParentData>(&mut app).flex = Some(1);
flex.set_spacing(&mut app, 10.0);
flex.layout(
&mut app,
BoxConstraints::tight(Size::new(200.0, 100.0)),
false,
);
assert_eq!(flex.size(&app), Size::new(200.0, 100.0));
assert_eq!(fixed.size(&app), Size::new(50.0, 30.0));
assert_eq!(flexible.size(&app), Size::new(140.0, 10.0));
assert_eq!(offset_of(&app, fixed), Offset::new(0.0, 35.0));
assert_eq!(offset_of(&app, flexible), Offset::new(60.0, 45.0));
}
#[test]
fn main_axis_alignment_positions_children() {
let cases = [
(MainAxisAlignment::Start, 0.0, 20.0),
(MainAxisAlignment::End, 60.0, 80.0),
(MainAxisAlignment::Center, 30.0, 50.0),
(MainAxisAlignment::SpaceBetween, 0.0, 80.0),
(MainAxisAlignment::SpaceAround, 15.0, 65.0),
(MainAxisAlignment::SpaceEvenly, 20.0, 60.0),
];
for (alignment, first_x, second_x) in cases {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let first = sized_box(&mut app, Size::new(20.0, 20.0));
let second = sized_box(&mut app, Size::new(20.0, 20.0));
let flex = row(&mut app, vec![first, second]);
flex.set_main_axis_alignment(&mut app, alignment);
flex.layout(
&mut app,
BoxConstraints::tight(Size::new(100.0, 20.0)),
false,
);
assert_eq!(
(offset_of(&app, first).dx(), offset_of(&app, second).dx()),
(first_x, second_x),
"{alignment:?}"
);
}
}
#[test]
fn cross_axis_alignment_positions_children() {
let cases = [
(CrossAxisAlignment::Start, 0.0, 20.0),
(CrossAxisAlignment::Center, 40.0, 20.0),
(CrossAxisAlignment::End, 80.0, 20.0),
(CrossAxisAlignment::Stretch, 0.0, 100.0),
(CrossAxisAlignment::Baseline, 0.0, 20.0),
];
for (alignment, y, height) in cases {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let child = sized_box(&mut app, Size::new(20.0, 20.0));
let flex = row(&mut app, vec![child]);
flex.set_cross_axis_alignment(&mut app, alignment);
if alignment == CrossAxisAlignment::Baseline {
flex.set_text_baseline(&mut app, Some(TextBaseline::Alphabetic));
}
flex.layout(
&mut app,
BoxConstraints::tight(Size::new(100.0, 100.0)),
false,
);
assert_eq!(offset_of(&app, child).dy(), y, "{alignment:?}");
assert_eq!(child.size(&app).height(), height, "{alignment:?}");
}
}
#[test]
fn overflow_is_clipped() {
for (clip_behavior, clips) in [(Clip::None, false), (Clip::HardEdge, true)] {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let first = sized_box(&mut app, Size::new(80.0, 20.0));
let second = sized_box(&mut app, Size::new(80.0, 20.0));
let flex = row(&mut app, vec![first, second]);
flex.set_clip_behavior(&mut app, clip_behavior);
let root = RenderRepaintBoundary::new(&mut app, Some(flex.as_box()));
let owner = first_frame(
&mut app,
root.as_box(),
BoxConstraints::tight(Size::new(100.0, 20.0)),
);
assert_eq!(flex.size(&app), Size::new(100.0, 20.0));
let clipped = picture_has_clip(&app, root.as_object());
assert_eq!(clipped, clips, "{clip_behavior:?}");
let _ = owner;
}
}
#[test]
fn hit_test_reaches_the_child_under_the_position() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let first = sized_box(&mut app, Size::new(50.0, 50.0));
let second = sized_box(&mut app, Size::new(50.0, 50.0));
let flex = row(&mut app, vec![first, second]);
flex.layout(
&mut app,
BoxConstraints::tight(Size::new(100.0, 50.0)),
false,
);
let mut result = HitTestResult::new();
let hit = flex.hit_test(
&mut app,
&mut BoxHitTestResult::wrap(&mut result),
Offset::new(75.0, 25.0),
);
assert!(hit);
assert_eq!(hit_boxes(&result), vec![second, flex.as_box()]);
}
}