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 {
72 break;
73 }
74
75 let (mut end, aux_end) = obstacle.bottomright().swap_axis(xaxis);
76
77 if total_main > 0.0 {
78 end = total_main - end;
79 }
80
81 if aux_end < aux {
83 i += 1;
84 *min = i;
85 continue;
86 }
87
88 if main > start {
90 i += 1;
91 continue;
92 }
93
94 return (start, end);
95 }
96
97 (f32::INFINITY, 0.0)
98}
99
100fn justify_inner_outer(align: FlexJustify, total: f32, used: f32, count: i32) -> (f32, f32) {
101 match align {
102 FlexJustify::SpaceBetween => (0.0, (total - used) / (count - 1) as f32),
103 FlexJustify::SpaceAround => {
104 let r = (total - used) / count as f32;
105 (r / 2.0, r)
106 }
107 FlexJustify::SpaceFull => {
108 let r = (total - used) / (count + 1) as f32;
109 (r, r)
110 }
111 FlexJustify::Center => ((total - used) / 2.0, 0.0),
112 FlexJustify::End => (total - used, 0.0),
113 _ => (0.0, 0.0),
114 }
115}
116
117struct Linebreak {
119 index: usize,
120 lineheight: f32,
121 skip: f32, aux_margin: f32, }
124
125impl Linebreak {
126 pub fn new(index: usize, lineheight: f32, skip: f32, aux_margin: f32) -> Self {
127 Self {
128 index,
129 lineheight,
130 skip,
131 aux_margin,
132 }
133 }
134}
135
136#[derive(Clone)]
137struct ChildCache {
138 basis: f32,
139 grow: f32,
140 shrink: f32,
141 aux: f32,
142 margin: PxPerimeter,
143 limits: PxLimits,
144}
145
146#[allow(clippy::too_many_arguments)]
147fn wrap_line(
148 childareas: &im::Vector<Option<ChildCache>>,
149 props: &dyn Prop,
150 xaxis: bool,
151 total_main: f32,
152 total_aux: f32,
153 mut used_aux: f32,
154 mut linecount: i32,
155 justify: FlexJustify,
156 backwards: bool,
157 dpi: RelDim,
158) -> (SmallVec<[Linebreak; 10]>, i32, f32) {
159 let mut breaks: SmallVec<[Linebreak; 10]> = SmallVec::new();
160
161 let (mut aux, inner) = justify_inner_outer(justify, total_aux, used_aux, linecount);
162
163 linecount = 1;
165 used_aux = 0.0;
166 let mut max_aux = 0.0;
167 let mut max_aux_margin: f32 = 0.0;
168 let mut max_aux_upper_margin: f32 = 0.0;
169 let mut main = 0.0;
170 let mut prev_margin = f32::NAN;
171 let mut min_obstacle = 0;
172 let reversed = if backwards { total_main } else { -1.0 };
173 let mut obstacle = next_obstacle(
174 props.obstacles(),
175 max_aux,
176 main,
177 aux,
178 xaxis,
179 reversed,
180 &mut min_obstacle,
181 dpi,
182 );
183
184 let mut i = 0;
185 while i < childareas.len() {
186 let Some(ref b) = childareas[i] else {
187 i += 1;
188 continue;
189 };
190
191 if !prev_margin.is_nan() {
193 main += prev_margin.max(b.margin.topleft().width);
194 }
195
196 if main + b.basis > obstacle.0 {
199 breaks.push(Linebreak::new(i, obstacle.1, obstacle.0, f32::NAN));
200 main = obstacle.1; prev_margin = f32::NAN; obstacle = next_obstacle(
204 props.obstacles(),
205 max_aux,
206 main,
207 aux,
208 xaxis,
209 reversed,
210 &mut min_obstacle,
211 dpi,
212 );
213
214 continue;
217 }
218
219 if main + b.basis > total_main {
221 let emptyline = if max_aux == 0.0 {
224 max_aux = b.aux;
225
226 if let Some(b) = breaks.last() {
229 b.lineheight >= 0.0
230 } else {
231 true
232 }
233 } else {
234 false
235 };
236
237 main = 0.0;
238 if let Some(b) = breaks.last_mut() {
239 b.aux_margin = b.aux_margin.max(max_aux_upper_margin);
240 }
241 breaks.push(Linebreak::new(i, max_aux, f32::INFINITY, max_aux_margin));
242 prev_margin = f32::NAN;
243 used_aux += max_aux;
244 aux += max_aux + inner;
245 max_aux = 0.0;
246 max_aux_margin = 0.0;
247 max_aux_upper_margin = 0.0;
248 linecount += 1;
249 obstacle = next_obstacle(
250 props.obstacles(),
251 max_aux,
252 main,
253 aux,
254 xaxis,
255 reversed,
256 &mut min_obstacle,
257 dpi,
258 );
259
260 if !emptyline {
261 continue;
262 }
263 }
264
265 main += b.basis;
266 prev_margin = b.margin.bottomright().width;
267 max_aux_margin = max_aux_margin.max(b.margin.bottomright().height);
268 max_aux_upper_margin = max_aux_upper_margin.max(b.margin.topleft().height);
269 max_aux = max_aux.max(b.aux);
270 i += 1;
271 }
272
273 if let Some(b) = breaks.last_mut() {
274 b.aux_margin = b.aux_margin.max(max_aux_upper_margin);
275 }
276
277 breaks.push(Linebreak::new(
278 childareas.len(),
279 max_aux,
280 f32::INFINITY,
281 f32::NAN,
282 ));
283
284 (breaks, linecount, used_aux)
285}
286
287impl Desc for dyn Prop {
288 type Props = dyn Prop;
289 type Child = dyn Child;
290 type Children = im::Vector<Option<Box<dyn Layout<Self::Child>>>>;
291
292 fn stage<'a>(
293 props: &Self::Props,
294 outer_area: crate::PxRect,
295 outer_limits: crate::PxLimits,
296 children: &Self::Children,
297 id: std::sync::Weak<crate::SourceID>,
298 renderable: Option<Rc<dyn Renderable>>,
299 window: &mut crate::component::window::WindowState,
300 ) -> Box<dyn Staged + 'a> {
301 use super::Swappable;
302
303 let myarea = props.area().resolve(window.dpi);
304 let limits = outer_limits + props.limits().resolve(window.dpi);
307 let inner_dim = super::limit_dim(super::eval_dim(myarea, outer_area.dim()), limits);
308 let outer_safe = nuetralize_unsized(outer_area);
309
310 let xaxis = match props.direction() {
311 RowDirection::LeftToRight | RowDirection::RightToLeft => true,
312 RowDirection::TopToBottom | RowDirection::BottomToTop => false,
313 };
314
315 let mut childareas: im::Vector<Option<ChildCache>> = im::Vector::new();
316 let (dpi_main, _) = window.dpi.swap_axis(xaxis);
317 let (outer_main, _) = outer_safe.dim().swap_axis(xaxis);
318
319 for child in children.iter() {
321 let imposed = child.as_ref().unwrap().get_props();
322
323 let child_limit = super::apply_limit(inner_dim, limits, *imposed.rlimits());
324 let basis = imposed.basis().resolve(dpi_main).resolve(outer_main);
325
326 assert!(
327 basis.is_finite(),
328 "Basis can be unsized, but never infinite!"
329 );
330
331 let inner_area = PxRect::corners(
332 PxPoint::zero(),
333 if xaxis {
334 PxPoint::new(basis, UNSIZED_AXIS)
335 } else {
336 PxPoint::new(UNSIZED_AXIS, basis)
337 },
338 );
339
340 let stage = child
341 .as_ref()
342 .unwrap()
343 .stage(inner_area, child_limit, window);
344
345 let (main, aux) = stage.get_area().dim().swap_axis(xaxis);
346
347 let mut cache = ChildCache {
348 basis,
349 grow: imposed.grow(),
350 shrink: imposed.shrink(),
351 aux,
352 margin: imposed
353 .margin()
354 .resolve(window.dpi)
355 .to_perimeter(outer_safe),
356 limits: child_limit,
357 };
358 if cache.basis == UNSIZED_AXIS {
359 cache.basis = main;
360 }
361
362 if !xaxis {
364 let ltrb = cache.margin.v.as_array_mut();
365 ltrb.swap(0, 1);
366 ltrb.swap(2, 3);
367 }
368
369 childareas.push_back(Some(cache));
370 }
371
372 let mut fold = VectorFold::new(Persist2::new(&|prev: (f32, f32, f32),
378 n: &Option<ChildCache>|
379 -> (f32, f32, f32) {
380 let cache = n.as_ref().unwrap();
381 (
382 cache.basis + prev.0 + merge_margin(prev.2, cache.margin.topleft().width),
383 cache.aux.max(prev.1),
384 cache.margin.bottomright().width,
385 )
386 }));
387
388 let (_, (used_main, used_aux, _)) =
389 fold.call(fold.init(), (0.0, 0.0, f32::NAN), &childareas);
390
391 let evaluated_area = {
392 let (used_x, used_y) = super::swap_pair(xaxis, (used_main, used_aux));
393 let area = map_unsized_area(myarea, PxDim::new(used_x, used_y));
394
395 super::limit_area(area * outer_safe, limits)
397 };
398
399 let (unsized_x, unsized_y) = check_unsized_abs(outer_area.bottomright());
400
401 let mut staging: im::Vector<Option<Box<dyn Staged>>> = im::Vector::new();
402 let mut nodes: im::Vector<Option<Rc<rtree::Node>>> = im::Vector::new();
403
404 if (unsized_x && xaxis) || (unsized_y && !xaxis) {
405 return Box::new(Concrete {
407 area: evaluated_area,
408 renderable: None,
409 rtree: rtree::Node::new(
410 evaluated_area.to_untyped(),
411 Some(props.zindex()),
412 nodes,
413 id,
414 window,
415 ),
416 children: staging,
417 layer: None,
418 });
419 }
420
421 let (total_main, total_aux) = inner_dim.swap_axis(xaxis);
422 let (breaks, linecount, used_aux) = if props.wrap() {
424 let r = wrap_line(
428 &childareas,
429 props,
430 xaxis,
431 total_main,
432 total_aux,
433 used_aux,
434 1,
435 props.align(),
436 props.direction() == RowDirection::BottomToTop
437 || props.direction() == RowDirection::RightToLeft,
438 window.dpi,
439 );
440
441 if !props.obstacles().is_empty() && props.align() != FlexJustify::Start {
442 let mut used_aux = used_aux;
444 let mut prev = (SmallVec::new(), 1, used_aux);
445 let mut linecount = 0;
446
447 while linecount != prev.1 || (used_aux - prev.2) > 0.001 {
449 linecount = prev.1;
450 used_aux = prev.2;
451 prev = wrap_line(
452 &childareas,
453 props,
454 xaxis,
455 total_main,
456 total_aux,
457 prev.2,
458 prev.1,
459 props.align(),
460 props.direction() == RowDirection::BottomToTop
461 || props.direction() == RowDirection::RightToLeft,
462 window.dpi,
463 );
464 }
465
466 prev
467 } else {
468 (r.0, r.1, used_aux)
469 }
470 } else {
471 let mut breaks = SmallVec::<[Linebreak; 10]>::new();
472 breaks.push(Linebreak::new(
473 childareas.len(),
474 total_aux,
475 f32::INFINITY,
476 f32::NAN,
477 ));
478 (breaks, 1, used_aux)
479 };
480
481 let (mut aux, inner_aux) =
483 justify_inner_outer(props.align(), total_aux, used_aux, linecount);
484
485 let mut main = 0.0;
486 let mut curindex = 0;
487 let mut prev_margin = f32::NAN;
488
489 for indice in 0..breaks.len() {
491 let b = &breaks[indice];
492 let mut totalgrow = 0.0;
493 let mut totalshrink = 0.0;
494 let mut used = 0.0;
495
496 for i in curindex..b.index {
498 let Some(a) = &childareas[i] else {
499 continue;
500 };
501 totalgrow += a.grow;
502 totalshrink += a.shrink;
503 used += a.basis;
504 }
505
506 let (total_span, max_aux) = if b.skip.is_finite() {
508 let mut max_aux = breaks.last().unwrap().lineheight;
509 for j in indice..breaks.len() {
510 if breaks[j].skip.is_infinite() {
511 max_aux = breaks[j].lineheight;
512 break;
513 }
514 }
515
516 (b.skip - main, max_aux)
517 } else {
518 (total_main - main, b.lineheight)
519 };
520
521 let diff = total_span - used;
522 let ratio = if diff > 0.0 {
523 if totalgrow != 0.0 {
524 diff / totalgrow
525 } else {
526 0.0
527 }
528 } else if totalshrink != 0.0 {
529 diff / totalshrink
530 } else {
531 0.0
532 };
533
534 for i in curindex..b.index {
535 if let Some(child) = &mut childareas[i] {
536 child.basis += ratio * (if diff > 0.0 { child.grow } else { child.shrink });
537 }
538 }
539
540 let (outer, inner) = justify_inner_outer(
541 props.justify(),
542 total_span,
543 used,
544 (b.index - curindex) as i32,
545 );
546 main += outer;
547
548 for i in curindex..b.index {
550 let Some(c) = &childareas[i] else {
551 continue;
552 };
553
554 if !prev_margin.is_nan() {
556 main += prev_margin.max(c.margin.topleft().width);
557 }
558 prev_margin = c.margin.bottomright().width;
559
560 let mut area = if props.direction() == RowDirection::RightToLeft
562 || props.direction() == RowDirection::BottomToTop
563 {
564 PxRect::new(
565 total_main - main,
566 aux,
567 total_main - (main + c.basis),
568 aux + max_aux,
569 )
570 } else {
571 PxRect::new(main, aux, main + c.basis, aux + max_aux)
572 };
573
574 super::assert_sized(area);
575 area.set_topleft(area.topleft().min(area.bottomright()));
576 if !xaxis {
578 let ltrb = area.v.as_array_mut();
579 ltrb.swap(0, 1);
580 ltrb.swap(2, 3);
581 }
582
583 let stage = children[i]
584 .as_ref()
585 .unwrap()
586 .stage(area, c.limits + limits, window);
587 if let Some(node) = stage.get_rtree().upgrade() {
588 nodes.push_back(Some(node));
589 }
590 staging.push_back(Some(stage));
591
592 main += c.basis + inner;
593 }
594
595 if b.skip.is_finite() {
596 main = b.lineheight;
597 } else {
598 main = 0.0;
599 aux += b.lineheight + inner_aux;
600
601 if !b.aux_margin.is_nan() {
602 aux += b.aux_margin;
603 }
604 }
605 prev_margin = f32::NAN;
606 curindex = b.index;
607 }
608
609 Box::new(Concrete {
610 area: evaluated_area,
611 renderable,
612 rtree: rtree::Node::new(
613 evaluated_area.to_untyped(),
614 Some(props.zindex()),
615 nodes,
616 id,
617 window,
618 ),
619 children: staging,
620 layer: None,
621 })
622 }
623}