1use super::{
5 Concrete, Desc, Layout, Renderable, Staged, base, check_unsized_abs, map_unsized_area,
6 merge_margin, nuetralize_unsized,
7};
8use crate::layout::Swappable;
9use crate::persist::{FnPersist2, Persist2, VectorFold};
10use crate::{
11 DAbsRect, DValue, PxDim, PxLimits, PxPerimeter, PxPoint, PxRect, RelDim, RowDirection,
12 UNSIZED_AXIS, rtree,
13};
14use derive_more::TryFrom;
15use smallvec::SmallVec;
16use std::rc::Rc;
17
18#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, TryFrom, derive_more::Display)]
19#[try_from(repr)]
20#[repr(u8)]
21pub enum FlexJustify {
22 #[default]
23 Start,
24 Center,
25 End,
26 SpaceBetween,
27 SpaceAround,
28 SpaceFull,
29}
30
31pub trait Prop:
32 base::ZIndex + base::Obstacles + base::Limits + base::Direction + base::Area
33{
34 fn wrap(&self) -> bool;
35 fn justify(&self) -> FlexJustify;
36 fn align(&self) -> FlexJustify;
37}
38
39crate::gen_from_to_dyn!(Prop);
40
41pub trait Child: base::Margin + base::RLimits + base::Order {
42 fn grow(&self) -> f32;
43 fn shrink(&self) -> f32;
44 fn basis(&self) -> DValue;
45}
46
47crate::gen_from_to_dyn!(Child);
48
49#[allow(clippy::too_many_arguments)]
50fn next_obstacle(
51 obstacles: &[DAbsRect],
52 max_aux: f32,
53 main: f32,
54 aux: f32,
55 xaxis: bool,
56 total_main: f32,
57 min: &mut usize,
58 dpi: RelDim,
59) -> (f32, f32) {
60 let mut i = *min;
62 while i < obstacles.len() {
63 let obstacle = obstacles[i].resolve(dpi);
64 let (mut start, aux_start) = obstacle.topleft().swap_axis(xaxis);
65
66 if total_main > 0.0 {
67 start = total_main - start;
68 }
69
70 if aux_start > aux + max_aux {
73 break;
74 }
75
76 let (mut end, aux_end) = obstacle.bottomright().swap_axis(xaxis);
77
78 if total_main > 0.0 {
79 end = total_main - end;
80 }
81
82 if aux_end < aux {
85 i += 1;
86 *min = i;
87 continue;
88 }
89
90 if main > start {
93 i += 1;
94 continue;
95 }
96
97 return (start, end);
98 }
99
100 (f32::INFINITY, 0.0)
101}
102
103fn justify_inner_outer(align: FlexJustify, total: f32, used: f32, count: i32) -> (f32, f32) {
104 match align {
105 FlexJustify::SpaceBetween => (0.0, (total - used) / (count - 1) as f32),
106 FlexJustify::SpaceAround => {
107 let r = (total - used) / count as f32;
108 (r / 2.0, r)
109 }
110 FlexJustify::SpaceFull => {
111 let r = (total - used) / (count + 1) as f32;
112 (r, r)
113 }
114 FlexJustify::Center => ((total - used) / 2.0, 0.0),
115 FlexJustify::End => (total - used, 0.0),
116 _ => (0.0, 0.0),
117 }
118}
119
120struct Linebreak {
122 index: usize,
123 lineheight: f32,
124 skip: f32, aux_margin: f32, }
127
128impl Linebreak {
129 pub fn new(index: usize, lineheight: f32, skip: f32, aux_margin: f32) -> Self {
130 Self {
131 index,
132 lineheight,
133 skip,
134 aux_margin,
135 }
136 }
137}
138
139#[derive(Clone)]
140struct ChildCache {
141 basis: f32,
142 grow: f32,
143 shrink: f32,
144 aux: f32,
145 margin: PxPerimeter,
146 limits: PxLimits,
147}
148
149#[allow(clippy::too_many_arguments)]
150fn wrap_line(
151 childareas: &im::Vector<Option<ChildCache>>,
152 props: &dyn Prop,
153 xaxis: bool,
154 total_main: f32,
155 total_aux: f32,
156 mut used_aux: f32,
157 mut linecount: i32,
158 justify: FlexJustify,
159 backwards: bool,
160 dpi: RelDim,
161) -> (SmallVec<[Linebreak; 10]>, i32, f32) {
162 let mut breaks: SmallVec<[Linebreak; 10]> = SmallVec::new();
163
164 let (mut aux, inner) = justify_inner_outer(justify, total_aux, used_aux, linecount);
165
166 linecount = 1;
168 used_aux = 0.0;
169 let mut max_aux = 0.0;
170 let mut max_aux_margin: f32 = 0.0;
171 let mut max_aux_upper_margin: f32 = 0.0;
172 let mut main = 0.0;
173 let mut prev_margin = f32::NAN;
174 let mut min_obstacle = 0;
175 let reversed = if backwards { total_main } else { -1.0 };
176 let mut obstacle = next_obstacle(
177 props.obstacles(),
178 max_aux,
179 main,
180 aux,
181 xaxis,
182 reversed,
183 &mut min_obstacle,
184 dpi,
185 );
186
187 let mut i = 0;
188 while i < childareas.len() {
189 let Some(ref b) = childareas[i] else {
190 i += 1;
191 continue;
192 };
193
194 if !prev_margin.is_nan() {
197 main += prev_margin.max(b.margin.topleft().width);
198 }
199
200 if main + b.basis > obstacle.0 {
204 breaks.push(Linebreak::new(i, obstacle.1, obstacle.0, f32::NAN));
205 main = obstacle.1; prev_margin = f32::NAN; obstacle = next_obstacle(
209 props.obstacles(),
210 max_aux,
211 main,
212 aux,
213 xaxis,
214 reversed,
215 &mut min_obstacle,
216 dpi,
217 );
218
219 continue;
223 }
224
225 if main + b.basis > total_main {
227 let emptyline = if max_aux == 0.0 {
231 max_aux = b.aux;
232
233 if let Some(b) = breaks.last() {
238 b.lineheight >= 0.0
239 } else {
240 true
241 }
242 } else {
243 false
244 };
245
246 main = 0.0;
247 if let Some(b) = breaks.last_mut() {
248 b.aux_margin = b.aux_margin.max(max_aux_upper_margin);
249 }
250 breaks.push(Linebreak::new(i, max_aux, f32::INFINITY, max_aux_margin));
251 prev_margin = f32::NAN;
252 used_aux += max_aux;
253 aux += max_aux + inner;
254 max_aux = 0.0;
255 max_aux_margin = 0.0;
256 max_aux_upper_margin = 0.0;
257 linecount += 1;
258 obstacle = next_obstacle(
259 props.obstacles(),
260 max_aux,
261 main,
262 aux,
263 xaxis,
264 reversed,
265 &mut min_obstacle,
266 dpi,
267 );
268
269 if !emptyline {
270 continue;
271 }
272 }
273
274 main += b.basis;
275 prev_margin = b.margin.bottomright().width;
276 max_aux_margin = max_aux_margin.max(b.margin.bottomright().height);
277 max_aux_upper_margin = max_aux_upper_margin.max(b.margin.topleft().height);
278 max_aux = max_aux.max(b.aux);
279 i += 1;
280 }
281
282 if let Some(b) = breaks.last_mut() {
283 b.aux_margin = b.aux_margin.max(max_aux_upper_margin);
284 }
285
286 breaks.push(Linebreak::new(
287 childareas.len(),
288 max_aux,
289 f32::INFINITY,
290 f32::NAN,
291 ));
292
293 (breaks, linecount, used_aux)
294}
295
296impl Desc for dyn Prop {
297 type Props = dyn Prop;
298 type Child = dyn Child;
299 type Children = im::Vector<Option<Box<dyn Layout<Self::Child>>>>;
300
301 fn stage<'a>(
302 props: &Self::Props,
303 outer_area: crate::PxRect,
304 outer_limits: crate::PxLimits,
305 children: &Self::Children,
306 id: std::sync::Weak<crate::SourceID>,
307 renderable: Option<Rc<dyn Renderable>>,
308 window: &mut crate::component::window::WindowState,
309 ) -> Box<dyn Staged + 'a> {
310 use super::Swappable;
311
312 let myarea = props.area().resolve(window.dpi);
313 let limits = outer_limits + props.limits().resolve(window.dpi);
316 let inner_dim = super::limit_dim(super::eval_dim(myarea, outer_area.dim()), limits);
317 let outer_safe = nuetralize_unsized(outer_area);
318
319 let xaxis = match props.direction() {
320 RowDirection::LeftToRight | RowDirection::RightToLeft => true,
321 RowDirection::TopToBottom | RowDirection::BottomToTop => false,
322 };
323
324 let mut childareas: im::Vector<Option<ChildCache>> = im::Vector::new();
325 let (dpi_main, _) = window.dpi.swap_axis(xaxis);
326 let (outer_main, _) = outer_safe.dim().swap_axis(xaxis);
327
328 for child in children.iter() {
331 let imposed = child.as_ref().unwrap().get_props();
332
333 let child_limit = super::apply_limit(inner_dim, limits, *imposed.rlimits());
334 let basis = imposed.basis().resolve(dpi_main).resolve(outer_main);
335
336 assert!(
337 basis.is_finite(),
338 "Basis can be unsized, but never infinite!"
339 );
340
341 let inner_area = PxRect::corners(
342 PxPoint::zero(),
343 if xaxis {
344 PxPoint::new(basis, UNSIZED_AXIS)
345 } else {
346 PxPoint::new(UNSIZED_AXIS, basis)
347 },
348 );
349
350 let stage = child
351 .as_ref()
352 .unwrap()
353 .stage(inner_area, child_limit, window);
354
355 let (main, aux) = stage.get_area().dim().swap_axis(xaxis);
356
357 let mut cache = ChildCache {
358 basis,
359 grow: imposed.grow(),
360 shrink: imposed.shrink(),
361 aux,
362 margin: imposed
363 .margin()
364 .resolve(window.dpi)
365 .to_perimeter(outer_safe),
366 limits: child_limit,
367 };
368 if cache.basis == UNSIZED_AXIS {
369 cache.basis = main;
370 }
371
372 if !xaxis {
374 let ltrb = cache.margin.v.as_array_mut();
375 ltrb.swap(0, 1);
376 ltrb.swap(2, 3);
377 }
378
379 childareas.push_back(Some(cache));
380 }
381
382 let mut fold = VectorFold::new(Persist2::new(&|prev: (f32, f32, f32),
389 n: &Option<ChildCache>|
390 -> (f32, f32, f32) {
391 let cache = n.as_ref().unwrap();
392 (
393 cache.basis + prev.0 + merge_margin(prev.2, cache.margin.topleft().width),
394 cache.aux.max(prev.1),
395 cache.margin.bottomright().width,
396 )
397 }));
398
399 let (_, (used_main, used_aux, _)) =
400 fold.call(fold.init(), (0.0, 0.0, f32::NAN), &childareas);
401
402 let evaluated_area = {
403 let (used_x, used_y) = super::swap_pair(xaxis, (used_main, used_aux));
404 let area = map_unsized_area(myarea, PxDim::new(used_x, used_y));
405
406 super::limit_area(area * outer_safe, limits)
408 };
409
410 debug_assert!(
411 evaluated_area.v.is_finite().all(),
412 "non-finite evaluated area!"
413 );
414
415 let (unsized_x, unsized_y) = check_unsized_abs(outer_area.bottomright());
416
417 let mut staging: im::Vector<Option<Box<dyn Staged>>> = im::Vector::new();
418 let mut nodes: im::Vector<Option<Rc<rtree::Node>>> = im::Vector::new();
419
420 if (unsized_x && xaxis) || (unsized_y && !xaxis) {
421 debug_assert!(evaluated_area.v.is_finite().all());
424 return Box::new(Concrete {
425 area: evaluated_area,
426 renderable: None,
427 rtree: rtree::Node::new(
428 evaluated_area.to_untyped(),
429 Some(props.zindex()),
430 nodes,
431 id,
432 window,
433 ),
434 children: staging,
435 layer: None,
436 });
437 }
438
439 let (total_main, total_aux) = inner_dim.swap_axis(xaxis);
440 let (breaks, linecount, used_aux) = if props.wrap() {
443 let r = wrap_line(
449 &childareas,
450 props,
451 xaxis,
452 total_main,
453 total_aux,
454 used_aux,
455 1,
456 props.align(),
457 props.direction() == RowDirection::BottomToTop
458 || props.direction() == RowDirection::RightToLeft,
459 window.dpi,
460 );
461
462 if !props.obstacles().is_empty() && props.align() != FlexJustify::Start {
463 let mut used_aux = used_aux;
466 let mut prev = (SmallVec::new(), 1, used_aux);
467 let mut linecount = 0;
468
469 while linecount != prev.1 || (used_aux - prev.2) > 0.001 {
472 linecount = prev.1;
473 used_aux = prev.2;
474 prev = wrap_line(
475 &childareas,
476 props,
477 xaxis,
478 total_main,
479 total_aux,
480 prev.2,
481 prev.1,
482 props.align(),
483 props.direction() == RowDirection::BottomToTop
484 || props.direction() == RowDirection::RightToLeft,
485 window.dpi,
486 );
487 }
488
489 prev
490 } else {
491 (r.0, r.1, used_aux)
492 }
493 } else {
494 let mut breaks = SmallVec::<[Linebreak; 10]>::new();
495 breaks.push(Linebreak::new(
496 childareas.len(),
497 total_aux,
498 f32::INFINITY,
499 f32::NAN,
500 ));
501 (breaks, 1, used_aux)
502 };
503
504 let (mut aux, inner_aux) =
507 justify_inner_outer(props.align(), total_aux, used_aux, linecount);
508
509 let mut main = 0.0;
510 let mut curindex = 0;
511 let mut prev_margin = f32::NAN;
512
513 for indice in 0..breaks.len() {
515 let b = &breaks[indice];
516 let mut totalgrow = 0.0;
517 let mut totalshrink = 0.0;
518 let mut used = 0.0;
519
520 for i in curindex..b.index {
522 let Some(a) = &childareas[i] else {
523 continue;
524 };
525 totalgrow += a.grow;
526 totalshrink += a.shrink;
527 used += a.basis;
528 }
529
530 let (total_span, max_aux) = if b.skip.is_finite() {
533 let mut max_aux = breaks.last().unwrap().lineheight;
534 for j in indice..breaks.len() {
535 if breaks[j].skip.is_infinite() {
536 max_aux = breaks[j].lineheight;
537 break;
538 }
539 }
540
541 (b.skip - main, max_aux)
542 } else {
543 (total_main - main, b.lineheight)
544 };
545
546 let diff = total_span - used;
547 let ratio = if diff > 0.0 {
548 if totalgrow != 0.0 {
549 diff / totalgrow
550 } else {
551 0.0
552 }
553 } else if totalshrink != 0.0 {
554 diff / totalshrink
555 } else {
556 0.0
557 };
558
559 for i in curindex..b.index {
560 if let Some(child) = &mut childareas[i] {
561 child.basis += ratio * (if diff > 0.0 { child.grow } else { child.shrink });
562 }
563 }
564
565 let (outer, inner) = justify_inner_outer(
566 props.justify(),
567 total_span,
568 used,
569 (b.index - curindex) as i32,
570 );
571 main += outer;
572
573 for i in curindex..b.index {
575 let Some(c) = &childareas[i] else {
576 continue;
577 };
578
579 if !prev_margin.is_nan() {
581 main += prev_margin.max(c.margin.topleft().width);
582 }
583 prev_margin = c.margin.bottomright().width;
584
585 let mut area = if props.direction() == RowDirection::RightToLeft
588 || props.direction() == RowDirection::BottomToTop
589 {
590 PxRect::new(
591 total_main - main,
592 aux,
593 total_main - (main + c.basis),
594 aux + max_aux,
595 )
596 } else {
597 PxRect::new(main, aux, main + c.basis, aux + max_aux)
598 };
599
600 super::assert_sized(area);
601 area.set_topleft(area.topleft().min(area.bottomright()));
602 if !xaxis {
604 let ltrb = area.v.as_array_mut();
605 ltrb.swap(0, 1);
606 ltrb.swap(2, 3);
607 }
608
609 let stage = children[i]
610 .as_ref()
611 .unwrap()
612 .stage(area, c.limits + limits, window);
613 if let Some(node) = stage.get_rtree().upgrade() {
614 nodes.push_back(Some(node));
615 }
616 staging.push_back(Some(stage));
617
618 main += c.basis + inner;
619 }
620
621 if b.skip.is_finite() {
622 main = b.lineheight;
623 } else {
624 main = 0.0;
625 aux += b.lineheight + inner_aux;
626
627 if !b.aux_margin.is_nan() {
628 aux += b.aux_margin;
629 }
630 }
631 prev_margin = f32::NAN;
632 curindex = b.index;
633 }
634
635 debug_assert!(evaluated_area.v.is_finite().all());
636 Box::new(Concrete {
637 area: evaluated_area,
638 renderable,
639 rtree: rtree::Node::new(
640 evaluated_area.to_untyped(),
641 Some(props.zindex()),
642 nodes,
643 id,
644 window,
645 ),
646 children: staging,
647 layer: None,
648 })
649 }
650}