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