use super::super::ElementHostWidget;
use crate::declarative::frame::layout_style_for_node;
use crate::declarative::layout_helpers::clamp_to_constraints;
use crate::declarative::prelude::*;
use crate::layout_constraints::AvailableSpace as RuntimeAvailableSpace;
use crate::widget::MeasureCx;
impl ElementHostWidget {
pub(super) fn layout_flex_impl<H: UiHost>(
&mut self,
cx: &mut LayoutCx<'_, H>,
window: AppWindowId,
props: FlexProps,
) -> Size {
self.layout_flex_impl_engine(cx, window, props)
}
fn layout_flex_impl_engine<H: UiHost>(
&mut self,
cx: &mut LayoutCx<'_, H>,
window: AppWindowId,
props: FlexProps,
) -> Size {
if cx.pass_kind == crate::layout_pass::LayoutPassKind::Probe {
let mut constraints = cx.probe_constraints_for_size(cx.available);
if cx.available.width.0 <= 0.0 {
constraints.available.width = RuntimeAvailableSpace::MaxContent;
}
if cx.available.height.0 <= 0.0 {
constraints.available.height = RuntimeAvailableSpace::MaxContent;
}
let mut measure_cx = MeasureCx {
app: cx.app,
tree: cx.tree,
node: cx.node,
window: cx.window,
focus: cx.focus,
children: cx.children,
constraints,
scale_factor: cx.scale_factor,
services: cx.services,
observe_model: cx.observe_model,
observe_global: cx.observe_global,
};
return self.measure_impl(&mut measure_cx);
}
if cx.children.is_empty() {
return clamp_to_constraints(cx.available, props.layout, cx.available);
}
let needs_engine_solve = cx.children.iter().copied().any(|child| {
cx.tree
.layout_engine_child_local_rect(cx.node, child)
.is_none()
});
if needs_engine_solve {
let missing_child = cx.children.iter().copied().find(|&child| {
cx.tree
.layout_engine_child_local_rect(cx.node, child)
.is_none()
});
cx.tree.record_layout_engine_widget_fallback_solve(
cx.app,
window,
cx.node,
"flex",
missing_child,
);
cx.tree.solve_barrier_flow_root(
cx.app,
cx.services,
cx.node,
cx.bounds,
cx.scale_factor,
);
}
let padding_basis = cx.available.width;
let pad_left = super::spacing_px_for_basis(props.padding.left, padding_basis);
let pad_right = super::spacing_px_for_basis(props.padding.right, padding_basis);
let pad_top = super::spacing_px_for_basis(props.padding.top, padding_basis);
let pad_bottom = super::spacing_px_for_basis(props.padding.bottom, padding_basis);
let pad_w = pad_left + pad_right;
let pad_h = pad_top + pad_bottom;
let inner_size = Size::new(
Px((cx.available.width.0 - pad_w).max(0.0)),
Px((cx.available.height.0 - pad_h).max(0.0)),
);
let gap_x = super::spacing_px_for_basis(props.gap, inner_size.width);
let auto_margin_inner_size = inner_size;
let mut ml_auto_tail_group_start: Option<usize> = None;
let mut ml_auto_tail_shift_x = 0.0f32;
if props.direction == fret_core::Axis::Horizontal && cx.children.len() > 1 {
for (idx, &child) in cx.children.iter().enumerate() {
let child_style = layout_style_for_node(cx.app, window, child);
if matches!(child_style.margin.left, crate::element::MarginEdge::Auto) {
if idx + 1 < cx.children.len() {
ml_auto_tail_group_start = Some(idx);
}
break;
}
}
}
if let Some(start) = ml_auto_tail_group_start {
let mut tail_right = 0.0f32;
for &child in &cx.children[start..] {
let Some(layout) = cx.tree.layout_engine_child_local_rect(cx.node, child) else {
continue;
};
let child_style = layout_style_for_node(cx.app, window, child);
let right_margin = match child_style.margin.right {
crate::element::MarginEdge::Px(px) => px.0,
crate::element::MarginEdge::Fill | crate::element::MarginEdge::Fraction(_) => {
0.0
}
crate::element::MarginEdge::Auto => 0.0,
};
let x = layout.origin.x.0 - pad_left;
tail_right = tail_right.max(x + layout.size.width.0 + right_margin);
}
ml_auto_tail_shift_x = (auto_margin_inner_size.width.0 - tail_right).max(0.0);
}
for (child_index, &child) in cx.children.iter().enumerate() {
let Some(layout) = cx.tree.layout_engine_child_local_rect(cx.node, child) else {
continue;
};
let child_style = layout_style_for_node(cx.app, window, child);
let single_child = cx.children.len() == 1;
let mut x = layout.origin.x.0 - pad_left;
let mut y = layout.origin.y.0 - pad_top;
let margin_left_auto =
matches!(child_style.margin.left, crate::element::MarginEdge::Auto);
let margin_right_auto =
matches!(child_style.margin.right, crate::element::MarginEdge::Auto);
let margin_top_auto =
matches!(child_style.margin.top, crate::element::MarginEdge::Auto);
let margin_bottom_auto =
matches!(child_style.margin.bottom, crate::element::MarginEdge::Auto);
let margin_px = |edge: crate::element::MarginEdge| match edge {
crate::element::MarginEdge::Px(px) => px.0,
crate::element::MarginEdge::Fill | crate::element::MarginEdge::Fraction(_) => 0.0,
crate::element::MarginEdge::Auto => 0.0,
};
match props.direction {
fret_core::Axis::Horizontal => {
if ml_auto_tail_group_start == Some(child_index) {
if let Some(&next_child) = cx.children.get(child_index + 1)
&& let Some(next_layout) =
cx.tree.layout_engine_child_local_rect(cx.node, next_child)
{
let next_x = next_layout.origin.x.0 - pad_left;
let desired = (next_x - gap_x - layout.size.width.0).max(0.0);
x = x.min(desired);
}
}
if margin_left_auto || margin_right_auto {
let left = if margin_left_auto {
0.0
} else {
margin_px(child_style.margin.left)
};
let right = if margin_right_auto {
0.0
} else {
margin_px(child_style.margin.right)
};
if margin_left_auto && margin_right_auto {
if single_child {
let free = auto_margin_inner_size.width.0
- layout.size.width.0
- left
- right;
x = (left + (free.max(0.0) / 2.0)).max(0.0);
}
} else if margin_left_auto {
if ml_auto_tail_group_start == Some(child_index) {
} else {
let desired = (auto_margin_inner_size.width.0
- layout.size.width.0
- margin_px(child_style.margin.right))
.max(0.0);
x = x.max(desired);
}
} else if margin_right_auto && single_child {
let free =
auto_margin_inner_size.width.0 - layout.size.width.0 - left - right;
x = left.max(0.0).min((left + free.max(0.0)).max(0.0));
}
}
if margin_top_auto || margin_bottom_auto {
let top = if margin_top_auto {
0.0
} else {
margin_px(child_style.margin.top)
};
let bottom = if margin_bottom_auto {
0.0
} else {
margin_px(child_style.margin.bottom)
};
let free =
auto_margin_inner_size.height.0 - layout.size.height.0 - top - bottom;
if margin_top_auto && margin_bottom_auto {
y = (top + (free.max(0.0) / 2.0)).max(0.0);
} else if margin_top_auto {
y = (top + free.max(0.0)).max(0.0);
} else if margin_bottom_auto {
y = top.max(0.0);
}
}
}
fret_core::Axis::Vertical => {
if single_child && (margin_top_auto || margin_bottom_auto) {
let top = if margin_top_auto {
0.0
} else {
margin_px(child_style.margin.top)
};
let bottom = if margin_bottom_auto {
0.0
} else {
margin_px(child_style.margin.bottom)
};
let free =
auto_margin_inner_size.height.0 - layout.size.height.0 - top - bottom;
if margin_top_auto && margin_bottom_auto {
y = (top + (free.max(0.0) / 2.0)).max(0.0);
} else if margin_top_auto {
y = (top + free.max(0.0)).max(0.0);
} else if margin_bottom_auto {
y = top.max(0.0);
}
}
if margin_left_auto || margin_right_auto {
let left = if margin_left_auto {
0.0
} else {
margin_px(child_style.margin.left)
};
let right = if margin_right_auto {
0.0
} else {
margin_px(child_style.margin.right)
};
let free =
auto_margin_inner_size.width.0 - layout.size.width.0 - left - right;
if margin_left_auto && margin_right_auto {
x = (left + (free.max(0.0) / 2.0)).max(0.0);
} else if margin_left_auto {
x = (left + free.max(0.0)).max(0.0);
} else if margin_right_auto {
x = left.max(0.0);
}
}
}
}
if ml_auto_tail_shift_x > 0.0
&& ml_auto_tail_group_start.is_some_and(|start| child_index >= start)
{
x += ml_auto_tail_shift_x;
}
let local_x = x + pad_left;
let local_y = y + pad_top;
let rect = Rect::new(
fret_core::Point::new(
Px(cx.bounds.origin.x.0 + local_x),
Px(cx.bounds.origin.y.0 + local_y),
),
layout.size,
);
let _ = cx.layout_in(child, rect);
}
clamp_to_constraints(cx.available, props.layout, cx.available)
}
}