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