use super::*;
use crate::finite::FiniteOr;
use crate::layout::{Axis, Extent};
use crate::tree::LayoutCtx;
pub(super) struct BoxLengths {
pub padding: Padding,
pub width: Length,
pub height: Length,
pub overflow: Overflow,
}
pub(super) struct ChildLayout {
pub constraints: Constraints,
pub origin: (f32, f32),
pub seen_through: Size,
}
impl Container {
pub(super) fn is_relayout_boundary_for(&self, constraints: Constraints) -> bool {
let has_active_layout_anim = self.anims.as_deref().is_some_and(|declared| {
declared
.slots()
.any(|slot| slot.moves_the_box() && slot.is_animating())
});
if has_active_layout_anim {
return false;
}
let has_fixed_width = self
.width
.get_or_untracked(Length::default())
.exact_size()
.is_some();
let has_fixed_height = self
.height
.get_or_untracked(Length::default())
.exact_size()
.is_some();
let tight_width = constraints.min_width == constraints.max_width;
let tight_height = constraints.min_height == constraints.max_height;
(has_fixed_width || tight_width) && (has_fixed_height || tight_height)
}
pub(super) fn read_box_lengths(
&self,
ctx: &mut LayoutCtx,
id: WidgetId,
constraints: Constraints,
) -> BoxLengths {
let (padding, mut width, mut height, overflow) = (
self.animated_padding(ctx, id),
self.width.get_finite_or(Length::default(), id, "width"),
self.height.get_finite_or(Length::default(), id, "height"),
self.overflow.get_or(Overflow::Visible),
);
width.resolve_fraction(constraints.max_width);
height.resolve_fraction(constraints.max_height);
self.overflow_resolved.set(overflow);
BoxLengths {
padding,
width,
height,
overflow,
}
}
fn animated_extent(
&self,
ctx: &mut LayoutCtx,
anim: Option<&AnimationState<f32>>,
length: &Length,
available: f32,
) -> f32 {
match length.exact_size() {
Some(exact) => {
let exact = match anim {
Some(anim) if !anim.is_initial() => anim.displayed_in(ctx),
_ => exact,
};
exact_within(length, exact, available)
}
None => length.clamp_max(available),
}
}
pub(super) fn child_layout(
&self,
ctx: &mut LayoutCtx,
lengths: &BoxLengths,
constraints: Constraints,
) -> ChildLayout {
let padding = lengths.padding;
let layout_width = self.animated_extent(
ctx,
self.anims.as_ref().and_then(|a| a.width()),
&lengths.width,
constraints.max_width,
);
let layout_height = self.animated_extent(
ctx,
self.anims.as_ref().and_then(|a| a.height()),
&lengths.height,
constraints.max_height,
);
let (gutter_width, gutter_height) = match self.scroll_data {
Some(ref sd)
if sd.metrics.reserve_gutter
&& sd.scrollbar_visibility != ScrollbarVisibility::Hidden =>
{
let gutter = sd.metrics.width + sd.metrics.margin * 2.0;
(
if self.scroll_axis.allows_vertical() {
gutter
} else {
0.0
},
if self.scroll_axis.allows_horizontal() {
gutter
} else {
0.0
},
)
}
_ => (0.0, 0.0),
};
let max_width = (layout_width - padding.horizontal_total() - gutter_width).max(0.0);
let max_height = (layout_height - padding.vertical_total() - gutter_height).max(0.0);
let min_width = if lengths.width.settled_without_content(max_width) {
max_width
} else {
let effective = lengths
.width
.min()
.unwrap_or(0.0)
.max(constraints.min_width);
(effective - padding.horizontal_total())
.max(0.0)
.min(max_width)
};
let min_height = if lengths.height.settled_without_content(max_height) {
max_height
} else {
let effective = lengths
.height
.min()
.unwrap_or(0.0)
.max(constraints.min_height);
(effective - padding.vertical_total())
.max(0.0)
.min(max_height)
};
let viewport = Size::new(max_width, max_height);
let seen_through = Size::new(
(layout_width - gutter_width).max(0.0),
(layout_height - gutter_height).max(0.0),
);
let fills = self.scroll_data.as_ref().is_some_and(|sd| sd.fill_viewport);
let floor_of = |visible: f32| {
if fills && visible.is_finite() {
visible
} else {
0.0
}
};
let floor = Size::new(floor_of(viewport.width), floor_of(viewport.height));
let child = match self.scroll_axis {
ScrollAxis::Vertical => Constraints {
min_width: 0.0,
min_height: floor.height,
max_width,
max_height: f32::INFINITY,
},
ScrollAxis::Horizontal => Constraints {
min_width: floor.width,
min_height: 0.0,
max_width: f32::INFINITY,
max_height,
},
ScrollAxis::Both => Constraints {
min_width: floor.width,
min_height: floor.height,
max_width: f32::INFINITY,
max_height: f32::INFINITY,
},
ScrollAxis::None => Constraints {
min_width,
min_height,
max_width,
max_height,
},
};
ChildLayout {
constraints: child,
origin: (padding.left, padding.top),
seen_through,
}
}
pub(super) fn resolve_size(
&self,
ctx: &mut LayoutCtx,
lengths: &BoxLengths,
constraints: Constraints,
content: Size,
) -> Size {
Size::new(
self.resolve_axis(
ctx,
Axis::Horizontal,
&lengths.width,
lengths.overflow,
content.width + lengths.padding.horizontal_total(),
constraints.min_width,
constraints.max_width,
),
self.resolve_axis(
ctx,
Axis::Vertical,
&lengths.height,
lengths.overflow,
content.height + lengths.padding.vertical_total(),
constraints.min_height,
constraints.max_height,
),
)
}
pub(super) fn least_size(&self, lengths: &BoxLengths, content: Size, size: Size) -> Size {
let least = |axis: Axis, length: &Length, padding: f32, content: f32, size: f32| {
if self.size_anim(axis).is_some_and(|a| a.is_animating()) {
return size;
}
if let Some(exact) = length.exact_size() {
return length.clamp(exact);
}
let declared = length.min().unwrap_or(0.0);
if self.content_may_overflow(axis, lengths.overflow) {
declared
} else {
(content + padding).max(declared)
}
};
Size::new(
least(
Axis::Horizontal,
&lengths.width,
lengths.padding.horizontal_total(),
content.width,
size.width,
),
least(
Axis::Vertical,
&lengths.height,
lengths.padding.vertical_total(),
content.height,
size.height,
),
)
}
fn size_anim(&self, axis: Axis) -> Option<&AnimationState<f32>> {
self.anims.as_ref().and_then(|a| match axis {
Axis::Horizontal => a.width(),
Axis::Vertical => a.height(),
})
}
fn content_may_overflow(&self, axis: Axis, overflow: Overflow) -> bool {
overflow == Overflow::Hidden
|| match axis {
Axis::Horizontal => self.scroll_axis.allows_horizontal(),
Axis::Vertical => self.scroll_axis.allows_vertical(),
}
}
#[allow(clippy::too_many_arguments)]
fn resolve_axis(
&self,
ctx: &mut LayoutCtx,
axis: Axis,
length: &Length,
overflow: Overflow,
content: f32,
parent_min: f32,
parent_max: f32,
) -> f32 {
let anim = self.size_anim(axis);
let animating = anim.is_some_and(|a| a.is_animating());
let has_exact = length.exact_size().is_some();
let allow_shrink = self.content_may_overflow(axis, overflow) || animating || has_exact;
let mut size = if let Some(anim) = anim {
let animated = anim.displayed_in(ctx);
if allow_shrink {
animated
} else {
content.max(animated)
}
} else {
match length.extent() {
Extent::Exact(exact) => exact,
Extent::Fill if parent_max.is_finite() => parent_max,
Extent::Fill => content,
Extent::Auto | Extent::Fraction(_) => content,
}
};
if has_exact {
return exact_within(length, size, parent_max);
}
size = length.clamp(size);
if !allow_shrink && anim.is_none() {
size = size.max(content);
}
size.max(parent_min).min(parent_max)
}
}
fn exact_within(length: &Length, size: f32, parent_max: f32) -> f32 {
length.clamp(size).min(parent_max)
}