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, LayoutContext, PaintContext, SceneBuilder,
6 SizeConstraint, Vector2FExt, ViewContext,
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: Cell<Option<usize>>,
17 scroll_position: Cell<f32>,
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.default_element_state::<Tag, Rc<ScrollState>>(element_id);
71 scroll_state.read(cx).scroll_to.set(scroll_to);
72 self.scroll_state = Some((scroll_state, cx.handle().id()));
73 self
74 }
75
76 pub fn is_empty(&self) -> bool {
77 self.children.is_empty()
78 }
79
80 fn layout_flex_children(
81 &mut self,
82 layout_expanded: bool,
83 constraint: SizeConstraint,
84 remaining_space: &mut f32,
85 remaining_flex: &mut f32,
86 cross_axis_max: &mut f32,
87 view: &mut V,
88 cx: &mut LayoutContext<V>,
89 ) {
90 let cross_axis = self.axis.invert();
91 let last = self.children.len() - 1;
92 for (ix, child) in &mut self.children.iter_mut().enumerate() {
93 if let Some(metadata) = child.metadata::<FlexParentData>() {
94 if let Some((flex, expanded)) = metadata.flex {
95 if expanded != layout_expanded {
96 continue;
97 }
98
99 let child_max = if *remaining_flex == 0.0 {
100 *remaining_space
101 } else {
102 let space_per_flex = *remaining_space / *remaining_flex;
103 space_per_flex * flex
104 } - if ix == 0 || ix == last {
105 self.spacing / 2.
106 } else {
107 self.spacing
108 };
109 let child_min = if expanded { child_max } else { 0. };
110 let child_constraint = match self.axis {
111 Axis::Horizontal => SizeConstraint::new(
112 vec2f(child_min, constraint.min.y()),
113 vec2f(child_max, constraint.max.y()),
114 ),
115 Axis::Vertical => SizeConstraint::new(
116 vec2f(constraint.min.x(), child_min),
117 vec2f(constraint.max.x(), child_max),
118 ),
119 };
120 let child_size = child.layout(child_constraint, view, cx);
121 *remaining_space -= child_size.along(self.axis);
122 *remaining_flex -= flex;
123 *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
124 }
125 }
126 }
127 }
128}
129
130impl<V> Extend<AnyElement<V>> for Flex<V> {
131 fn extend<T: IntoIterator<Item = AnyElement<V>>>(&mut self, children: T) {
132 self.children.extend(children);
133 }
134}
135
136impl<V: 'static> Element<V> for Flex<V> {
137 type LayoutState = f32;
138 type PaintState = ();
139
140 fn layout(
141 &mut self,
142 constraint: SizeConstraint,
143 view: &mut V,
144 cx: &mut LayoutContext<V>,
145 ) -> (Vector2F, Self::LayoutState) {
146 let mut total_flex = None;
147 let mut fixed_space = 0.0;
148 let mut contains_float = false;
149
150 let cross_axis = self.axis.invert();
151 let mut cross_axis_max: f32 = 0.0;
152 let last = self.children.len().saturating_sub(1);
153 for (ix, child) in &mut self.children.iter_mut().enumerate() {
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 + if ix == 0 || ix == last {
173 self.spacing / 2.
174 } else {
175 self.spacing
176 };
177 cross_axis_max = cross_axis_max.max(size.along(cross_axis));
178 }
179 }
180
181 let mut remaining_space = constraint.max_along(self.axis) - fixed_space;
182 let mut size = if let Some(mut remaining_flex) = total_flex {
183 if remaining_space.is_infinite() {
184 panic!("flex contains flexible children but has an infinite constraint along the flex axis");
185 }
186
187 self.layout_flex_children(
188 false,
189 constraint,
190 &mut remaining_space,
191 &mut remaining_flex,
192 &mut cross_axis_max,
193 view,
194 cx,
195 );
196 self.layout_flex_children(
197 true,
198 constraint,
199 &mut remaining_space,
200 &mut remaining_flex,
201 &mut cross_axis_max,
202 view,
203 cx,
204 );
205
206 match self.axis {
207 Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
208 Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
209 }
210 } else {
211 match self.axis {
212 Axis::Horizontal => vec2f(fixed_space, cross_axis_max),
213 Axis::Vertical => vec2f(cross_axis_max, fixed_space),
214 }
215 };
216
217 if contains_float {
218 match self.axis {
219 Axis::Horizontal => size.set_x(size.x().max(constraint.max.x())),
220 Axis::Vertical => size.set_y(size.y().max(constraint.max.y())),
221 }
222 }
223
224 if constraint.min.x().is_finite() {
225 size.set_x(size.x().max(constraint.min.x()));
226 }
227 if constraint.min.y().is_finite() {
228 size.set_y(size.y().max(constraint.min.y()));
229 }
230
231 if size.x() > constraint.max.x() {
232 size.set_x(constraint.max.x());
233 }
234 if size.y() > constraint.max.y() {
235 size.set_y(constraint.max.y());
236 }
237
238 if let Some(scroll_state) = self.scroll_state.as_ref() {
239 scroll_state.0.update(cx.view_context(), |scroll_state, _| {
240 if let Some(scroll_to) = scroll_state.scroll_to.take() {
241 let visible_start = scroll_state.scroll_position.get();
242 let visible_end = visible_start + size.along(self.axis);
243 if let Some(child) = self.children.get(scroll_to) {
244 let child_start: f32 = self.children[..scroll_to]
245 .iter()
246 .map(|c| c.size().along(self.axis))
247 .sum();
248 let child_end = child_start + child.size().along(self.axis);
249 if child_start < visible_start {
250 scroll_state.scroll_position.set(child_start);
251 } else if child_end > visible_end {
252 scroll_state
253 .scroll_position
254 .set(child_end - size.along(self.axis));
255 }
256 }
257 }
258
259 scroll_state.scroll_position.set(
260 scroll_state
261 .scroll_position
262 .get()
263 .min(-remaining_space)
264 .max(0.),
265 );
266 });
267 }
268
269 (size, remaining_space)
270 }
271
272 fn paint(
273 &mut self,
274 scene: &mut SceneBuilder,
275 bounds: RectF,
276 visible_bounds: RectF,
277 remaining_space: &mut Self::LayoutState,
278 view: &mut V,
279 cx: &mut PaintContext<V>,
280 ) -> Self::PaintState {
281 let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
282
283 let mut remaining_space = *remaining_space;
284 let overflowing = remaining_space < 0.;
285 if overflowing {
286 scene.push_layer(Some(visible_bounds));
287 }
288
289 if let Some(scroll_state) = &self.scroll_state {
290 scene.push_mouse_region(
291 crate::MouseRegion::new::<Self>(scroll_state.1, 0, bounds)
292 .on_scroll({
293 let scroll_state = scroll_state.0.read(cx).clone();
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 let last = self.children.len().saturating_sub(1);
337 for (ix, child) in &mut self.children.iter_mut().enumerate() {
338 if remaining_space > 0. {
339 if let Some(metadata) = child.metadata::<FlexParentData>() {
340 if metadata.float {
341 match self.axis {
342 Axis::Horizontal => child_origin += vec2f(remaining_space, 0.0),
343 Axis::Vertical => child_origin += vec2f(0.0, remaining_space),
344 }
345 remaining_space = 0.;
346 }
347 }
348 }
349
350 // We use the child_alignment f32 to determine a point along the cross axis of the
351 // overall flex element and each child. We then align these points. So 0 would center
352 // each child relative to the overall height/width of the flex. -1 puts children at
353 // the start. 1 puts children at the end.
354 let aligned_child_origin = {
355 let cross_axis = self.axis.invert();
356 let my_center = bounds.size().along(cross_axis) / 2.;
357 let my_target = my_center + my_center * self.child_alignment;
358
359 let child_center = child.size().along(cross_axis) / 2.;
360 let child_target = child_center + child_center * self.child_alignment;
361
362 let mut aligned_child_origin = child_origin;
363 match self.axis {
364 Axis::Horizontal => aligned_child_origin
365 .set_y(aligned_child_origin.y() - (child_target - my_target)),
366 Axis::Vertical => aligned_child_origin
367 .set_x(aligned_child_origin.x() - (child_target - my_target)),
368 }
369
370 aligned_child_origin
371 };
372
373 child.paint(scene, aligned_child_origin, visible_bounds, view, cx);
374
375 let spacing = if ix == last { 0. } else { self.spacing };
376
377 match self.axis {
378 Axis::Horizontal => child_origin += vec2f(child.size().x() + spacing, 0.0),
379 Axis::Vertical => child_origin += vec2f(0.0, child.size().y() + spacing),
380 }
381 }
382
383 if overflowing {
384 scene.pop_layer();
385 }
386 }
387
388 fn rect_for_text_range(
389 &self,
390 range_utf16: Range<usize>,
391 _: RectF,
392 _: RectF,
393 _: &Self::LayoutState,
394 _: &Self::PaintState,
395 view: &V,
396 cx: &ViewContext<V>,
397 ) -> Option<RectF> {
398 self.children
399 .iter()
400 .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
401 }
402
403 fn debug(
404 &self,
405 bounds: RectF,
406 _: &Self::LayoutState,
407 _: &Self::PaintState,
408 view: &V,
409 cx: &ViewContext<V>,
410 ) -> json::Value {
411 json!({
412 "type": "Flex",
413 "bounds": bounds.to_json(),
414 "axis": self.axis.to_json(),
415 "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
416 })
417 }
418}
419
420struct FlexParentData {
421 flex: Option<(f32, bool)>,
422 float: bool,
423}
424
425pub struct FlexItem<V> {
426 metadata: FlexParentData,
427 child: AnyElement<V>,
428}
429
430impl<V: 'static> FlexItem<V> {
431 pub fn new(child: impl Element<V>) -> Self {
432 FlexItem {
433 metadata: FlexParentData {
434 flex: None,
435 float: false,
436 },
437 child: child.into_any(),
438 }
439 }
440
441 pub fn flex(mut self, flex: f32, expanded: bool) -> Self {
442 self.metadata.flex = Some((flex, expanded));
443 self
444 }
445
446 pub fn float(mut self) -> Self {
447 self.metadata.float = true;
448 self
449 }
450}
451
452impl<V: 'static> Element<V> for FlexItem<V> {
453 type LayoutState = ();
454 type PaintState = ();
455
456 fn layout(
457 &mut self,
458 constraint: SizeConstraint,
459 view: &mut V,
460 cx: &mut LayoutContext<V>,
461 ) -> (Vector2F, Self::LayoutState) {
462 let size = self.child.layout(constraint, view, cx);
463 (size, ())
464 }
465
466 fn paint(
467 &mut self,
468 scene: &mut SceneBuilder,
469 bounds: RectF,
470 visible_bounds: RectF,
471 _: &mut Self::LayoutState,
472 view: &mut V,
473 cx: &mut PaintContext<V>,
474 ) -> Self::PaintState {
475 self.child
476 .paint(scene, bounds.origin(), visible_bounds, view, cx)
477 }
478
479 fn rect_for_text_range(
480 &self,
481 range_utf16: Range<usize>,
482 _: RectF,
483 _: RectF,
484 _: &Self::LayoutState,
485 _: &Self::PaintState,
486 view: &V,
487 cx: &ViewContext<V>,
488 ) -> Option<RectF> {
489 self.child.rect_for_text_range(range_utf16, view, cx)
490 }
491
492 fn metadata(&self) -> Option<&dyn Any> {
493 Some(&self.metadata)
494 }
495
496 fn debug(
497 &self,
498 _: RectF,
499 _: &Self::LayoutState,
500 _: &Self::PaintState,
501 view: &V,
502 cx: &ViewContext<V>,
503 ) -> Value {
504 json!({
505 "type": "Flexible",
506 "flex": self.metadata.flex,
507 "child": self.child.debug(view, cx)
508 })
509 }
510}