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