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