flex.rs

  1use std::{any::Any, f32::INFINITY};
  2
  3use crate::{
  4    json::{self, ToJson, Value},
  5    Axis, DebugContext, Element, ElementBox, ElementStateContext, ElementStateHandle, Event,
  6    EventContext, LayoutContext, PaintContext, SizeConstraint, Vector2FExt,
  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, C>(
 44        mut self,
 45        element_id: usize,
 46        scroll_to: Option<usize>,
 47        cx: &mut C,
 48    ) -> Self
 49    where
 50        Tag: 'static,
 51        C: ElementStateContext,
 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        handled
323    }
324
325    fn debug(
326        &self,
327        bounds: RectF,
328        _: &Self::LayoutState,
329        _: &Self::PaintState,
330        cx: &DebugContext,
331    ) -> json::Value {
332        json!({
333            "type": "Flex",
334            "bounds": bounds.to_json(),
335            "axis": self.axis.to_json(),
336            "children": self.children.iter().map(|child| child.debug(cx)).collect::<Vec<json::Value>>()
337        })
338    }
339}
340
341struct FlexParentData {
342    flex: Option<(f32, bool)>,
343    float: bool,
344}
345
346pub struct FlexItem {
347    metadata: FlexParentData,
348    child: ElementBox,
349}
350
351impl FlexItem {
352    pub fn new(child: ElementBox) -> Self {
353        FlexItem {
354            metadata: FlexParentData {
355                flex: None,
356                float: false,
357            },
358            child,
359        }
360    }
361
362    pub fn flex(mut self, flex: f32, expanded: bool) -> Self {
363        self.metadata.flex = Some((flex, expanded));
364        self
365    }
366
367    pub fn float(mut self) -> Self {
368        self.metadata.float = true;
369        self
370    }
371}
372
373impl Element for FlexItem {
374    type LayoutState = ();
375    type PaintState = ();
376
377    fn layout(
378        &mut self,
379        constraint: SizeConstraint,
380        cx: &mut LayoutContext,
381    ) -> (Vector2F, Self::LayoutState) {
382        let size = self.child.layout(constraint, cx);
383        (size, ())
384    }
385
386    fn paint(
387        &mut self,
388        bounds: RectF,
389        visible_bounds: RectF,
390        _: &mut Self::LayoutState,
391        cx: &mut PaintContext,
392    ) -> Self::PaintState {
393        self.child.paint(bounds.origin(), visible_bounds, cx)
394    }
395
396    fn dispatch_event(
397        &mut self,
398        event: &Event,
399        _: RectF,
400        _: RectF,
401        _: &mut Self::LayoutState,
402        _: &mut Self::PaintState,
403        cx: &mut EventContext,
404    ) -> bool {
405        self.child.dispatch_event(event, cx)
406    }
407
408    fn metadata(&self) -> Option<&dyn Any> {
409        Some(&self.metadata)
410    }
411
412    fn debug(
413        &self,
414        _: RectF,
415        _: &Self::LayoutState,
416        _: &Self::PaintState,
417        cx: &DebugContext,
418    ) -> Value {
419        json!({
420            "type": "Flexible",
421            "flex": self.metadata.flex,
422            "child": self.child.debug(cx)
423        })
424    }
425}