use crate::layout_algorithm::axis_utils::Axis;
use crate::layout_struct::layout_states::{ChildWrapping, LayoutElement, LayoutElementInfo};
use crate::layout_struct::layout_tree::{LayoutTreeCursor, LayoutTreeNodeLocalMut};
use cotis_defaults::element_configs::style::types::{
FloatingAttachPointType, LayoutAlignmentX, LayoutAlignmentY, LayoutDirection,
};
use cotis_utils::math::BoundingBox;
pub(crate) fn calculate_child_positions(local: &mut LayoutTreeCursor) {
{
let mut local = local.get_local_mut();
if local.self_element.config.layout.layout_direction == LayoutDirection::LeftToRight {
layout_ltr(&mut local);
} else {
layout_ttb(&mut local);
}
}
for child in local.get_all_child_ids() {
calculate_child_positions(&mut local.get_child_cursor(child).unwrap());
}
}
fn layout_ltr(local: &mut LayoutTreeNodeLocalMut<'_>) {
if local.self_element.config.layout.wrapping == ChildWrapping::Text {
layout_ltr_wrapped(local);
return;
}
let mut parent_bounding_box = parent_bound_box(local.self_element);
let pad = local.self_element.config.layout.padding;
let gap = local.self_element.config.layout.child_gap;
let x_off = clip_x_off(local.self_element);
let y_off = clip_y_off(local.self_element);
let parent_h = local
.self_element
.info
.get_final_height()
.expect("parent must have final height");
parent_bounding_box.height = parent_h;
match local.self_element.config.layout.child_alignment.x {
LayoutAlignmentX::Left => {
sequence_forward(
&mut local.children_elements,
PhysicalAxis::X,
parent_bounding_box,
parent_bounding_box.x + pad.left + x_off,
gap,
);
}
LayoutAlignmentX::Right => {
sequence_backward(
&mut local.children_elements,
PhysicalAxis::X,
parent_bounding_box,
parent_bounding_box.x + parent_bounding_box.width - pad.right + x_off,
gap,
);
}
LayoutAlignmentX::Center => {
let lead = local.free_space_axis(Axis::Width) / 2.0;
sequence_forward(
&mut local.children_elements,
PhysicalAxis::X,
parent_bounding_box,
parent_bounding_box.x + lead + pad.left + x_off,
gap,
);
}
}
match local.self_element.config.layout.child_alignment.y {
LayoutAlignmentY::Top => {
cross_fixed_start(
&mut local.children_elements,
PhysicalAxis::Y,
parent_bounding_box,
parent_bounding_box.y + pad.top + y_off,
);
}
LayoutAlignmentY::Bottom => {
cross_align_end(
&mut local.children_elements,
PhysicalAxis::Y,
parent_bounding_box,
parent_bounding_box.y + parent_h - pad.bottom + y_off,
);
}
LayoutAlignmentY::Center => {
let inner = parent_h - local.self_element.internal_space_height();
cross_center_each(
&mut local.children_elements,
PhysicalAxis::Y,
parent_bounding_box,
inner,
parent_bounding_box.y,
pad.top,
y_off,
);
}
}
}
fn layout_ltr_wrapped(local: &mut LayoutTreeNodeLocalMut<'_>) {
let parent_bounding_box = parent_bound_box(local.self_element);
let pad = local.self_element.config.layout.padding;
let gap = local.self_element.config.layout.child_gap;
let x_off = clip_x_off(local.self_element);
let y_off = clip_y_off(local.self_element);
let parent_h = local
.self_element
.info
.get_final_height()
.expect("parent must have final height");
let content_left = parent_bounding_box.x + pad.left + x_off;
let content_right = parent_bounding_box.x + parent_bounding_box.width - pad.right + x_off;
let content_width = (content_right - content_left).max(0.0);
let children = &mut local.children_elements;
for child in children.iter_mut() {
if child.config.floating.config.is_some() {
try_float_x(child, parent_bounding_box.x, parent_bounding_box.width);
try_float_y(child, parent_bounding_box.y, parent_h);
}
}
let in_flow: Vec<usize> = children
.iter()
.enumerate()
.filter(|(_, c)| c.config.floating.config.is_none())
.map(|(i, _)| i)
.collect();
let mut lines: Vec<Vec<usize>> = Vec::new();
let mut current_line: Vec<usize> = Vec::new();
let mut row_width = 0.0f32;
for &i in &in_flow {
let w = children[i].info.get_final_width().expect("child width");
if current_line.is_empty() {
current_line.push(i);
row_width = w;
} else if row_width + gap + w > content_width {
lines.push(std::mem::take(&mut current_line));
current_line.push(i);
row_width = w;
} else {
current_line.push(i);
row_width += gap + w;
}
}
if !current_line.is_empty() {
lines.push(current_line);
}
if lines.is_empty() {
return;
}
let line_heights: Vec<f32> = lines
.iter()
.map(|line| wrapped_line_max_height(children.as_mut_slice(), line))
.collect();
let lines_inner_height: f32 =
line_heights.iter().sum::<f32>() + gap * line_heights.len().saturating_sub(1) as f32;
let inner_h = parent_h - pad.top - pad.bottom;
let block_start_y = match local.self_element.config.layout.child_alignment.y {
LayoutAlignmentY::Top => parent_bounding_box.y + pad.top + y_off,
LayoutAlignmentY::Bottom => {
parent_bounding_box.y + parent_h - pad.bottom + y_off - lines_inner_height
}
LayoutAlignmentY::Center => {
parent_bounding_box.y + pad.top + y_off + (inner_h - lines_inner_height) / 2.0
}
};
let mut line_y = block_start_y;
for (line_idx, line) in lines.iter().enumerate() {
let row_h = line_heights[line_idx];
let used_w = wrapped_line_used_width(children.as_mut_slice(), line, gap);
let line_start_x = match local.self_element.config.layout.child_alignment.x {
LayoutAlignmentX::Left => content_left,
LayoutAlignmentX::Right => content_right - used_w,
LayoutAlignmentX::Center => content_left + (content_width - used_w) / 2.0,
};
let mut cursor_x = line_start_x;
for &i in line {
let child = &mut children[i];
let child_w = child.info.get_final_width().expect("child width");
let child_h = child.info.get_final_height().expect("child height");
let y = match local.self_element.config.layout.child_alignment.y {
LayoutAlignmentY::Top => line_y,
LayoutAlignmentY::Bottom => line_y + row_h - child_h,
LayoutAlignmentY::Center => line_y + (row_h - child_h) / 2.0,
};
child.info.set_x(cursor_x);
child.info.set_y(y);
cursor_x += child_w + gap;
}
line_y += row_h + gap;
}
}
fn wrapped_line_used_width(children: &mut [&mut LayoutElement], line: &[usize], gap: f32) -> f32 {
if line.is_empty() {
return 0.0;
}
let sum_w: f32 = line
.iter()
.map(|&i| children[i].info.get_final_width().expect("child width"))
.sum();
sum_w + gap * (line.len().saturating_sub(1) as f32)
}
fn wrapped_line_max_height(children: &mut [&mut LayoutElement], line: &[usize]) -> f32 {
line.iter()
.map(|&i| {
children[i]
.info
.get_final_axis(Axis::Height)
.expect("child min height for wrapped layout")
})
.fold(0.0f32, f32::max)
}
fn layout_ttb(local: &mut LayoutTreeNodeLocalMut<'_>) {
let mut parent_bounding_box = parent_bound_box(local.self_element);
let pad = local.self_element.config.layout.padding;
let gap = local.self_element.config.layout.child_gap;
let x_off = clip_x_off(local.self_element);
let y_off = clip_y_off(local.self_element);
let parent_h = local
.self_element
.info
.get_final_height()
.expect("parent must have final height");
parent_bounding_box.height = parent_h;
match local.self_element.config.layout.child_alignment.y {
LayoutAlignmentY::Top => {
sequence_forward(
&mut local.children_elements,
PhysicalAxis::Y,
parent_bounding_box,
parent_bounding_box.y + pad.top + y_off,
gap,
);
}
LayoutAlignmentY::Bottom => {
sequence_backward(
&mut local.children_elements,
PhysicalAxis::Y,
parent_bounding_box,
parent_bounding_box.y + parent_h - pad.bottom + y_off,
gap,
);
}
LayoutAlignmentY::Center => {
let lead = local.free_space_axis(Axis::Height) / 2.0;
sequence_forward(
&mut local.children_elements,
PhysicalAxis::Y,
parent_bounding_box,
parent_bounding_box.y + lead + pad.top + y_off,
gap,
);
}
}
match local.self_element.config.layout.child_alignment.x {
LayoutAlignmentX::Left => {
cross_fixed_start(
&mut local.children_elements,
PhysicalAxis::X,
parent_bounding_box,
parent_bounding_box.x + pad.left + x_off,
);
}
LayoutAlignmentX::Right => {
cross_align_end(
&mut local.children_elements,
PhysicalAxis::X,
parent_bounding_box,
parent_bounding_box.x + parent_bounding_box.width - pad.right + x_off,
);
}
LayoutAlignmentX::Center => {
let inner = parent_bounding_box.width - local.self_element.internal_space_width();
cross_center_each(
&mut local.children_elements,
PhysicalAxis::X,
parent_bounding_box,
inner,
parent_bounding_box.x,
pad.left,
x_off,
);
}
}
}
#[derive(Clone, Copy)]
enum PhysicalAxis {
X,
Y,
}
impl PhysicalAxis {
fn try_float(
self,
child: &mut LayoutElement,
px: f32,
py: f32,
parent_w: f32,
parent_h: f32,
) -> bool {
match self {
PhysicalAxis::X => try_float_x(child, px, parent_w),
PhysicalAxis::Y => try_float_y(child, py, parent_h),
}
}
fn size(self, child: &LayoutElement) -> f32 {
match self {
PhysicalAxis::X => child.info.get_final_width().expect("child width"),
PhysicalAxis::Y => child.info.get_final_height().expect("child height"),
}
}
fn set(self, child: &mut LayoutElement, v: f32) {
match self {
PhysicalAxis::X => child.info.set_x(v),
PhysicalAxis::Y => child.info.set_y(v),
}
}
}
fn with_children_order(
children: &mut Vec<&mut LayoutElement>,
reverse: bool,
mut f: impl FnMut(&mut LayoutElement),
) {
if reverse {
for c in children.iter_mut().rev() {
f(c);
}
} else {
for c in children.iter_mut() {
f(c);
}
}
}
fn sequence_forward(
children: &mut Vec<&mut LayoutElement>,
axis: PhysicalAxis,
parent_bounding_box: BoundingBox,
mut cursor: f32,
gap: f32,
) {
with_children_order(children, false, |child| {
if axis.try_float(
child,
parent_bounding_box.x,
parent_bounding_box.y,
parent_bounding_box.width,
parent_bounding_box.height,
) {
return;
}
let s = axis.size(child);
axis.set(child, cursor);
cursor += s + gap;
});
}
fn sequence_backward(
children: &mut Vec<&mut LayoutElement>,
axis: PhysicalAxis,
parent_bounding_box: BoundingBox,
mut cursor: f32,
gap: f32,
) {
with_children_order(children, true, |child| {
if axis.try_float(
child,
parent_bounding_box.x,
parent_bounding_box.y,
parent_bounding_box.width,
parent_bounding_box.height,
) {
return;
}
let s = axis.size(child);
axis.set(child, cursor - s);
cursor -= s + gap;
});
}
fn cross_fixed_start(
children: &mut Vec<&mut LayoutElement>,
axis: PhysicalAxis,
parent_bounding_box: BoundingBox,
coord: f32,
) {
with_children_order(children, false, |child| {
if axis.try_float(
child,
parent_bounding_box.x,
parent_bounding_box.y,
parent_bounding_box.width,
parent_bounding_box.height,
) {
return;
}
axis.set(child, coord);
});
}
fn cross_align_end(
children: &mut Vec<&mut LayoutElement>,
axis: PhysicalAxis,
parent_bounding_box: BoundingBox,
end: f32,
) {
with_children_order(children, true, |child| {
if axis.try_float(
child,
parent_bounding_box.x,
parent_bounding_box.y,
parent_bounding_box.width,
parent_bounding_box.height,
) {
return;
}
let s = axis.size(child);
axis.set(child, end - s);
});
}
fn cross_center_each(
children: &mut Vec<&mut LayoutElement>,
axis: PhysicalAxis,
parent_bounding_box: BoundingBox,
inner: f32,
origin: f32,
pad_start: f32,
clip_off: f32,
) {
with_children_order(children, false, |child| {
if axis.try_float(
child,
parent_bounding_box.x,
parent_bounding_box.y,
parent_bounding_box.width,
parent_bounding_box.height,
) {
return;
}
let s = axis.size(child);
let half = (inner - s) / 2.0;
axis.set(child, origin + half + pad_start + clip_off);
});
}
fn parent_bound_box(parent: &LayoutElement) -> BoundingBox {
match &parent.info {
LayoutElementInfo::TotalSizedAndPosition(p) => BoundingBox {
x: p.x,
y: p.y,
width: p.width,
height: p.height,
},
_ => panic!("tried using parent without position"),
}
}
fn clip_x_off(parent: &LayoutElement) -> f32 {
if parent.config.clip.horizontal {
parent.config.clip.offset.x
} else {
0.0
}
}
fn clip_y_off(parent: &LayoutElement) -> f32 {
if parent.config.clip.vertical {
parent.config.clip.offset.y
} else {
0.0
}
}
fn try_float_x(child: &mut LayoutElement, parent_x: f32, parent_w: f32) -> bool {
if let Some(cfg) = &child.config.floating.config {
let child_w = child.info.get_final_width().expect("child width");
let anchor_x = floating_anchor_x(parent_x, parent_w, cfg.attach_point);
let child_anchor_x = floating_child_anchor_x(child_w, cfg.attach_point);
child.info.set_x(anchor_x - child_anchor_x + cfg.offset.x);
true
} else {
false
}
}
fn try_float_y(child: &mut LayoutElement, parent_y: f32, parent_h: f32) -> bool {
if let Some(cfg) = &child.config.floating.config {
let child_h = child.info.get_final_height().expect("child height");
let anchor_y = floating_anchor_y(parent_y, parent_h, cfg.attach_point);
let child_anchor_y = floating_child_anchor_y(child_h, cfg.attach_point);
child.info.set_y(anchor_y - child_anchor_y + cfg.offset.y);
true
} else {
false
}
}
fn floating_anchor_x(parent_x: f32, parent_w: f32, attach: FloatingAttachPointType) -> f32 {
match attach {
FloatingAttachPointType::LeftTop
| FloatingAttachPointType::LeftCenter
| FloatingAttachPointType::LeftBottom => parent_x,
FloatingAttachPointType::CenterTop
| FloatingAttachPointType::CenterCenter
| FloatingAttachPointType::CenterBottom => parent_x + parent_w / 2.0,
FloatingAttachPointType::RightTop
| FloatingAttachPointType::RightCenter
| FloatingAttachPointType::RightBottom => parent_x + parent_w,
}
}
fn floating_anchor_y(parent_y: f32, parent_h: f32, attach: FloatingAttachPointType) -> f32 {
match attach {
FloatingAttachPointType::LeftTop
| FloatingAttachPointType::CenterTop
| FloatingAttachPointType::RightTop => parent_y,
FloatingAttachPointType::LeftCenter
| FloatingAttachPointType::CenterCenter
| FloatingAttachPointType::RightCenter => parent_y + parent_h / 2.0,
FloatingAttachPointType::LeftBottom
| FloatingAttachPointType::CenterBottom
| FloatingAttachPointType::RightBottom => parent_y + parent_h,
}
}
fn floating_child_anchor_x(child_w: f32, attach: FloatingAttachPointType) -> f32 {
match attach {
FloatingAttachPointType::LeftTop
| FloatingAttachPointType::LeftCenter
| FloatingAttachPointType::LeftBottom => 0.0,
FloatingAttachPointType::CenterTop
| FloatingAttachPointType::CenterCenter
| FloatingAttachPointType::CenterBottom => child_w / 2.0,
FloatingAttachPointType::RightTop
| FloatingAttachPointType::RightCenter
| FloatingAttachPointType::RightBottom => child_w,
}
}
fn floating_child_anchor_y(child_h: f32, attach: FloatingAttachPointType) -> f32 {
match attach {
FloatingAttachPointType::LeftTop
| FloatingAttachPointType::CenterTop
| FloatingAttachPointType::RightTop => 0.0,
FloatingAttachPointType::LeftCenter
| FloatingAttachPointType::CenterCenter
| FloatingAttachPointType::RightCenter => child_h / 2.0,
FloatingAttachPointType::LeftBottom
| FloatingAttachPointType::CenterBottom
| FloatingAttachPointType::RightBottom => child_h,
}
}