1use crate::{
2 Scalar,
3 layout::{CoordsMapping, Layout, LayoutEngine, LayoutItem, LayoutNode},
4 widget::{
5 WidgetId,
6 unit::{
7 WidgetUnit,
8 area::AreaBox,
9 content::ContentBox,
10 flex::FlexBox,
11 grid::GridBox,
12 image::{ImageBox, ImageBoxSizeValue},
13 size::{SizeBox, SizeBoxAspectRatio, SizeBoxSizeValue},
14 text::{TextBox, TextBoxSizeValue},
15 },
16 utils::{Rect, Vec2, lerp},
17 },
18};
19use std::collections::HashMap;
20
21pub trait TextMeasurementEngine {
22 fn measure_text(
23 &self,
24 size_available: Vec2,
25 mapping: &CoordsMapping,
26 widget: &TextBox,
27 ) -> Option<Rect>;
28}
29
30impl TextMeasurementEngine for () {
31 fn measure_text(&self, _: Vec2, _: &CoordsMapping, _: &TextBox) -> Option<Rect> {
32 None
33 }
34}
35
36pub struct DefaultLayoutEngine<TME: TextMeasurementEngine = ()> {
37 text_measurement_engine: TME,
38}
39
40impl<TME: TextMeasurementEngine + Default> Default for DefaultLayoutEngine<TME> {
41 fn default() -> Self {
42 Self {
43 text_measurement_engine: TME::default(),
44 }
45 }
46}
47
48impl<TME: TextMeasurementEngine + Clone> Clone for DefaultLayoutEngine<TME> {
49 fn clone(&self) -> Self {
50 Self {
51 text_measurement_engine: self.text_measurement_engine.clone(),
52 }
53 }
54}
55
56impl<TME: TextMeasurementEngine + Copy> Copy for DefaultLayoutEngine<TME> {}
57
58impl<TME: TextMeasurementEngine> DefaultLayoutEngine<TME> {
59 pub fn new(engine: TME) -> Self {
60 Self {
61 text_measurement_engine: engine,
62 }
63 }
64
65 pub fn layout_node(
66 &self,
67 size_available: Vec2,
68 mapping: &CoordsMapping,
69 unit: &WidgetUnit,
70 ) -> Option<LayoutNode> {
71 match unit {
72 WidgetUnit::None | WidgetUnit::PortalBox(_) => None,
73 WidgetUnit::AreaBox(b) => self.layout_area_box(size_available, mapping, b),
74 WidgetUnit::ContentBox(b) => self.layout_content_box(size_available, mapping, b),
75 WidgetUnit::FlexBox(b) => self.layout_flex_box(size_available, mapping, b),
76 WidgetUnit::GridBox(b) => self.layout_grid_box(size_available, mapping, b),
77 WidgetUnit::SizeBox(b) => self.layout_size_box(size_available, mapping, b),
78 WidgetUnit::ImageBox(b) => self.layout_image_box(size_available, b),
79 WidgetUnit::TextBox(b) => self.layout_text_box(size_available, mapping, b),
80 }
81 }
82
83 pub fn layout_area_box(
84 &self,
85 size_available: Vec2,
86 mapping: &CoordsMapping,
87 unit: &AreaBox,
88 ) -> Option<LayoutNode> {
89 if !unit.id.is_valid() {
90 return None;
91 }
92 let (children, w, h) =
93 if let Some(child) = self.layout_node(size_available, mapping, &unit.slot) {
94 let w = child.local_space.width();
95 let h = child.local_space.height();
96 (vec![child], w, h)
97 } else {
98 (vec![], 0.0, 0.0)
99 };
100 let local_space = Rect {
101 left: 0.0,
102 right: w,
103 top: 0.0,
104 bottom: h,
105 };
106 Some(LayoutNode {
107 id: unit.id.to_owned(),
108 local_space,
109 children,
110 })
111 }
112
113 pub fn layout_content_box(
114 &self,
115 size_available: Vec2,
116 mapping: &CoordsMapping,
117 unit: &ContentBox,
118 ) -> Option<LayoutNode> {
119 if !unit.id.is_valid() {
120 return None;
121 }
122 let children = unit
123 .items
124 .iter()
125 .filter_map(|item| {
126 let left = lerp(0.0, size_available.x, item.layout.anchors.left);
127 let left = left
128 + item.layout.margin.left
129 + item.layout.offset.x
130 + unit.content_reposition.offset.x;
131 let left = left * unit.content_reposition.scale.x;
132 let right = lerp(0.0, size_available.x, item.layout.anchors.right);
133 let right = right - item.layout.margin.right
134 + item.layout.offset.x
135 + unit.content_reposition.offset.x;
136 let right = right * unit.content_reposition.scale.x;
137 let top = lerp(0.0, size_available.y, item.layout.anchors.top);
138 let top = top
139 + item.layout.margin.top
140 + item.layout.offset.y
141 + unit.content_reposition.offset.y;
142 let top = top * unit.content_reposition.scale.y;
143 let bottom = lerp(0.0, size_available.y, item.layout.anchors.bottom);
144 let bottom = bottom - item.layout.margin.bottom
145 + item.layout.offset.y
146 + unit.content_reposition.offset.y;
147 let bottom = bottom * unit.content_reposition.scale.y;
148 let width = (right - left).max(0.0);
149 let height = (bottom - top).max(0.0);
150 let size = Vec2 {
151 x: width,
152 y: height,
153 };
154 if let Some(mut child) = self.layout_node(size, mapping, &item.slot) {
155 let diff = child.local_space.width() - width;
156 let ox = lerp(0.0, diff, item.layout.align.x);
157 child.local_space.left += left - ox;
158 child.local_space.right += left - ox;
159 let diff = child.local_space.height() - height;
160 let oy = lerp(0.0, diff, item.layout.align.y);
161 child.local_space.top += top - oy;
162 child.local_space.bottom += top - oy;
163 let w = child.local_space.width().min(size_available.x);
164 let h = child.local_space.height().min(size_available.y);
165 if item.layout.keep_in_bounds.cut.left {
166 child.local_space.left = child.local_space.left.max(0.0);
167 if item.layout.keep_in_bounds.preserve.width {
168 child.local_space.right = child.local_space.left + w;
169 }
170 }
171 if item.layout.keep_in_bounds.cut.right {
172 child.local_space.right = child.local_space.right.min(size_available.x);
173 if item.layout.keep_in_bounds.preserve.width {
174 child.local_space.left = child.local_space.right - w;
175 }
176 }
177 if item.layout.keep_in_bounds.cut.top {
178 child.local_space.top = child.local_space.top.max(0.0);
179 if item.layout.keep_in_bounds.preserve.height {
180 child.local_space.bottom = child.local_space.top + h;
181 }
182 }
183 if item.layout.keep_in_bounds.cut.bottom {
184 child.local_space.bottom = child.local_space.bottom.min(size_available.y);
185 if item.layout.keep_in_bounds.preserve.height {
186 child.local_space.top = child.local_space.bottom - h;
187 }
188 }
189 Some(child)
190 } else {
191 None
192 }
193 })
194 .collect::<Vec<_>>();
195 Some(LayoutNode {
196 id: unit.id.to_owned(),
197 local_space: Rect {
198 left: 0.0,
199 right: size_available.x,
200 top: 0.0,
201 bottom: size_available.y,
202 },
203 children,
204 })
205 }
206
207 pub fn layout_flex_box(
208 &self,
209 size_available: Vec2,
210 mapping: &CoordsMapping,
211 unit: &FlexBox,
212 ) -> Option<LayoutNode> {
213 if !unit.id.is_valid() {
214 return None;
215 }
216 if unit.wrap {
217 Some(self.layout_flex_box_wrapping(size_available, mapping, unit))
218 } else {
219 Some(self.layout_flex_box_no_wrap(size_available, mapping, unit))
220 }
221 }
222
223 pub fn layout_flex_box_wrapping(
224 &self,
225 size_available: Vec2,
226 mapping: &CoordsMapping,
227 unit: &FlexBox,
228 ) -> LayoutNode {
229 let main_available = if unit.direction.is_horizontal() {
230 size_available.x
231 } else {
232 size_available.y
233 };
234 let (lines, count) = {
235 let mut main = 0.0;
236 let mut cross: Scalar = 0.0;
237 let mut grow = 0.0;
238 let items = unit
239 .items
240 .iter()
241 .filter(|item| item.slot.is_some() && item.slot.as_data().unwrap().id().is_valid())
242 .collect::<Vec<_>>();
243 let count = items.len();
244 let mut lines = vec![];
245 let mut line = vec![];
246 for item in items {
247 let local_main = item.layout.basis.unwrap_or_else(|| {
248 if unit.direction.is_horizontal() {
249 self.calc_unit_min_width(size_available, mapping, &item.slot)
250 } else {
251 self.calc_unit_min_height(size_available, mapping, &item.slot)
252 }
253 });
254 let local_main = local_main
255 + if unit.direction.is_horizontal() {
256 item.layout.margin.left + item.layout.margin.right
257 } else {
258 item.layout.margin.top + item.layout.margin.bottom
259 };
260 let local_cross = if unit.direction.is_horizontal() {
261 self.calc_unit_min_height(size_available, mapping, &item.slot)
262 } else {
263 self.calc_unit_min_width(size_available, mapping, &item.slot)
264 };
265 let local_cross = local_cross
266 + if unit.direction.is_horizontal() {
267 item.layout.margin.top + item.layout.margin.bottom
268 } else {
269 item.layout.margin.left + item.layout.margin.right
270 };
271 if !line.is_empty() && main + local_main > main_available {
272 main += line.len().saturating_sub(1) as Scalar * unit.separation;
273 lines.push((main, cross, grow, std::mem::take(&mut line)));
274 main = 0.0;
275 cross = 0.0;
276 grow = 0.0;
277 }
278 main += local_main;
279 cross = cross.max(local_cross);
280 grow += item.layout.grow;
281 line.push((item, local_main, local_cross));
282 }
283 main += line.len().saturating_sub(1) as Scalar * unit.separation;
284 lines.push((main, cross, grow, line));
285 (lines, count)
286 };
287 let mut children = Vec::with_capacity(count);
288 let mut main_max: Scalar = 0.0;
289 let mut cross_max = 0.0;
290 for (main, cross_available, grow, items) in lines {
291 let diff = main_available - main;
292 let mut new_main = 0.0;
293 let mut new_cross: Scalar = 0.0;
294 for (item, local_main, local_cross) in items {
295 let child_main = if main < main_available {
296 local_main
297 + if grow > 0.0 {
298 diff * item.layout.grow / grow
299 } else {
300 0.0
301 }
302 } else {
303 local_main
304 };
305 let child_main = (child_main
306 - if unit.direction.is_horizontal() {
307 item.layout.margin.left + item.layout.margin.right
308 } else {
309 item.layout.margin.top + item.layout.margin.bottom
310 })
311 .max(0.0);
312 let child_cross = (local_cross
313 - if unit.direction.is_horizontal() {
314 item.layout.margin.top + item.layout.margin.bottom
315 } else {
316 item.layout.margin.left + item.layout.margin.right
317 })
318 .max(0.0);
319 let child_cross = lerp(child_cross, cross_available, item.layout.fill);
320 let rect = if unit.direction.is_horizontal() {
321 Vec2 {
322 x: child_main,
323 y: child_cross,
324 }
325 } else {
326 Vec2 {
327 x: child_cross,
328 y: child_main,
329 }
330 };
331 if let Some(mut child) = self.layout_node(rect, mapping, &item.slot) {
332 if unit.direction.is_horizontal() {
333 if unit.direction.is_order_ascending() {
334 child.local_space.left += new_main + item.layout.margin.left;
335 child.local_space.right += new_main + item.layout.margin.left;
336 } else {
337 let left = child.local_space.left;
338 let right = child.local_space.right;
339 child.local_space.left =
340 size_available.x - right - new_main - item.layout.margin.right;
341 child.local_space.right =
342 size_available.x - left - new_main - item.layout.margin.right;
343 }
344 new_main += rect.x + item.layout.margin.left + item.layout.margin.right;
345 let diff = lerp(
346 0.0,
347 cross_available - child.local_space.height(),
348 item.layout.align,
349 );
350 child.local_space.top += cross_max + item.layout.margin.top + diff;
351 child.local_space.bottom += cross_max + item.layout.margin.top + diff;
352 new_cross = new_cross.max(rect.y);
353 } else {
354 if unit.direction.is_order_ascending() {
355 child.local_space.top += new_main + item.layout.margin.top;
356 child.local_space.bottom += new_main + item.layout.margin.top;
357 } else {
358 let top = child.local_space.top;
359 let bottom = child.local_space.bottom;
360 child.local_space.top =
361 size_available.y - bottom - new_main - item.layout.margin.bottom;
362 child.local_space.bottom =
363 size_available.y - top - new_main - item.layout.margin.bottom;
364 }
365 new_main += rect.y + item.layout.margin.top + item.layout.margin.bottom;
366 let diff = lerp(
367 0.0,
368 cross_available - child.local_space.width(),
369 item.layout.align,
370 );
371 child.local_space.left += cross_max + item.layout.margin.left + diff;
372 child.local_space.right += cross_max + item.layout.margin.left + diff;
373 new_cross = new_cross.max(rect.x);
374 }
375 new_main += unit.separation;
376 children.push(child);
377 }
378 }
379 new_main = (new_main - unit.separation).max(0.0);
380 main_max = main_max.max(new_main);
381 cross_max += new_cross + unit.separation;
382 }
383 cross_max = (cross_max - unit.separation).max(0.0);
384 let local_space = if unit.direction.is_horizontal() {
385 Rect {
386 left: 0.0,
387 right: main_max,
388 top: 0.0,
389 bottom: cross_max,
390 }
391 } else {
392 Rect {
393 left: 0.0,
394 right: cross_max,
395 top: 0.0,
396 bottom: main_max,
397 }
398 };
399 LayoutNode {
400 id: unit.id.to_owned(),
401 local_space,
402 children,
403 }
404 }
405
406 pub fn layout_flex_box_no_wrap(
407 &self,
408 size_available: Vec2,
409 mapping: &CoordsMapping,
410 unit: &FlexBox,
411 ) -> LayoutNode {
412 let (main_available, cross_available) = if unit.direction.is_horizontal() {
413 (size_available.x, size_available.y)
414 } else {
415 (size_available.y, size_available.x)
416 };
417 let mut main = 0.0;
418 let mut cross: Scalar = 0.0;
419 let mut grow = 0.0;
420 let mut shrink = 0.0;
421 let items = unit
422 .items
423 .iter()
424 .filter(|item| item.slot.is_some() && item.slot.as_data().unwrap().id().is_valid())
425 .collect::<Vec<_>>();
426 let mut axis_sizes = Vec::with_capacity(items.len());
427 for item in &items {
428 let local_main = item.layout.basis.unwrap_or_else(|| {
429 if unit.direction.is_horizontal() {
430 self.calc_unit_min_width(size_available, mapping, &item.slot)
431 } else {
432 self.calc_unit_min_height(size_available, mapping, &item.slot)
433 }
434 });
435 let local_main = local_main
436 + if unit.direction.is_horizontal() {
437 item.layout.margin.left + item.layout.margin.right
438 } else {
439 item.layout.margin.top + item.layout.margin.bottom
440 };
441 let local_cross = if unit.direction.is_horizontal() {
442 self.calc_unit_min_height(size_available, mapping, &item.slot)
443 } else {
444 self.calc_unit_min_width(size_available, mapping, &item.slot)
445 };
446 let local_cross = local_cross
447 + if unit.direction.is_horizontal() {
448 item.layout.margin.top + item.layout.margin.bottom
449 } else {
450 item.layout.margin.left + item.layout.margin.right
451 };
452 let local_cross = lerp(local_cross, cross_available, item.layout.fill);
453 main += local_main;
454 cross = cross.max(local_cross);
455 grow += item.layout.grow;
456 shrink += item.layout.shrink;
457 axis_sizes.push((local_main, local_cross));
458 }
459 main += items.len().saturating_sub(1) as Scalar * unit.separation;
460 let diff = main_available - main;
461 let mut new_main = 0.0;
462 let mut new_cross: Scalar = 0.0;
463 let children = items
464 .into_iter()
465 .zip(axis_sizes)
466 .filter_map(|(item, axis_size)| {
467 let child_main = if main < main_available {
468 axis_size.0
469 + if grow > 0.0 {
470 diff * item.layout.grow / grow
471 } else {
472 0.0
473 }
474 } else if main > main_available {
475 axis_size.0
476 + if shrink > 0.0 {
477 diff * item.layout.shrink / shrink
478 } else {
479 0.0
480 }
481 } else {
482 axis_size.0
483 };
484 let child_main = (child_main
485 - if unit.direction.is_horizontal() {
486 item.layout.margin.left + item.layout.margin.right
487 } else {
488 item.layout.margin.top + item.layout.margin.bottom
489 })
490 .max(0.0);
491 let child_cross = (axis_size.1
492 - if unit.direction.is_horizontal() {
493 item.layout.margin.top + item.layout.margin.bottom
494 } else {
495 item.layout.margin.left + item.layout.margin.right
496 })
497 .max(0.0);
498 let rect = if unit.direction.is_horizontal() {
499 Vec2 {
500 x: child_main,
501 y: child_cross,
502 }
503 } else {
504 Vec2 {
505 x: child_cross,
506 y: child_main,
507 }
508 };
509 if let Some(mut child) = self.layout_node(rect, mapping, &item.slot) {
510 if unit.direction.is_horizontal() {
511 if unit.direction.is_order_ascending() {
512 child.local_space.left += new_main + item.layout.margin.left;
513 child.local_space.right += new_main + item.layout.margin.left;
514 } else {
515 let left = child.local_space.left;
516 let right = child.local_space.right;
517 child.local_space.left =
518 size_available.x - right - new_main - item.layout.margin.right;
519 child.local_space.right =
520 size_available.x - left - new_main - item.layout.margin.right;
521 }
522 new_main += rect.x + item.layout.margin.left + item.layout.margin.right;
523 let diff = lerp(
524 0.0,
525 cross_available - child.local_space.height(),
526 item.layout.align,
527 );
528 child.local_space.top += item.layout.margin.top + diff;
529 child.local_space.bottom += item.layout.margin.top + diff;
530 new_cross = new_cross.max(rect.y);
531 } else {
532 if unit.direction.is_order_ascending() {
533 child.local_space.top += new_main + item.layout.margin.top;
534 child.local_space.bottom += new_main + item.layout.margin.top;
535 } else {
536 let top = child.local_space.top;
537 let bottom = child.local_space.bottom;
538 child.local_space.top =
539 size_available.y - bottom - new_main - item.layout.margin.bottom;
540 child.local_space.bottom =
541 size_available.y - top - new_main - item.layout.margin.bottom;
542 }
543 new_main += rect.y + item.layout.margin.top + item.layout.margin.bottom;
544 let diff = lerp(
545 0.0,
546 cross_available - child.local_space.width(),
547 item.layout.align,
548 );
549 child.local_space.left += item.layout.margin.left + diff;
550 child.local_space.right += item.layout.margin.left + diff;
551 new_cross = new_cross.max(rect.x);
552 }
553 new_main += unit.separation;
554 Some(child)
555 } else {
556 None
557 }
558 })
559 .collect::<Vec<_>>();
560 new_main = (new_main - unit.separation).max(0.0);
561 let local_space = if unit.direction.is_horizontal() {
562 Rect {
563 left: 0.0,
564 right: new_main,
565 top: 0.0,
566 bottom: new_cross,
567 }
568 } else {
569 Rect {
570 left: 0.0,
571 right: new_cross,
572 top: 0.0,
573 bottom: new_main,
574 }
575 };
576 LayoutNode {
577 id: unit.id.to_owned(),
578 local_space,
579 children,
580 }
581 }
582
583 pub fn layout_grid_box(
584 &self,
585 size_available: Vec2,
586 mapping: &CoordsMapping,
587 unit: &GridBox,
588 ) -> Option<LayoutNode> {
589 if !unit.id.is_valid() {
590 return None;
591 }
592 let cell_width = if unit.cols > 0 {
593 size_available.x / unit.cols as Scalar
594 } else {
595 0.0
596 };
597 let cell_height = if unit.rows > 0 {
598 size_available.y / unit.rows as Scalar
599 } else {
600 0.0
601 };
602 let children = unit
603 .items
604 .iter()
605 .filter_map(|item| {
606 let left = item.layout.space_occupancy.left as Scalar * cell_width;
607 let right = item.layout.space_occupancy.right as Scalar * cell_width;
608 let top = item.layout.space_occupancy.top as Scalar * cell_height;
609 let bottom = item.layout.space_occupancy.bottom as Scalar * cell_height;
610 let width =
611 (right - left - item.layout.margin.left - item.layout.margin.right).max(0.0);
612 let height =
613 (bottom - top - item.layout.margin.top - item.layout.margin.bottom).max(0.0);
614 let size = Vec2 {
615 x: width,
616 y: height,
617 };
618 if let Some(mut child) = self.layout_node(size, mapping, &item.slot) {
619 let diff = size.x - child.local_space.width();
620 let ox = lerp(0.0, diff, item.layout.horizontal_align);
621 let diff = size.y - child.local_space.height();
622 let oy = lerp(0.0, diff, item.layout.vertical_align);
623 child.local_space.left += left + item.layout.margin.left - ox;
624 child.local_space.right += left + item.layout.margin.left - ox;
625 child.local_space.top += top + item.layout.margin.top - oy;
626 child.local_space.bottom += top + item.layout.margin.top - oy;
627 Some(child)
628 } else {
629 None
630 }
631 })
632 .collect::<Vec<_>>();
633 Some(LayoutNode {
634 id: unit.id.to_owned(),
635 local_space: Rect {
636 left: 0.0,
637 right: size_available.x,
638 top: 0.0,
639 bottom: size_available.y,
640 },
641 children,
642 })
643 }
644
645 pub fn layout_size_box(
646 &self,
647 size_available: Vec2,
648 mapping: &CoordsMapping,
649 unit: &SizeBox,
650 ) -> Option<LayoutNode> {
651 if !unit.id.is_valid() {
652 return None;
653 }
654 let mut size = Vec2 {
655 x: match unit.width {
656 SizeBoxSizeValue::Content => {
657 self.calc_unit_min_width(size_available, mapping, &unit.slot)
658 }
659 SizeBoxSizeValue::Fill => size_available.x - unit.margin.left - unit.margin.right,
660 SizeBoxSizeValue::Exact(v) => v,
661 },
662 y: match unit.height {
663 SizeBoxSizeValue::Content => {
664 self.calc_unit_min_height(size_available, mapping, &unit.slot)
665 }
666 SizeBoxSizeValue::Fill => size_available.y - unit.margin.top - unit.margin.bottom,
667 SizeBoxSizeValue::Exact(v) => v,
668 },
669 };
670 match unit.keep_aspect_ratio {
671 SizeBoxAspectRatio::None => {}
672 SizeBoxAspectRatio::WidthOfHeight(factor) => {
673 size.x = (size.y * factor).max(0.0);
674 }
675 SizeBoxAspectRatio::HeightOfWidth(factor) => {
676 size.y = (size.x * factor).max(0.0);
677 }
678 }
679 let children = if let Some(mut child) = self.layout_node(size, mapping, &unit.slot) {
680 child.local_space.left += unit.margin.left;
681 child.local_space.right += unit.margin.left;
682 child.local_space.top += unit.margin.top;
683 child.local_space.bottom += unit.margin.top;
684 vec![child]
685 } else {
686 vec![]
687 };
688 let local_space = Rect {
689 left: 0.0,
690 right: size.x,
691 top: 0.0,
692 bottom: size.y,
693 };
694 Some(LayoutNode {
695 id: unit.id.to_owned(),
696 local_space,
697 children,
698 })
699 }
700
701 pub fn layout_image_box(&self, size_available: Vec2, unit: &ImageBox) -> Option<LayoutNode> {
702 if !unit.id.is_valid() {
703 return None;
704 }
705 let local_space = Rect {
706 left: 0.0,
707 right: match unit.width {
708 ImageBoxSizeValue::Fill => size_available.x,
709 ImageBoxSizeValue::Exact(v) => v,
710 },
711 top: 0.0,
712 bottom: match unit.height {
713 ImageBoxSizeValue::Fill => size_available.y,
714 ImageBoxSizeValue::Exact(v) => v,
715 },
716 };
717 Some(LayoutNode {
718 id: unit.id.to_owned(),
719 local_space,
720 children: vec![],
721 })
722 }
723
724 pub fn layout_text_box(
725 &self,
726 size_available: Vec2,
727 mapping: &CoordsMapping,
728 unit: &TextBox,
729 ) -> Option<LayoutNode> {
730 if !unit.id.is_valid() {
731 return None;
732 }
733 let aabb = self
734 .text_measurement_engine
735 .measure_text(size_available, mapping, unit)
736 .unwrap_or_default();
737 let local_space = Rect {
738 left: 0.0,
739 right: match unit.width {
740 TextBoxSizeValue::Content => aabb.width(),
741 TextBoxSizeValue::Fill => size_available.x,
742 TextBoxSizeValue::Exact(v) => v,
743 },
744 top: 0.0,
745 bottom: match unit.height {
746 TextBoxSizeValue::Content => aabb.height(),
747 TextBoxSizeValue::Fill => size_available.y,
748 TextBoxSizeValue::Exact(v) => v,
749 },
750 };
751 Some(LayoutNode {
752 id: unit.id.to_owned(),
753 local_space,
754 children: vec![],
755 })
756 }
757
758 fn calc_unit_min_width(
759 &self,
760 size_available: Vec2,
761 mapping: &CoordsMapping,
762 unit: &WidgetUnit,
763 ) -> Scalar {
764 match unit {
765 WidgetUnit::None | WidgetUnit::PortalBox(_) => 0.0,
766 WidgetUnit::AreaBox(b) => self.calc_unit_min_width(size_available, mapping, &b.slot),
767 WidgetUnit::ContentBox(b) => {
768 self.calc_content_box_min_width(size_available, mapping, b)
769 }
770 WidgetUnit::FlexBox(b) => self.calc_flex_box_min_width(size_available, mapping, b),
771 WidgetUnit::GridBox(b) => self.calc_grid_box_min_width(size_available, mapping, b),
772 WidgetUnit::SizeBox(b) => {
773 (match b.width {
774 SizeBoxSizeValue::Content => {
775 self.calc_unit_min_width(size_available, mapping, &b.slot)
776 }
777 SizeBoxSizeValue::Fill => 0.0,
778 SizeBoxSizeValue::Exact(v) => v,
779 }) + b.margin.left
780 + b.margin.right
781 }
782 WidgetUnit::ImageBox(b) => match b.width {
783 ImageBoxSizeValue::Fill => 0.0,
784 ImageBoxSizeValue::Exact(v) => v,
785 },
786 WidgetUnit::TextBox(b) => {
787 let aabb = self
788 .text_measurement_engine
789 .measure_text(size_available, mapping, b)
790 .unwrap_or_default();
791 match b.width {
792 TextBoxSizeValue::Content => aabb.width(),
793 TextBoxSizeValue::Fill => 0.0,
794 TextBoxSizeValue::Exact(v) => v,
795 }
796 }
797 }
798 }
799
800 fn calc_content_box_min_width(
801 &self,
802 size_available: Vec2,
803 mapping: &CoordsMapping,
804 unit: &ContentBox,
805 ) -> Scalar {
806 let mut result: Scalar = 0.0;
807 for item in &unit.items {
808 let size = self.calc_unit_min_width(size_available, mapping, &item.slot)
809 + item.layout.margin.left
810 + item.layout.margin.right;
811 let width = item.layout.anchors.right - item.layout.anchors.left;
812 let size = if width > 0.0 { size / width } else { 0.0 };
813 result = result.max(size);
814 }
815 result
816 }
817
818 fn calc_flex_box_min_width(
819 &self,
820 size_available: Vec2,
821 mapping: &CoordsMapping,
822 unit: &FlexBox,
823 ) -> Scalar {
824 if unit.direction.is_horizontal() {
825 self.calc_horizontal_flex_box_min_width(size_available, mapping, unit)
826 } else {
827 self.calc_vertical_flex_box_min_width(size_available, mapping, unit)
828 }
829 }
830
831 fn calc_horizontal_flex_box_min_width(
832 &self,
833 size_available: Vec2,
834 mapping: &CoordsMapping,
835 unit: &FlexBox,
836 ) -> Scalar {
837 if unit.wrap {
838 let mut result: Scalar = 0.0;
839 let mut line = 0.0;
840 let mut first = true;
841 for item in &unit.items {
842 let size = self.calc_unit_min_width(size_available, mapping, &item.slot)
843 + item.layout.margin.left
844 + item.layout.margin.right;
845 if first || line + size <= size_available.x {
846 line += size;
847 if !first {
848 line += unit.separation;
849 }
850 first = false;
851 } else {
852 result = result.max(line);
853 line = 0.0;
854 first = true;
855 }
856 }
857 result.max(line)
858 } else {
859 let mut result = 0.0;
860 for item in &unit.items {
861 result += self.calc_unit_min_width(size_available, mapping, &item.slot)
862 + item.layout.margin.left
863 + item.layout.margin.right;
864 }
865 result + (unit.items.len().saturating_sub(1) as Scalar) * unit.separation
866 }
867 }
868
869 fn calc_vertical_flex_box_min_width(
870 &self,
871 size_available: Vec2,
872 mapping: &CoordsMapping,
873 unit: &FlexBox,
874 ) -> Scalar {
875 if unit.wrap {
876 let mut result = 0.0;
877 let mut line_length = 0.0;
878 let mut line: Scalar = 0.0;
879 let mut lines: usize = 0;
880 let mut first = true;
881 for item in &unit.items {
882 let width = self.calc_unit_min_width(size_available, mapping, &item.slot)
883 + item.layout.margin.left
884 + item.layout.margin.right;
885 let height = self.calc_unit_min_height(size_available, mapping, &item.slot)
886 + item.layout.margin.top
887 + item.layout.margin.bottom;
888 if first || line_length + height <= size_available.y {
889 line_length += height;
890 if !first {
891 line_length += unit.separation;
892 }
893 line = line.max(width);
894 first = false;
895 } else {
896 result += line;
897 line_length = 0.0;
898 line = 0.0;
899 lines += 1;
900 first = true;
901 }
902 }
903 result += line;
904 lines += 1;
905 result + (lines.saturating_sub(1) as Scalar) * unit.separation
906 } else {
907 unit.items.iter().fold(0.0, |a, item| {
908 (self.calc_unit_min_width(size_available, mapping, &item.slot)
909 + item.layout.margin.left
910 + item.layout.margin.right)
911 .max(a)
912 })
913 }
914 }
915
916 fn calc_grid_box_min_width(
917 &self,
918 size_available: Vec2,
919 mapping: &CoordsMapping,
920 unit: &GridBox,
921 ) -> Scalar {
922 let mut result: Scalar = 0.0;
923 for item in &unit.items {
924 let size = self.calc_unit_min_width(size_available, mapping, &item.slot)
925 + item.layout.margin.left
926 + item.layout.margin.right;
927 let size = if size > 0.0 {
928 (item.layout.space_occupancy.width() as Scalar * size) / unit.cols as Scalar
929 } else {
930 0.0
931 };
932 result = result.max(size);
933 }
934 result
935 }
936
937 fn calc_unit_min_height(
938 &self,
939 size_available: Vec2,
940 mapping: &CoordsMapping,
941 unit: &WidgetUnit,
942 ) -> Scalar {
943 match unit {
944 WidgetUnit::None | WidgetUnit::PortalBox(_) => 0.0,
945 WidgetUnit::AreaBox(b) => self.calc_unit_min_height(size_available, mapping, &b.slot),
946 WidgetUnit::ContentBox(b) => {
947 self.calc_content_box_min_height(size_available, mapping, b)
948 }
949 WidgetUnit::FlexBox(b) => self.calc_flex_box_min_height(size_available, mapping, b),
950 WidgetUnit::GridBox(b) => self.calc_grid_box_min_height(size_available, mapping, b),
951 WidgetUnit::SizeBox(b) => {
952 (match b.height {
953 SizeBoxSizeValue::Content => {
954 self.calc_unit_min_height(size_available, mapping, &b.slot)
955 }
956 SizeBoxSizeValue::Fill => 0.0,
957 SizeBoxSizeValue::Exact(v) => v,
958 }) + b.margin.top
959 + b.margin.bottom
960 }
961 WidgetUnit::ImageBox(b) => match b.height {
962 ImageBoxSizeValue::Fill => 0.0,
963 ImageBoxSizeValue::Exact(v) => v,
964 },
965 WidgetUnit::TextBox(b) => {
966 let aabb = self
967 .text_measurement_engine
968 .measure_text(size_available, mapping, b)
969 .unwrap_or_default();
970 match b.height {
971 TextBoxSizeValue::Content => aabb.height(),
972 TextBoxSizeValue::Fill => 0.0,
973 TextBoxSizeValue::Exact(v) => v,
974 }
975 }
976 }
977 }
978
979 fn calc_content_box_min_height(
980 &self,
981 size_available: Vec2,
982 mapping: &CoordsMapping,
983 unit: &ContentBox,
984 ) -> Scalar {
985 let mut result: Scalar = 0.0;
986 for item in &unit.items {
987 let size = self.calc_unit_min_height(size_available, mapping, &item.slot)
988 + item.layout.margin.top
989 + item.layout.margin.bottom;
990 let height = item.layout.anchors.bottom - item.layout.anchors.top;
991 let size = if height > 0.0 { size / height } else { 0.0 };
992 result = result.max(size);
993 }
994 result
995 }
996
997 fn calc_flex_box_min_height(
998 &self,
999 size_available: Vec2,
1000 mapping: &CoordsMapping,
1001 unit: &FlexBox,
1002 ) -> Scalar {
1003 if unit.direction.is_horizontal() {
1004 self.calc_horizontal_flex_box_min_height(size_available, mapping, unit)
1005 } else {
1006 self.calc_vertical_flex_box_min_height(size_available, mapping, unit)
1007 }
1008 }
1009
1010 fn calc_horizontal_flex_box_min_height(
1011 &self,
1012 size_available: Vec2,
1013 mapping: &CoordsMapping,
1014 unit: &FlexBox,
1015 ) -> Scalar {
1016 if unit.wrap {
1017 let mut result = 0.0;
1018 let mut line_length = 0.0;
1019 let mut line: Scalar = 0.0;
1020 let mut lines: usize = 0;
1021 let mut first = true;
1022 for item in &unit.items {
1023 let width = self.calc_unit_min_width(size_available, mapping, &item.slot)
1024 + item.layout.margin.left
1025 + item.layout.margin.right;
1026 let height = self.calc_unit_min_height(size_available, mapping, &item.slot)
1027 + item.layout.margin.top
1028 + item.layout.margin.bottom;
1029 if first || line_length + width <= size_available.x {
1030 line_length += width;
1031 if !first {
1032 line_length += unit.separation;
1033 }
1034 line = line.max(height);
1035 first = false;
1036 } else {
1037 result += line;
1038 line_length = 0.0;
1039 line = 0.0;
1040 lines += 1;
1041 first = true;
1042 }
1043 }
1044 result += line;
1045 lines += 1;
1046 result + (lines.saturating_sub(1) as Scalar) * unit.separation
1047 } else {
1048 unit.items.iter().fold(0.0, |a, item| {
1049 (self.calc_unit_min_height(size_available, mapping, &item.slot)
1050 + item.layout.margin.top
1051 + item.layout.margin.bottom)
1052 .max(a)
1053 })
1054 }
1055 }
1056
1057 fn calc_vertical_flex_box_min_height(
1058 &self,
1059 size_available: Vec2,
1060 mapping: &CoordsMapping,
1061 unit: &FlexBox,
1062 ) -> Scalar {
1063 if unit.wrap {
1064 let mut result: Scalar = 0.0;
1065 let mut line = 0.0;
1066 let mut first = true;
1067 for item in &unit.items {
1068 let size = self.calc_unit_min_height(size_available, mapping, &item.slot)
1069 + item.layout.margin.top
1070 + item.layout.margin.bottom;
1071 if first || line + size <= size_available.y {
1072 line += size;
1073 if !first {
1074 line += unit.separation;
1075 }
1076 first = false;
1077 } else {
1078 result = result.max(line);
1079 line = 0.0;
1080 first = true;
1081 }
1082 }
1083 result.max(line)
1084 } else {
1085 let mut result = 0.0;
1086 for item in &unit.items {
1087 result += self.calc_unit_min_height(size_available, mapping, &item.slot)
1088 + item.layout.margin.top
1089 + item.layout.margin.bottom;
1090 }
1091 result + (unit.items.len().saturating_sub(1) as Scalar) * unit.separation
1092 }
1093 }
1094
1095 fn calc_grid_box_min_height(
1096 &self,
1097 size_available: Vec2,
1098 mapping: &CoordsMapping,
1099 unit: &GridBox,
1100 ) -> Scalar {
1101 let mut result: Scalar = 0.0;
1102 for item in &unit.items {
1103 let size = self.calc_unit_min_height(size_available, mapping, &item.slot)
1104 + item.layout.margin.top
1105 + item.layout.margin.bottom;
1106 let size = if size > 0.0 {
1107 (item.layout.space_occupancy.height() as Scalar * size) / unit.cols as Scalar
1108 } else {
1109 0.0
1110 };
1111 result = result.max(size);
1112 }
1113 result
1114 }
1115
1116 fn unpack_node(
1117 parent: Option<&WidgetId>,
1118 ui_space: Rect,
1119 node: LayoutNode,
1120 items: &mut HashMap<WidgetId, LayoutItem>,
1121 ) {
1122 let LayoutNode {
1123 id,
1124 local_space,
1125 children,
1126 } = node;
1127 let ui_space = Rect {
1128 left: local_space.left + ui_space.left,
1129 right: local_space.right + ui_space.left,
1130 top: local_space.top + ui_space.top,
1131 bottom: local_space.bottom + ui_space.top,
1132 };
1133 for node in children {
1134 Self::unpack_node(Some(&id), ui_space, node, items);
1135 }
1136 items.insert(
1137 id,
1138 LayoutItem {
1139 local_space,
1140 ui_space,
1141 parent: parent.cloned(),
1142 },
1143 );
1144 }
1145}
1146
1147impl<TME: TextMeasurementEngine> LayoutEngine<()> for DefaultLayoutEngine<TME> {
1148 fn layout(&mut self, mapping: &CoordsMapping, tree: &WidgetUnit) -> Result<Layout, ()> {
1149 let ui_space = mapping.virtual_area();
1150 if let Some(root) = self.layout_node(ui_space.size(), mapping, tree) {
1151 let mut items = HashMap::with_capacity(root.count());
1152 Self::unpack_node(None, ui_space, root, &mut items);
1153 Ok(Layout { ui_space, items })
1154 } else {
1155 Ok(Layout {
1156 ui_space,
1157 items: Default::default(),
1158 })
1159 }
1160 }
1161}