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