use teksilo_canvas::{Size, SizeProposal};
use teksilo_core::widget::{LayoutContext, LayoutResponse};
use teksilo_core::widget_id::WidgetId;
const EPS: f32 = 1.0e-3;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum Axis {
Horizontal,
Vertical,
}
impl Axis {
#[inline]
fn main_of(self, s: Size) -> f32 {
match self {
Axis::Horizontal => s.width,
Axis::Vertical => s.height,
}
}
#[inline]
fn cross_of(self, s: Size) -> f32 {
match self {
Axis::Horizontal => s.height,
Axis::Vertical => s.width,
}
}
#[inline]
fn size(self, main: f32, cross: f32) -> Size {
match self {
Axis::Horizontal => Size::new(main, cross),
Axis::Vertical => Size::new(cross, main),
}
}
#[inline]
fn stack_axis(self) -> teksilo_core::widget::StackAxis {
match self {
Axis::Horizontal => teksilo_core::widget::StackAxis::Horizontal,
Axis::Vertical => teksilo_core::widget::StackAxis::Vertical,
}
}
#[inline]
fn proposal(self, main: Option<f32>, cross: Option<f32>) -> SizeProposal {
match self {
Axis::Horizontal => SizeProposal {
width: main,
height: cross,
},
Axis::Vertical => SizeProposal {
width: cross,
height: main,
},
}
}
}
pub(crate) struct ChildSizes {
pub main: Vec<f32>,
pub cross: Vec<f32>,
}
pub(crate) struct Negotiated {
pub size: Size,
pub flex: f32,
pub shrink: f32,
pub min: Size,
pub children: ChildSizes,
}
pub(crate) fn negotiate(
ids: &[WidgetId],
ctx: &LayoutContext,
main_extent: Option<f32>,
cross_extent: Option<f32>,
spacing: f32,
axis: Axis,
) -> Negotiated {
let n = ids.len();
let cctx = ctx.with_stack_main_axis(axis.stack_axis());
let ctx = &cctx;
let main_proposal = axis.proposal(None, cross_extent);
let mut active = vec![false; n];
let mut wanted_main = vec![0.0_f32; n];
let mut wanted_cross = vec![0.0_f32; n];
let mut flex = vec![0.0_f32; n];
let mut shrink = vec![0.0_f32; n];
let mut min_main = vec![0.0_f32; n];
let mut min_cross = vec![0.0_f32; n];
let mut active_count = 0usize;
let mut sum_wanted = 0.0_f32;
let mut total_flex = 0.0_f32;
for (i, &id) in ids.iter().enumerate() {
let Some(r) = ctx.child_layout_response(id, main_proposal) else {
continue; };
active[i] = true;
active_count += 1;
wanted_main[i] = axis.main_of(r.size);
wanted_cross[i] = axis.cross_of(r.size);
flex[i] = r.flex;
shrink[i] = r.shrink;
min_main[i] = axis.main_of(r.min);
min_cross[i] = axis.cross_of(r.min);
sum_wanted += wanted_main[i];
total_flex += r.flex;
}
let total_spacing = spacing * (active_count.saturating_sub(1)) as f32;
let mut cur_main = wanted_main.clone();
if let Some(extent) = main_extent {
let raw = extent - sum_wanted - total_spacing;
if raw > EPS && total_flex > 0.0 {
for i in 0..n {
if active[i] && flex[i] > 0.0 {
cur_main[i] = wanted_main[i] + (flex[i] / total_flex) * raw;
}
}
} else if raw < -EPS {
distribute_deficit(-raw, &active, &mut cur_main, &min_main, &shrink);
}
}
let mut cross = wanted_cross.clone();
for i in 0..n {
if active[i] && (cur_main[i] - wanted_main[i]).abs() > EPS {
let p = axis.proposal(Some(cur_main[i]), cross_extent);
if let Some(r) = ctx.child_layout_response(ids[i], p) {
cross[i] = axis.cross_of(r.size);
}
}
}
let mut content_main = total_spacing;
let mut self_cross = 0.0_f32;
let mut agg_min_main = total_spacing;
let mut agg_min_cross = 0.0_f32;
let mut any_shrink = false;
for i in 0..n {
if !active[i] {
continue;
}
content_main += cur_main[i];
self_cross = self_cross.max(cross[i]);
agg_min_main += min_main[i];
agg_min_cross = agg_min_cross.max(min_cross[i]);
if shrink[i] > 0.0 {
any_shrink = true;
}
}
let self_main = content_main.max(0.0);
let self_cross = cross_extent
.map(|e| e.max(self_cross))
.unwrap_or(self_cross);
let size = axis.size(self_main, self_cross);
let min = axis.size(agg_min_main, agg_min_cross);
let distribute_main = main_extent.is_none();
let agg_flex = if distribute_main && total_flex > 0.0 {
total_flex
} else {
0.0
};
let agg_shrink = if distribute_main && any_shrink {
1.0
} else {
0.0
};
Negotiated {
size,
flex: agg_flex,
shrink: agg_shrink,
min,
children: ChildSizes {
main: cur_main,
cross,
},
}
}
pub(crate) fn response(n: &Negotiated) -> LayoutResponse {
LayoutResponse::flexible(n.size, n.flex)
.with_shrink(n.shrink)
.with_min(n.min)
}
fn distribute_deficit(
mut deficit: f32,
active: &[bool],
cur_main: &mut [f32],
min_main: &[f32],
shrink: &[f32],
) {
let n = cur_main.len();
let mut frozen = vec![false; n];
loop {
let mut total_shrink = 0.0_f32;
for i in 0..n {
if active[i] && !frozen[i] && shrink[i] > 0.0 && cur_main[i] - min_main[i] > EPS {
total_shrink += shrink[i];
}
}
if total_shrink < EPS || deficit < 0.01 {
break; }
let mut clamped_any = false;
let mut absorbed = 0.0_f32;
for i in 0..n {
if !(active[i] && !frozen[i] && shrink[i] > 0.0) {
continue;
}
let room = cur_main[i] - min_main[i];
if room <= EPS {
frozen[i] = true;
continue;
}
let want = deficit * (shrink[i] / total_shrink);
let take = want.min(room);
cur_main[i] -= take;
absorbed += take;
if room - take <= EPS {
frozen[i] = true;
clamped_any = true;
}
}
deficit -= absorbed;
if absorbed < EPS && !clamped_any {
break;
}
}
}