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