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