polyhorn-layout 0.2.0

Abstraction over flexbox layout algorithms.
Documentation
use polyhorn_ui::geometry::{Dimension, Point, Size};
use polyhorn_ui::layout::LayoutAxisX;
use polyhorn_ui::styles::{Position, ViewStyle};
use std::cell::RefCell;
use std::collections::HashMap;

use super::Algorithm;
use crate::{Layout, MeasureFunc};

mod convert;

use convert::IntoYoga;

pub struct Flexbox {
    counter: usize,
    nodes: HashMap<usize, RefCell<yoga::Node>>,
}

impl Flexbox {
    fn next_id(&mut self) -> usize {
        let id = self.counter;
        self.counter += 1;
        id
    }
}

#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
pub struct Node(usize);

impl Algorithm for Flexbox {
    type Node = Node;

    fn new() -> Self {
        Flexbox {
            counter: 0,
            nodes: Default::default(),
        }
    }

    fn new_node(&mut self, style: ViewStyle, children: &[Self::Node]) -> Self::Node {
        let id = self.next_id();
        let mut node = yoga::Node::new();

        for (i, child) in children.iter().enumerate() {
            node.insert_child(
                &mut self.nodes.get(&child.0).unwrap().borrow_mut(),
                i as u32,
            );
        }

        self.nodes.insert(id, RefCell::new(node));

        let node = Node(id);

        self.set_style(node, style);

        node
    }

    fn new_leaf(&mut self, style: ViewStyle, measure: MeasureFunc) -> Self::Node {
        let id = self.next_id();
        let node = yoga::Node::new();

        self.nodes.insert(id, RefCell::new(node));

        let node = Node(id);

        self.set_style(node, style);
        self.set_measure(node, measure);

        node
    }

    fn add_child(&mut self, parent: Self::Node, child: Self::Node) {
        let mut parent = self.nodes.get(&parent.0).unwrap().borrow_mut();
        let mut child = self.nodes.get(&child.0).unwrap().borrow_mut();
        let child_count = parent.child_count();
        parent.insert_child(&mut child, child_count);
    }

    fn remove_child(&mut self, parent: Self::Node, child: Self::Node) {
        let mut parent = self.nodes.get(&parent.0).unwrap().borrow_mut();
        let mut child = self.nodes.get(&child.0).unwrap().borrow_mut();
        parent.remove_child(&mut child);
    }

    fn child_count(&self, parent: Self::Node) -> usize {
        self.nodes.get(&parent.0).unwrap().borrow().child_count() as usize
    }

    fn remove(&mut self, node: Self::Node) {
        let _ = self.nodes.remove(&node.0);
    }

    fn set_style(&mut self, node: Self::Node, style: ViewStyle) {
        let mut node = self.nodes.get(&node.0).unwrap().borrow_mut();

        match style.position {
            Position::Absolute(absolute) => {
                node.set_position_type(yoga::PositionType::Absolute);

                node.set_position(yoga::Edge::Top, absolute.distances.vertical.top.into_yoga());
                node.set_position(
                    yoga::Edge::Bottom,
                    absolute.distances.vertical.bottom.into_yoga(),
                );

                match absolute.distances.horizontal {
                    LayoutAxisX::DirectionDependent { leading, trailing } => {
                        node.set_position(yoga::Edge::Start, leading.into_yoga());
                        node.set_position(yoga::Edge::End, trailing.into_yoga());
                    }
                    LayoutAxisX::DirectionIndependent { left, right } => {
                        node.set_position(yoga::Edge::Left, left.into_yoga());
                        node.set_position(yoga::Edge::Right, right.into_yoga());
                    }
                }
            }
            Position::Relative(relative) => {
                node.set_position_type(yoga::PositionType::Relative);
                node.set_flex_basis(relative.flex_basis.into_yoga());
                node.set_flex_grow(relative.flex_grow);
                node.set_flex_shrink(relative.flex_shrink);
            }
        };

        node.set_flex_direction(style.flex_direction.into_yoga());
        node.set_align_items(style.align_items.into_yoga());
        node.set_justify_content(style.justify_content.into_yoga());

        node.set_min_width(style.min_size.width.into_yoga());
        node.set_width(style.size.width.into_yoga());
        node.set_max_width(style.max_size.width.into_yoga());

        node.set_min_height(style.min_size.height.into_yoga());
        node.set_height(style.size.height.into_yoga());
        node.set_max_height(style.max_size.height.into_yoga());

        node.set_padding(yoga::Edge::Top, style.padding.vertical.top.into_yoga());
        node.set_padding(
            yoga::Edge::Bottom,
            style.padding.vertical.bottom.into_yoga(),
        );

        match style.padding.horizontal {
            LayoutAxisX::DirectionDependent { leading, trailing } => {
                node.set_padding(yoga::Edge::Start, leading.into_yoga());
                node.set_padding(yoga::Edge::End, trailing.into_yoga());
            }
            LayoutAxisX::DirectionIndependent { left, right } => {
                node.set_padding(yoga::Edge::Left, left.into_yoga());
                node.set_padding(yoga::Edge::Right, right.into_yoga());
            }
        }

        node.set_margin(yoga::Edge::Top, style.margin.vertical.top.into_yoga());
        node.set_margin(yoga::Edge::Bottom, style.margin.vertical.bottom.into_yoga());

        match style.margin.horizontal {
            LayoutAxisX::DirectionDependent { leading, trailing } => {
                node.set_margin(yoga::Edge::Start, leading.into_yoga());
                node.set_margin(yoga::Edge::End, trailing.into_yoga());
            }
            LayoutAxisX::DirectionIndependent { left, right } => {
                node.set_margin(yoga::Edge::Left, left.into_yoga());
                node.set_margin(yoga::Edge::Right, right.into_yoga());
            }
        }

        node.set_overflow(style.overflow.into_yoga());
    }

    fn set_measure(&mut self, node: Self::Node, measure: MeasureFunc) {
        let mut node = self.nodes.get(&node.0).unwrap().borrow_mut();

        node.set_context(Some(yoga::Context::new(measure)));

        extern "C" fn measure_fn(
            node: yoga::NodeRef,
            width: f32,
            _width_mode: yoga::MeasureMode,
            height: f32,
            _height_mode: yoga::MeasureMode,
        ) -> yoga::Size {
            let measure = yoga::Node::get_context(&node)
                .unwrap()
                .downcast_ref::<MeasureFunc>()
                .unwrap();

            match measure {
                MeasureFunc::Boxed(boxed) => {
                    let result = boxed(Size {
                        width: Dimension::Points(width),
                        height: Dimension::Points(height),
                    });

                    yoga::Size {
                        width: result.width,
                        height: result.height,
                    }
                }
            }
        }

        node.set_measure_func(Some(measure_fn))
    }

    fn compute_layout(&mut self, node: Self::Node, size: Size<Dimension<f32>>) {
        let mut node = self.nodes.get(&node.0).unwrap().borrow_mut();

        node.calculate_layout(
            match size.width {
                Dimension::Points(width) => width,
                _ => 0.0,
            },
            match size.height {
                Dimension::Points(height) => height,
                _ => 0.0,
            },
            yoga::Direction::LTR,
        );
    }

    fn layout(&self, node: Self::Node) -> Layout {
        let node = self.nodes.get(&node.0).unwrap().borrow();

        Layout {
            origin: Point {
                x: node.get_layout_left(),
                y: node.get_layout_top(),
            },
            size: Size {
                width: node.get_layout_width(),
                height: node.get_layout_height(),
            },
        }
    }
}