1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2025 Fundament Research Institute <https://fundament.institute>
use super::{
Concrete, Desc, Layout, Renderable, Staged, base, check_unsized, check_unsized_abs,
map_unsized_area,
};
use crate::{PxDim, PxRect, rtree};
use std::rc::Rc;
pub trait Prop: base::Area + base::Anchor + base::Limits + base::ZIndex {}
crate::gen_from_to_dyn!(Prop);
pub trait Child: base::RLimits {}
crate::gen_from_to_dyn!(Child);
impl Prop for crate::DRect {}
impl Child for crate::DRect {}
impl Desc for dyn Prop {
type Props = dyn Prop;
type Child = dyn Child;
type Children = im::Vector<Option<Box<dyn Layout<Self::Child>>>>;
fn stage<'a>(
props: &Self::Props,
outer_area: PxRect,
outer_limits: crate::PxLimits,
children: &Self::Children,
id: std::sync::Weak<crate::SourceID>,
renderable: Option<Rc<dyn Renderable>>,
window: &mut crate::component::window::WindowState,
) -> Box<dyn Staged + 'a> {
// If we have an unsized outer_area, any sized object with relative dimensions
// must evaluate to 0 (or to the minimum limited size). An
// unsized object can never have relative dimensions, as that creates a logic
// loop - instead it can only have a single relative anchor.
// If both axes are sized, then all limits are applied as if outer_area was
// unsized, and children calculations are skipped.
//
// If we have an unsized outer_area and an unsized myarea.rel, then limits are
// applied as if outer_area was unsized, and furthermore,
// they are reduced by myarea.abs.bottomright(), because that will be added on
// to the total area later, which will still be subject to size
// limits, so we must anticipate this when calculating how much size the
// children will have available to them. This forces limits to be
// true infinite numbers, so we can subtract finite amounts and still have
// infinity. We can't use infinity anywhere else, because infinity times
// zero is NaN, so we cap certain calculations at f32::MAX
//
// If outer_area is sized and myarea.rel is zero or nonzero, all limits are
// applied normally and child calculations are skipped. If outer_area is
// sized and myarea.rel is unsized, limits are applied normally, but are once
// again reduced by myarea.abs.bottomright() to account for how the area
// calculations will interact with the limits later on.
let limits = outer_limits + props.limits().resolve(window.dpi);
let myarea = props.area().resolve(window.dpi);
let (unsized_x, unsized_y) = check_unsized(myarea);
// Check if any axis is unsized in a way that requires us to calculate baseline
// child sizes
let evaluated_area = if unsized_x || unsized_y {
// When an axis is unsized, we don't apply any limits to it, so we don't have to
// worry about cases where the full evaluated area would invalidate
// the limit.
let inner_dim = super::limit_dim(super::eval_dim(myarea, outer_area.dim()), limits);
let inner_area = PxRect::from(inner_dim);
// The area we pass to children must be independent of our own area, so it
// starts at 0,0
let mut bottomright = PxDim::zero();
for child in children.iter() {
let child_props = child.as_ref().unwrap().get_props();
let child_limit = super::apply_limit(inner_dim, limits, *child_props.rlimits());
let stage = child
.as_ref()
.unwrap()
.stage(inner_area, child_limit, window);
bottomright = bottomright.max(stage.get_area().bottomright().to_vector().to_size());
}
let area = map_unsized_area(myarea, bottomright);
// No need to cap this because unsized axis have now been resolved
super::limit_area(area * crate::layout::nuetralize_unsized(outer_area), limits)
} else {
// If outer_area is unsized here, we nuetralize it when evaluating the relative
// coordinates.
super::limit_area(
myarea * crate::layout::nuetralize_unsized(outer_area),
limits,
)
};
let mut staging: im::Vector<Option<Box<dyn Staged>>> = im::Vector::new();
let mut nodes: im::Vector<Option<Rc<rtree::Node>>> = im::Vector::new();
// If our parent just wants a size estimate, no need to layout children or
// render anything
let (unsized_x, unsized_y) = check_unsized_abs(outer_area.bottomright());
if unsized_x || unsized_y {
return Box::new(Concrete::new(
None,
evaluated_area,
rtree::Node::new(
evaluated_area.to_untyped(),
Some(props.zindex()),
nodes,
id,
window,
),
staging,
));
}
// We had to evaluate the full area first because our final area calculation can
// change the dimensions in unsized cases. Thus, we calculate the final
// inner_area for the children from this evaluated area.
let evaluated_dim = evaluated_area.dim();
let inner_area = PxRect::from(evaluated_dim);
for child in children.iter() {
let child_props = child.as_ref().unwrap().get_props();
let child_limit = *child_props.rlimits() * evaluated_dim;
let stage = child
.as_ref()
.unwrap()
.stage(inner_area, child_limit, window);
if let Some(node) = stage.get_rtree().upgrade() {
nodes.push_back(Some(node));
}
staging.push_back(Some(stage));
}
// TODO: It isn't clear if the simple layout should attempt to handle children
// changing their estimated sizes after the initial estimate. If we were
// to handle this, we would need to recalculate the unsized
// axis with the new child results here, and repeat until it stops changing (we
// find the fixed point). Because the performance implications are
// unclear, this might need to be relagated to a special layout.
// Calculate the anchor using the final evaluated dimensions, after all unsized
// axis and limits are calculated. However, we can only apply the anchor
// if the parent isn't unsized on that axis.
let mut anchor = props.anchor().resolve(window.dpi) * evaluated_dim;
let (unsized_outer_x, unsized_outer_y) =
crate::layout::check_unsized_abs(outer_area.bottomright());
if unsized_outer_x {
anchor.x = 0.0;
}
if unsized_outer_y {
anchor.y = 0.0;
}
let evaluated_area = evaluated_area - anchor;
Box::new(Concrete::new(
renderable,
evaluated_area,
rtree::Node::new(
evaluated_area.to_untyped(),
Some(props.zindex()),
nodes,
id,
window,
),
staging,
))
}
}