use crate::geometry::{Edges, Point, Rect, Size};
use crate::tree::NodeId;
#[derive(Debug, Clone)]
pub struct Fragment {
pub node: NodeId,
pub position: Point,
pub size: Size,
pub padding: Edges,
pub border: Edges,
pub margin: Edges,
pub children: Vec<Fragment>,
pub kind: FragmentKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FragmentKind {
Box,
AnonymousBox,
LineBox,
TextRun,
}
impl Fragment {
pub fn new(node: NodeId, kind: FragmentKind) -> Self {
Self {
node,
position: Point::ZERO,
size: Size::ZERO,
padding: Edges::ZERO,
border: Edges::ZERO,
margin: Edges::ZERO,
children: Vec::new(),
kind,
}
}
pub fn border_box(&self) -> Rect {
Rect::new(
self.position.x,
self.position.y,
self.size.width + self.padding.horizontal() + self.border.horizontal(),
self.size.height + self.padding.vertical() + self.border.vertical(),
)
}
pub fn margin_box(&self) -> Rect {
Rect::new(
self.position.x - self.margin.left,
self.position.y - self.margin.top,
self.size.width
+ self.padding.horizontal()
+ self.border.horizontal()
+ self.margin.horizontal(),
self.size.height
+ self.padding.vertical()
+ self.border.vertical()
+ self.margin.vertical(),
)
}
pub fn content_box(&self) -> Rect {
Rect::new(
self.position.x + self.border.left + self.padding.left,
self.position.y + self.border.top + self.padding.top,
self.size.width,
self.size.height,
)
}
pub fn absolute_position(&self, ancestors: &[&Fragment]) -> Point {
let mut x = self.position.x;
let mut y = self.position.y;
for ancestor in ancestors.iter().rev() {
x += ancestor.position.x + ancestor.border.left + ancestor.padding.left;
y += ancestor.position.y + ancestor.border.top + ancestor.padding.top;
}
Point::new(x, y)
}
}
#[derive(Debug, Clone)]
pub struct LayoutResult {
pub root: Fragment,
}
impl LayoutResult {
pub fn find_fragment(&self, node: NodeId) -> Option<&Fragment> {
find_in_fragment(&self.root, node)
}
pub fn get_layout(&self, node: NodeId) -> Option<LayoutOutput> {
let mut path = Vec::new();
if find_path_to_node(&self.root, node, &mut path) {
let fragment = path.last().unwrap();
let ancestors: Vec<&Fragment> = path[..path.len() - 1].to_vec();
let abs_pos = fragment.absolute_position(&ancestors);
Some(LayoutOutput {
position: abs_pos,
size: fragment.size,
padding: fragment.padding,
border: fragment.border,
margin: fragment.margin,
})
} else {
None
}
}
pub fn bounding_rect(&self, node: NodeId) -> Option<Rect> {
self.get_layout(node).map(|l| {
Rect::new(
l.position.x,
l.position.y,
l.size.width + l.padding.horizontal() + l.border.horizontal(),
l.size.height + l.padding.vertical() + l.border.vertical(),
)
})
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LayoutOutput {
pub position: Point,
pub size: Size,
pub padding: Edges,
pub border: Edges,
pub margin: Edges,
}
fn find_in_fragment(fragment: &Fragment, node: NodeId) -> Option<&Fragment> {
if fragment.node == node {
return Some(fragment);
}
for child in &fragment.children {
if let Some(found) = find_in_fragment(child, node) {
return Some(found);
}
}
None
}
fn find_path_to_node<'a>(
fragment: &'a Fragment,
node: NodeId,
path: &mut Vec<&'a Fragment>,
) -> bool {
path.push(fragment);
if fragment.node == node {
return true;
}
for child in &fragment.children {
if find_path_to_node(child, node, path) {
return true;
}
}
path.pop();
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_border_box() {
let mut f = Fragment::new(NodeId(0), FragmentKind::Box);
f.position = Point::new(10.0, 20.0);
f.size = Size::new(100.0, 50.0);
f.padding = Edges::all(5.0);
f.border = Edges::all(1.0);
let bb = f.border_box();
assert_eq!(bb.width, 112.0); assert_eq!(bb.height, 62.0); }
}