flex.rs

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