1use std::{any::Any, f32::INFINITY};
2
3use crate::{
4 json::{self, ToJson, Value},
5 AfterLayoutContext, Axis, DebugContext, Element, ElementBox, Event, EventContext,
6 LayoutContext, PaintContext, SizeConstraint, Vector2FExt,
7};
8use pathfinder_geometry::{
9 rect::RectF,
10 vector::{vec2f, Vector2F},
11};
12use serde_json::json;
13
14pub struct Flex {
15 axis: Axis,
16 children: Vec<ElementBox>,
17}
18
19impl Flex {
20 pub fn new(axis: Axis) -> Self {
21 Self {
22 axis,
23 children: Default::default(),
24 }
25 }
26
27 pub fn row() -> Self {
28 Self::new(Axis::Horizontal)
29 }
30
31 pub fn column() -> Self {
32 Self::new(Axis::Vertical)
33 }
34
35 fn child_flex<'b>(child: &ElementBox) -> Option<f32> {
36 child
37 .metadata()
38 .and_then(|d| d.downcast_ref::<FlexParentData>())
39 .map(|data| data.flex)
40 }
41}
42
43impl Extend<ElementBox> for Flex {
44 fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) {
45 self.children.extend(children);
46 }
47}
48
49impl Element for Flex {
50 type LayoutState = ();
51 type PaintState = ();
52
53 fn layout(
54 &mut self,
55 constraint: SizeConstraint,
56 cx: &mut LayoutContext,
57 ) -> (Vector2F, Self::LayoutState) {
58 let mut total_flex = 0.0;
59 let mut fixed_space = 0.0;
60
61 let cross_axis = self.axis.invert();
62 let mut cross_axis_max: f32 = 0.0;
63 for child in &mut self.children {
64 if let Some(flex) = Self::child_flex(&child) {
65 total_flex += flex;
66 } else {
67 let child_constraint = match self.axis {
68 Axis::Horizontal => SizeConstraint::new(
69 vec2f(0.0, constraint.min.y()),
70 vec2f(INFINITY, constraint.max.y()),
71 ),
72 Axis::Vertical => SizeConstraint::new(
73 vec2f(constraint.min.x(), 0.0),
74 vec2f(constraint.max.x(), INFINITY),
75 ),
76 };
77 let size = child.layout(child_constraint, cx);
78 fixed_space += size.along(self.axis);
79 cross_axis_max = cross_axis_max.max(size.along(cross_axis));
80 }
81 }
82
83 let mut size = if total_flex > 0.0 {
84 if constraint.max_along(self.axis).is_infinite() {
85 panic!("flex contains flexible children but has an infinite constraint along the flex axis");
86 }
87
88 let mut remaining_space = constraint.max_along(self.axis) - fixed_space;
89 let mut remaining_flex = total_flex;
90 for child in &mut self.children {
91 if let Some(flex) = Self::child_flex(&child) {
92 let child_max = if remaining_flex == 0.0 {
93 remaining_space
94 } else {
95 let space_per_flex = remaining_space / remaining_flex;
96 space_per_flex * flex
97 };
98 let child_constraint = match self.axis {
99 Axis::Horizontal => SizeConstraint::new(
100 vec2f(0.0, constraint.min.y()),
101 vec2f(child_max, constraint.max.y()),
102 ),
103 Axis::Vertical => SizeConstraint::new(
104 vec2f(constraint.min.x(), 0.0),
105 vec2f(constraint.max.x(), child_max),
106 ),
107 };
108 let child_size = child.layout(child_constraint, cx);
109 remaining_space -= child_size.along(self.axis);
110 remaining_flex -= flex;
111 cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
112 }
113 }
114
115 match self.axis {
116 Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
117 Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
118 }
119 } else {
120 match self.axis {
121 Axis::Horizontal => vec2f(fixed_space, cross_axis_max),
122 Axis::Vertical => vec2f(cross_axis_max, fixed_space),
123 }
124 };
125
126 if constraint.min.x().is_finite() {
127 size.set_x(size.x().max(constraint.min.x()));
128 }
129
130 if constraint.min.y().is_finite() {
131 size.set_y(size.y().max(constraint.min.y()));
132 }
133
134 (size, ())
135 }
136
137 fn after_layout(
138 &mut self,
139 _: Vector2F,
140 _: &mut Self::LayoutState,
141 cx: &mut AfterLayoutContext,
142 ) {
143 for child in &mut self.children {
144 child.after_layout(cx);
145 }
146 }
147
148 fn paint(
149 &mut self,
150 bounds: RectF,
151 _: &mut Self::LayoutState,
152 cx: &mut PaintContext,
153 ) -> Self::PaintState {
154 let mut child_origin = bounds.origin();
155 for child in &mut self.children {
156 child.paint(child_origin, cx);
157 match self.axis {
158 Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
159 Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
160 }
161 }
162 }
163
164 fn dispatch_event(
165 &mut self,
166 event: &Event,
167 _: RectF,
168 _: &mut Self::LayoutState,
169 _: &mut Self::PaintState,
170 cx: &mut EventContext,
171 ) -> bool {
172 let mut handled = false;
173 for child in &mut self.children {
174 handled = child.dispatch_event(event, cx) || handled;
175 }
176 handled
177 }
178
179 fn debug(
180 &self,
181 bounds: RectF,
182 _: &Self::LayoutState,
183 _: &Self::PaintState,
184 cx: &DebugContext,
185 ) -> json::Value {
186 json!({
187 "type": "Flex",
188 "bounds": bounds.to_json(),
189 "axis": self.axis.to_json(),
190 "children": self.children.iter().map(|child| child.debug(cx)).collect::<Vec<json::Value>>()
191 })
192 }
193}
194
195struct FlexParentData {
196 flex: f32,
197}
198
199pub struct Expanded {
200 metadata: FlexParentData,
201 child: ElementBox,
202}
203
204impl Expanded {
205 pub fn new(flex: f32, child: ElementBox) -> Self {
206 Expanded {
207 metadata: FlexParentData { flex },
208 child,
209 }
210 }
211}
212
213impl Element for Expanded {
214 type LayoutState = ();
215 type PaintState = ();
216
217 fn layout(
218 &mut self,
219 constraint: SizeConstraint,
220 cx: &mut LayoutContext,
221 ) -> (Vector2F, Self::LayoutState) {
222 let size = self.child.layout(constraint, cx);
223 (size, ())
224 }
225
226 fn after_layout(
227 &mut self,
228 _: Vector2F,
229 _: &mut Self::LayoutState,
230 cx: &mut AfterLayoutContext,
231 ) {
232 self.child.after_layout(cx);
233 }
234
235 fn paint(
236 &mut self,
237 bounds: RectF,
238 _: &mut Self::LayoutState,
239 cx: &mut PaintContext,
240 ) -> Self::PaintState {
241 self.child.paint(bounds.origin(), cx)
242 }
243
244 fn dispatch_event(
245 &mut self,
246 event: &Event,
247 _: RectF,
248 _: &mut Self::LayoutState,
249 _: &mut Self::PaintState,
250 cx: &mut EventContext,
251 ) -> bool {
252 self.child.dispatch_event(event, cx)
253 }
254
255 fn metadata(&self) -> Option<&dyn Any> {
256 Some(&self.metadata)
257 }
258
259 fn debug(
260 &self,
261 _: RectF,
262 _: &Self::LayoutState,
263 _: &Self::PaintState,
264 cx: &DebugContext,
265 ) -> Value {
266 json!({
267 "type": "Expanded",
268 "flex": self.metadata.flex,
269 "child": self.child.debug(cx)
270 })
271 }
272}