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