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}
306
307#[cfg(test)]
308mod tests {
309 use super::*;
310 use crate::scene::Color;
311
312 fn scene_with_root(tag: &str) -> (SceneGraph, NodeId) {
314 let mut scene = SceneGraph::new();
315 let root = scene.add_node(ElementKind::from_tag(tag), tag.to_string());
316 (scene, root)
317 }
318
319 fn child(scene: &mut SceneGraph, parent: NodeId, tag: &str) -> NodeId {
320 let id = scene.add_node(ElementKind::from_tag(tag), tag.to_string());
321 scene.add_child(parent, id);
322 id
323 }
324
325 fn text_child(scene: &mut SceneGraph, parent: NodeId, text: &str) -> NodeId {
326 let id = scene.add_node(ElementKind::Text, "#text".to_string());
327 scene.get_mut(id).text_content = Some(text.to_string());
328 scene.add_child(parent, id);
329 id
330 }
331
332 #[test]
333 fn flex_row_children_are_laid_out_side_by_side() {
334 let (mut scene, root) = scene_with_root("div");
335 scene.get_mut(root).style.display = Display::Flex;
336 scene.get_mut(root).style.flex_direction = FlexDirection::Row;
337 scene.get_mut(root).style.width = SizeValue::Px(300.0);
338 scene.get_mut(root).style.height = SizeValue::Px(100.0);
339
340 let a = child(&mut scene, root, "div");
341 let b = child(&mut scene, root, "div");
342 for id in [a, b] {
343 scene.get_mut(id).style.width = SizeValue::Px(50.0);
344 scene.get_mut(id).style.height = SizeValue::Px(20.0);
345 }
346
347 LayoutEngine::new().compute(&mut scene, 800.0, 600.0);
348
349 assert_eq!(scene.get(root).layout.width, 300.0);
350 assert_eq!(scene.get(a).layout.x, 0.0);
351 assert_eq!(scene.get(b).layout.x, 50.0);
352 assert_eq!(scene.get(a).layout.y, scene.get(b).layout.y);
353 assert_eq!(scene.get(a).layout.width, 50.0);
354 }
355
356 #[test]
357 fn flex_gap_separates_children() {
358 let (mut scene, root) = scene_with_root("div");
359 scene.get_mut(root).style.display = Display::Flex;
360 scene.get_mut(root).style.width = SizeValue::Px(300.0);
361 scene.get_mut(root).style.gap = 12.0;
362
363 let a = child(&mut scene, root, "div");
364 let b = child(&mut scene, root, "div");
365 for id in [a, b] {
366 scene.get_mut(id).style.width = SizeValue::Px(50.0);
367 scene.get_mut(id).style.height = SizeValue::Px(20.0);
368 }
369
370 LayoutEngine::new().compute(&mut scene, 800.0, 600.0);
371
372 assert_eq!(scene.get(b).layout.x - scene.get(a).layout.x, 62.0);
373 }
374
375 #[test]
376 fn nested_flex_positions_are_absolute_not_relative() {
377 let (mut scene, outer) = scene_with_root("div");
379 scene.get_mut(outer).style.display = Display::Flex;
380 scene.get_mut(outer).style.width = SizeValue::Px(400.0);
381 scene.get_mut(outer).style.height = SizeValue::Px(300.0);
382 scene.get_mut(outer).style.padding = [10.0, 10.0, 10.0, 10.0];
383
384 let middle = child(&mut scene, outer, "div");
385 scene.get_mut(middle).style.display = Display::Flex;
386 scene.get_mut(middle).style.flex_direction = FlexDirection::Column;
387 scene.get_mut(middle).style.width = SizeValue::Px(200.0);
388 scene.get_mut(middle).style.height = SizeValue::Px(200.0);
389 scene.get_mut(middle).style.margin = [5.0, 0.0, 0.0, 0.0];
390
391 let inner = child(&mut scene, middle, "div");
392 scene.get_mut(inner).style.width = SizeValue::Px(30.0);
393 scene.get_mut(inner).style.height = SizeValue::Px(30.0);
394
395 LayoutEngine::new().compute(&mut scene, 800.0, 600.0);
396
397 assert_eq!(scene.get(middle).layout.x, 10.0);
399 assert_eq!(scene.get(middle).layout.y, 15.0);
400 assert_eq!(scene.get(inner).layout.x, 10.0);
402 assert_eq!(scene.get(inner).layout.y, 15.0);
403 assert_eq!(scene.get(inner).layout.width, 30.0);
404 }
405
406 #[test]
407 fn padding_shrinks_the_content_box_of_a_grown_child() {
408 let (mut scene, root) = scene_with_root("div");
409 scene.get_mut(root).style.display = Display::Flex;
410 scene.get_mut(root).style.width = SizeValue::Px(200.0);
411 scene.get_mut(root).style.height = SizeValue::Px(100.0);
412 scene.get_mut(root).style.padding = [10.0, 20.0, 10.0, 20.0];
413
414 let inner = child(&mut scene, root, "div");
415 scene.get_mut(inner).style.flex_grow = 1.0;
416
417 LayoutEngine::new().compute(&mut scene, 800.0, 600.0);
418
419 let l = &scene.get(inner).layout;
420 assert_eq!(l.x, 20.0);
421 assert_eq!(l.y, 10.0);
422 assert_eq!(l.width, 160.0); assert_eq!(l.height, 80.0); }
425
426 #[test]
427 fn margins_offset_a_child_on_every_side() {
428 let (mut scene, root) = scene_with_root("div");
429 scene.get_mut(root).style.display = Display::Flex;
430 scene.get_mut(root).style.width = SizeValue::Px(200.0);
431 scene.get_mut(root).style.height = SizeValue::Px(200.0);
432
433 let inner = child(&mut scene, root, "div");
434 scene.get_mut(inner).style.margin = [7.0, 11.0, 13.0, 17.0];
436 scene.get_mut(inner).style.flex_grow = 1.0;
437
438 LayoutEngine::new().compute(&mut scene, 800.0, 600.0);
439
440 let l = &scene.get(inner).layout;
441 assert_eq!(l.x, 17.0);
442 assert_eq!(l.y, 7.0);
443 assert_eq!(l.width, 200.0 - 17.0 - 11.0);
444 assert_eq!(l.height, 200.0 - 7.0 - 13.0);
445 }
446
447 #[test]
448 fn border_width_is_part_of_the_box() {
449 let (mut scene, root) = scene_with_root("div");
450 scene.get_mut(root).style.display = Display::Flex;
451 scene.get_mut(root).style.width = SizeValue::Px(100.0);
452 scene.get_mut(root).style.height = SizeValue::Px(100.0);
453 scene.get_mut(root).style.border_width = [4.0; 4];
454 scene.get_mut(root).style.border_color = Color::BLACK;
455
456 let inner = child(&mut scene, root, "div");
457 scene.get_mut(inner).style.flex_grow = 1.0;
458
459 LayoutEngine::new().compute(&mut scene, 800.0, 600.0);
460
461 assert_eq!(scene.get(root).layout.width, 100.0);
463 assert_eq!(scene.get(inner).layout.x, 4.0);
464 assert_eq!(scene.get(inner).layout.width, 92.0);
465 }
466
467 #[test]
468 fn percent_em_rem_and_viewport_units_resolve() {
469 let (mut scene, root) = scene_with_root("div");
470 scene.get_mut(root).style.display = Display::Flex;
471 scene.get_mut(root).style.width = SizeValue::Px(400.0);
472 scene.get_mut(root).style.height = SizeValue::Px(400.0);
473
474 let pct = child(&mut scene, root, "div");
475 scene.get_mut(pct).style.width = SizeValue::Percent(25.0);
476 scene.get_mut(pct).style.height = SizeValue::Px(10.0);
477
478 let em = child(&mut scene, root, "div");
479 scene.get_mut(em).style.font_size = 20.0;
480 scene.get_mut(em).style.width = SizeValue::Em(2.0);
481 scene.get_mut(em).style.height = SizeValue::Px(10.0);
482
483 let rem = child(&mut scene, root, "div");
484 scene.get_mut(rem).style.font_size = 20.0; scene.get_mut(rem).style.width = SizeValue::Rem(2.0);
486 scene.get_mut(rem).style.height = SizeValue::Px(10.0);
487
488 let vw = child(&mut scene, root, "div");
489 scene.get_mut(vw).style.width = SizeValue::Vw(50.0);
490 scene.get_mut(vw).style.height = SizeValue::Vh(10.0);
491
492 for id in [pct, em, rem, vw] {
494 scene.get_mut(id).style.flex_shrink = 0.0;
495 }
496
497 LayoutEngine::new().compute(&mut scene, 1000.0, 600.0);
498
499 assert_eq!(scene.get(pct).layout.width, 100.0); assert_eq!(scene.get(em).layout.width, 40.0); assert_eq!(scene.get(rem).layout.width, 32.0); assert_eq!(scene.get(vw).layout.width, 500.0); assert_eq!(scene.get(vw).layout.height, 60.0); }
505
506 #[test]
507 fn absolute_positioning_uses_inset() {
508 let (mut scene, root) = scene_with_root("div");
509 scene.get_mut(root).style.display = Display::Flex;
510 scene.get_mut(root).style.width = SizeValue::Px(300.0);
511 scene.get_mut(root).style.height = SizeValue::Px(300.0);
512
513 let abs = child(&mut scene, root, "div");
514 scene.get_mut(abs).style.position = Position::Absolute;
515 scene.get_mut(abs).style.top = SizeValue::Px(25.0);
516 scene.get_mut(abs).style.left = SizeValue::Px(40.0);
517 scene.get_mut(abs).style.width = SizeValue::Px(50.0);
518 scene.get_mut(abs).style.height = SizeValue::Px(50.0);
519
520 LayoutEngine::new().compute(&mut scene, 800.0, 600.0);
521
522 assert_eq!(scene.get(abs).layout.x, 40.0);
523 assert_eq!(scene.get(abs).layout.y, 25.0);
524 }
525
526 #[test]
527 fn display_none_collapses_the_box() {
528 let (mut scene, root) = scene_with_root("div");
529 scene.get_mut(root).style.display = Display::Flex;
530 scene.get_mut(root).style.width = SizeValue::Px(200.0);
531 scene.get_mut(root).style.height = SizeValue::Px(200.0);
532
533 let hidden = child(&mut scene, root, "div");
534 scene.get_mut(hidden).style.display = Display::None;
535 scene.get_mut(hidden).style.width = SizeValue::Px(80.0);
536 scene.get_mut(hidden).style.height = SizeValue::Px(80.0);
537
538 let shown = child(&mut scene, root, "div");
539 scene.get_mut(shown).style.width = SizeValue::Px(80.0);
540 scene.get_mut(shown).style.height = SizeValue::Px(80.0);
541
542 LayoutEngine::new().compute(&mut scene, 800.0, 600.0);
543
544 assert_eq!(scene.get(hidden).layout.width, 0.0);
545 assert_eq!(scene.get(hidden).layout.height, 0.0);
546 assert_eq!(scene.get(shown).layout.x, 0.0);
548 }
549
550 #[test]
551 fn block_display_stacks_children_vertically() {
552 let (mut scene, root) = scene_with_root("div");
553 scene.get_mut(root).style.display = Display::Block;
554 scene.get_mut(root).style.width = SizeValue::Px(200.0);
555 scene.get_mut(root).style.height = SizeValue::Px(200.0);
556
557 let a = child(&mut scene, root, "div");
558 let b = child(&mut scene, root, "div");
559 for id in [a, b] {
560 scene.get_mut(id).style.width = SizeValue::Px(50.0);
561 scene.get_mut(id).style.height = SizeValue::Px(30.0);
562 }
563
564 LayoutEngine::new().compute(&mut scene, 800.0, 600.0);
565
566 assert_eq!(scene.get(a).layout.y, 0.0);
567 assert_eq!(scene.get(b).layout.y, 30.0);
568 assert_eq!(scene.get(a).layout.x, scene.get(b).layout.x);
569 }
570
571 #[test]
572 fn text_nodes_are_measured_from_the_parent_font() {
573 let (mut scene, root) = scene_with_root("div");
574 scene.get_mut(root).style.display = Display::Flex;
575 scene.get_mut(root).style.width = SizeValue::Px(1000.0);
576 scene.get_mut(root).style.height = SizeValue::Px(200.0);
577
578 let p = child(&mut scene, root, "p");
579 scene.get_mut(p).style.font_size = 20.0;
580 scene.get_mut(p).style.line_height = 1.5;
581 let t = text_child(&mut scene, p, "hello"); LayoutEngine::new().compute(&mut scene, 800.0, 600.0);
584
585 let l = &scene.get(t).layout;
586 assert!(
588 (l.width - 5.0 * 20.0 * 0.62).abs() < 0.01,
589 "width {}",
590 l.width
591 );
592 assert!((l.height - 20.0 * 1.5).abs() < 0.01, "height {}", l.height);
593 }
594
595 #[test]
596 fn bold_text_is_measured_wider_than_regular_text() {
597 let mut widths = Vec::new();
598 for weight in [400u16, 700u16] {
599 let (mut scene, root) = scene_with_root("div");
600 scene.get_mut(root).style.display = Display::Flex;
601 scene.get_mut(root).style.width = SizeValue::Px(1000.0);
602 let p = child(&mut scene, root, "p");
603 scene.get_mut(p).style.font_weight = weight;
604 let t = text_child(&mut scene, p, "hello world");
605 LayoutEngine::new().compute(&mut scene, 800.0, 600.0);
606 widths.push(scene.get(t).layout.width);
607 }
608 assert!(widths[1] > widths[0], "{:?}", widths);
609 }
610
611 #[test]
612 fn html_and_body_are_stretched_to_the_viewport() {
613 let mut scene = SceneGraph::new();
614 let html = scene.add_node(ElementKind::from_tag("html"), "html".to_string());
615 let body = child(&mut scene, html, "body");
616
617 LayoutEngine::new().compute(&mut scene, 1024.0, 768.0);
618
619 assert_eq!(scene.get(html).layout.width, 1024.0);
620 assert_eq!(scene.get(body).layout.width, 1024.0);
621 assert!(scene.get(html).layout.height >= 768.0);
622 assert_eq!(scene.get(body).style.overflow, Overflow::Scroll);
624 }
625
626 #[test]
627 fn explicit_html_size_is_not_overridden() {
628 let mut scene = SceneGraph::new();
629 let html = scene.add_node(ElementKind::from_tag("html"), "html".to_string());
630 scene.get_mut(html).style.width = SizeValue::Px(300.0);
631
632 LayoutEngine::new().compute(&mut scene, 1024.0, 768.0);
633
634 assert_eq!(scene.get(html).layout.width, 300.0);
635 }
636
637 #[test]
638 fn content_height_tracks_overflowing_children() {
639 let (mut scene, root) = scene_with_root("div");
640 scene.get_mut(root).style.display = Display::Flex;
641 scene.get_mut(root).style.flex_direction = FlexDirection::Column;
642 scene.get_mut(root).style.width = SizeValue::Px(200.0);
643 scene.get_mut(root).style.height = SizeValue::Px(100.0);
644 scene.get_mut(root).style.overflow = Overflow::Scroll;
645
646 for _ in 0..4 {
647 let c = child(&mut scene, root, "div");
648 scene.get_mut(c).style.height = SizeValue::Px(60.0);
649 scene.get_mut(c).style.flex_shrink = 0.0;
650 }
651
652 LayoutEngine::new().compute(&mut scene, 800.0, 600.0);
653
654 assert_eq!(scene.get(root).layout.height, 100.0);
655 assert_eq!(scene.get(root).content_height, 240.0);
656 }
657
658 #[test]
659 fn min_and_max_size_constraints_are_applied() {
660 let (mut scene, root) = scene_with_root("div");
661 scene.get_mut(root).style.display = Display::Flex;
662 scene.get_mut(root).style.width = SizeValue::Px(400.0);
663 scene.get_mut(root).style.height = SizeValue::Px(400.0);
664
665 let clamped = child(&mut scene, root, "div");
666 scene.get_mut(clamped).style.width = SizeValue::Px(300.0);
667 scene.get_mut(clamped).style.max_width = SizeValue::Px(120.0);
668 scene.get_mut(clamped).style.height = SizeValue::Px(10.0);
669 scene.get_mut(clamped).style.min_height = SizeValue::Px(50.0);
670
671 LayoutEngine::new().compute(&mut scene, 800.0, 600.0);
672
673 assert_eq!(scene.get(clamped).layout.width, 120.0);
674 assert_eq!(scene.get(clamped).layout.height, 50.0);
675 }
676
677 #[test]
678 fn justify_content_center_centres_the_child() {
679 let (mut scene, root) = scene_with_root("div");
680 scene.get_mut(root).style.display = Display::Flex;
681 scene.get_mut(root).style.justify_content = JustifyContent::Center;
682 scene.get_mut(root).style.align_items = AlignItems::Center;
683 scene.get_mut(root).style.width = SizeValue::Px(200.0);
684 scene.get_mut(root).style.height = SizeValue::Px(200.0);
685
686 let inner = child(&mut scene, root, "div");
687 scene.get_mut(inner).style.width = SizeValue::Px(40.0);
688 scene.get_mut(inner).style.height = SizeValue::Px(40.0);
689
690 LayoutEngine::new().compute(&mut scene, 800.0, 600.0);
691
692 assert_eq!(scene.get(inner).layout.x, 80.0);
693 assert_eq!(scene.get(inner).layout.y, 80.0);
694 }
695
696 #[test]
697 fn layout_clears_the_dirty_flag() {
698 let (mut scene, root) = scene_with_root("div");
699 scene.get_mut(root).style.width = SizeValue::Px(10.0);
700 scene.get_mut(root).style.height = SizeValue::Px(10.0);
701 scene.get_mut(root).dirty = true;
702
703 LayoutEngine::new().compute(&mut scene, 800.0, 600.0);
704
705 assert!(!scene.get(root).dirty);
706 }
707}