flex.rs

  1use std::{any::Any, f32::INFINITY};
  2
  3use crate::{
  4    json::{self, ToJson, Value},
  5    Axis, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
  6    SizeConstraint, Vector2FExt,
  7};
  8use pathfinder_geometry::{
  9    rect::RectF,
 10    vector::{vec2f, Vector2F},
 11};
 12use serde_json::json;
 13
 14pub struct Flex {
 15    axis: Axis,
 16    children: Vec<ElementBox>,
 17}
 18
 19impl Flex {
 20    pub fn new(axis: Axis) -> Self {
 21        Self {
 22            axis,
 23            children: Default::default(),
 24        }
 25    }
 26
 27    pub fn row() -> Self {
 28        Self::new(Axis::Horizontal)
 29    }
 30
 31    pub fn column() -> Self {
 32        Self::new(Axis::Vertical)
 33    }
 34
 35    fn layout_flex_children(
 36        &mut self,
 37        layout_expanded: bool,
 38        constraint: SizeConstraint,
 39        remaining_space: &mut f32,
 40        remaining_flex: &mut f32,
 41        cross_axis_max: &mut f32,
 42        cx: &mut LayoutContext,
 43    ) {
 44        let cross_axis = self.axis.invert();
 45        for child in &mut self.children {
 46            if let Some(metadata) = child.metadata::<FlexParentData>() {
 47                if let Some((flex, expanded)) = metadata.flex {
 48                    if expanded != layout_expanded {
 49                        continue;
 50                    }
 51
 52                    let child_max = if *remaining_flex == 0.0 {
 53                        *remaining_space
 54                    } else {
 55                        let space_per_flex = *remaining_space / *remaining_flex;
 56                        space_per_flex * flex
 57                    };
 58                    let child_min = if expanded { child_max } else { 0. };
 59                    let child_constraint = match self.axis {
 60                        Axis::Horizontal => SizeConstraint::new(
 61                            vec2f(child_min, constraint.min.y()),
 62                            vec2f(child_max, constraint.max.y()),
 63                        ),
 64                        Axis::Vertical => SizeConstraint::new(
 65                            vec2f(constraint.min.x(), child_min),
 66                            vec2f(constraint.max.x(), child_max),
 67                        ),
 68                    };
 69                    let child_size = child.layout(child_constraint, cx);
 70                    *remaining_space -= child_size.along(self.axis);
 71                    *remaining_flex -= flex;
 72                    *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
 73                }
 74            }
 75        }
 76    }
 77}
 78
 79impl Extend<ElementBox> for Flex {
 80    fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) {
 81        self.children.extend(children);
 82    }
 83}
 84
 85impl Element for Flex {
 86    type LayoutState = f32;
 87    type PaintState = ();
 88
 89    fn layout(
 90        &mut self,
 91        constraint: SizeConstraint,
 92        cx: &mut LayoutContext,
 93    ) -> (Vector2F, Self::LayoutState) {
 94        let mut total_flex = None;
 95        let mut fixed_space = 0.0;
 96
 97        let cross_axis = self.axis.invert();
 98        let mut cross_axis_max: f32 = 0.0;
 99        for child in &mut self.children {
100            if let Some(flex) = child
101                .metadata::<FlexParentData>()
102                .and_then(|metadata| metadata.flex.map(|(flex, _)| flex))
103            {
104                *total_flex.get_or_insert(0.) += flex;
105            } else {
106                let child_constraint = match self.axis {
107                    Axis::Horizontal => SizeConstraint::new(
108                        vec2f(0.0, constraint.min.y()),
109                        vec2f(INFINITY, constraint.max.y()),
110                    ),
111                    Axis::Vertical => SizeConstraint::new(
112                        vec2f(constraint.min.x(), 0.0),
113                        vec2f(constraint.max.x(), INFINITY),
114                    ),
115                };
116                let size = child.layout(child_constraint, cx);
117                fixed_space += size.along(self.axis);
118                cross_axis_max = cross_axis_max.max(size.along(cross_axis));
119            }
120        }
121
122        let mut remaining_space = constraint.max_along(self.axis) - fixed_space;
123        let mut size = if let Some(mut remaining_flex) = total_flex {
124            if remaining_space.is_infinite() {
125                panic!("flex contains flexible children but has an infinite constraint along the flex axis");
126            }
127
128            self.layout_flex_children(
129                false,
130                constraint,
131                &mut remaining_space,
132                &mut remaining_flex,
133                &mut cross_axis_max,
134                cx,
135            );
136            self.layout_flex_children(
137                true,
138                constraint,
139                &mut remaining_space,
140                &mut remaining_flex,
141                &mut cross_axis_max,
142                cx,
143            );
144
145            match self.axis {
146                Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
147                Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
148            }
149        } else {
150            match self.axis {
151                Axis::Horizontal => vec2f(fixed_space, cross_axis_max),
152                Axis::Vertical => vec2f(cross_axis_max, fixed_space),
153            }
154        };
155
156        if constraint.min.x().is_finite() {
157            size.set_x(size.x().max(constraint.min.x()));
158        }
159        if constraint.min.y().is_finite() {
160            size.set_y(size.y().max(constraint.min.y()));
161        }
162
163        if size.x() > constraint.max.x() {
164            size.set_x(constraint.max.x());
165        }
166        if size.y() > constraint.max.y() {
167            size.set_y(constraint.max.y());
168        }
169
170        (size, remaining_space)
171    }
172
173    fn paint(
174        &mut self,
175        bounds: RectF,
176        visible_bounds: RectF,
177        remaining_space: &mut Self::LayoutState,
178        cx: &mut PaintContext,
179    ) -> Self::PaintState {
180        let overflowing = *remaining_space < 0.;
181        if overflowing {
182            cx.scene.push_layer(Some(bounds));
183        }
184        let mut child_origin = bounds.origin();
185        for child in &mut self.children {
186            if *remaining_space > 0. {
187                if let Some(metadata) = child.metadata::<FlexParentData>() {
188                    if metadata.float {
189                        match self.axis {
190                            Axis::Horizontal => child_origin += vec2f(*remaining_space, 0.0),
191                            Axis::Vertical => child_origin += vec2f(0.0, *remaining_space),
192                        }
193                        *remaining_space = 0.;
194                    }
195                }
196            }
197            child.paint(child_origin, visible_bounds, cx);
198            match self.axis {
199                Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
200                Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
201            }
202        }
203        if overflowing {
204            cx.scene.pop_layer();
205        }
206    }
207
208    fn dispatch_event(
209        &mut self,
210        event: &Event,
211        _: RectF,
212        _: &mut Self::LayoutState,
213        _: &mut Self::PaintState,
214        cx: &mut EventContext,
215    ) -> bool {
216        let mut handled = false;
217        for child in &mut self.children {
218            handled = child.dispatch_event(event, cx) || handled;
219        }
220        handled
221    }
222
223    fn debug(
224        &self,
225        bounds: RectF,
226        _: &Self::LayoutState,
227        _: &Self::PaintState,
228        cx: &DebugContext,
229    ) -> json::Value {
230        json!({
231            "type": "Flex",
232            "bounds": bounds.to_json(),
233            "axis": self.axis.to_json(),
234            "children": self.children.iter().map(|child| child.debug(cx)).collect::<Vec<json::Value>>()
235        })
236    }
237}
238
239struct FlexParentData {
240    flex: Option<(f32, bool)>,
241    float: bool,
242}
243
244pub struct FlexItem {
245    metadata: FlexParentData,
246    child: ElementBox,
247}
248
249impl FlexItem {
250    pub fn new(child: ElementBox) -> Self {
251        FlexItem {
252            metadata: FlexParentData {
253                flex: None,
254                float: false,
255            },
256            child,
257        }
258    }
259
260    pub fn flex(mut self, flex: f32, expanded: bool) -> Self {
261        self.metadata.flex = Some((flex, expanded));
262        self
263    }
264
265    pub fn float(mut self) -> Self {
266        self.metadata.float = true;
267        self
268    }
269}
270
271impl Element for FlexItem {
272    type LayoutState = ();
273    type PaintState = ();
274
275    fn layout(
276        &mut self,
277        constraint: SizeConstraint,
278        cx: &mut LayoutContext,
279    ) -> (Vector2F, Self::LayoutState) {
280        let size = self.child.layout(constraint, cx);
281        (size, ())
282    }
283
284    fn paint(
285        &mut self,
286        bounds: RectF,
287        visible_bounds: RectF,
288        _: &mut Self::LayoutState,
289        cx: &mut PaintContext,
290    ) -> Self::PaintState {
291        self.child.paint(bounds.origin(), visible_bounds, cx)
292    }
293
294    fn dispatch_event(
295        &mut self,
296        event: &Event,
297        _: RectF,
298        _: &mut Self::LayoutState,
299        _: &mut Self::PaintState,
300        cx: &mut EventContext,
301    ) -> bool {
302        self.child.dispatch_event(event, cx)
303    }
304
305    fn metadata(&self) -> Option<&dyn Any> {
306        Some(&self.metadata)
307    }
308
309    fn debug(
310        &self,
311        _: RectF,
312        _: &Self::LayoutState,
313        _: &Self::PaintState,
314        cx: &DebugContext,
315    ) -> Value {
316        json!({
317            "type": "Flexible",
318            "flex": self.metadata.flex,
319            "child": self.child.debug(cx)
320        })
321    }
322}