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