cotis-layout 0.1.0-alpha.1

Flexbox-style layout engine for Cotis
Documentation
use crate::layout_algorithm::axis_utils::Axis;
use crate::layout_struct::layout_states::LayoutElement;
use crate::layout_struct::layout_tree::{LayoutTreeCursor, LayoutTreeNodeLocalMut};

/// Preferred size along `axis` for free-space math: temp sizing, then finalized axis, then
/// intrinsic bounds from config (covers states like `TextWrappedMinHeight` and ordering edge cases).
pub(crate) fn child_axis_size_for_aggregate(child: &LayoutElement, axis: Axis) -> f32 {
    if let Some(v) = child.info.temp_axis(axis) {
        return v;
    }
    if let Some(v) = child.info.get_final_axis(axis) {
        return v;
    }
    panic!("tried to get_child_axis_total but child was not initialized");
}

/// Sum of in-flow child sizes along `axis`, plus `(n - 1) * child_gap` on the main axis.
pub(crate) fn get_children_axis_total<'a>(
    children: impl Iterator<Item = &'a LayoutElement>,
    axis: Axis,
    child_gap: f32,
) -> f32 {
    let mut res = 0.0;
    let mut in_flow_count = 0usize;
    for child in children {
        if child.config.floating.config.is_some() {
            continue;
        }
        in_flow_count += 1;
        res += child_axis_size_for_aggregate(child, axis);
    }
    if in_flow_count > 1 {
        res += child_gap * (in_flow_count - 1) as f32;
    }
    res
}

pub(crate) fn get_child_axis_max<'a>(
    children: impl Iterator<Item = &'a LayoutElement>,
    axis: Axis,
) -> f32 {
    let mut res: f32 = 0.0;
    for child in children {
        if child.config.floating.config.is_some() {
            continue;
        }
        res = res.max(child_axis_size_for_aggregate(child, axis));
    }
    res
}

impl LayoutTreeNodeLocalMut<'_> {
    pub(crate) fn free_space_axis(&self, axis: Axis) -> f32 {
        let element: &LayoutElement = self.self_element;
        let children = self.children_elements.iter().map(|c| &**c);
        let axis_size = element
            .info
            .get_final_axis(axis)
            .expect("Expected element to have finalized axis for free space calculation");
        let mut res = axis_size;
        res -= element.internal_space_axis(axis);
        if element.config.layout.layout_direction == axis.get_direction() {
            res -= get_children_axis_total(children, axis, element.config.layout.child_gap);
        } else if element.config.clip.horizontal {
            res = res.max(get_child_axis_max(children, axis))
        }
        if element.config.clip.horizontal {
            res = res.max(0.0);
        }
        res
    }

    fn grow_children_axis(&mut self, axis: Axis) {
        if self.self_element.config.layout.layout_direction == axis.get_direction() {
            loop {
                let free_space = self.free_space_axis(axis);
                let mut growing_children: Vec<_> = Vec::new();
                for child in self.children_elements.iter_mut() {
                    if child.wants_to_grow_axis(axis) {
                        growing_children.push(child);
                    }
                }
                if free_space <= 0.0 || growing_children.is_empty() {
                    break;
                }
                let grow_size_per_child = free_space / growing_children.len() as f32;
                for child in growing_children.iter_mut() {
                    child.grow_axis_at_most(grow_size_per_child, axis);
                }
            }
        } else {
            let free_space = self.free_space_axis(axis);
            for child in self.children_elements.iter_mut() {
                if child.wants_to_grow_axis(axis) {
                    child.grow_axis_to(free_space, axis);
                }
            }
        }
    }

    fn shrink_children_axis(&mut self, axis: Axis) {
        if self.self_element.config.layout.layout_direction == axis.get_direction() {
            loop {
                let free_space = self.free_space_axis(axis);
                let mut shrinking_children: Vec<_> = Vec::new();
                for child in self.children_elements.iter_mut() {
                    if child.can_shrink_axis(axis) {
                        shrinking_children.push(child);
                    }
                }
                if free_space >= 0.0 || shrinking_children.is_empty() {
                    break;
                }
                let shrink_size_per_child = -free_space / shrinking_children.len() as f32;
                for child in shrinking_children.iter_mut() {
                    child.shrink_axis_at_most(shrink_size_per_child, axis);
                }
            }
        } else {
            let free_space = self.free_space_axis(axis);
            for child in self.children_elements.iter_mut() {
                if child.can_shrink_axis(axis) {
                    child.shrink_axis_to(free_space, axis);
                }
            }
        }
    }
}

pub(crate) fn fit_grow_shrink_children_axis(local: &mut LayoutTreeCursor, axis: Axis) {
    {
        let mut local = local.get_local_mut();
        if local.self_element.config.layout.layout_direction == axis.get_direction() {
            let axis_size = local
                .self_element
                .info
                .get_final_axis(axis)
                .expect("tried to fit_grow_shrink_children_axis before finalized parent axis");
            // Set percent containers
            for child in local.children_elements.iter_mut() {
                child.set_percent_sizing_for_axis(axis_size, axis);
                child.set_floating_for_axis(axis_size, axis_size, axis);
            }
            // check for free space
            let free = local.free_space_axis(axis);
            if free > 0.0 {
                local.grow_children_axis(axis);
            } else if free < 0.0 && !local.self_element.config.clip.horizontal {
                local.shrink_children_axis(axis);
            }
            for child in local.children_elements.iter_mut() {
                child.upgrade_axis(axis);
            }
        } else {
            let axis_size = local
                .self_element
                .info
                .get_final_axis(axis)
                .expect("tried to fit_grow_shrink_children_axis before finalized parent axis");
            // Set percent containers
            for child in local.children_elements.iter_mut() {
                child.set_percent_sizing_for_axis(axis_size, axis);
                child.set_floating_for_axis(axis_size, axis_size, axis);
            }
            local.grow_children_axis(axis);
            local.shrink_children_axis(axis);
            for child in local.children_elements.iter_mut() {
                child.upgrade_axis(axis);
            }
        }
    }
    for child in local.get_all_child_ids() {
        fit_grow_shrink_children_axis(&mut local.get_child_cursor(child).unwrap(), axis)
    }
}