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 viewport: 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 {
if let Some(anim) = anim
&& !anim.is_initial()
&& length.exact_size().is_some()
{
return anim.displayed_in(ctx);
}
length
.exact_size()
.unwrap_or_else(|| 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 mut max_width = (layout_width - padding.horizontal_total()).max(0.0);
let mut max_height = (layout_height - padding.vertical_total()).max(0.0);
if let Some(ref sd) = self.scroll_data
&& 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() {
max_width = (max_width - gutter).max(0.0);
}
if self.scroll_axis.allows_horizontal() {
max_height = (max_height - gutter).max(0.0);
}
}
let min_width = if lengths.width.settled_without_content() {
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
} 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 child = match self.scroll_axis {
ScrollAxis::Vertical => Constraints {
min_width: 0.0,
min_height: 0.0,
max_width,
max_height: f32::INFINITY,
},
ScrollAxis::Horizontal => Constraints {
min_width: 0.0,
min_height: 0.0,
max_width: f32::INFINITY,
max_height,
},
ScrollAxis::Both => Constraints {
min_width: 0.0,
min_height: 0.0,
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),
viewport,
}
}
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,
),
)
}
#[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.anims.as_ref().and_then(|a| match axis {
Axis::Horizontal => a.width(),
Axis::Vertical => a.height(),
});
let animating = anim.is_some_and(|a| a.is_animating());
let has_exact = length.exact_size().is_some();
let allow_shrink = overflow == Overflow::Hidden
|| animating
|| has_exact
|| match axis {
Axis::Horizontal => self.scroll_axis.allows_horizontal(),
Axis::Vertical => self.scroll_axis.allows_vertical(),
};
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 => parent_max,
Extent::Auto | Extent::Fraction(_) => content,
}
};
size = length.clamp(size);
if !allow_shrink && anim.is_none() && !has_exact {
size = size.max(content);
}
if has_exact {
size.min(parent_max)
} else {
size.max(parent_min).min(parent_max)
}
}
}