use rustc_hash::FxHashMap;
use taffy::prelude::*;
use super::{flex::FlexStyle, rect::Rect};
pub struct LayoutEngine {
tree: TaffyTree<()>,
node_map: FxHashMap<u64, NodeId>,
reverse_map: FxHashMap<NodeId, u64>,
layout_cache: FxHashMap<u64, Rect>,
next_id: u64,
root: Option<u64>,
}
impl LayoutEngine {
pub fn new() -> Self {
Self {
tree: TaffyTree::new(),
node_map: FxHashMap::default(),
reverse_map: FxHashMap::default(),
layout_cache: FxHashMap::default(),
next_id: 0,
root: None,
}
}
pub fn new_node(&mut self, style: &FlexStyle) -> u64 {
let id = self.next_id;
self.next_id += 1;
let taffy_style = style.to_taffy();
let node_id = self
.tree
.new_leaf(taffy_style)
.expect("Failed to create node");
self.node_map.insert(id, node_id);
self.reverse_map.insert(node_id, id);
id
}
pub fn new_leaf(&mut self, style: &FlexStyle, width: f32, height: f32) -> u64 {
let id = self.next_id;
self.next_id += 1;
let mut taffy_style = style.to_taffy();
taffy_style.size = Size {
width: Dimension::Length(width),
height: Dimension::Length(height),
};
let node_id = self
.tree
.new_leaf(taffy_style)
.expect("Failed to create leaf");
self.node_map.insert(id, node_id);
self.reverse_map.insert(node_id, id);
id
}
pub fn set_root(&mut self, id: u64) {
self.root = Some(id);
}
pub fn root(&self) -> Option<u64> {
self.root
}
pub fn add_child(&mut self, parent: u64, child: u64) {
if let (Some(&parent_id), Some(&child_id)) =
(self.node_map.get(&parent), self.node_map.get(&child))
{
self.tree
.add_child(parent_id, child_id)
.expect("Failed to add child");
}
}
pub fn remove_child(&mut self, parent: u64, child: u64) {
if let (Some(&parent_id), Some(&child_id)) =
(self.node_map.get(&parent), self.node_map.get(&child))
{
self.tree
.remove_child(parent_id, child_id)
.expect("Failed to remove child");
}
}
pub fn set_style(&mut self, id: u64, style: &FlexStyle) {
if let Some(&node_id) = self.node_map.get(&id) {
let taffy_style = style.to_taffy();
self.tree
.set_style(node_id, taffy_style)
.expect("Failed to set style");
}
}
pub fn remove(&mut self, id: u64) {
if let Some(node_id) = self.node_map.remove(&id) {
self.reverse_map.remove(&node_id);
self.layout_cache.remove(&id);
self.tree.remove(node_id).expect("Failed to remove node");
}
}
pub fn compute(&mut self, available_width: f32, available_height: f32) {
if let Some(root_id) = self.root.and_then(|id| self.node_map.get(&id).copied()) {
let available = Size {
width: AvailableSpace::Definite(available_width),
height: AvailableSpace::Definite(available_height),
};
self.tree
.compute_layout(root_id, available)
.expect("Failed to compute layout");
self.cache_layouts(root_id, 0.0, 0.0);
}
}
fn cache_layouts(&mut self, node_id: NodeId, parent_x: f32, parent_y: f32) {
let layout = self.tree.layout(node_id).expect("Failed to get layout");
let style = self.tree.style(node_id).expect("Failed to get style");
let padding_top = layout.padding.top;
let padding_left = layout.padding.left;
let is_column = style.flex_direction == taffy::FlexDirection::Column;
if let Some(&id) = self.reverse_map.get(&node_id) {
self.layout_cache.insert(
id,
Rect::new(
parent_x.round() as u16,
parent_y.round() as u16,
layout.size.width.round() as u16,
layout.size.height.round() as u16,
),
);
}
let children: Vec<_> = self.tree.children(node_id).unwrap_or_default();
let child_info: Vec<(NodeId, f32, f32, f32, f32, f32, f32)> = children
.iter()
.map(|&cid| {
let cl = self.tree.layout(cid).expect("child layout");
let cs = self.tree.style(cid).expect("child style");
let mt = resolve_margin(cs.margin.top);
let mr = resolve_margin(cs.margin.right);
let mb = resolve_margin(cs.margin.bottom);
let ml = resolve_margin(cs.margin.left);
(cid, cl.size.width, cl.size.height, mt, mr, mb, ml)
})
.collect();
let mut offset_x = parent_x + padding_left;
let mut offset_y = parent_y + padding_top;
for (child_id, child_width, child_height, mt, mr, mb, ml) in child_info {
let child_x = offset_x + ml;
let child_y = offset_y + mt;
self.cache_layouts(child_id, child_x, child_y);
if is_column {
offset_y += mt + child_height + mb;
} else {
offset_x += ml + child_width + mr;
}
}
}
pub fn layout(&self, id: u64) -> Option<Rect> {
self.layout_cache.get(&id).copied()
}
pub fn layouts(&self) -> &FxHashMap<u64, Rect> {
&self.layout_cache
}
pub fn clear(&mut self) {
self.tree = TaffyTree::new();
self.node_map.clear();
self.reverse_map.clear();
self.layout_cache.clear();
self.next_id = 0;
self.root = None;
}
pub fn node_count(&self) -> usize {
self.node_map.len()
}
}
impl Default for LayoutEngine {
fn default() -> Self {
Self::new()
}
}
fn resolve_margin(m: taffy::LengthPercentageAuto) -> f32 {
match m {
taffy::LengthPercentageAuto::Length(v) => v,
taffy::LengthPercentageAuto::Percent(_v) => 0.0, taffy::LengthPercentageAuto::Auto => 0.0,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_engine_new() {
let engine = LayoutEngine::new();
assert_eq!(engine.node_count(), 0);
}
#[test]
fn test_engine_create_node() {
let mut engine = LayoutEngine::new();
let style = FlexStyle::new();
let id = engine.new_node(&style);
assert_eq!(engine.node_count(), 1);
assert_eq!(id, 0);
}
#[test]
fn test_engine_add_child() {
let mut engine = LayoutEngine::new();
let style = FlexStyle::new();
let parent = engine.new_node(&style);
let child = engine.new_node(&style);
engine.add_child(parent, child);
assert_eq!(engine.node_count(), 2);
}
#[test]
fn test_engine_compute_layout() {
use super::super::flex::{Dimension, FlexDirection};
let mut engine = LayoutEngine::new();
let mut root_style = FlexStyle::new();
root_style.flex_direction = FlexDirection::Column;
root_style.width = Dimension::Points(100.0);
root_style.height = Dimension::Points(100.0);
let root = engine.new_node(&root_style);
let mut child_style = FlexStyle::new();
child_style.height = Dimension::Points(50.0);
let child = engine.new_leaf(&child_style, 100.0, 50.0);
engine.add_child(root, child);
engine.set_root(root);
engine.compute(100.0, 100.0);
let root_layout = engine.layout(root).unwrap();
assert_eq!(root_layout.width, 100);
assert_eq!(root_layout.height, 100);
let child_layout = engine.layout(child).unwrap();
assert_eq!(child_layout.width, 100);
assert_eq!(child_layout.height, 50);
}
}