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 = ();
 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 = 0.0;
 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 += 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 total_flex > 0.0 {
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            let mut remaining_flex = total_flex;
125            self.layout_flex_children(
126                false,
127                constraint,
128                &mut remaining_space,
129                &mut remaining_flex,
130                &mut cross_axis_max,
131                cx,
132            );
133            self.layout_flex_children(
134                true,
135                constraint,
136                &mut remaining_space,
137                &mut remaining_flex,
138                &mut cross_axis_max,
139                cx,
140            );
141
142            match self.axis {
143                Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
144                Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
145            }
146        } else {
147            match self.axis {
148                Axis::Horizontal => vec2f(fixed_space, cross_axis_max),
149                Axis::Vertical => vec2f(cross_axis_max, fixed_space),
150            }
151        };
152
153        if constraint.min.x().is_finite() {
154            size.set_x(size.x().max(constraint.min.x()));
155        }
156
157        if constraint.min.y().is_finite() {
158            size.set_y(size.y().max(constraint.min.y()));
159        }
160
161        (size, ())
162    }
163
164    fn paint(
165        &mut self,
166        bounds: RectF,
167        visible_bounds: RectF,
168        _: &mut Self::LayoutState,
169        cx: &mut PaintContext,
170    ) -> Self::PaintState {
171        let mut child_origin = bounds.origin();
172        for child in &mut self.children {
173            child.paint(child_origin, visible_bounds, cx);
174            match self.axis {
175                Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
176                Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
177            }
178        }
179    }
180
181    fn dispatch_event(
182        &mut self,
183        event: &Event,
184        _: RectF,
185        _: &mut Self::LayoutState,
186        _: &mut Self::PaintState,
187        cx: &mut EventContext,
188    ) -> bool {
189        let mut handled = false;
190        for child in &mut self.children {
191            handled = child.dispatch_event(event, cx) || handled;
192        }
193        handled
194    }
195
196    fn debug(
197        &self,
198        bounds: RectF,
199        _: &Self::LayoutState,
200        _: &Self::PaintState,
201        cx: &DebugContext,
202    ) -> json::Value {
203        json!({
204            "type": "Flex",
205            "bounds": bounds.to_json(),
206            "axis": self.axis.to_json(),
207            "children": self.children.iter().map(|child| child.debug(cx)).collect::<Vec<json::Value>>()
208        })
209    }
210}
211
212struct FlexParentData {
213    flex: f32,
214    expanded: bool,
215}
216
217pub struct Expanded {
218    metadata: FlexParentData,
219    child: ElementBox,
220}
221
222impl Expanded {
223    pub fn new(flex: f32, child: ElementBox) -> Self {
224        Expanded {
225            metadata: FlexParentData {
226                flex,
227                expanded: true,
228            },
229            child,
230        }
231    }
232}
233
234impl Element for Expanded {
235    type LayoutState = ();
236    type PaintState = ();
237
238    fn layout(
239        &mut self,
240        constraint: SizeConstraint,
241        cx: &mut LayoutContext,
242    ) -> (Vector2F, Self::LayoutState) {
243        let size = self.child.layout(constraint, cx);
244        (size, ())
245    }
246
247    fn paint(
248        &mut self,
249        bounds: RectF,
250        visible_bounds: RectF,
251        _: &mut Self::LayoutState,
252        cx: &mut PaintContext,
253    ) -> Self::PaintState {
254        self.child.paint(bounds.origin(), visible_bounds, cx)
255    }
256
257    fn dispatch_event(
258        &mut self,
259        event: &Event,
260        _: RectF,
261        _: &mut Self::LayoutState,
262        _: &mut Self::PaintState,
263        cx: &mut EventContext,
264    ) -> bool {
265        self.child.dispatch_event(event, cx)
266    }
267
268    fn metadata(&self) -> Option<&dyn Any> {
269        Some(&self.metadata)
270    }
271
272    fn debug(
273        &self,
274        _: RectF,
275        _: &Self::LayoutState,
276        _: &Self::PaintState,
277        cx: &DebugContext,
278    ) -> Value {
279        json!({
280            "type": "Expanded",
281            "flex": self.metadata.flex,
282            "child": self.child.debug(cx)
283        })
284    }
285}
286
287pub struct Flexible {
288    metadata: FlexParentData,
289    child: ElementBox,
290}
291
292impl Flexible {
293    pub fn new(flex: f32, child: ElementBox) -> Self {
294        Flexible {
295            metadata: FlexParentData {
296                flex,
297                expanded: false,
298            },
299            child,
300        }
301    }
302}
303
304impl Element for Flexible {
305    type LayoutState = ();
306    type PaintState = ();
307
308    fn layout(
309        &mut self,
310        constraint: SizeConstraint,
311        cx: &mut LayoutContext,
312    ) -> (Vector2F, Self::LayoutState) {
313        let size = self.child.layout(constraint, cx);
314        (size, ())
315    }
316
317    fn paint(
318        &mut self,
319        bounds: RectF,
320        visible_bounds: RectF,
321        _: &mut Self::LayoutState,
322        cx: &mut PaintContext,
323    ) -> Self::PaintState {
324        self.child.paint(bounds.origin(), visible_bounds, cx)
325    }
326
327    fn dispatch_event(
328        &mut self,
329        event: &Event,
330        _: RectF,
331        _: &mut Self::LayoutState,
332        _: &mut Self::PaintState,
333        cx: &mut EventContext,
334    ) -> bool {
335        self.child.dispatch_event(event, cx)
336    }
337
338    fn metadata(&self) -> Option<&dyn Any> {
339        Some(&self.metadata)
340    }
341
342    fn debug(
343        &self,
344        _: RectF,
345        _: &Self::LayoutState,
346        _: &Self::PaintState,
347        cx: &DebugContext,
348    ) -> Value {
349        json!({
350            "type": "Flexible",
351            "flex": self.metadata.flex,
352            "child": self.child.debug(cx)
353        })
354    }
355}