use crate::core::element::{Element, ElementKind};
use crate::layout::axis::{Axis, requested_main_axis};
use crate::layout::measure::min_size_constrained;
use crate::style::{Length, Padding};
pub(crate) fn measure_center_child(
el: &Element,
max_w: Option<u16>,
max_h: Option<u16>,
) -> (u16, u16) {
match &el.kind {
ElementKind::VStack(vs) => measure_natural_stack(
vs.props.gap,
vs.props.border,
vs.props.padding,
&vs.children,
Axis::Vertical,
max_h,
max_w,
),
ElementKind::HStack(hs) => measure_natural_stack(
hs.props.gap,
hs.props.border,
hs.props.padding,
&hs.children,
Axis::Horizontal,
max_w,
max_h,
),
_ => min_size_constrained(el, max_w, max_h),
}
}
fn natural_child_main_size(child: &Element, axis: Axis, available_cross: Option<u16>) -> u16 {
let len = requested_main_axis(child, axis, None);
match len {
Length::Px(px) => {
let constraints = child.layout_constraints();
let min = match axis {
Axis::Vertical => constraints.min_h.resolve_as_min(px),
Axis::Horizontal => constraints.min_w.resolve_as_min(px),
};
px.max(min)
}
_ => {
let (cw, ch) = measure_center_child(child, available_cross, None);
match axis {
Axis::Vertical => ch,
Axis::Horizontal => cw,
}
}
}
}
fn measure_natural_stack(
gap: u16,
border: bool,
padding: Padding,
children: &[Element],
axis: Axis,
available_main: Option<u16>,
available_cross: Option<u16>,
) -> (u16, u16) {
let chrome_main = if border { 2u16 } else { 0 }.saturating_add(match axis {
Axis::Vertical => padding.vertical(),
Axis::Horizontal => padding.horizontal(),
});
let chrome_cross = if border { 2u16 } else { 0 }.saturating_add(match axis {
Axis::Vertical => padding.horizontal(),
Axis::Horizontal => padding.vertical(),
});
if children.is_empty() {
return match axis {
Axis::Vertical => (chrome_cross, chrome_main),
Axis::Horizontal => (chrome_main, chrome_cross),
};
}
let inner_cross = available_cross.map(|c| c.saturating_sub(chrome_cross));
let mut main = 0u16;
let mut cross = 0u16;
for child in children {
let child_main = natural_child_main_size(child, axis, inner_cross);
let (cw, ch) = measure_center_child(
child,
match axis {
Axis::Vertical => inner_cross,
Axis::Horizontal => available_main,
},
match axis {
Axis::Vertical => None,
Axis::Horizontal => inner_cross,
},
);
let child_cross = match axis {
Axis::Vertical => cw,
Axis::Horizontal => ch,
};
main = main.saturating_add(child_main);
cross = cross.max(child_cross);
}
let gaps = gap.saturating_mul(children.len().saturating_sub(1) as u16);
main = main.saturating_add(gaps).saturating_add(chrome_main);
cross = cross.saturating_add(chrome_cross);
match axis {
Axis::Vertical => (cross, main),
Axis::Horizontal => (main, cross),
}
}
pub(crate) fn measure_center_pin(
cp: &super::CenterPin,
max_w: Option<u16>,
max_h: Option<u16>,
) -> (u16, u16) {
let (center_w, center_h) = cp
.center
.as_deref()
.map(|c| measure_center_child(c, max_w, max_h))
.unwrap_or((0, 0));
let (top_w, top_h) = cp
.top
.as_deref()
.map(|c| min_size_constrained(c, max_w, max_h))
.unwrap_or((0, 0));
let (bottom_w, bottom_h) = cp
.bottom
.as_deref()
.map(|c| min_size_constrained(c, max_w, max_h))
.unwrap_or((0, 0));
let w = center_w.max(top_w).max(bottom_w);
let h = center_h.saturating_add(top_h).saturating_add(bottom_h);
(w, h)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::style::Length;
use crate::widgets::{CenterPin, Frame, HStack, Spacer, Text, VStack};
#[test]
fn spacer_px_height_contributes_to_center_h() {
let center_el: crate::core::element::Element = VStack::new()
.child(Text::new("logo")) .child(Spacer::new().height(Length::Px(2))) .child(Frame::new().child(Text::new("dialog"))) .into();
let (_, center_h) = measure_center_child(¢er_el, Some(80), None);
assert_eq!(
center_h, 6,
"Spacer{{Px(2)}} must contribute 2 rows; got center_h={}",
center_h
);
}
#[test]
fn flex_frame_inside_flex_vstack_measures_full_content() {
let center_el: crate::core::element::Element = VStack::new()
.child(Frame::new().child(Text::new("prompt")))
.into();
let (_, center_h) = measure_center_child(¢er_el, Some(80), None);
assert_eq!(
center_h, 3,
"Frame{{Flex}} inside VStack{{Flex}} must measure at full content height; got {}",
center_h
);
}
#[test]
fn center_pin_flex_vstack_center_has_nonzero_height() {
let center_el: crate::core::element::Element = VStack::new()
.child(Text::new("logo placeholder"))
.child(Spacer::new().height(Length::Px(2)))
.child(
Frame::new()
.height(Length::Auto)
.child(Text::new("content")),
)
.into();
let (_, center_h) = measure_center_child(¢er_el, Some(80), Some(48));
assert!(
center_h > 0,
"center_h must be non-zero so the center zone renders; got {}",
center_h
);
}
#[test]
fn measure_center_pin_with_flex_center_is_nonzero() {
let cp = CenterPin::new()
.center(
VStack::new()
.child(Text::new("logo"))
.child(Spacer::new().height(Length::Px(2)))
.child(Frame::new().height(Length::Auto).child(Text::new("dialog"))),
)
.bottom(HStack::new().child(Text::new("shortcuts")));
let (_, min_h) = measure_center_pin(&cp, None, None);
assert!(min_h > 0, "CenterPin min_h must be non-zero; got {}", min_h);
}
#[test]
fn all_flex_center_children_still_measure_content() {
let center_el: crate::core::element::Element = VStack::new()
.child(VStack::new().child(Text::new("logo"))) .child(Frame::new()) .into();
let (_, center_h) = measure_center_child(¢er_el, Some(80), Some(48));
assert!(
center_h > 0,
"measure_center_child must return non-zero even for all-Flex children; got {}",
center_h
);
}
}