xengui 0.2.2

xengui: a reactive gui library in rust
// SPDX-License-Identifier: Apache-2.0
use crate::{ LayoutBox, LayoutContext, RenderCache, Widget, style_to_taffy };
use taffy::prelude::*;

pub struct LayoutEngine;

impl LayoutEngine {
    pub fn layout(
        tree: &mut [Box<dyn Widget>],
        ctx: &mut LayoutContext,
        cache: &mut RenderCache,
        viewport_width: f32,
        viewport_height: f32
    ) {
        let mut taffy: TaffyTree<()> = TaffyTree::new();

        let child_ids: Vec<NodeId> = tree
            .iter()
            .enumerate()
            .map(|(i, w)| build_taffy_node(w.as_ref(), &mut taffy, ctx, cache, &i.to_string()))
            .collect();

        let root_style = taffy::style::Style {
            display: taffy::style::Display::Flex,
            flex_direction: taffy::style::FlexDirection::Column,
            size: Size {
                width: length(viewport_width),
                height: length(viewport_height),
            },
            ..Default::default()
        };
        let root_id = taffy
            .new_with_children(root_style, &child_ids)
            .expect("cannot create taffy root node");

        taffy
            .compute_layout(root_id, Size {
                width: AvailableSpace::Definite(viewport_width),
                height: AvailableSpace::Definite(viewport_height),
            })
            .expect("cannot calculate taffy layout");

        for (widget, node_id) in tree.iter_mut().zip(child_ids) {
            apply_layout(widget.as_mut(), &taffy, node_id, 0.0, 0.0);
        }
    }
}

fn build_taffy_node(
    widget: &dyn Widget,
    taffy: &mut TaffyTree<()>,
    ctx: &mut LayoutContext,
    cache: &mut RenderCache,
    path: &str
) -> NodeId {
    let mut style = style_to_taffy(widget.style(), ctx.scale_factor);
    let children = widget.children();

    if children.is_empty() {
        let auto_w = style.size.width == taffy::style::Dimension::auto();
        let auto_h = style.size.height == taffy::style::Dimension::auto();

        if auto_w && auto_h {
            let (w, h) = if widget.is_dirty() {
                let size = widget.measure(ctx);
                cache.store_measure(path, size);
                size
            } else {
                cache.cached_measure(path).unwrap_or_else(|| {
                    let size = widget.measure(ctx);
                    cache.store_measure(path, size);
                    size
                })
            };
            style.size = Size {
                width: length(w),
                height: length(h),
            };
            if style.min_size.width == taffy::style::Dimension::auto() {
                style.min_size.width = length(w);
            }
            if style.min_size.height == taffy::style::Dimension::auto() {
                style.min_size.height = length(h);
            }
        }
        taffy.new_leaf(style).expect("cannot create taffy leaf")
    } else {
        let child_ids: Vec<NodeId> = children
            .iter()
            .enumerate()
            .map(|(i, c)| build_taffy_node(c.as_ref(), taffy, ctx, cache, &format!("{path}.{i}")))
            .collect();
        taffy.new_with_children(style, &child_ids).expect("cannot create taffy node")
    }
}

fn apply_layout(
    widget: &mut dyn Widget,
    taffy: &TaffyTree<()>,
    node_id: NodeId,
    parent_x: f32,
    parent_y: f32
) {
    let layout = taffy.layout(node_id).expect("cannot find taffy layout result");
    let abs_x = parent_x + layout.location.x;
    let abs_y = parent_y + layout.location.y;

    widget.layout(LayoutBox {
        x: abs_x,
        y: abs_y,
        width: layout.size.width,
        height: layout.size.height,
    });

    if let Some(children) = widget.children_mut() && let Ok(child_ids) = taffy.children(node_id) {
        for (child, child_id) in children.iter_mut().zip(child_ids) {
            apply_layout(child.as_mut(), taffy, child_id, abs_x, abs_y);
        }
    }
}