use rdom_core::{Dom, NodeId};
use crate::ext::TuiExt;
use crate::layout::{AspectRatio, Direction, Display, MarginValue, MinSize, Size, clamp_size};
use crate::render::layout_pass::intrinsic::intrinsic_size;
use crate::style::ComputedStyle;
pub(super) struct ResolvedMain {
pub(super) size: u16,
pub(super) was_auto: bool,
}
pub(super) struct CrossPlacement {
pub(super) size: u16,
pub(super) offset: i32,
}
pub(super) fn place_cross(
dom: &Dom<TuiExt>,
child_id: NodeId,
child_computed: &ComputedStyle,
container_width: u16,
cross_budget: u16,
direction: Direction,
main: ResolvedMain,
) -> CrossPlacement {
let cb_width = container_width;
let (cross_start_m, cross_end_m) = match direction {
Direction::Row => (&child_computed.margin.top, &child_computed.margin.bottom),
Direction::Column => (&child_computed.margin.left, &child_computed.margin.right),
};
let cross_cells = |m: &MarginValue| -> i32 {
if m.is_auto() {
0
} else {
i32::from(m.resolve(cb_width))
}
};
let cross_start_cells = cross_cells(cross_start_m);
let cross_end_cells = cross_cells(cross_end_m);
let cross_avail = (i32::from(cross_budget) - cross_start_cells - cross_end_cells)
.clamp(0, i32::from(u16::MAX)) as u16;
let stretch = !(cross_start_m.is_auto() || cross_end_m.is_auto());
let cross_size = resolve_cross_size(
dom,
child_id,
child_computed,
cross_avail,
container_width,
direction,
MainAxisFacts {
size: main.size,
was_auto: main.was_auto,
stretch,
},
);
let cross_free = i32::from(cross_avail.saturating_sub(cross_size));
let cross_offset: i32 = match (cross_start_m.is_auto(), cross_end_m.is_auto()) {
(true, true) => cross_start_cells + cross_free / 2,
(true, false) => cross_start_cells + cross_free,
_ => cross_start_cells,
};
CrossPlacement {
size: cross_size,
offset: cross_offset,
}
}
fn aspect_cross_from_main(main: u16, ratio: AspectRatio, direction: Direction) -> u16 {
let r = ratio.as_f32();
let cross_f = match direction {
Direction::Row => (main as f32) / r,
Direction::Column => (main as f32) * r,
};
if cross_f.is_finite() {
cross_f.max(0.0).round_ties_even() as u16
} else {
0
}
}
struct MainAxisFacts {
size: u16,
was_auto: bool,
stretch: bool,
}
fn resolve_cross_size(
dom: &Dom<TuiExt>,
child_id: NodeId,
computed: &ComputedStyle,
container_cross: u16,
container_width: u16,
direction: Direction,
main: MainAxisFacts,
) -> u16 {
let MainAxisFacts {
size: main_size,
was_auto: main_was_auto,
stretch,
} = main;
let (cross_size, min_raw, max) = match direction {
Direction::Row => (&computed.height, computed.min_height, computed.max_height),
Direction::Column => (&computed.width, computed.min_width, computed.max_width),
};
let cross_dir = match direction {
Direction::Row => Direction::Column,
Direction::Column => Direction::Row,
};
let natural = match cross_size {
Size::Fixed(n) => *n,
Size::Flex(_) => container_cross,
Size::Percent(p) => {
Size::percent_of(container_cross as i32, *p).clamp(0, u16::MAX as i32) as u16
}
Size::Calc(expr) => {
let v = expr.resolve(&rdom_style::calc::ResolveCtx::new(container_cross as i32));
v.max(0).min(u16::MAX as i32) as u16
}
Size::Auto => {
if let Some(ratio) = computed.aspect_ratio
&& !main_was_auto
&& main_size > 0
{
aspect_cross_from_main(main_size, ratio, direction)
} else if computed.display == Display::InlineBlock {
intrinsic_size(dom, child_id, cross_dir, container_cross, container_width)
} else if stretch {
container_cross
} else {
intrinsic_size(dom, child_id, cross_dir, container_cross, container_width)
}
}
};
let min = match min_raw {
None => None,
Some(MinSize::Cells(n)) => Some(n),
Some(MinSize::Auto) => Some(intrinsic_size(
dom,
child_id,
cross_dir,
container_cross,
container_width,
)),
};
clamp_size(natural, min, max)
}