use crate::error::{PaneError, TreeError, ViewportError};
use crate::node::{Node, NodeId, PanelId};
use crate::panel::{Align, Axis, Constraints};
use crate::tree::LayoutTree;
use crate::validate::{FloatInvalid, check_f32_non_negative};
pub struct CompileResult {
pub taffy_tree: taffy::TaffyTree,
pub node_map: Vec<Option<taffy::NodeId>>,
pub root: taffy::NodeId,
}
struct CompileCtx<'a> {
taffy_tree: taffy::TaffyTree,
node_map: Vec<Option<taffy::NodeId>>,
panel_sizes: &'a [Option<(f32, f32)>],
}
fn merge_dim(axis_dim: taffy::Dimension, absolute_dim: taffy::Dimension) -> taffy::Dimension {
match absolute_dim == taffy::Dimension::auto() {
true => axis_dim,
false => absolute_dim,
}
}
pub fn constraints_to_style(constraints: &Constraints, axis: Axis) -> taffy::Style {
let (flex_grow, flex_basis, flex_shrink) = match (constraints.grow, constraints.fixed) {
(Some(g), _) => (g, taffy::Dimension::length(0.0), 1.0),
(_, Some(f)) => (0.0, taffy::Dimension::length(f), 0.0),
(None, None) => (1.0, taffy::Dimension::length(0.0), 1.0),
};
let flex_basis = match constraints.size_mode {
Some(_) => taffy::Dimension::auto(),
None => flex_basis,
};
let min_dim = constraints
.min
.map_or(taffy::Dimension::auto(), taffy::Dimension::length);
let max_dim = constraints
.max
.map_or(taffy::Dimension::auto(), taffy::Dimension::length);
let cross_min_w = constraints
.min_width
.map_or(taffy::Dimension::auto(), taffy::Dimension::length);
let cross_max_w = constraints
.max_width
.map_or(taffy::Dimension::auto(), taffy::Dimension::length);
let cross_min_h = constraints
.min_height
.map_or(taffy::Dimension::auto(), taffy::Dimension::length);
let cross_max_h = constraints
.max_height
.map_or(taffy::Dimension::auto(), taffy::Dimension::length);
let (min_size, max_size) = match axis {
Axis::Row => (
taffy::Size {
width: merge_dim(min_dim, cross_min_w),
height: cross_min_h,
},
taffy::Size {
width: merge_dim(max_dim, cross_max_w),
height: cross_max_h,
},
),
Axis::Col => (
taffy::Size {
width: cross_min_w,
height: merge_dim(min_dim, cross_min_h),
},
taffy::Size {
width: cross_max_w,
height: merge_dim(max_dim, cross_max_h),
},
),
};
let align_self = match constraints.align {
Some(Align::Start) => Some(taffy::AlignSelf::Start),
Some(Align::Center) => Some(taffy::AlignSelf::Center),
Some(Align::End) => Some(taffy::AlignSelf::End),
Some(Align::Stretch) | None => None,
};
taffy::Style {
flex_grow,
flex_basis,
flex_shrink,
min_size,
max_size,
align_self,
..Default::default()
}
}
fn container_style(node: &Node, parent_axis: Axis, is_root: bool) -> taffy::Style {
let (direction, gap_value, constraints) = match node {
Node::Row {
gap, constraints, ..
} => (taffy::FlexDirection::Row, *gap, constraints.as_ref()),
Node::Col {
gap, constraints, ..
} => (taffy::FlexDirection::Column, *gap, constraints.as_ref()),
Node::Grid {
columns,
gap,
auto_rows,
..
} => return crate::preset::simple_grid_style(*columns, *gap, *auto_rows),
Node::TaffyPassthrough { style, .. } => return style.as_ref().clone(),
Node::Panel { .. } | Node::GridItemWrapper { .. } => return taffy::Style::default(),
};
let gap_size = match direction {
taffy::FlexDirection::Row | taffy::FlexDirection::RowReverse => taffy::Size {
width: taffy::LengthPercentage::length(gap_value),
height: taffy::LengthPercentage::length(0.0),
},
_ => taffy::Size {
width: taffy::LengthPercentage::length(0.0),
height: taffy::LengthPercentage::length(gap_value),
},
};
match (is_root, constraints) {
(true, _) => taffy::Style {
flex_direction: direction,
size: taffy::Size {
width: taffy::Dimension::percent(1.0),
height: taffy::Dimension::percent(1.0),
},
flex_grow: 0.0,
flex_basis: taffy::Dimension::auto(),
flex_shrink: 0.0,
gap: gap_size,
..Default::default()
},
(false, Some(c)) => {
let mut style = constraints_to_style(c, parent_axis);
style.flex_direction = direction;
style.gap = gap_size;
style
}
(false, None) => taffy::Style {
flex_direction: direction,
size: taffy::Size::auto(),
flex_grow: 1.0,
flex_basis: taffy::Dimension::length(0.0),
flex_shrink: 1.0,
gap: gap_size,
..Default::default()
},
}
}
pub(crate) fn axis_of(node: &Node) -> Axis {
match node {
Node::Col { .. } => Axis::Col,
Node::TaffyPassthrough { style, .. }
if matches!(
style.flex_direction,
taffy::FlexDirection::Column | taffy::FlexDirection::ColumnReverse
) =>
{
Axis::Col
}
_ => Axis::Row,
}
}
pub fn compile(tree: &LayoutTree) -> Result<CompileResult, PaneError> {
compile_with(tree, None)
}
pub fn compile_with(
tree: &LayoutTree,
reuse: Option<CompileResult>,
) -> Result<CompileResult, PaneError> {
compile_with_sizes(tree, reuse, &[])
}
pub fn compile_with_sizes(
tree: &LayoutTree,
reuse: Option<CompileResult>,
panel_sizes: &[Option<(f32, f32)>],
) -> Result<CompileResult, PaneError> {
#[cfg(debug_assertions)]
tree.validate()?;
let root_id = tree
.root()
.ok_or(PaneError::InvalidTree(TreeError::RootNotSet))?;
let arena_len = tree.arena_len();
let (taffy_tree, node_map) = match reuse {
Some(prev) => {
let mut t = prev.taffy_tree;
t.clear();
let mut nm = prev.node_map;
nm.iter_mut().for_each(|slot| *slot = None);
nm.resize(arena_len, None);
(t, nm)
}
None => (taffy::TaffyTree::new(), vec![None; arena_len]),
};
let mut ctx = CompileCtx {
taffy_tree,
node_map,
panel_sizes,
};
let root_node = tree.node(root_id).ok_or(PaneError::NodeNotFound(root_id))?;
let root_axis = axis_of(root_node);
let taffy_root = compile_node(tree, root_id, root_axis, true, &mut ctx)?;
Ok(CompileResult {
taffy_tree: ctx.taffy_tree,
node_map: ctx.node_map,
root: taffy_root,
})
}
fn panel_leaf_style(
constraints: &Constraints,
parent_axis: Axis,
intrinsic: Option<(f32, f32)>,
) -> taffy::Style {
let mut style = constraints_to_style(constraints, parent_axis);
match intrinsic {
Some((w, h)) => {
style.size = taffy::Size {
width: taffy::Dimension::length(w),
height: taffy::Dimension::length(h),
};
style.flex_grow = 0.0;
style.flex_shrink = 0.0;
style.flex_basis = taffy::Dimension::auto();
}
None => {}
}
style
}
fn compile_node(
tree: &LayoutTree,
nid: NodeId,
parent_axis: Axis,
is_root: bool,
ctx: &mut CompileCtx<'_>,
) -> Result<taffy::NodeId, PaneError> {
let node = tree.node(nid).ok_or(PaneError::NodeNotFound(nid))?;
let taffy_id = match node {
Node::Panel {
constraints, id, ..
} => {
let intrinsic = ctx.panel_sizes.get(id.raw() as usize).copied().flatten();
let style = panel_leaf_style(constraints, parent_axis, intrinsic);
ctx.taffy_tree
.new_leaf(style)
.map_err(|e| PaneError::InvalidTree(TreeError::TaffyError(e.to_string().into())))?
}
Node::GridItemWrapper { span, child } => {
let style = crate::preset::grid_item_style(*span);
let child_axis = Axis::Row;
let child_taffy = compile_node(tree, *child, child_axis, false, ctx)?;
ctx.taffy_tree
.new_with_children(style, &[child_taffy])
.map_err(|e| PaneError::InvalidTree(TreeError::TaffyError(e.to_string().into())))?
}
Node::Row { .. } | Node::Col { .. } | Node::Grid { .. } | Node::TaffyPassthrough { .. } => {
let taffy_children = compile_children(tree, node, ctx)?;
let style = container_style(node, parent_axis, is_root);
ctx.taffy_tree
.new_with_children(style, &taffy_children)
.map_err(|e| PaneError::InvalidTree(TreeError::TaffyError(e.to_string().into())))?
}
};
*ctx.node_map
.get_mut(nid.raw() as usize)
.ok_or(PaneError::NodeNotFound(nid))? = Some(taffy_id);
Ok(taffy_id)
}
fn compile_children(
tree: &LayoutTree,
node: &Node,
ctx: &mut CompileCtx<'_>,
) -> Result<Vec<taffy::NodeId>, PaneError> {
let child_axis = axis_of(node);
node.children()
.iter()
.map(|&child_nid| compile_node(tree, child_nid, child_axis, false, ctx))
.collect()
}
fn validate_viewport(width: f32, height: f32) -> Result<(), PaneError> {
validate_dimension(width)?;
validate_dimension(height)
}
fn validate_dimension(d: f32) -> Result<(), PaneError> {
check_f32_non_negative(d).map_err(|e| {
PaneError::InvalidViewport(match e {
FloatInvalid::Nan => ViewportError::IsNan,
FloatInvalid::Negative => ViewportError::IsNegative,
FloatInvalid::Infinite => ViewportError::IsInfinite,
})
})
}
pub fn compute_layout(
result: &mut CompileResult,
width: f32,
height: f32,
) -> Result<(), PaneError> {
validate_viewport(width, height)?;
let available = taffy::Size {
width: taffy::AvailableSpace::Definite(width),
height: taffy::AvailableSpace::Definite(height),
};
result
.taffy_tree
.compute_layout(result.root, available)
.map_err(|e| PaneError::InvalidTree(TreeError::TaffyError(e.to_string().into())))
}
pub fn panel_layout<'a>(
result: &'a CompileResult,
tree: &LayoutTree,
pid: PanelId,
) -> Option<&'a taffy::Layout> {
let nid = tree.node_for_panel(pid)?;
let taffy_id = result.node_map.get(nid.raw() as usize)?.as_ref()?;
result.taffy_tree.layout(*taffy_id).ok()
}