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.default_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 ..
297 }) = event
298 {
299 if *remaining_space < 0. && bounds.contains_point(position) {
300 if let Some(scroll_state) = self.scroll_state.as_ref() {
301 scroll_state.update(cx, |scroll_state, cx| {
302 let mut delta = match self.axis {
303 Axis::Horizontal => {
304 if delta.x() != 0. {
305 delta.x()
306 } else {
307 delta.y()
308 }
309 }
310 Axis::Vertical => delta.y(),
311 };
312 if !precise {
313 delta *= 20.;
314 }
315
316 scroll_state.scroll_position -= delta;
317
318 handled = true;
319 cx.notify();
320 });
321 }
322 }
323 }
324 }
325
326 if !handled {
327 if let &Event::MouseMoved(MouseMovedEvent { position, .. }) = event {
328 // If this is a scrollable flex, and the mouse is over it, eat the scroll event to prevent
329 // propogating it to the element below.
330 if self.scroll_state.is_some() && bounds.contains_point(position) {
331 handled = true;
332 }
333 }
334 }
335
336 handled
337 }
338
339 fn rect_for_text_range(
340 &self,
341 range_utf16: Range<usize>,
342 _: RectF,
343 _: RectF,
344 _: &Self::LayoutState,
345 _: &Self::PaintState,
346 cx: &MeasurementContext,
347 ) -> Option<RectF> {
348 self.children
349 .iter()
350 .find_map(|child| child.rect_for_text_range(range_utf16.clone(), cx))
351 }
352
353 fn debug(
354 &self,
355 bounds: RectF,
356 _: &Self::LayoutState,
357 _: &Self::PaintState,
358 cx: &DebugContext,
359 ) -> json::Value {
360 json!({
361 "type": "Flex",
362 "bounds": bounds.to_json(),
363 "axis": self.axis.to_json(),
364 "children": self.children.iter().map(|child| child.debug(cx)).collect::<Vec<json::Value>>()
365 })
366 }
367}
368
369struct FlexParentData {
370 flex: Option<(f32, bool)>,
371 float: bool,
372}
373
374pub struct FlexItem {
375 metadata: FlexParentData,
376 child: ElementBox,
377}
378
379impl FlexItem {
380 pub fn new(child: ElementBox) -> Self {
381 FlexItem {
382 metadata: FlexParentData {
383 flex: None,
384 float: false,
385 },
386 child,
387 }
388 }
389
390 pub fn flex(mut self, flex: f32, expanded: bool) -> Self {
391 self.metadata.flex = Some((flex, expanded));
392 self
393 }
394
395 pub fn float(mut self) -> Self {
396 self.metadata.float = true;
397 self
398 }
399}
400
401impl Element for FlexItem {
402 type LayoutState = ();
403 type PaintState = ();
404
405 fn layout(
406 &mut self,
407 constraint: SizeConstraint,
408 cx: &mut LayoutContext,
409 ) -> (Vector2F, Self::LayoutState) {
410 let size = self.child.layout(constraint, cx);
411 (size, ())
412 }
413
414 fn paint(
415 &mut self,
416 bounds: RectF,
417 visible_bounds: RectF,
418 _: &mut Self::LayoutState,
419 cx: &mut PaintContext,
420 ) -> Self::PaintState {
421 self.child.paint(bounds.origin(), visible_bounds, cx)
422 }
423
424 fn dispatch_event(
425 &mut self,
426 event: &Event,
427 _: RectF,
428 _: RectF,
429 _: &mut Self::LayoutState,
430 _: &mut Self::PaintState,
431 cx: &mut EventContext,
432 ) -> bool {
433 self.child.dispatch_event(event, cx)
434 }
435
436 fn rect_for_text_range(
437 &self,
438 range_utf16: Range<usize>,
439 _: RectF,
440 _: RectF,
441 _: &Self::LayoutState,
442 _: &Self::PaintState,
443 cx: &MeasurementContext,
444 ) -> Option<RectF> {
445 self.child.rect_for_text_range(range_utf16, cx)
446 }
447
448 fn metadata(&self) -> Option<&dyn Any> {
449 Some(&self.metadata)
450 }
451
452 fn debug(
453 &self,
454 _: RectF,
455 _: &Self::LayoutState,
456 _: &Self::PaintState,
457 cx: &DebugContext,
458 ) -> Value {
459 json!({
460 "type": "Flexible",
461 "flex": self.metadata.flex,
462 "child": self.child.debug(cx)
463 })
464 }
465}