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