1use std::{any::Any, f32::INFINITY};
2
3use crate::{
4 json::{self, ToJson, Value},
5 Axis, DebugContext, Element, ElementBox, ElementStateContext, ElementStateHandle, Event,
6 EventContext, LayoutContext, PaintContext, SizeConstraint, Vector2FExt,
7};
8use pathfinder_geometry::{
9 rect::RectF,
10 vector::{vec2f, Vector2F},
11};
12use serde_json::json;
13
14#[derive(Default)]
15struct ScrollState {
16 scroll_to: Option<usize>,
17 scroll_position: f32,
18}
19
20pub struct Flex {
21 axis: Axis,
22 children: Vec<ElementBox>,
23 scroll_state: Option<ElementStateHandle<ScrollState>>,
24}
25
26impl Flex {
27 pub fn new(axis: Axis) -> Self {
28 Self {
29 axis,
30 children: Default::default(),
31 scroll_state: None,
32 }
33 }
34
35 pub fn row() -> Self {
36 Self::new(Axis::Horizontal)
37 }
38
39 pub fn column() -> Self {
40 Self::new(Axis::Vertical)
41 }
42
43 pub fn scrollable<Tag, C>(
44 mut self,
45 element_id: usize,
46 scroll_to: Option<usize>,
47 cx: &mut C,
48 ) -> Self
49 where
50 Tag: 'static,
51 C: ElementStateContext,
52 {
53 let scroll_state = cx.element_state::<Tag, ScrollState>(element_id);
54 scroll_state.update(cx, |scroll_state, _| scroll_state.scroll_to = scroll_to);
55 self.scroll_state = Some(scroll_state);
56 self
57 }
58
59 fn layout_flex_children(
60 &mut self,
61 layout_expanded: bool,
62 constraint: SizeConstraint,
63 remaining_space: &mut f32,
64 remaining_flex: &mut f32,
65 cross_axis_max: &mut f32,
66 cx: &mut LayoutContext,
67 ) {
68 let cross_axis = self.axis.invert();
69 for child in &mut self.children {
70 if let Some(metadata) = child.metadata::<FlexParentData>() {
71 if let Some((flex, expanded)) = metadata.flex {
72 if expanded != layout_expanded {
73 continue;
74 }
75
76 let child_max = if *remaining_flex == 0.0 {
77 *remaining_space
78 } else {
79 let space_per_flex = *remaining_space / *remaining_flex;
80 space_per_flex * flex
81 };
82 let child_min = if expanded { child_max } else { 0. };
83 let child_constraint = match self.axis {
84 Axis::Horizontal => SizeConstraint::new(
85 vec2f(child_min, constraint.min.y()),
86 vec2f(child_max, constraint.max.y()),
87 ),
88 Axis::Vertical => SizeConstraint::new(
89 vec2f(constraint.min.x(), child_min),
90 vec2f(constraint.max.x(), child_max),
91 ),
92 };
93 let child_size = child.layout(child_constraint, cx);
94 *remaining_space -= child_size.along(self.axis);
95 *remaining_flex -= flex;
96 *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
97 }
98 }
99 }
100 }
101}
102
103impl Extend<ElementBox> for Flex {
104 fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) {
105 self.children.extend(children);
106 }
107}
108
109impl Element for Flex {
110 type LayoutState = f32;
111 type PaintState = ();
112
113 fn layout(
114 &mut self,
115 constraint: SizeConstraint,
116 cx: &mut LayoutContext,
117 ) -> (Vector2F, Self::LayoutState) {
118 let mut total_flex = None;
119 let mut fixed_space = 0.0;
120
121 let cross_axis = self.axis.invert();
122 let mut cross_axis_max: f32 = 0.0;
123 for child in &mut self.children {
124 if let Some(flex) = child
125 .metadata::<FlexParentData>()
126 .and_then(|metadata| metadata.flex.map(|(flex, _)| flex))
127 {
128 *total_flex.get_or_insert(0.) += flex;
129 } else {
130 let child_constraint = match self.axis {
131 Axis::Horizontal => SizeConstraint::new(
132 vec2f(0.0, constraint.min.y()),
133 vec2f(INFINITY, constraint.max.y()),
134 ),
135 Axis::Vertical => SizeConstraint::new(
136 vec2f(constraint.min.x(), 0.0),
137 vec2f(constraint.max.x(), INFINITY),
138 ),
139 };
140 let size = child.layout(child_constraint, cx);
141 fixed_space += size.along(self.axis);
142 cross_axis_max = cross_axis_max.max(size.along(cross_axis));
143 }
144 }
145
146 let mut remaining_space = constraint.max_along(self.axis) - fixed_space;
147 let mut size = if let Some(mut remaining_flex) = total_flex {
148 if remaining_space.is_infinite() {
149 panic!("flex contains flexible children but has an infinite constraint along the flex axis");
150 }
151
152 self.layout_flex_children(
153 false,
154 constraint,
155 &mut remaining_space,
156 &mut remaining_flex,
157 &mut cross_axis_max,
158 cx,
159 );
160 self.layout_flex_children(
161 true,
162 constraint,
163 &mut remaining_space,
164 &mut remaining_flex,
165 &mut cross_axis_max,
166 cx,
167 );
168
169 match self.axis {
170 Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
171 Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
172 }
173 } else {
174 match self.axis {
175 Axis::Horizontal => vec2f(fixed_space, cross_axis_max),
176 Axis::Vertical => vec2f(cross_axis_max, fixed_space),
177 }
178 };
179
180 if constraint.min.x().is_finite() {
181 size.set_x(size.x().max(constraint.min.x()));
182 }
183 if constraint.min.y().is_finite() {
184 size.set_y(size.y().max(constraint.min.y()));
185 }
186
187 if size.x() > constraint.max.x() {
188 size.set_x(constraint.max.x());
189 }
190 if size.y() > constraint.max.y() {
191 size.set_y(constraint.max.y());
192 }
193
194 if let Some(scroll_state) = self.scroll_state.as_ref() {
195 scroll_state.update(cx, |scroll_state, _| {
196 if let Some(scroll_to) = scroll_state.scroll_to.take() {
197 let visible_start = scroll_state.scroll_position;
198 let visible_end = visible_start + size.along(self.axis);
199 if let Some(child) = self.children.get(scroll_to) {
200 let child_start: f32 = self.children[..scroll_to]
201 .iter()
202 .map(|c| c.size().along(self.axis))
203 .sum();
204 let child_end = child_start + child.size().along(self.axis);
205 if child_start < visible_start {
206 scroll_state.scroll_position = child_start;
207 } else if child_end > visible_end {
208 scroll_state.scroll_position = child_end - size.along(self.axis);
209 }
210 }
211 }
212
213 scroll_state.scroll_position =
214 scroll_state.scroll_position.min(-remaining_space).max(0.);
215 });
216 }
217
218 (size, remaining_space)
219 }
220
221 fn paint(
222 &mut self,
223 bounds: RectF,
224 visible_bounds: RectF,
225 remaining_space: &mut Self::LayoutState,
226 cx: &mut PaintContext,
227 ) -> Self::PaintState {
228 let overflowing = *remaining_space < 0.;
229 if overflowing {
230 cx.scene.push_layer(Some(bounds));
231 }
232
233 let mut child_origin = bounds.origin();
234 if let Some(scroll_state) = self.scroll_state.as_ref() {
235 let scroll_position = scroll_state.read(cx).scroll_position;
236 match self.axis {
237 Axis::Horizontal => child_origin.set_x(child_origin.x() - scroll_position),
238 Axis::Vertical => child_origin.set_y(child_origin.y() - scroll_position),
239 }
240 }
241
242 for child in &mut self.children {
243 if *remaining_space > 0. {
244 if let Some(metadata) = child.metadata::<FlexParentData>() {
245 if metadata.float {
246 match self.axis {
247 Axis::Horizontal => child_origin += vec2f(*remaining_space, 0.0),
248 Axis::Vertical => child_origin += vec2f(0.0, *remaining_space),
249 }
250 *remaining_space = 0.;
251 }
252 }
253 }
254 child.paint(child_origin, visible_bounds, cx);
255 match self.axis {
256 Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
257 Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
258 }
259 }
260 if overflowing {
261 cx.scene.pop_layer();
262 }
263 }
264
265 fn dispatch_event(
266 &mut self,
267 event: &Event,
268 bounds: RectF,
269 _: RectF,
270 remaining_space: &mut Self::LayoutState,
271 _: &mut Self::PaintState,
272 cx: &mut EventContext,
273 ) -> bool {
274 if let Some(position) = event.position() {
275 if !bounds.contains_point(position) {
276 return false;
277 }
278 }
279
280 let mut handled = false;
281 for child in &mut self.children {
282 handled = child.dispatch_event(event, cx) || handled;
283 }
284 if !handled {
285 if let &Event::ScrollWheel {
286 position,
287 delta,
288 precise,
289 } = event
290 {
291 if *remaining_space < 0. && bounds.contains_point(position) {
292 if let Some(scroll_state) = self.scroll_state.as_ref() {
293 scroll_state.update(cx, |scroll_state, cx| {
294 let mut delta = match self.axis {
295 Axis::Horizontal => {
296 if delta.x() != 0. {
297 delta.x()
298 } else {
299 delta.y()
300 }
301 }
302 Axis::Vertical => delta.y(),
303 };
304 if !precise {
305 delta *= 20.;
306 }
307
308 scroll_state.scroll_position -= delta;
309
310 handled = true;
311 cx.notify();
312 });
313 }
314 }
315 }
316 }
317 handled
318 }
319
320 fn debug(
321 &self,
322 bounds: RectF,
323 _: &Self::LayoutState,
324 _: &Self::PaintState,
325 cx: &DebugContext,
326 ) -> json::Value {
327 json!({
328 "type": "Flex",
329 "bounds": bounds.to_json(),
330 "axis": self.axis.to_json(),
331 "children": self.children.iter().map(|child| child.debug(cx)).collect::<Vec<json::Value>>()
332 })
333 }
334}
335
336struct FlexParentData {
337 flex: Option<(f32, bool)>,
338 float: bool,
339}
340
341pub struct FlexItem {
342 metadata: FlexParentData,
343 child: ElementBox,
344}
345
346impl FlexItem {
347 pub fn new(child: ElementBox) -> Self {
348 FlexItem {
349 metadata: FlexParentData {
350 flex: None,
351 float: false,
352 },
353 child,
354 }
355 }
356
357 pub fn flex(mut self, flex: f32, expanded: bool) -> Self {
358 self.metadata.flex = Some((flex, expanded));
359 self
360 }
361
362 pub fn float(mut self) -> Self {
363 self.metadata.float = true;
364 self
365 }
366}
367
368impl Element for FlexItem {
369 type LayoutState = ();
370 type PaintState = ();
371
372 fn layout(
373 &mut self,
374 constraint: SizeConstraint,
375 cx: &mut LayoutContext,
376 ) -> (Vector2F, Self::LayoutState) {
377 let size = self.child.layout(constraint, cx);
378 (size, ())
379 }
380
381 fn paint(
382 &mut self,
383 bounds: RectF,
384 visible_bounds: RectF,
385 _: &mut Self::LayoutState,
386 cx: &mut PaintContext,
387 ) -> Self::PaintState {
388 self.child.paint(bounds.origin(), visible_bounds, cx)
389 }
390
391 fn dispatch_event(
392 &mut self,
393 event: &Event,
394 _: RectF,
395 _: RectF,
396 _: &mut Self::LayoutState,
397 _: &mut Self::PaintState,
398 cx: &mut EventContext,
399 ) -> bool {
400 self.child.dispatch_event(event, cx)
401 }
402
403 fn metadata(&self) -> Option<&dyn Any> {
404 Some(&self.metadata)
405 }
406
407 fn debug(
408 &self,
409 _: RectF,
410 _: &Self::LayoutState,
411 _: &Self::PaintState,
412 cx: &DebugContext,
413 ) -> Value {
414 json!({
415 "type": "Flexible",
416 "flex": self.metadata.flex,
417 "child": self.child.debug(cx)
418 })
419 }
420}