1use std::{any::Any, f32::INFINITY, ops::Range};
2
3use crate::{
4 json::{self, ToJson, Value},
5 presenter::MeasurementContext,
6 Axis, DebugContext, Element, ElementBox, ElementStateHandle, Event, EventContext,
7 LayoutContext, MouseMovedEvent, 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)]
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<ScrollState>>,
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 = cx.element_state::<Tag, ScrollState>(element_id);
56 scroll_state.update(cx, |scroll_state, _| scroll_state.scroll_to = scroll_to);
57 self.scroll_state = Some(scroll_state);
58 self
59 }
60
61 fn layout_flex_children(
62 &mut self,
63 layout_expanded: bool,
64 constraint: SizeConstraint,
65 remaining_space: &mut f32,
66 remaining_flex: &mut f32,
67 cross_axis_max: &mut f32,
68 cx: &mut LayoutContext,
69 ) {
70 let cross_axis = self.axis.invert();
71 for child in &mut self.children {
72 if let Some(metadata) = child.metadata::<FlexParentData>() {
73 if let Some((flex, expanded)) = metadata.flex {
74 if expanded != layout_expanded {
75 continue;
76 }
77
78 let child_max = if *remaining_flex == 0.0 {
79 *remaining_space
80 } else {
81 let space_per_flex = *remaining_space / *remaining_flex;
82 space_per_flex * flex
83 };
84 let child_min = if expanded { child_max } else { 0. };
85 let child_constraint = match self.axis {
86 Axis::Horizontal => SizeConstraint::new(
87 vec2f(child_min, constraint.min.y()),
88 vec2f(child_max, constraint.max.y()),
89 ),
90 Axis::Vertical => SizeConstraint::new(
91 vec2f(constraint.min.x(), child_min),
92 vec2f(constraint.max.x(), child_max),
93 ),
94 };
95 let child_size = child.layout(child_constraint, cx);
96 *remaining_space -= child_size.along(self.axis);
97 *remaining_flex -= flex;
98 *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
99 }
100 }
101 }
102 }
103}
104
105impl Extend<ElementBox> for Flex {
106 fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) {
107 self.children.extend(children);
108 }
109}
110
111impl Element for Flex {
112 type LayoutState = f32;
113 type PaintState = ();
114
115 fn layout(
116 &mut self,
117 constraint: SizeConstraint,
118 cx: &mut LayoutContext,
119 ) -> (Vector2F, Self::LayoutState) {
120 let mut total_flex = None;
121 let mut fixed_space = 0.0;
122 let mut contains_float = false;
123
124 let cross_axis = self.axis.invert();
125 let mut cross_axis_max: f32 = 0.0;
126 for child in &mut self.children {
127 let metadata = child.metadata::<FlexParentData>();
128 contains_float |= metadata.map_or(false, |metadata| metadata.float);
129
130 if let Some(flex) = metadata.and_then(|metadata| metadata.flex.map(|(flex, _)| flex)) {
131 *total_flex.get_or_insert(0.) += flex;
132 } else {
133 let child_constraint = match self.axis {
134 Axis::Horizontal => SizeConstraint::new(
135 vec2f(0.0, constraint.min.y()),
136 vec2f(INFINITY, constraint.max.y()),
137 ),
138 Axis::Vertical => SizeConstraint::new(
139 vec2f(constraint.min.x(), 0.0),
140 vec2f(constraint.max.x(), INFINITY),
141 ),
142 };
143 let size = child.layout(child_constraint, cx);
144 fixed_space += size.along(self.axis);
145 cross_axis_max = cross_axis_max.max(size.along(cross_axis));
146 }
147 }
148
149 let mut remaining_space = constraint.max_along(self.axis) - fixed_space;
150 let mut size = if let Some(mut remaining_flex) = total_flex {
151 if remaining_space.is_infinite() {
152 panic!("flex contains flexible children but has an infinite constraint along the flex axis");
153 }
154
155 self.layout_flex_children(
156 false,
157 constraint,
158 &mut remaining_space,
159 &mut remaining_flex,
160 &mut cross_axis_max,
161 cx,
162 );
163 self.layout_flex_children(
164 true,
165 constraint,
166 &mut remaining_space,
167 &mut remaining_flex,
168 &mut cross_axis_max,
169 cx,
170 );
171
172 match self.axis {
173 Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
174 Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
175 }
176 } else {
177 match self.axis {
178 Axis::Horizontal => vec2f(fixed_space, cross_axis_max),
179 Axis::Vertical => vec2f(cross_axis_max, fixed_space),
180 }
181 };
182
183 if contains_float {
184 match self.axis {
185 Axis::Horizontal => size.set_x(size.x().max(constraint.max.x())),
186 Axis::Vertical => size.set_y(size.y().max(constraint.max.y())),
187 }
188 }
189
190 if constraint.min.x().is_finite() {
191 size.set_x(size.x().max(constraint.min.x()));
192 }
193 if constraint.min.y().is_finite() {
194 size.set_y(size.y().max(constraint.min.y()));
195 }
196
197 if size.x() > constraint.max.x() {
198 size.set_x(constraint.max.x());
199 }
200 if size.y() > constraint.max.y() {
201 size.set_y(constraint.max.y());
202 }
203
204 if let Some(scroll_state) = self.scroll_state.as_ref() {
205 scroll_state.update(cx, |scroll_state, _| {
206 if let Some(scroll_to) = scroll_state.scroll_to.take() {
207 let visible_start = scroll_state.scroll_position;
208 let visible_end = visible_start + size.along(self.axis);
209 if let Some(child) = self.children.get(scroll_to) {
210 let child_start: f32 = self.children[..scroll_to]
211 .iter()
212 .map(|c| c.size().along(self.axis))
213 .sum();
214 let child_end = child_start + child.size().along(self.axis);
215 if child_start < visible_start {
216 scroll_state.scroll_position = child_start;
217 } else if child_end > visible_end {
218 scroll_state.scroll_position = child_end - size.along(self.axis);
219 }
220 }
221 }
222
223 scroll_state.scroll_position =
224 scroll_state.scroll_position.min(-remaining_space).max(0.);
225 });
226 }
227
228 (size, remaining_space)
229 }
230
231 fn paint(
232 &mut self,
233 bounds: RectF,
234 visible_bounds: RectF,
235 remaining_space: &mut Self::LayoutState,
236 cx: &mut PaintContext,
237 ) -> Self::PaintState {
238 let mut remaining_space = *remaining_space;
239
240 let overflowing = remaining_space < 0.;
241 if overflowing {
242 cx.scene.push_layer(Some(bounds));
243 }
244
245 let mut child_origin = bounds.origin();
246 if let Some(scroll_state) = self.scroll_state.as_ref() {
247 let scroll_position = scroll_state.read(cx).scroll_position;
248 match self.axis {
249 Axis::Horizontal => child_origin.set_x(child_origin.x() - scroll_position),
250 Axis::Vertical => child_origin.set_y(child_origin.y() - scroll_position),
251 }
252 }
253
254 for child in &mut self.children {
255 if remaining_space > 0. {
256 if let Some(metadata) = child.metadata::<FlexParentData>() {
257 if metadata.float {
258 match self.axis {
259 Axis::Horizontal => child_origin += vec2f(remaining_space, 0.0),
260 Axis::Vertical => child_origin += vec2f(0.0, remaining_space),
261 }
262 remaining_space = 0.;
263 }
264 }
265 }
266 child.paint(child_origin, visible_bounds, cx);
267 match self.axis {
268 Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
269 Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
270 }
271 }
272
273 if overflowing {
274 cx.scene.pop_layer();
275 }
276 }
277
278 fn dispatch_event(
279 &mut self,
280 event: &Event,
281 bounds: RectF,
282 _: RectF,
283 remaining_space: &mut Self::LayoutState,
284 _: &mut Self::PaintState,
285 cx: &mut EventContext,
286 ) -> bool {
287 let mut handled = false;
288 for child in &mut self.children {
289 handled = child.dispatch_event(event, cx) || handled;
290 }
291 if !handled {
292 if let &Event::ScrollWheel(ScrollWheelEvent {
293 position,
294 delta,
295 precise,
296 }) = event
297 {
298 if *remaining_space < 0. && bounds.contains_point(position) {
299 if let Some(scroll_state) = self.scroll_state.as_ref() {
300 scroll_state.update(cx, |scroll_state, cx| {
301 let mut delta = match self.axis {
302 Axis::Horizontal => {
303 if delta.x() != 0. {
304 delta.x()
305 } else {
306 delta.y()
307 }
308 }
309 Axis::Vertical => delta.y(),
310 };
311 if !precise {
312 delta *= 20.;
313 }
314
315 scroll_state.scroll_position -= delta;
316
317 handled = true;
318 cx.notify();
319 });
320 }
321 }
322 }
323 }
324
325 if !handled {
326 if let &Event::MouseMoved(MouseMovedEvent { position, .. }) = event {
327 // If this is a scrollable flex, and the mouse is over it, eat the scroll event to prevent
328 // propogating it to the element below.
329 if self.scroll_state.is_some() && bounds.contains_point(position) {
330 handled = true;
331 }
332 }
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}