use slotmap::{new_key_type, Key, SlotMap};
use std::collections::HashMap;
use taffy::prelude::*;
use crate::element::ElementBounds;
use crate::text_measure::{measure_text_with_options, TextLayoutOptions};
new_key_type! {
pub struct LayoutNodeId;
}
#[derive(Clone, Debug)]
pub struct TextMeasureContext {
pub content: String,
pub font_size: f32,
pub line_height: f32,
pub wrap: bool,
pub font_name: Option<String>,
pub generic_font: crate::div::GenericFont,
pub font_weight: u16,
pub italic: bool,
}
impl LayoutNodeId {
pub fn to_raw(self) -> u64 {
self.data().as_ffi()
}
pub fn from_raw(raw: u64) -> Self {
Self::from(slotmap::KeyData::from_ffi(raw))
}
}
fn text_measure_function(
known_dimensions: Size<Option<f32>>,
available_space: Size<AvailableSpace>,
_node_id: NodeId,
node_context: Option<&mut TextMeasureContext>,
_style: &Style,
) -> Size<f32> {
let width = known_dimensions.width;
let height = known_dimensions.height;
if let (Some(w), Some(h)) = (width, height) {
return Size {
width: w,
height: h,
};
}
let Some(ctx) = node_context else {
return Size::ZERO;
};
if !ctx.wrap {
let mut options = TextLayoutOptions::new();
options.font_name = ctx.font_name.clone();
options.generic_font = ctx.generic_font;
options.font_weight = ctx.font_weight;
options.italic = ctx.italic;
options.line_height = ctx.line_height;
let metrics = measure_text_with_options(&ctx.content, ctx.font_size, &options);
return Size {
width: width.unwrap_or(metrics.width),
height: height.unwrap_or(metrics.height),
};
}
let max_width = match available_space.width {
AvailableSpace::Definite(w) => Some(w),
AvailableSpace::MaxContent => None,
AvailableSpace::MinContent => Some(0.0), };
let max_width = width.or(max_width);
let mut options = TextLayoutOptions::new();
options.font_name = ctx.font_name.clone();
options.generic_font = ctx.generic_font;
options.font_weight = ctx.font_weight;
options.italic = ctx.italic;
options.line_height = ctx.line_height;
options.max_width = max_width;
let metrics = measure_text_with_options(&ctx.content, ctx.font_size, &options);
Size {
width: width.unwrap_or(metrics.width),
height: height.unwrap_or(metrics.height),
}
}
pub struct LayoutTree {
taffy: TaffyTree<TextMeasureContext>,
node_map: SlotMap<LayoutNodeId, NodeId>,
reverse_map: HashMap<NodeId, LayoutNodeId>,
}
impl LayoutTree {
pub fn new() -> Self {
Self {
taffy: TaffyTree::new(),
node_map: SlotMap::with_key(),
reverse_map: HashMap::new(),
}
}
pub fn create_node(&mut self, style: Style) -> LayoutNodeId {
let taffy_node = self.taffy.new_leaf(style).unwrap();
let id = self.node_map.insert(taffy_node);
self.reverse_map.insert(taffy_node, id);
id
}
pub fn create_text_node(&mut self, style: Style, context: TextMeasureContext) -> LayoutNodeId {
let taffy_node = self.taffy.new_leaf_with_context(style, context).unwrap();
let id = self.node_map.insert(taffy_node);
self.reverse_map.insert(taffy_node, id);
id
}
pub fn set_style(&mut self, id: LayoutNodeId, style: Style) {
if let Some(&taffy_node) = self.node_map.get(id) {
let _ = self.taffy.set_style(taffy_node, style);
}
}
pub fn get_style(&self, id: LayoutNodeId) -> Option<Style> {
self.node_map
.get(id)
.and_then(|&taffy_node| self.taffy.style(taffy_node).ok())
.cloned()
}
pub fn add_child(&mut self, parent: LayoutNodeId, child: LayoutNodeId) {
if let (Some(&parent_node), Some(&child_node)) =
(self.node_map.get(parent), self.node_map.get(child))
{
let _ = self.taffy.add_child(parent_node, child_node);
}
}
pub fn compute_layout(&mut self, root: LayoutNodeId, available_space: Size<AvailableSpace>) {
if let Some(&taffy_node) = self.node_map.get(root) {
let _ = self.taffy.compute_layout_with_measure(
taffy_node,
available_space,
text_measure_function,
);
}
}
pub fn get_layout(&self, id: LayoutNodeId) -> Option<&Layout> {
self.node_map
.get(id)
.and_then(|&taffy_node| self.taffy.layout(taffy_node).ok())
}
pub fn node_exists(&self, id: LayoutNodeId) -> bool {
self.node_map.contains_key(id)
}
pub fn remove_node(&mut self, id: LayoutNodeId) {
if let Some(taffy_node) = self.node_map.remove(id) {
self.reverse_map.remove(&taffy_node);
let _ = self.taffy.remove(taffy_node);
}
}
pub fn children(&self, parent: LayoutNodeId) -> Vec<LayoutNodeId> {
let Some(&taffy_node) = self.node_map.get(parent) else {
return Vec::new();
};
let Ok(children) = self.taffy.children(taffy_node) else {
return Vec::new();
};
children
.iter()
.filter_map(|&child_taffy| self.reverse_map.get(&child_taffy).copied())
.collect()
}
pub fn get_bounds(&self, id: LayoutNodeId, parent_offset: (f32, f32)) -> Option<ElementBounds> {
self.get_layout(id)
.map(|layout| ElementBounds::from_layout(layout, parent_offset))
}
pub fn get_absolute_bounds(&self, id: LayoutNodeId) -> Option<ElementBounds> {
let &taffy_node = self.node_map.get(id)?;
let layout = self.taffy.layout(taffy_node).ok()?;
let mut offset_x = 0.0f32;
let mut offset_y = 0.0f32;
let mut current = taffy_node;
while let Some(parent) = self.taffy.parent(current) {
if let Ok(parent_layout) = self.taffy.layout(parent) {
offset_x += parent_layout.location.x;
offset_y += parent_layout.location.y;
}
current = parent;
}
Some(ElementBounds {
x: offset_x + layout.location.x,
y: offset_y + layout.location.y,
width: layout.size.width,
height: layout.size.height,
})
}
pub fn ancestors(&self, id: LayoutNodeId) -> Vec<LayoutNodeId> {
let mut result = Vec::new();
let Some(&taffy_node) = self.node_map.get(id) else {
return result;
};
let mut current = taffy_node;
while let Some(parent) = self.taffy.parent(current) {
if let Some(&layout_id) = self.reverse_map.get(&parent) {
result.push(layout_id);
}
current = parent;
}
result
}
pub fn get_content_size(&self, id: LayoutNodeId) -> Option<(f32, f32)> {
self.get_layout(id)
.map(|layout| (layout.content_size.width, layout.content_size.height))
}
pub fn len(&self) -> usize {
self.node_map.len()
}
pub fn is_empty(&self) -> bool {
self.node_map.is_empty()
}
pub fn clear_children(&mut self, parent: LayoutNodeId) {
let Some(&parent_taffy) = self.node_map.get(parent) else {
return;
};
let Ok(children) = self.taffy.children(parent_taffy) else {
return;
};
let children_to_remove: Vec<_> = children.to_vec();
for child_taffy in children_to_remove {
if let Some(&child_id) = self.reverse_map.get(&child_taffy) {
self.remove_subtree(child_id);
}
}
}
pub fn remove_subtree(&mut self, id: LayoutNodeId) {
let children = self.children(id);
for child in children {
self.remove_subtree(child);
}
self.remove_node(id);
}
pub fn replace_children(
&mut self,
parent: LayoutNodeId,
new_children: Vec<LayoutNodeId>,
) -> Vec<LayoutNodeId> {
let Some(&parent_taffy) = self.node_map.get(parent) else {
return Vec::new();
};
let old_children = self.children(parent);
let new_taffy_children: Vec<_> = new_children
.iter()
.filter_map(|&id| self.node_map.get(id).copied())
.collect();
let _ = self.taffy.set_children(parent_taffy, &new_taffy_children);
old_children
}
}
impl Default for LayoutTree {
fn default() -> Self {
Self::new()
}
}