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        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 metadata.expanded != expanded {
 48                    continue;
 49                }
 50
 51                let flex = metadata.flex;
 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
 78impl Extend<ElementBox> for Flex {
 79    fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) {
 80        self.children.extend(children);
 81    }
 82}
 83
 84impl Element for Flex {
 85    type LayoutState = bool;
 86    type PaintState = ();
 87
 88    fn layout(
 89        &mut self,
 90        constraint: SizeConstraint,
 91        cx: &mut LayoutContext,
 92    ) -> (Vector2F, Self::LayoutState) {
 93        let mut total_flex = None;
 94        let mut fixed_space = 0.0;
 95
 96        let cross_axis = self.axis.invert();
 97        let mut cross_axis_max: f32 = 0.0;
 98        for child in &mut self.children {
 99            if let Some(metadata) = child.metadata::<FlexParentData>() {
100                *total_flex.get_or_insert(0.) += metadata.flex;
101            } else {
102                let child_constraint = match self.axis {
103                    Axis::Horizontal => SizeConstraint::new(
104                        vec2f(0.0, constraint.min.y()),
105                        vec2f(INFINITY, constraint.max.y()),
106                    ),
107                    Axis::Vertical => SizeConstraint::new(
108                        vec2f(constraint.min.x(), 0.0),
109                        vec2f(constraint.max.x(), INFINITY),
110                    ),
111                };
112                let size = child.layout(child_constraint, cx);
113                fixed_space += size.along(self.axis);
114                cross_axis_max = cross_axis_max.max(size.along(cross_axis));
115            }
116        }
117
118        let mut size = if let Some(mut remaining_flex) = total_flex {
119            if constraint.max_along(self.axis).is_infinite() {
120                panic!("flex contains flexible children but has an infinite constraint along the flex axis");
121            }
122
123            let mut remaining_space = constraint.max_along(self.axis) - fixed_space;
124            self.layout_flex_children(
125                false,
126                constraint,
127                &mut remaining_space,
128                &mut remaining_flex,
129                &mut cross_axis_max,
130                cx,
131            );
132            self.layout_flex_children(
133                true,
134                constraint,
135                &mut remaining_space,
136                &mut remaining_flex,
137                &mut cross_axis_max,
138                cx,
139            );
140
141            match self.axis {
142                Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
143                Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
144            }
145        } else {
146            match self.axis {
147                Axis::Horizontal => vec2f(fixed_space, cross_axis_max),
148                Axis::Vertical => vec2f(cross_axis_max, fixed_space),
149            }
150        };
151
152        if constraint.min.x().is_finite() {
153            size.set_x(size.x().max(constraint.min.x()));
154        }
155        if constraint.min.y().is_finite() {
156            size.set_y(size.y().max(constraint.min.y()));
157        }
158
159        let mut overflowing = false;
160        if size.x() > constraint.max.x() {
161            size.set_x(constraint.max.x());
162            overflowing = true;
163        }
164        if size.y() > constraint.max.y() {
165            size.set_y(constraint.max.y());
166            overflowing = true;
167        }
168
169        (size, overflowing)
170    }
171
172    fn paint(
173        &mut self,
174        bounds: RectF,
175        visible_bounds: RectF,
176        overflowing: &mut Self::LayoutState,
177        cx: &mut PaintContext,
178    ) -> Self::PaintState {
179        if *overflowing {
180            cx.scene.push_layer(Some(bounds));
181        }
182        let mut child_origin = bounds.origin();
183        for child in &mut self.children {
184            child.paint(child_origin, visible_bounds, cx);
185            match self.axis {
186                Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
187                Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
188            }
189        }
190        if *overflowing {
191            cx.scene.pop_layer();
192        }
193    }
194
195    fn dispatch_event(
196        &mut self,
197        event: &Event,
198        _: RectF,
199        _: &mut Self::LayoutState,
200        _: &mut Self::PaintState,
201        cx: &mut EventContext,
202    ) -> bool {
203        let mut handled = false;
204        for child in &mut self.children {
205            handled = child.dispatch_event(event, cx) || handled;
206        }
207        handled
208    }
209
210    fn debug(
211        &self,
212        bounds: RectF,
213        _: &Self::LayoutState,
214        _: &Self::PaintState,
215        cx: &DebugContext,
216    ) -> json::Value {
217        json!({
218            "type": "Flex",
219            "bounds": bounds.to_json(),
220            "axis": self.axis.to_json(),
221            "children": self.children.iter().map(|child| child.debug(cx)).collect::<Vec<json::Value>>()
222        })
223    }
224}
225
226struct FlexParentData {
227    flex: f32,
228    expanded: bool,
229}
230
231pub struct Flexible {
232    metadata: FlexParentData,
233    child: ElementBox,
234}
235
236impl Flexible {
237    pub fn new(flex: f32, expanded: bool, child: ElementBox) -> Self {
238        Flexible {
239            metadata: FlexParentData { flex, expanded },
240            child,
241        }
242    }
243}
244
245impl Element for Flexible {
246    type LayoutState = ();
247    type PaintState = ();
248
249    fn layout(
250        &mut self,
251        constraint: SizeConstraint,
252        cx: &mut LayoutContext,
253    ) -> (Vector2F, Self::LayoutState) {
254        let size = self.child.layout(constraint, cx);
255        (size, ())
256    }
257
258    fn paint(
259        &mut self,
260        bounds: RectF,
261        visible_bounds: RectF,
262        _: &mut Self::LayoutState,
263        cx: &mut PaintContext,
264    ) -> Self::PaintState {
265        self.child.paint(bounds.origin(), visible_bounds, cx)
266    }
267
268    fn dispatch_event(
269        &mut self,
270        event: &Event,
271        _: RectF,
272        _: &mut Self::LayoutState,
273        _: &mut Self::PaintState,
274        cx: &mut EventContext,
275    ) -> bool {
276        self.child.dispatch_event(event, cx)
277    }
278
279    fn metadata(&self) -> Option<&dyn Any> {
280        Some(&self.metadata)
281    }
282
283    fn debug(
284        &self,
285        _: RectF,
286        _: &Self::LayoutState,
287        _: &Self::PaintState,
288        cx: &DebugContext,
289    ) -> Value {
290        json!({
291            "type": "Flexible",
292            "flex": self.metadata.flex,
293            "child": self.child.debug(cx)
294        })
295    }
296}