1use std::collections::HashMap;
2use taffy::prelude::*;
3
4use crate::scene::{
5 AlignItems, Display, ElementKind, FlexDirection, FlexWrap, JustifyContent, LayoutRect, NodeId,
6 Overflow, Position, ResolvedStyle, SceneGraph, SceneNode, SizeValue,
7};
8
9pub struct LayoutEngine {
10 tree: TaffyTree,
11 node_map: HashMap<NodeId, taffy::NodeId>,
12 reverse_map: HashMap<taffy::NodeId, NodeId>,
13}
14
15impl Default for LayoutEngine {
16 fn default() -> Self {
17 Self::new()
18 }
19}
20
21impl LayoutEngine {
22 pub fn new() -> Self {
23 Self {
24 tree: TaffyTree::new(),
25 node_map: HashMap::new(),
26 reverse_map: HashMap::new(),
27 }
28 }
29
30 pub fn compute(&mut self, scene: &mut SceneGraph, viewport_width: f32, viewport_height: f32) {
31 self.tree = TaffyTree::new();
32 self.node_map.clear();
33 self.reverse_map.clear();
34
35 if let Some(root_id) = scene.root {
36 for i in 0..scene.nodes.len() {
38 let tag = scene.nodes[i].tag.clone();
39 if tag == "html" || tag == "body" {
40 if scene.nodes[i].style.width == SizeValue::Auto {
41 scene.nodes[i].style.width = SizeValue::Px(viewport_width);
42 }
43 if scene.nodes[i].style.height == SizeValue::Auto {
44 scene.nodes[i].style.min_height = SizeValue::Px(viewport_height);
45 }
46 if tag == "body" && scene.nodes[i].style.overflow == Overflow::Visible {
48 scene.nodes[i].style.overflow = Overflow::Scroll;
49 }
50 }
51 }
52
53 let taffy_root = self.build_taffy_tree(scene, root_id, viewport_width, viewport_height);
54
55 self.tree
56 .compute_layout(
57 taffy_root,
58 Size {
59 width: AvailableSpace::Definite(viewport_width),
60 height: AvailableSpace::Definite(viewport_height),
61 },
62 )
63 .ok();
64
65 self.apply_layout(scene, root_id, 0.0, 0.0);
66 }
67 }
68
69 fn build_taffy_tree(
70 &mut self,
71 scene: &SceneGraph,
72 node_id: NodeId,
73 vw: f32,
74 vh: f32,
75 ) -> taffy::NodeId {
76 let node = scene.get(node_id);
77 let style = &node.style;
78
79 let taffy_style = self.convert_style(style, node, vw, vh, scene);
80
81 let children: Vec<taffy::NodeId> = node
82 .children
83 .iter()
84 .map(|&child_id| self.build_taffy_tree(scene, child_id, vw, vh))
85 .collect();
86
87 let taffy_node = self.tree.new_with_children(taffy_style, &children).unwrap();
88 self.node_map.insert(node_id, taffy_node);
89 self.reverse_map.insert(taffy_node, node_id);
90 taffy_node
91 }
92
93 fn convert_style(
94 &self,
95 style: &ResolvedStyle,
96 node: &SceneNode,
97 vw: f32,
98 vh: f32,
99 scene_graph: &SceneGraph,
100 ) -> Style {
101 self.build_taffy_style(style, node, vw, vh, scene_graph)
102 }
103
104 #[allow(clippy::field_reassign_with_default)]
105 fn build_taffy_style(
106 &self,
107 style: &ResolvedStyle,
108 node: &SceneNode,
109 vw: f32,
110 vh: f32,
111 scene_graph: &SceneGraph,
112 ) -> Style {
113 let mut ts = Style::default();
114
115 ts.display = match style.display {
117 Display::Flex => taffy::Display::Flex,
118 Display::Block => taffy::Display::Block,
119 Display::None => taffy::Display::None,
120 Display::Inline => taffy::Display::Flex, };
122
123 ts.flex_direction = match style.flex_direction {
125 FlexDirection::Row => taffy::FlexDirection::Row,
126 FlexDirection::Column => taffy::FlexDirection::Column,
127 FlexDirection::RowReverse => taffy::FlexDirection::RowReverse,
128 FlexDirection::ColumnReverse => taffy::FlexDirection::ColumnReverse,
129 };
130
131 ts.justify_content = Some(match style.justify_content {
132 JustifyContent::Start => taffy::JustifyContent::FlexStart,
133 JustifyContent::Center => taffy::JustifyContent::Center,
134 JustifyContent::End => taffy::JustifyContent::FlexEnd,
135 JustifyContent::SpaceBetween => taffy::JustifyContent::SpaceBetween,
136 JustifyContent::SpaceAround => taffy::JustifyContent::SpaceAround,
137 JustifyContent::SpaceEvenly => taffy::JustifyContent::SpaceEvenly,
138 });
139
140 ts.align_items = Some(match style.align_items {
141 AlignItems::Stretch => taffy::AlignItems::Stretch,
142 AlignItems::Start => taffy::AlignItems::FlexStart,
143 AlignItems::Center => taffy::AlignItems::Center,
144 AlignItems::End => taffy::AlignItems::FlexEnd,
145 });
146
147 ts.flex_wrap = match style.flex_wrap {
148 FlexWrap::NoWrap => taffy::FlexWrap::NoWrap,
149 FlexWrap::Wrap => taffy::FlexWrap::Wrap,
150 };
151
152 ts.flex_grow = style.flex_grow;
153 ts.flex_shrink = style.flex_shrink;
154 ts.gap = Size {
155 width: LengthPercentage::Length(style.gap),
156 height: LengthPercentage::Length(style.gap),
157 };
158
159 ts.position = match style.position {
161 Position::Relative => taffy::Position::Relative,
162 Position::Absolute | Position::Fixed => taffy::Position::Absolute,
163 };
164
165 ts.inset = Rect {
166 top: self.convert_length_auto(style.top, vw, vh),
167 right: self.convert_length_auto(style.right, vw, vh),
168 bottom: self.convert_length_auto(style.bottom, vw, vh),
169 left: self.convert_length_auto(style.left, vw, vh),
170 };
171
172 ts.size = Size {
174 width: self.convert_dimension(style.font_size, style.width, vw, vh),
175 height: self.convert_dimension(style.font_size, style.height, vw, vh),
176 };
177 ts.min_size = Size {
178 width: self.convert_dimension(style.font_size, style.min_width, vw, vh),
179 height: self.convert_dimension(style.font_size, style.min_height, vw, vh),
180 };
181 ts.max_size = Size {
182 width: self.convert_dimension(style.font_size, style.max_width, vw, vh),
183 height: self.convert_dimension(style.font_size, style.max_height, vw, vh),
184 };
185
186 ts.margin = Rect {
188 top: LengthPercentageAuto::Length(style.margin[0]),
189 right: LengthPercentageAuto::Length(style.margin[1]),
190 bottom: LengthPercentageAuto::Length(style.margin[2]),
191 left: LengthPercentageAuto::Length(style.margin[3]),
192 };
193
194 ts.padding = Rect {
196 top: LengthPercentage::Length(style.padding[0]),
197 right: LengthPercentage::Length(style.padding[1]),
198 bottom: LengthPercentage::Length(style.padding[2]),
199 left: LengthPercentage::Length(style.padding[3]),
200 };
201
202 ts.border = Rect {
204 top: LengthPercentage::Length(style.border_width[0]),
205 right: LengthPercentage::Length(style.border_width[1]),
206 bottom: LengthPercentage::Length(style.border_width[2]),
207 left: LengthPercentage::Length(style.border_width[3]),
208 };
209
210 if node.kind == ElementKind::Text {
212 if let Some(text) = &node.text_content {
213 let font_size = if let Some(parent_id) = node.parent {
215 scene_graph.get(parent_id).style.font_size
216 } else {
217 style.font_size
218 };
219 let font_weight = if let Some(parent_id) = node.parent {
220 scene_graph.get(parent_id).style.font_weight
221 } else {
222 style.font_weight
223 };
224 let line_height = if let Some(parent_id) = node.parent {
225 scene_graph.get(parent_id).style.line_height
226 } else {
227 style.line_height
228 };
229
230 let char_count = text.chars().count() as f32;
231 let ratio = if font_weight >= 700 { 0.72 } else { 0.62 };
232 let text_width = char_count * font_size * ratio;
233 let text_height = font_size * line_height;
234 ts.size = Size {
235 width: Dimension::Length(text_width),
236 height: Dimension::Length(text_height),
237 };
238 }
239 }
240
241 if style.display == Display::Block {
243 ts.display = taffy::Display::Flex;
244 ts.flex_direction = taffy::FlexDirection::Column;
245 }
246
247 ts
248 }
249
250 fn convert_dimension(&self, font_size: f32, val: SizeValue, vw: f32, vh: f32) -> Dimension {
251 match val {
252 SizeValue::Px(v) => Dimension::Length(v),
253 SizeValue::Percent(v) => Dimension::Percent(v / 100.0),
254 SizeValue::Em(v) => Dimension::Length(v * font_size),
255 SizeValue::Rem(v) => Dimension::Length(v * 16.0),
256 SizeValue::Vh(v) => Dimension::Length(v / 100.0 * vh),
257 SizeValue::Vw(v) => Dimension::Length(v / 100.0 * vw),
258 SizeValue::Auto => Dimension::Auto,
259 }
260 }
261
262 fn convert_length_auto(&self, val: SizeValue, vw: f32, vh: f32) -> LengthPercentageAuto {
263 match val {
264 SizeValue::Px(v) => LengthPercentageAuto::Length(v),
265 SizeValue::Percent(v) => LengthPercentageAuto::Percent(v / 100.0),
266 SizeValue::Vh(v) => LengthPercentageAuto::Length(v / 100.0 * vh),
267 SizeValue::Vw(v) => LengthPercentageAuto::Length(v / 100.0 * vw),
268 _ => LengthPercentageAuto::Auto,
269 }
270 }
271
272 fn apply_layout(&self, scene: &mut SceneGraph, node_id: NodeId, parent_x: f32, parent_y: f32) {
273 if let Some(&taffy_node) = self.node_map.get(&node_id) {
274 if let Ok(layout) = self.tree.layout(taffy_node) {
275 let x = parent_x + layout.location.x;
276 let y = parent_y + layout.location.y;
277
278 let scene_node = scene.get_mut(node_id);
279 scene_node.layout = LayoutRect {
280 x,
281 y,
282 width: layout.size.width,
283 height: layout.size.height,
284 };
285 scene_node.dirty = false;
286
287 let children: Vec<NodeId> = scene_node.children.clone();
288 for child_id in children {
289 self.apply_layout(scene, child_id, x, y);
290 }
291
292 let mut max_bottom: f32 = 0.0;
294 for &child_id in &scene.get(node_id).children {
295 let child = scene.get(child_id);
296 let child_bottom = child.layout.y + child.layout.height - y;
297 if child_bottom > max_bottom {
298 max_bottom = child_bottom;
299 }
300 }
301 scene.get_mut(node_id).content_height = max_bottom;
302 }
303 }
304 }
305}