1use std::rc::Rc;
9
10day_reactive::tls_slots! {
11 layout;
12 static REPORTED: std::cell::RefCell<std::collections::HashSet<RNode>> =
15 std::cell::RefCell::new(std::collections::HashSet::new());
16}
17
18use day_spec::*;
19
20use crate::tree::{Flex, RNode, Tree, TreeOps};
21
22pub trait Layout: 'static {
25 fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size;
26 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect);
27 fn baseline(&self, _cx: &mut dyn LayoutOps, _children: &[RNode], _size: Size) -> Option<f64> {
35 None
36 }
37}
38
39pub trait LayoutOps {
41 fn measure_child(&mut self, child: RNode, p: Proposal) -> Size;
42 fn place_child(&mut self, child: RNode, rect: Rect);
43 fn place_child_native(&mut self, child: RNode, rect: Rect) {
46 self.place_child(child, rect);
47 }
48 fn flex_of(&self, child: RNode) -> Flex;
49 fn children_of(&self, node: RNode) -> Vec<RNode>;
50 fn measure_leaf(&mut self, p: Proposal) -> Size;
52 fn baseline_of(&mut self, child: RNode, size: Size) -> Option<f64>;
56 fn set_scroll_content(&mut self, content: Size);
58 fn scroll_bar_inset(&mut self, _vertical: bool) -> f64 {
61 0.0
62 }
63 fn report_overflow(&mut self, _needed: f64, _available: f64) {}
69}
70
71pub struct EngineCx<'a, B: Toolkit> {
72 pub(crate) tree: &'a mut Tree<B>,
73 pub(crate) offset: Point,
74 pub(crate) current: RNode,
75 pub(crate) parent_size: Size,
77}
78
79impl<B: Toolkit> LayoutOps for EngineCx<'_, B> {
80 fn measure_child(&mut self, child: RNode, p: Proposal) -> Size {
81 measure_node(self.tree, child, p)
82 }
83 fn place_child(&mut self, child: RNode, rect: Rect) {
84 let rect = if crate::layout_direction() == day_geometry::LayoutDirection::Rtl {
90 Rect::new(
91 self.parent_size.width - rect.origin.x - rect.size.width,
92 rect.origin.y,
93 rect.size.width,
94 rect.size.height,
95 )
96 } else {
97 rect
98 };
99 place_node(self.tree, child, rect, self.offset, false);
100 }
101 fn place_child_native(&mut self, child: RNode, rect: Rect) {
102 place_node(self.tree, child, rect, self.offset, false);
103 }
104 fn flex_of(&self, child: RNode) -> Flex {
105 self.tree.node(child).map(|n| n.flex).unwrap_or_default()
106 }
107 fn children_of(&self, node: RNode) -> Vec<RNode> {
108 self.tree
109 .node(node)
110 .map(|n| n.children.clone())
111 .unwrap_or_default()
112 }
113 fn measure_leaf(&mut self, p: Proposal) -> Size {
114 let Some(n) = self.tree.node(self.current) else {
115 return Size::ZERO;
116 };
117 let kind = n.kind;
118 let Some(h) = n.handle.clone() else {
119 return Size::ZERO;
120 };
121 self.tree.toolkit.measure(&h, kind, p)
122 }
123 fn baseline_of(&mut self, child: RNode, size: Size) -> Option<f64> {
124 baseline_node(self.tree, child, size)
125 }
126 fn set_scroll_content(&mut self, content: Size) {
127 let current = self.current;
128 let Some(n) = self.tree.node_mut(current) else {
129 return;
130 };
131 n.scroll_content = Some(content);
133 let Some(h) = n.handle.clone() else { return };
134 self.tree.toolkit.set_scroll_content(&h, content);
135 }
136 fn scroll_bar_inset(&mut self, vertical: bool) -> f64 {
137 let Some(h) = self.tree.node(self.current).and_then(|n| n.handle.clone()) else {
138 return 0.0;
139 };
140 self.tree.toolkit.scroll_bar_inset(&h, vertical).max(0.0)
141 }
142
143 #[cfg(debug_assertions)]
144 fn report_overflow(&mut self, needed: f64, available: f64) {
145 if REPORTED.with(|r| !r.borrow_mut().insert(self.current)) {
149 return;
150 }
151 let mut ids: Vec<String> = Vec::new();
156 let mut stack = vec![self.current];
157 while let Some(node) = stack.pop() {
158 if ids.len() >= 6 {
159 break;
160 }
161 if let Some(n) = self.tree.node(node) {
162 ids.extend(n.id.clone());
163 stack.extend(n.children.iter().rev());
164 }
165 }
166 let who = if ids.is_empty() {
167 String::from("no .id in reach")
168 } else {
169 ids.join(", ")
170 };
171 log::warn!(
172 "day layout: children overflow their container by {:.0}pt ({needed:.0} needed, \
173 {available:.0} available; {who}) — give the row a fit policy: \
174 .fit(RowFit::Wrap/Scroll) or adaptive(..) (docs/size-classes.md)",
175 needed - available,
176 );
177 }
178}
179
180pub(crate) fn measure_node<B: Toolkit>(tree: &mut Tree<B>, node: RNode, p: Proposal) -> Size {
181 let key = p.cache_key();
182 let (layout, children) = {
183 let Some(n) = tree.node(node) else {
184 return Size::ZERO;
185 };
186 if !n.needs_measure
187 && let Some(&(_, s)) = n.cache.iter().find(|(k, _)| *k == key)
188 {
189 return s;
190 }
191 (n.layout.clone(), n.children.clone())
192 };
193 let mut cx = EngineCx {
194 tree,
195 offset: Point::ZERO,
196 current: node,
197 parent_size: Size::ZERO, };
199 let size = layout.measure(&mut cx, &children, p);
200 if let Some(n) = tree.node_mut(node) {
201 n.needs_measure = false;
202 if n.cache.len() >= 4 {
203 n.cache.clear();
204 }
205 n.cache.push((key, size));
206 }
207 size
208}
209
210pub(crate) fn baseline_node<B: Toolkit>(
216 tree: &mut Tree<B>,
217 node: RNode,
218 size: Size,
219) -> Option<f64> {
220 let key = Proposal::exact(size).cache_key();
221 let (layout, children, handle, kind) = {
222 let n = tree.node(node)?;
223 if !n.needs_measure
224 && let Some((k, b)) = n.baseline_cache
225 && k == key
226 {
227 return b;
228 }
229 (
230 n.layout.clone(),
231 n.children.clone(),
232 n.handle.clone(),
233 n.kind,
234 )
235 };
236 let baseline = match handle {
238 Some(h) if children.is_empty() => tree.toolkit.first_baseline(&h, kind, size),
239 _ => {
240 let mut cx = EngineCx {
241 tree,
242 offset: Point::ZERO,
243 current: node,
244 parent_size: Size::ZERO,
245 };
246 layout.baseline(&mut cx, &children, size)
247 }
248 };
249 if let Some(n) = tree.node_mut(node) {
250 n.baseline_cache = Some((key, baseline));
251 }
252 baseline
253}
254
255pub(crate) fn place_node<B: Toolkit>(
258 tree: &mut Tree<B>,
259 node: RNode,
260 rect: Rect,
261 offset: Point,
262 is_root: bool,
263) {
264 let abs = Rect {
265 origin: rect.origin.offset(offset.x, offset.y),
266 size: rect.size,
267 };
268 let (layout, children, has_handle) = {
269 let Some(n) = tree.node(node) else { return };
270 (n.layout.clone(), n.children.clone(), n.handle.is_some())
271 };
272 let native_frame = tree
280 .node(node)
281 .map(|n| n.kind == day_spec::kinds::COVER)
282 .unwrap_or(false);
283 let child_offset = if has_handle {
284 if !is_root && !native_frame {
285 let changed = tree
286 .node(node)
287 .map(|n| {
288 n.last_native_frame
289 .map(|f| !f.approx_eq(&abs, 0.25))
290 .unwrap_or(true)
291 })
292 .unwrap_or(false);
293 if changed {
294 let h = tree.node(node).and_then(|n| n.handle.clone());
295 if let Some(h) = h {
296 let anim = tree.resolve_anim(node);
297 tree.toolkit.set_frame(&h, abs, anim.as_ref());
298 }
299 if tree
300 .node(node)
301 .map(|n| n.kind == day_spec::kinds::CANVAS)
302 .unwrap_or(false)
303 {
304 crate::tree::enqueue_event(
306 crate::tree::rnode_to_id(node),
307 day_spec::Event::FrameChanged(abs.size),
308 );
309 }
310 }
311 }
312 Point::ZERO
313 } else {
314 abs.origin
315 };
316 let relayout_cells = tree
323 .node(node)
324 .map(|n| {
325 n.kind == day_spec::kinds::LIST
326 && n.last_native_frame
327 .map(|f| (f.size.width - abs.size.width).abs() > 0.25)
328 .unwrap_or(false)
329 })
330 .unwrap_or(false);
331 if let Some(n) = tree.node_mut(node) {
332 n.last_native_frame = Some(abs);
333 n.needs_layout = false;
334 }
335 if relayout_cells {
336 for key in tree.list_cell_keys(node) {
337 tree.list_layout_cell(node, key);
338 }
339 }
340 let mut cx = EngineCx {
341 tree,
342 offset: child_offset,
343 current: node,
344 parent_size: rect.size,
345 };
346 layout.place(&mut cx, &children, Rect::from_size(rect.size));
347}
348
349fn wrapper_baseline(
362 cx: &mut dyn LayoutOps,
363 children: &[RNode],
364 child_size: Size,
365 dy: f64,
366) -> Option<f64> {
367 let &c = children.first()?;
368 Some(cx.baseline_of(c, child_size)? + dy)
369}
370
371pub struct PassThrough;
373
374impl Layout for PassThrough {
375 fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
376 match children.first() {
377 Some(&c) => cx.measure_child(c, p),
378 None => Size::ZERO,
379 }
380 }
381 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
382 if let Some(&c) = children.first() {
383 let s = cx.measure_child(c, Proposal::exact(bounds.size));
384 cx.place_child(c, Rect::from_size(s));
385 }
386 }
387}
388
389pub struct CellCenter;
394
395impl Layout for CellCenter {
396 fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
397 match children.first() {
398 Some(&c) => cx.measure_child(c, p),
399 None => Size::ZERO,
400 }
401 }
402 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
403 if let Some(&c) = children.first() {
404 let s = cx.measure_child(c, Proposal::exact(bounds.size));
405 let y = ((bounds.size.height - s.height) / 2.0).max(0.0);
406 cx.place_child(
407 c,
408 Rect::new(0.0, y, s.width, s.height.min(bounds.size.height)),
409 );
410 }
411 }
412 fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
413 PassThrough::forward(cx, children, size)
414 }
415}
416
417pub struct CellMetrics(pub day_spec::RowMetrics);
421
422impl CellMetrics {
423 fn for_child(&self, cx: &dyn LayoutOps, child: RNode) -> day_spec::RowMetrics {
426 let mut node = child;
428 for _ in 0..8 {
429 if cx.flex_of(node).whole_row {
430 let i = self.0.insets;
431 return day_spec::RowMetrics {
432 insets: day_spec::Insets {
433 top: 0.0,
434 leading: i.leading,
435 bottom: 0.0,
436 trailing: i.trailing,
437 },
438 min_height: 0.0,
439 };
440 }
441 match cx.children_of(node)[..] {
442 [only] => node = only,
443 _ => break,
444 }
445 }
446 self.0
447 }
448}
449
450impl Layout for CellMetrics {
451 fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
452 let Some(&c) = children.first() else {
453 return Size::ZERO;
454 };
455 let m = self.for_child(cx, c);
456 let i = m.insets;
457 let (dx, dy) = (i.leading + i.trailing, i.top + i.bottom);
458 let s = cx.measure_child(
459 c,
460 Proposal::new(
461 p.width.map(|w| (w - dx).max(0.0)),
462 p.height.map(|h| (h - dy).max(0.0)),
463 ),
464 );
465 Size::new(s.width + dx, (s.height + dy).max(m.min_height))
466 }
467 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
468 let Some(&c) = children.first() else {
469 return;
470 };
471 let i = self.for_child(cx, c).insets;
472 let inner = Size::new(
473 (bounds.size.width - i.leading - i.trailing).max(0.0),
474 (bounds.size.height - i.top - i.bottom).max(0.0),
475 );
476 let s = cx.measure_child(c, Proposal::new(Some(inner.width), None));
477 let (y, h) = if s.height <= inner.height {
480 (i.top + (inner.height - s.height) / 2.0, s.height)
481 } else {
482 let h = s.height.min(bounds.size.height);
483 (((bounds.size.height - h) / 2.0).max(0.0), h)
484 };
485 cx.place_child(c, Rect::new(i.leading, y, inner.width, h));
486 }
487 fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
488 PassThrough::forward(cx, children, size)
489 }
490}
491
492impl PassThrough {
493 fn forward(cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
495 let &c = children.first()?;
496 let cs = cx.measure_child(c, Proposal::exact(size));
497 wrapper_baseline(cx, children, cs, 0.0)
498 }
499}
500
501pub struct FillThrough;
508
509impl Layout for FillThrough {
510 fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
511 match children.first() {
512 Some(&c) => cx.measure_child(c, p),
513 None => Size::ZERO,
514 }
515 }
516 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
517 if let Some(&c) = children.first() {
518 cx.place_child(c, Rect::from_size(bounds.size));
519 }
520 }
521 fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
522 wrapper_baseline(cx, children, size, 0.0)
523 }
524}
525
526pub struct LeafLayout;
528
529impl Layout for LeafLayout {
530 fn measure(&self, cx: &mut dyn LayoutOps, _children: &[RNode], p: Proposal) -> Size {
531 cx.measure_leaf(p)
532 }
533 fn place(&self, _cx: &mut dyn LayoutOps, _children: &[RNode], _bounds: Rect) {}
534}
535
536#[derive(Clone, Copy, PartialEq, Eq)]
537pub enum Axis {
538 Vertical,
539 Horizontal,
540}
541
542#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
543pub enum CrossAlign {
544 Leading,
545 #[default]
546 Center,
547 Trailing,
548 FirstBaseline,
553}
554
555impl CrossAlign {
556 fn fraction(self) -> f64 {
558 match self {
559 CrossAlign::Leading => 0.0,
560 CrossAlign::Center | CrossAlign::FirstBaseline => 0.5,
563 CrossAlign::Trailing => 1.0,
564 }
565 }
566}
567
568pub struct StackLayout {
571 pub axis: Axis,
572 pub spacing: f64,
573 pub align: CrossAlign,
574}
575
576impl StackLayout {
577 fn main(&self, s: Size) -> f64 {
578 match self.axis {
579 Axis::Vertical => s.height,
580 Axis::Horizontal => s.width,
581 }
582 }
583 fn cross(&self, s: Size) -> f64 {
584 match self.axis {
585 Axis::Vertical => s.width,
586 Axis::Horizontal => s.height,
587 }
588 }
589 fn mk(&self, main: f64, cross: f64) -> Size {
590 match self.axis {
591 Axis::Vertical => Size::new(cross, main),
592 Axis::Horizontal => Size::new(main, cross),
593 }
594 }
595 fn split(&self, p: Proposal) -> (Option<f64>, Option<f64>) {
596 match self.axis {
597 Axis::Vertical => (p.height, p.width),
598 Axis::Horizontal => (p.width, p.height),
599 }
600 }
601 fn proposal(&self, main: Option<f64>, cross: Option<f64>) -> Proposal {
602 match self.axis {
603 Axis::Vertical => Proposal::new(cross, main),
604 Axis::Horizontal => Proposal::new(main, cross),
605 }
606 }
607 fn grows_main(&self, f: Flex) -> bool {
608 f.is_spacer
609 || match self.axis {
610 Axis::Vertical => f.grow_h,
611 Axis::Horizontal => f.grow_w,
612 }
613 }
614 fn yields_main(&self, f: Flex) -> bool {
617 self.axis == Axis::Vertical && f.yields_h && !self.grows_main(f)
618 }
619 fn grows_cross(&self, f: Flex) -> bool {
620 match self.axis {
621 Axis::Vertical => f.grow_w,
622 Axis::Horizontal => f.grow_h,
623 }
624 }
625
626 fn flatten(cx: &mut dyn LayoutOps, children: &[RNode], out: &mut Vec<RNode>) {
627 for &c in children {
628 if cx.flex_of(c).is_group {
629 let inner = cx.children_of(c);
630 Self::flatten(cx, &inner, out);
631 } else {
632 out.push(c);
633 }
634 }
635 }
636
637 fn negotiate(&self, cx: &mut dyn LayoutOps, kids: &[RNode], p: Proposal) -> Vec<Size> {
638 let (main_p, cross_p) = self.split(p);
639 let mut sizes = vec![Size::ZERO; kids.len()];
640 let mut flex_idx = Vec::new();
641 let mut yield_idx = Vec::new();
642 let mut rigid_main = 0.0;
643 for (i, &k) in kids.iter().enumerate() {
644 let f = cx.flex_of(k);
645 if self.grows_main(f) {
646 flex_idx.push(i);
647 } else if self.yields_main(f) {
648 yield_idx.push(i);
649 } else {
650 let s = cx.measure_child(k, self.proposal(None, cross_p));
651 rigid_main += self.main(s);
652 sizes[i] = s;
653 }
654 }
655 if let Some(mp) = main_p {
662 let spacing_total = self.spacing * (kids.len().saturating_sub(1)) as f64;
663 let available = (mp - spacing_total).max(0.0);
664 if rigid_main > available {
665 let mut budget = available;
666 rigid_main = 0.0;
667 for (i, &k) in kids.iter().enumerate() {
668 let f = cx.flex_of(k);
669 if self.grows_main(f) || self.yields_main(f) {
670 continue;
671 }
672 if self.main(sizes[i]) > budget {
673 sizes[i] = cx.measure_child(k, self.proposal(Some(budget), cross_p));
674 }
675 let m = self.main(sizes[i]);
676 rigid_main += m;
677 budget = (budget - m).max(0.0);
678 }
679 }
680 }
681 for &i in &yield_idx {
684 let spacing_total = self.spacing * (kids.len().saturating_sub(1)) as f64;
685 let left = main_p.map(|mp| (mp - rigid_main - spacing_total).max(0.0));
686 sizes[i] = cx.measure_child(kids[i], self.proposal(left, cross_p));
687 rigid_main += self.main(sizes[i]);
688 }
689 if !flex_idx.is_empty() {
690 let spacing_total = self.spacing * (kids.len().saturating_sub(1)) as f64;
691 match main_p {
692 Some(mp) => {
693 let remaining = (mp - rigid_main - spacing_total).max(0.0);
694 let share = remaining / flex_idx.len() as f64;
695 for &i in &flex_idx {
696 let f = cx.flex_of(kids[i]);
697 sizes[i] = if f.is_spacer {
698 self.mk(share, 0.0)
699 } else {
700 cx.measure_child(kids[i], self.proposal(Some(share), cross_p))
701 };
702 }
703 }
704 None => {
705 for &i in &flex_idx {
706 let f = cx.flex_of(kids[i]);
707 sizes[i] = if f.is_spacer {
708 Size::ZERO
709 } else {
710 cx.measure_child(kids[i], self.proposal(None, cross_p))
711 };
712 }
713 }
714 }
715 }
716 sizes
717 }
718
719 fn baselines(
723 &self,
724 cx: &mut dyn LayoutOps,
725 kids: &[RNode],
726 sizes: &[Size],
727 ) -> Vec<Option<f64>> {
728 if self.align != CrossAlign::FirstBaseline || self.axis != Axis::Horizontal {
729 return vec![None; kids.len()];
730 }
731 kids.iter()
732 .zip(sizes)
733 .map(|(&k, &s)| cx.baseline_of(k, s))
734 .collect()
735 }
736
737 fn baseline_cross_extent(&self, baselines: &[Option<f64>], sizes: &[Size]) -> Option<f64> {
741 let deepest = baselines.iter().flatten().copied().fold(f64::MIN, f64::max);
742 if deepest == f64::MIN {
743 return None;
744 }
745 let below = baselines
746 .iter()
747 .zip(sizes)
748 .filter_map(|(b, s)| b.map(|b| self.cross(*s) - b))
749 .fold(0.0, f64::max);
750 Some(deepest + below)
751 }
752}
753
754impl Layout for StackLayout {
755 fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
756 let mut kids = Vec::new();
757 Self::flatten(cx, children, &mut kids);
758 if kids.is_empty() {
759 return Size::ZERO;
760 }
761 let (main_p, cross_p) = self.split(p);
762 let sizes = self.negotiate(cx, &kids, p);
763 let spacing_total = self.spacing * (kids.len() - 1) as f64;
764 let has_flex = kids.iter().any(|&k| self.grows_main(cx.flex_of(k)));
765 let needed = sizes.iter().map(|&s| self.main(s)).sum::<f64>() + spacing_total;
769 let main_total = match main_p {
770 Some(mp) if has_flex => mp.max(needed),
771 _ => needed,
772 };
773 let grows_cross = kids.iter().any(|&k| self.grows_cross(cx.flex_of(k)));
774 let tallest = sizes.iter().map(|&s| self.cross(s)).fold(0.0, f64::max);
775 let stacked = self
778 .baseline_cross_extent(&self.baselines(cx, &kids, &sizes), &sizes)
779 .unwrap_or(0.0)
780 .max(tallest);
781 let cross_total = match cross_p {
782 Some(cp) if grows_cross => cp,
783 _ => stacked,
784 };
785 self.mk(main_total, cross_total)
786 }
787
788 fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
792 let mut kids = Vec::new();
793 Self::flatten(cx, children, &mut kids);
794 if kids.is_empty() {
795 return None;
796 }
797 let sizes = self.negotiate(cx, &kids, Proposal::exact(size));
798 match self.axis {
799 Axis::Horizontal => {
800 let baselines: Vec<Option<f64>> = kids
801 .iter()
802 .zip(&sizes)
803 .map(|(&k, &s)| cx.baseline_of(k, s))
804 .collect();
805 let deepest = baselines.iter().flatten().copied().fold(f64::MIN, f64::max);
806 (deepest != f64::MIN).then_some(deepest)
807 }
808 Axis::Vertical => {
809 let mut pos = 0.0;
810 for (&k, &s) in kids.iter().zip(&sizes) {
811 if let Some(b) = cx.baseline_of(k, s) {
812 return Some(pos + b);
813 }
814 pos += self.main(s) + self.spacing;
815 }
816 None
817 }
818 }
819 }
820
821 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
822 let mut kids = Vec::new();
823 Self::flatten(cx, children, &mut kids);
824 if kids.is_empty() {
825 return;
826 }
827 let sizes = self.negotiate(cx, &kids, Proposal::exact(bounds.size));
828 let needed = sizes.iter().map(|&s| self.main(s)).sum::<f64>()
833 + self.spacing * (kids.len() - 1) as f64;
834 let available = self.main(bounds.size);
835 if available > 0.5 && needed > available + 0.5 {
841 cx.report_overflow(needed, available);
842 }
843 let bounds_cross = self.cross(bounds.size);
844 let baselines = self.baselines(cx, &kids, &sizes);
848 let deepest = baselines.iter().flatten().copied().fold(f64::MIN, f64::max);
849 let mut pos = 0.0;
850 for (i, &k) in kids.iter().enumerate() {
851 let s = sizes[i];
852 let cross_off = match (self.align, baselines[i]) {
853 (CrossAlign::FirstBaseline, Some(b)) => deepest - b,
854 (CrossAlign::Leading, _) => 0.0,
855 (CrossAlign::Center | CrossAlign::FirstBaseline, _) => {
856 ((bounds_cross - self.cross(s)) / 2.0).max(0.0)
857 }
858 (CrossAlign::Trailing, _) => (bounds_cross - self.cross(s)).max(0.0),
859 };
860 let rect = match self.axis {
861 Axis::Vertical => Rect::new(cross_off, pos, s.width, s.height),
862 Axis::Horizontal => Rect::new(pos, cross_off, s.width, s.height),
863 };
864 cx.place_child(k, rect);
865 pos += self.main(s) + self.spacing;
866 }
867 }
868}
869
870pub struct FlowLayout {
876 pub spacing: f64,
877 pub run_spacing: f64,
879 pub align: CrossAlign,
881 pub uniform: bool,
886}
887
888struct FlowLine {
890 start: usize,
891 end: usize,
892 height: f64,
893}
894
895struct FlowPlan {
898 sizes: Vec<Size>,
899 lines: Vec<FlowLine>,
900}
901
902impl FlowLayout {
903 fn pack(&self, sizes: &[Size], max_w: Option<f64>) -> Vec<FlowLine> {
906 let mut lines = Vec::new();
907 let mut start = 0;
908 let mut w = 0.0;
909 let mut h: f64 = 0.0;
910 for (i, s) in sizes.iter().enumerate() {
911 let grown = if i == start {
912 s.width
913 } else {
914 w + self.spacing + s.width
915 };
916 if i > start && max_w.is_some_and(|m| grown > m + 0.5) {
917 lines.push(FlowLine {
918 start,
919 end: i,
920 height: h,
921 });
922 start = i;
923 w = s.width;
924 h = s.height;
925 } else {
926 w = grown;
927 h = h.max(s.height);
928 }
929 }
930 if start < sizes.len() {
931 lines.push(FlowLine {
932 start,
933 end: sizes.len(),
934 height: h,
935 });
936 }
937 lines
938 }
939
940 fn natural_sizes(cx: &mut dyn LayoutOps, kids: &[RNode]) -> Vec<Size> {
941 kids.iter()
942 .map(|&k| cx.measure_child(k, Proposal::UNCONSTRAINED))
943 .collect()
944 }
945
946 fn plan(&self, cx: &mut dyn LayoutOps, kids: &[RNode], max_w: Option<f64>) -> FlowPlan {
949 let natural = Self::natural_sizes(cx, kids);
950 if !self.uniform {
951 let lines = self.pack(&natural, max_w);
952 return FlowPlan {
953 sizes: natural,
954 lines,
955 };
956 }
957 let cell_w = natural.iter().map(|s| s.width).fold(0.0, f64::max);
959 let cols = match max_w {
960 Some(m) if cell_w > 0.0 => {
963 (((m + self.spacing) / (cell_w + self.spacing)).floor() as usize).max(1)
964 }
965 _ => kids.len().max(1),
967 };
968 let col_w = match max_w {
973 Some(m) if kids.iter().any(|&k| cx.flex_of(k).grow_w) => {
974 ((m - self.spacing * (cols - 1) as f64) / cols as f64).max(cell_w)
975 }
976 _ => cell_w,
977 };
978 let col_w = max_w.map_or(col_w, |m| col_w.min(m));
982 let sizes: Vec<Size> = kids
986 .iter()
987 .map(|&k| {
988 let s = cx.measure_child(k, Proposal::new(Some(col_w), None));
989 Size::new(col_w, s.height)
990 })
991 .collect();
992 let mut lines = Vec::new();
993 let mut start = 0;
994 while start < sizes.len() {
995 let end = (start + cols).min(sizes.len());
996 let height = sizes[start..end]
997 .iter()
998 .map(|s| s.height)
999 .fold(0.0, f64::max);
1000 lines.push(FlowLine { start, end, height });
1001 start = end;
1002 }
1003 FlowPlan { sizes, lines }
1004 }
1005}
1006
1007impl Layout for FlowLayout {
1008 fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
1009 let mut kids = Vec::new();
1010 StackLayout::flatten(cx, children, &mut kids);
1011 if kids.is_empty() {
1012 return Size::ZERO;
1013 }
1014 let plan = self.plan(cx, &kids, p.width);
1015 let height = plan.lines.iter().map(|l| l.height).sum::<f64>()
1016 + self.run_spacing * plan.lines.len().saturating_sub(1) as f64;
1017 let width = p.width.unwrap_or_else(|| {
1020 plan.sizes.iter().map(|s| s.width).sum::<f64>()
1021 + self.spacing * plan.sizes.len().saturating_sub(1) as f64
1022 });
1023 Size::new(width, height)
1024 }
1025
1026 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
1027 let mut kids = Vec::new();
1028 StackLayout::flatten(cx, children, &mut kids);
1029 if kids.is_empty() {
1030 return;
1031 }
1032 let plan = self.plan(cx, &kids, Some(bounds.size.width));
1033 let mut y = 0.0;
1034 for line in &plan.lines {
1035 let mut x = 0.0;
1036 let range = line.start..line.end;
1037 for (&k, &s) in kids[range.clone()].iter().zip(&plan.sizes[range]) {
1038 let dy = ((line.height - s.height) * self.align.fraction()).max(0.0);
1039 cx.place_child(k, Rect::new(x, y + dy, s.width, s.height));
1040 x += s.width + self.spacing;
1041 }
1042 y += line.height + self.run_spacing;
1043 }
1044 }
1045}
1046
1047struct GridCell {
1050 node: RNode,
1051 col: usize,
1052 span: usize,
1053 flex: Flex,
1054}
1055
1056struct GridRowRef {
1058 cells: Vec<GridCell>,
1059 full_width: bool,
1060 valign: Option<CrossAlign>,
1061}
1062
1063struct GridGeom {
1065 rows: Vec<GridRowRef>,
1066 col_x: Vec<f64>,
1067 col_w: Vec<f64>,
1068 row_y: Vec<f64>,
1069 row_h: Vec<f64>,
1070 cell_sizes: Vec<Vec<Size>>,
1072 size: Size,
1073}
1074
1075pub struct GridLayout {
1082 pub row_spacing: f64,
1083 pub column_spacing: f64,
1084 pub align: Alignment,
1085}
1086
1087impl GridLayout {
1088 fn collect(&self, cx: &mut dyn LayoutOps, children: &[RNode]) -> (Vec<GridRowRef>, usize) {
1090 let mut tops = Vec::new();
1091 StackLayout::flatten(cx, children, &mut tops);
1092 let mut rows = Vec::new();
1093 let mut ncols = 0usize;
1094 for &t in &tops {
1095 let f = cx.flex_of(t);
1096 if f.grid.is_row {
1097 let inner = cx.children_of(t);
1098 let mut cell_nodes = Vec::new();
1099 StackLayout::flatten(cx, &inner, &mut cell_nodes);
1100 let mut cells = Vec::new();
1101 let mut col = 0usize;
1102 for &c in &cell_nodes {
1103 let cf = cx.flex_of(c);
1104 let span = cf.grid.col_span.max(1) as usize;
1105 cells.push(GridCell {
1106 node: c,
1107 col,
1108 span,
1109 flex: cf,
1110 });
1111 col += span;
1112 }
1113 ncols = ncols.max(col);
1114 rows.push(GridRowRef {
1115 cells,
1116 full_width: false,
1117 valign: f.grid.row_valign,
1118 });
1119 } else {
1120 rows.push(GridRowRef {
1123 cells: vec![GridCell {
1124 node: t,
1125 col: 0,
1126 span: 1,
1127 flex: f,
1128 }],
1129 full_width: true,
1130 valign: None,
1131 });
1132 }
1133 }
1134 (rows, ncols)
1135 }
1136
1137 fn span_width(&self, col_w: &[f64], col: usize, span: usize) -> f64 {
1138 let end = (col + span).min(col_w.len());
1139 let cols: f64 = col_w[col..end].iter().sum();
1140 cols + self.column_spacing * (end.saturating_sub(col + 1)) as f64
1141 }
1142
1143 fn geometry(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> GridGeom {
1148 let (mut rows, ncols) = self.collect(cx, children);
1149 if rows.is_empty() {
1150 return GridGeom {
1151 rows,
1152 col_x: Vec::new(),
1153 col_w: Vec::new(),
1154 row_y: Vec::new(),
1155 row_h: Vec::new(),
1156 cell_sizes: Vec::new(),
1157 size: Size::ZERO,
1158 };
1159 }
1160 let ncols = ncols.max(1);
1161 for r in &mut rows {
1162 if r.full_width {
1163 r.cells[0].span = ncols;
1164 }
1165 }
1166 let unconstrained = Proposal::new(None, None);
1167
1168 let mut col_ideal = vec![0.0f64; ncols];
1171 let mut col_flex = vec![false; ncols];
1172 let mut flex_ideal = vec![0.0f64; ncols];
1173 for r in rows.iter().filter(|r| !r.full_width) {
1174 for c in r.cells.iter().filter(|c| c.span == 1 && !c.flex.is_spacer) {
1175 if c.flex.grow_w {
1176 col_flex[c.col] = true;
1177 if p.width.is_none() {
1178 let s = cx.measure_child(c.node, unconstrained);
1179 flex_ideal[c.col] = flex_ideal[c.col].max(s.width);
1180 }
1181 } else {
1182 let s = cx.measure_child(c.node, unconstrained);
1183 col_ideal[c.col] = col_ideal[c.col].max(s.width);
1184 }
1185 }
1186 }
1187 for r in rows.iter().filter(|r| !r.full_width) {
1189 for c in r.cells.iter().filter(|c| c.span > 1 && !c.flex.is_spacer) {
1190 if c.flex.grow_w {
1191 let end = (c.col + c.span).min(ncols);
1192 col_flex[c.col..end].fill(true);
1193 }
1194 }
1195 }
1196 for r in rows.iter().filter(|r| !r.full_width) {
1199 for c in r.cells.iter().filter(|c| c.span > 1 && !c.flex.is_spacer) {
1200 if c.flex.grow_w {
1201 continue;
1202 }
1203 let s = cx.measure_child(c.node, unconstrained);
1204 let end = (c.col + c.span).min(ncols);
1205 let avail = self.span_width(&col_ideal, c.col, c.span);
1206 let deficit = s.width - avail;
1207 if deficit > 0.0 {
1208 let flexed: Vec<usize> = (c.col..end).filter(|&k| col_flex[k]).collect();
1209 let targets = if flexed.is_empty() {
1210 (c.col..end).collect()
1211 } else {
1212 flexed
1213 };
1214 let add = deficit / targets.len() as f64;
1215 for k in targets {
1216 col_ideal[k] += add;
1217 }
1218 }
1219 }
1220 }
1221 let mut fw_ideal = 0.0f64;
1224 let mut fw_flex = false;
1225 for r in rows.iter().filter(|r| r.full_width) {
1226 let c = &r.cells[0];
1227 if c.flex.is_spacer {
1228 continue;
1229 }
1230 if c.flex.grow_w {
1231 fw_flex = true;
1232 } else {
1233 let s = cx.measure_child(c.node, unconstrained);
1234 fw_ideal = fw_ideal.max(s.width);
1235 }
1236 }
1237
1238 let gutters = self.column_spacing * (ncols - 1) as f64;
1240 let has_flex_col = col_flex.iter().any(|&f| f);
1241 let mut col_w = vec![0.0f64; ncols];
1242 match p.width {
1243 Some(pw) if has_flex_col => {
1244 let rigid: f64 = (0..ncols)
1245 .filter(|&k| !col_flex[k])
1246 .map(|k| col_ideal[k])
1247 .sum();
1248 let share = (pw - rigid - gutters).max(0.0)
1249 / col_flex.iter().filter(|&&f| f).count() as f64;
1250 for k in 0..ncols {
1251 col_w[k] = if col_flex[k] { share } else { col_ideal[k] };
1252 }
1253 }
1254 _ => {
1255 for k in 0..ncols {
1256 col_w[k] = if col_flex[k] {
1257 flex_ideal[k].max(col_ideal[k])
1258 } else {
1259 col_ideal[k]
1260 };
1261 }
1262 }
1263 }
1264 let cols_total: f64 = col_w.iter().sum::<f64>() + gutters;
1265 let grid_w = match p.width {
1266 Some(pw) if has_flex_col || fw_flex => pw,
1267 _ => cols_total.max(fw_ideal),
1268 };
1269
1270 let nrows = rows.len();
1272 let mut cell_sizes: Vec<Vec<Size>> = Vec::with_capacity(nrows);
1273 let mut row_h = vec![0.0f64; nrows];
1274 let mut row_flex = vec![false; nrows];
1275 for (ri, r) in rows.iter().enumerate() {
1276 let mut sizes = Vec::with_capacity(r.cells.len());
1277 for c in &r.cells {
1278 if c.flex.is_spacer {
1279 sizes.push(Size::ZERO);
1280 continue;
1281 }
1282 let w = if r.full_width {
1283 grid_w
1284 } else {
1285 self.span_width(&col_w, c.col, c.span)
1286 };
1287 let s = cx.measure_child(c.node, Proposal::new(Some(w), None));
1288 row_h[ri] = row_h[ri].max(s.height);
1289 if c.flex.grow_h {
1290 row_flex[ri] = true;
1291 }
1292 sizes.push(s);
1293 }
1294 cell_sizes.push(sizes);
1295 }
1296 let vgutters = self.row_spacing * (nrows - 1) as f64;
1299 if let Some(ph) = p.height
1300 && row_flex.iter().any(|&f| f)
1301 {
1302 let total: f64 = row_h.iter().sum::<f64>() + vgutters;
1303 let extra = (ph - total).max(0.0) / row_flex.iter().filter(|&&f| f).count() as f64;
1304 for (ri, flexed) in row_flex.iter().enumerate() {
1305 if *flexed {
1306 row_h[ri] += extra;
1307 }
1308 }
1309 }
1310 let grid_h = row_h.iter().sum::<f64>() + vgutters;
1311
1312 let mut col_x = vec![0.0f64; ncols];
1313 let mut x = 0.0;
1314 for k in 0..ncols {
1315 col_x[k] = x;
1316 x += col_w[k] + self.column_spacing;
1317 }
1318 let mut row_y = vec![0.0f64; nrows];
1319 let mut y = 0.0;
1320 for ri in 0..nrows {
1321 row_y[ri] = y;
1322 y += row_h[ri] + self.row_spacing;
1323 }
1324 GridGeom {
1325 rows,
1326 col_x,
1327 col_w,
1328 row_y,
1329 row_h,
1330 cell_sizes,
1331 size: Size::new(grid_w, grid_h),
1332 }
1333 }
1334}
1335
1336impl Layout for GridLayout {
1337 fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
1338 self.geometry(cx, children, p).size
1339 }
1340
1341 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
1342 let g = self.geometry(cx, children, Proposal::exact(bounds.size));
1343 for (ri, r) in g.rows.iter().enumerate() {
1344 for (ci, c) in r.cells.iter().enumerate() {
1345 if c.flex.is_spacer {
1346 continue;
1347 }
1348 let cell = if r.full_width {
1349 Rect::new(0.0, g.row_y[ri], g.size.width, g.row_h[ri])
1350 } else {
1351 Rect::new(
1352 g.col_x[c.col],
1353 g.row_y[ri],
1354 self.span_width(&g.col_w, c.col, c.span),
1355 g.row_h[ri],
1356 )
1357 };
1358 let s = g.cell_sizes[ri][ci];
1359 let w = if c.flex.grow_w {
1360 cell.size.width
1361 } else {
1362 s.width.min(cell.size.width)
1363 };
1364 let h = if c.flex.grow_h {
1365 cell.size.height
1366 } else {
1367 s.height.min(cell.size.height)
1368 };
1369 let hf = match c.flex.grid.align {
1372 Some(a) => a.h_fraction(),
1373 None => self.align.h_fraction(),
1374 };
1375 let vf = match (c.flex.grid.align, r.valign) {
1376 (Some(a), _) => a.v_fraction(),
1377 (None, Some(v)) => v.fraction(),
1378 (None, None) => self.align.v_fraction(),
1379 };
1380 let x = cell.origin.x + (cell.size.width - w) * hf;
1381 let y = cell.origin.y + (cell.size.height - h) * vf;
1382 cx.place_child(c.node, Rect::new(x, y, w, h));
1383 }
1384 }
1385 }
1387}
1388
1389#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
1392pub enum Alignment {
1393 TopLeading,
1394 Top,
1395 TopTrailing,
1396 Leading,
1397 #[default]
1398 Center,
1399 Trailing,
1400 BottomLeading,
1401 Bottom,
1402 BottomTrailing,
1403}
1404
1405impl Alignment {
1406 fn h_fraction(self) -> f64 {
1408 match self {
1409 Alignment::TopLeading | Alignment::Leading | Alignment::BottomLeading => 0.0,
1410 Alignment::Top | Alignment::Center | Alignment::Bottom => 0.5,
1411 Alignment::TopTrailing | Alignment::Trailing | Alignment::BottomTrailing => 1.0,
1412 }
1413 }
1414 fn v_fraction(self) -> f64 {
1416 match self {
1417 Alignment::TopLeading | Alignment::Top | Alignment::TopTrailing => 0.0,
1418 Alignment::Leading | Alignment::Center | Alignment::Trailing => 0.5,
1419 Alignment::BottomLeading | Alignment::Bottom | Alignment::BottomTrailing => 1.0,
1420 }
1421 }
1422}
1423
1424pub struct OverlayLayout {
1431 pub align: Alignment,
1432 pub size_to_first: bool,
1433}
1434
1435impl OverlayLayout {
1436 fn flatten(cx: &mut dyn LayoutOps, children: &[RNode], out: &mut Vec<RNode>) {
1438 for &c in children {
1439 if cx.flex_of(c).is_group {
1440 let inner = cx.children_of(c);
1441 Self::flatten(cx, &inner, out);
1442 } else {
1443 out.push(c);
1444 }
1445 }
1446 }
1447}
1448
1449impl Layout for OverlayLayout {
1450 fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
1451 let mut kids = Vec::new();
1452 Self::flatten(cx, children, &mut kids);
1453 if self.size_to_first {
1454 return match kids.first() {
1455 Some(&c) => cx.measure_child(c, p),
1456 None => Size::ZERO,
1457 };
1458 }
1459 let mut size = Size::ZERO;
1460 for &c in &kids {
1461 let s = cx.measure_child(c, p);
1462 size.width = size.width.max(s.width);
1463 size.height = size.height.max(s.height);
1464 }
1465 size
1466 }
1467 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
1468 let mut kids = Vec::new();
1469 Self::flatten(cx, children, &mut kids);
1470 for &c in &kids {
1471 let s = cx.measure_child(c, Proposal::exact(bounds.size));
1472 let x = (bounds.size.width - s.width) * self.align.h_fraction();
1473 let y = (bounds.size.height - s.height) * self.align.v_fraction();
1474 cx.place_child(c, Rect::new(x, y, s.width, s.height));
1475 }
1476 }
1477}
1478
1479pub struct PaddingLayout {
1480 pub insets: Insets,
1481}
1482
1483impl Layout for PaddingLayout {
1484 fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
1485 let inner = Proposal::new(
1486 p.width
1487 .map(|w| (w - self.insets.horizontal_total()).max(0.0)),
1488 p.height
1489 .map(|h| (h - self.insets.vertical_total()).max(0.0)),
1490 );
1491 let s = match children.first() {
1492 Some(&c) => cx.measure_child(c, inner),
1493 None => Size::ZERO,
1494 };
1495 Size::new(
1496 s.width + self.insets.horizontal_total(),
1497 s.height + self.insets.vertical_total(),
1498 )
1499 }
1500 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
1501 if let Some(&c) = children.first() {
1502 let inner = bounds.inset_by(self.insets);
1503 let s = cx.measure_child(c, Proposal::exact(inner.size));
1504 cx.place_child(
1505 c,
1506 Rect {
1507 origin: inner.origin,
1508 size: s,
1509 },
1510 );
1511 }
1512 }
1513 fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
1514 let inner = Size::new(
1516 (size.width - self.insets.horizontal_total()).max(0.0),
1517 (size.height - self.insets.vertical_total()).max(0.0),
1518 );
1519 wrapper_baseline(cx, children, inner, self.insets.top)
1520 }
1521}
1522
1523pub struct AspectRatioLayout {
1530 pub ratio: f64,
1531}
1532
1533impl Layout for AspectRatioLayout {
1534 fn measure(&self, cx: &mut dyn LayoutOps, _children: &[RNode], p: Proposal) -> Size {
1535 match (p.width, p.height) {
1536 (Some(w), Some(h)) => {
1537 if w / h > self.ratio {
1538 Size::new(h * self.ratio, h)
1539 } else {
1540 Size::new(w, w / self.ratio)
1541 }
1542 }
1543 (Some(w), None) => Size::new(w, w / self.ratio),
1544 (None, Some(h)) => Size::new(h * self.ratio, h),
1545 (None, None) => cx.measure_leaf(p),
1546 }
1547 }
1548 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
1549 for &c in children {
1551 cx.place_child(c, Rect::from_size(bounds.size));
1552 }
1553 }
1554 fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
1555 PassThrough::forward(cx, children, size)
1556 }
1557}
1558
1559pub struct GrowLayout {
1563 pub w: bool,
1564 pub h: bool,
1565}
1566
1567impl Layout for GrowLayout {
1568 fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
1569 let cs = match children.first() {
1570 Some(&c) => cx.measure_child(c, p),
1571 None => Size::ZERO,
1572 };
1573 Size::new(
1577 if self.w {
1578 p.width.map_or(cs.width, |w| w.max(cs.width))
1579 } else {
1580 cs.width
1581 },
1582 if self.h {
1583 p.height.map_or(cs.height, |h| h.max(cs.height))
1584 } else {
1585 cs.height
1586 },
1587 )
1588 }
1589 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
1590 if let Some(&c) = children.first() {
1591 let cs = cx.measure_child(c, Proposal::exact(bounds.size));
1592 let w = if self.w { bounds.size.width } else { cs.width };
1594 let h = if self.h {
1595 bounds.size.height
1596 } else {
1597 cs.height
1598 };
1599 cx.place_child(c, Rect::from_size(Size::new(w, h)));
1600 }
1601 }
1602 fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
1603 PassThrough::forward(cx, children, size)
1604 }
1605}
1606
1607pub struct MaxWidthLayout {
1611 pub max: f64,
1612}
1613
1614impl Layout for MaxWidthLayout {
1615 fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
1616 let capped = Proposal::new(
1617 Some(p.width.map(|w| w.min(self.max)).unwrap_or(self.max)),
1618 p.height,
1619 );
1620 match children.first() {
1621 Some(&c) => {
1622 let s = cx.measure_child(c, capped);
1623 Size::new(s.width.min(self.max), s.height)
1624 }
1625 None => Size::ZERO,
1626 }
1627 }
1628 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
1629 if let Some(&c) = children.first() {
1630 let w = bounds.size.width.min(self.max);
1631 let s = cx.measure_child(c, Proposal::exact(Size::new(w, bounds.size.height)));
1632 cx.place_child(c, Rect::from_size(Size::new(s.width.min(w), s.height)));
1633 }
1634 }
1635 fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
1636 PassThrough::forward(cx, children, size)
1637 }
1638}
1639
1640pub struct MaxHeightLayout {
1646 pub max: f64,
1647}
1648
1649impl Layout for MaxHeightLayout {
1650 fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
1651 let capped = Proposal::new(
1652 p.width,
1653 Some(p.height.map(|h| h.min(self.max)).unwrap_or(self.max)),
1654 );
1655 match children.first() {
1656 Some(&c) => {
1657 let s = cx.measure_child(c, capped);
1658 Size::new(s.width, s.height.min(self.max))
1659 }
1660 None => Size::ZERO,
1661 }
1662 }
1663 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
1664 if let Some(&c) = children.first() {
1665 let h = bounds.size.height.min(self.max);
1666 let s = cx.measure_child(c, Proposal::exact(Size::new(bounds.size.width, h)));
1667 cx.place_child(c, Rect::from_size(Size::new(s.width, s.height.min(h))));
1668 }
1669 }
1670 fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
1671 PassThrough::forward(cx, children, size)
1672 }
1673}
1674
1675pub struct MinWidthLayout {
1681 pub min: f64,
1682}
1683
1684impl Layout for MinWidthLayout {
1685 fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
1686 let floored = Proposal::new(p.width.map(|w| w.max(self.min)), p.height);
1687 match children.first() {
1688 Some(&c) => {
1689 let s = cx.measure_child(c, floored);
1690 Size::new(s.width.max(self.min), s.height)
1691 }
1692 None => Size::new(self.min, 0.0),
1693 }
1694 }
1695 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
1696 if let Some(&c) = children.first() {
1697 let w = bounds.size.width.max(self.min);
1698 let s = cx.measure_child(c, Proposal::exact(Size::new(w, bounds.size.height)));
1699 cx.place_child(c, Rect::from_size(Size::new(w, s.height)));
1700 }
1701 }
1702 fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
1703 PassThrough::forward(cx, children, size)
1704 }
1705}
1706
1707pub struct ReserveLayout;
1720
1721impl Layout for ReserveLayout {
1722 fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
1723 let (Some(&sample), Some(&content)) = (children.first(), children.get(1)) else {
1724 return children
1725 .first()
1726 .map(|&c| cx.measure_child(c, p))
1727 .unwrap_or(Size::ZERO);
1728 };
1729 let s = cx.measure_child(sample, p);
1730 let c = cx.measure_child(content, p);
1731 Size::new(s.width.max(c.width), s.height.max(c.height))
1732 }
1733
1734 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
1735 if let Some(&sample) = children.first() {
1737 cx.place_child(sample, Rect::from_size(Size::ZERO));
1738 }
1739 if let Some(&content) = children.get(1) {
1740 let s = cx.measure_child(content, Proposal::exact(bounds.size));
1741 let _ = s;
1742 cx.place_child(content, Rect::from_size(bounds.size));
1743 }
1744 }
1745}
1746
1747pub struct FrameLayout {
1748 pub width: Option<f64>,
1749 pub height: Option<f64>,
1750}
1751
1752impl Layout for FrameLayout {
1753 fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
1754 let child_p = Proposal::new(self.width.or(p.width), self.height.or(p.height));
1755 let s = match children.first() {
1756 Some(&c) => cx.measure_child(c, child_p),
1757 None => Size::ZERO,
1758 };
1759 Size::new(
1760 self.width.unwrap_or(s.width),
1761 self.height.unwrap_or(s.height),
1762 )
1763 }
1764 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
1765 if let Some(&c) = children.first() {
1766 cx.place_child(c, bounds);
1767 }
1768 }
1769 fn baseline(&self, cx: &mut dyn LayoutOps, children: &[RNode], size: Size) -> Option<f64> {
1770 PassThrough::forward(cx, children, size)
1771 }
1772}
1773
1774pub struct ScrollLayout {
1778 pub axis: Axis,
1779}
1780
1781impl Layout for ScrollLayout {
1782 fn measure(&self, cx: &mut dyn LayoutOps, children: &[RNode], p: Proposal) -> Size {
1783 let content_p = match self.axis {
1784 Axis::Vertical => Proposal::new(p.width, None),
1785 Axis::Horizontal => Proposal::new(None, p.height),
1786 };
1787 let cs = match children.first() {
1788 Some(&c) => cx.measure_child(c, content_p),
1789 None => Size::ZERO,
1790 };
1791 Size::new(p.width.unwrap_or(cs.width), p.height.unwrap_or(cs.height))
1792 }
1793 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
1794 if let Some(&c) = children.first() {
1795 let vertical = self.axis == Axis::Vertical;
1796 let proposal = |cross: f64| match self.axis {
1797 Axis::Vertical => Proposal::new(Some(cross), None),
1798 Axis::Horizontal => Proposal::new(None, Some(cross)),
1799 };
1800 let (cross, main) = match self.axis {
1801 Axis::Vertical => (bounds.size.width, bounds.size.height),
1802 Axis::Horizontal => (bounds.size.height, bounds.size.width),
1803 };
1804 let mut cs = cx.measure_child(c, proposal(cross));
1805 let overflows = |s: Size| if vertical { s.height } else { s.width } > main;
1806 let mut cross = cross;
1810 if overflows(cs) {
1811 let inset = cx.scroll_bar_inset(vertical);
1812 if inset > 0.0 {
1813 cross = (cross - inset).max(0.0);
1814 cs = cx.measure_child(c, proposal(cross));
1815 }
1816 }
1817 let content = match self.axis {
1818 Axis::Vertical => Size::new(cross, cs.height.max(bounds.size.height)),
1819 Axis::Horizontal => Size::new(cs.width.max(bounds.size.width), cross),
1820 };
1821 cx.place_child(c, Rect::from_size(content));
1822 cx.set_scroll_content(content);
1823 }
1824 }
1825}
1826
1827pub struct NavLayout {
1832 pub sizes: std::rc::Rc<std::cell::RefCell<std::collections::HashMap<RNode, Size>>>,
1833 pub presentation: std::rc::Rc<std::cell::Cell<day_spec::props::NavPresentation>>,
1837 pub sidebar: std::rc::Rc<std::cell::Cell<Option<RNode>>>,
1840 pub list: std::rc::Rc<std::cell::Cell<Option<RNode>>>,
1842 pub list_width: f64,
1844 pub list_visible: std::rc::Rc<std::cell::Cell<bool>>,
1848}
1849
1850pub use day_spec::{NAV_LIST_WIDTH, NAV_SIDEBAR_WIDTH};
1851
1852impl NavLayout {
1853 pub fn stack(
1856 sizes: std::rc::Rc<std::cell::RefCell<std::collections::HashMap<RNode, Size>>>,
1857 ) -> Self {
1858 NavLayout {
1859 sizes,
1860 presentation: std::rc::Rc::new(std::cell::Cell::new(
1861 day_spec::props::NavPresentation::Stack,
1862 )),
1863 sidebar: std::rc::Rc::new(std::cell::Cell::new(None)),
1864 list: std::rc::Rc::new(std::cell::Cell::new(None)),
1865 list_width: NAV_LIST_WIDTH,
1866 list_visible: std::rc::Rc::new(std::cell::Cell::new(true)),
1867 }
1868 }
1869}
1870
1871impl Layout for NavLayout {
1872 fn measure(&self, _cx: &mut dyn LayoutOps, _children: &[RNode], p: Proposal) -> Size {
1873 Size::new(p.width.unwrap_or(480.0), p.height.unwrap_or(640.0))
1877 }
1878 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
1879 let pres = self.presentation.get();
1880 let split = pres.is_split();
1881 let sidebar = self.sidebar.get();
1882 let list = self.list.get();
1883 let list_w = if split && list.is_some() && self.list_visible.get() {
1886 self.list_width
1887 } else {
1888 0.0
1889 };
1890 for &page in children {
1891 let reported = self.sizes.borrow().get(&page).copied();
1892 let sz = reported.unwrap_or_else(|| {
1896 let is_sidebar = Some(page) == sidebar;
1897 let is_list = Some(page) == list;
1898 if pres.rows_are_chrome() {
1899 if is_sidebar {
1905 Size::new(NAV_SIDEBAR_WIDTH, bounds.size.height)
1906 } else if is_list {
1907 Size::new(self.list_width, bounds.size.height)
1908 } else {
1909 bounds.size
1910 }
1911 } else if !split {
1912 bounds.size
1916 } else if is_sidebar {
1917 Size::new(NAV_SIDEBAR_WIDTH, bounds.size.height)
1918 } else if is_list {
1919 Size::new(self.list_width, bounds.size.height)
1920 } else {
1921 Size::new(
1922 (bounds.size.width - NAV_SIDEBAR_WIDTH - list_w - 1.0).max(0.0),
1923 bounds.size.height,
1924 )
1925 }
1926 });
1927 cx.place_child_native(page, Rect::from_size(sz));
1928 }
1929 }
1930}
1931
1932pub struct InspectorLayout {
1938 pub sizes: std::rc::Rc<std::cell::RefCell<std::collections::HashMap<RNode, Size>>>,
1939 pub visible: std::rc::Rc<std::cell::Cell<bool>>,
1942 pub width: f64,
1944}
1945
1946impl Layout for InspectorLayout {
1947 fn measure(&self, _cx: &mut dyn LayoutOps, _children: &[RNode], p: Proposal) -> Size {
1948 Size::new(p.width.unwrap_or(480.0), p.height.unwrap_or(640.0))
1950 }
1951 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
1952 for (i, &pane) in children.iter().enumerate() {
1954 let reported = self.sizes.borrow().get(&pane).copied();
1955 let sz = reported.unwrap_or_else(|| {
1956 let panel_w = if self.visible.get() { self.width } else { 0.0 };
1957 if i == 0 {
1958 Size::new((bounds.size.width - panel_w).max(0.0), bounds.size.height)
1959 } else {
1960 Size::new(panel_w, bounds.size.height)
1961 }
1962 });
1963 cx.place_child_native(pane, Rect::from_size(sz));
1964 }
1965 }
1966}
1967
1968pub struct SplitLayout {
1973 pub sizes: std::rc::Rc<std::cell::RefCell<std::collections::HashMap<RNode, Size>>>,
1974 pub stacked: std::rc::Rc<std::cell::Cell<bool>>,
1976 pub fraction: std::rc::Rc<std::cell::Cell<f64>>,
1978}
1979
1980impl Layout for SplitLayout {
1981 fn measure(&self, _cx: &mut dyn LayoutOps, _children: &[RNode], p: Proposal) -> Size {
1982 Size::new(p.width.unwrap_or(480.0), p.height.unwrap_or(640.0))
1984 }
1985 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], bounds: Rect) {
1986 let f = self.fraction.get().clamp(0.0, 1.0);
1988 let (w, h) = (bounds.size.width, bounds.size.height);
1989 for (i, &pane) in children.iter().enumerate() {
1990 let reported = self.sizes.borrow().get(&pane).copied();
1991 let sz = reported.unwrap_or_else(|| {
1992 let share = if i == 0 { f } else { 1.0 - f };
1993 if self.stacked.get() {
1994 Size::new(w, (h * share).floor())
1995 } else {
1996 Size::new((w * share).floor(), h)
1997 }
1998 });
1999 cx.place_child_native(pane, Rect::from_size(sz));
2000 }
2001 }
2002}
2003
2004pub struct CoverLayout {
2009 pub size: std::rc::Rc<std::cell::RefCell<Option<Size>>>,
2010}
2011
2012impl Layout for CoverLayout {
2013 fn measure(&self, _cx: &mut dyn LayoutOps, _children: &[RNode], _p: Proposal) -> Size {
2014 Size::new(0.0, 0.0)
2015 }
2016 fn place(&self, cx: &mut dyn LayoutOps, children: &[RNode], _bounds: Rect) {
2017 let Some(sz) = *self.size.borrow() else {
2018 return; };
2020 for &child in children {
2021 cx.place_child_native(child, Rect::from_size(sz));
2022 }
2023 }
2024}
2025
2026pub fn rc_layout<L: Layout>(l: L) -> Rc<dyn Layout> {
2028 Rc::new(l)
2029}