1use std::any::Any;
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 ctx: &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, ctx);
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 let space_per_flex = remaining_space / remaining_flex;
92 if let Some(flex) = Self::child_flex(&child) {
93 let child_max = space_per_flex * flex;
94 let child_constraint = match self.axis {
95 Axis::Horizontal => SizeConstraint::new(
96 vec2f(0.0, constraint.min.y()),
97 vec2f(child_max, constraint.max.y()),
98 ),
99 Axis::Vertical => SizeConstraint::new(
100 vec2f(constraint.min.x(), 0.0),
101 vec2f(constraint.max.x(), child_max),
102 ),
103 };
104 let child_size = child.layout(child_constraint, ctx);
105 remaining_space -= child_size.along(self.axis);
106 remaining_flex -= flex;
107 cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
108 }
109 }
110
111 match self.axis {
112 Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
113 Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
114 }
115 } else {
116 match self.axis {
117 Axis::Horizontal => vec2f(fixed_space, cross_axis_max),
118 Axis::Vertical => vec2f(cross_axis_max, fixed_space),
119 }
120 };
121
122 if constraint.min.x().is_finite() {
123 size.set_x(size.x().max(constraint.min.x()));
124 }
125
126 if constraint.min.y().is_finite() {
127 size.set_y(size.y().max(constraint.min.y()));
128 }
129
130 (size, ())
131 }
132
133 fn after_layout(
134 &mut self,
135 _: Vector2F,
136 _: &mut Self::LayoutState,
137 ctx: &mut AfterLayoutContext,
138 ) {
139 for child in &mut self.children {
140 child.after_layout(ctx);
141 }
142 }
143
144 fn paint(
145 &mut self,
146 bounds: RectF,
147 _: &mut Self::LayoutState,
148 ctx: &mut PaintContext,
149 ) -> Self::PaintState {
150 let mut child_origin = bounds.origin();
151 for child in &mut self.children {
152 child.paint(child_origin, ctx);
153 match self.axis {
154 Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
155 Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
156 }
157 }
158 }
159
160 fn dispatch_event(
161 &mut self,
162 event: &Event,
163 _: RectF,
164 _: &mut Self::LayoutState,
165 _: &mut Self::PaintState,
166 ctx: &mut EventContext,
167 ) -> bool {
168 let mut handled = false;
169 for child in &mut self.children {
170 handled = child.dispatch_event(event, ctx) || handled;
171 }
172 handled
173 }
174
175 fn debug(
176 &self,
177 bounds: RectF,
178 _: &Self::LayoutState,
179 _: &Self::PaintState,
180 ctx: &DebugContext,
181 ) -> json::Value {
182 json!({
183 "type": "Flex",
184 "bounds": bounds.to_json(),
185 "axis": self.axis.to_json(),
186 "children": self.children.iter().map(|child| child.debug(ctx)).collect::<Vec<json::Value>>()
187 })
188 }
189}
190
191struct FlexParentData {
192 flex: f32,
193}
194
195pub struct Expanded {
196 metadata: FlexParentData,
197 child: ElementBox,
198}
199
200impl Expanded {
201 pub fn new(flex: f32, child: ElementBox) -> Self {
202 Expanded {
203 metadata: FlexParentData { flex },
204 child,
205 }
206 }
207}
208
209impl Element for Expanded {
210 type LayoutState = ();
211 type PaintState = ();
212
213 fn layout(
214 &mut self,
215 constraint: SizeConstraint,
216 ctx: &mut LayoutContext,
217 ) -> (Vector2F, Self::LayoutState) {
218 let size = self.child.layout(constraint, ctx);
219 (size, ())
220 }
221
222 fn after_layout(
223 &mut self,
224 _: Vector2F,
225 _: &mut Self::LayoutState,
226 ctx: &mut AfterLayoutContext,
227 ) {
228 self.child.after_layout(ctx);
229 }
230
231 fn paint(
232 &mut self,
233 bounds: RectF,
234 _: &mut Self::LayoutState,
235 ctx: &mut PaintContext,
236 ) -> Self::PaintState {
237 self.child.paint(bounds.origin(), ctx)
238 }
239
240 fn dispatch_event(
241 &mut self,
242 event: &Event,
243 _: RectF,
244 _: &mut Self::LayoutState,
245 _: &mut Self::PaintState,
246 ctx: &mut EventContext,
247 ) -> bool {
248 self.child.dispatch_event(event, ctx)
249 }
250
251 fn metadata(&self) -> Option<&dyn Any> {
252 Some(&self.metadata)
253 }
254
255 fn debug(
256 &self,
257 _: RectF,
258 _: &Self::LayoutState,
259 _: &Self::PaintState,
260 ctx: &DebugContext,
261 ) -> Value {
262 json!({
263 "type": "Expanded",
264 "flex": self.metadata.flex,
265 "child": self.child.debug(ctx)
266 })
267 }
268}