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