flex.rs

  1use std::{any::Any, f32::INFINITY, ops::Range};
  2
  3use crate::{
  4    json::{self, ToJson, Value},
  5    presenter::MeasurementContext,
  6    Axis, DebugContext, Element, ElementBox, ElementStateHandle, Event, EventContext,
  7    LayoutContext, MouseMovedEvent, 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)]
 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<ScrollState>>,
 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 = cx.element_state::<Tag, ScrollState>(element_id);
 56        scroll_state.update(cx, |scroll_state, _| scroll_state.scroll_to = scroll_to);
 57        self.scroll_state = Some(scroll_state);
 58        self
 59    }
 60
 61    fn layout_flex_children(
 62        &mut self,
 63        layout_expanded: bool,
 64        constraint: SizeConstraint,
 65        remaining_space: &mut f32,
 66        remaining_flex: &mut f32,
 67        cross_axis_max: &mut f32,
 68        cx: &mut LayoutContext,
 69    ) {
 70        let cross_axis = self.axis.invert();
 71        for child in &mut self.children {
 72            if let Some(metadata) = child.metadata::<FlexParentData>() {
 73                if let Some((flex, expanded)) = metadata.flex {
 74                    if expanded != layout_expanded {
 75                        continue;
 76                    }
 77
 78                    let child_max = if *remaining_flex == 0.0 {
 79                        *remaining_space
 80                    } else {
 81                        let space_per_flex = *remaining_space / *remaining_flex;
 82                        space_per_flex * flex
 83                    };
 84                    let child_min = if expanded { child_max } else { 0. };
 85                    let child_constraint = match self.axis {
 86                        Axis::Horizontal => SizeConstraint::new(
 87                            vec2f(child_min, constraint.min.y()),
 88                            vec2f(child_max, constraint.max.y()),
 89                        ),
 90                        Axis::Vertical => SizeConstraint::new(
 91                            vec2f(constraint.min.x(), child_min),
 92                            vec2f(constraint.max.x(), child_max),
 93                        ),
 94                    };
 95                    let child_size = child.layout(child_constraint, cx);
 96                    *remaining_space -= child_size.along(self.axis);
 97                    *remaining_flex -= flex;
 98                    *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
 99                }
100            }
101        }
102    }
103}
104
105impl Extend<ElementBox> for Flex {
106    fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) {
107        self.children.extend(children);
108    }
109}
110
111impl Element for Flex {
112    type LayoutState = f32;
113    type PaintState = ();
114
115    fn layout(
116        &mut self,
117        constraint: SizeConstraint,
118        cx: &mut LayoutContext,
119    ) -> (Vector2F, Self::LayoutState) {
120        let mut total_flex = None;
121        let mut fixed_space = 0.0;
122        let mut contains_float = false;
123
124        let cross_axis = self.axis.invert();
125        let mut cross_axis_max: f32 = 0.0;
126        for child in &mut self.children {
127            let metadata = child.metadata::<FlexParentData>();
128            contains_float |= metadata.map_or(false, |metadata| metadata.float);
129
130            if let Some(flex) = metadata.and_then(|metadata| metadata.flex.map(|(flex, _)| flex)) {
131                *total_flex.get_or_insert(0.) += flex;
132            } else {
133                let child_constraint = match self.axis {
134                    Axis::Horizontal => SizeConstraint::new(
135                        vec2f(0.0, constraint.min.y()),
136                        vec2f(INFINITY, constraint.max.y()),
137                    ),
138                    Axis::Vertical => SizeConstraint::new(
139                        vec2f(constraint.min.x(), 0.0),
140                        vec2f(constraint.max.x(), INFINITY),
141                    ),
142                };
143                let size = child.layout(child_constraint, cx);
144                fixed_space += size.along(self.axis);
145                cross_axis_max = cross_axis_max.max(size.along(cross_axis));
146            }
147        }
148
149        let mut remaining_space = constraint.max_along(self.axis) - fixed_space;
150        let mut size = if let Some(mut remaining_flex) = total_flex {
151            if remaining_space.is_infinite() {
152                panic!("flex contains flexible children but has an infinite constraint along the flex axis");
153            }
154
155            self.layout_flex_children(
156                false,
157                constraint,
158                &mut remaining_space,
159                &mut remaining_flex,
160                &mut cross_axis_max,
161                cx,
162            );
163            self.layout_flex_children(
164                true,
165                constraint,
166                &mut remaining_space,
167                &mut remaining_flex,
168                &mut cross_axis_max,
169                cx,
170            );
171
172            match self.axis {
173                Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
174                Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
175            }
176        } else {
177            match self.axis {
178                Axis::Horizontal => vec2f(fixed_space, cross_axis_max),
179                Axis::Vertical => vec2f(cross_axis_max, fixed_space),
180            }
181        };
182
183        if contains_float {
184            match self.axis {
185                Axis::Horizontal => size.set_x(size.x().max(constraint.max.x())),
186                Axis::Vertical => size.set_y(size.y().max(constraint.max.y())),
187            }
188        }
189
190        if constraint.min.x().is_finite() {
191            size.set_x(size.x().max(constraint.min.x()));
192        }
193        if constraint.min.y().is_finite() {
194            size.set_y(size.y().max(constraint.min.y()));
195        }
196
197        if size.x() > constraint.max.x() {
198            size.set_x(constraint.max.x());
199        }
200        if size.y() > constraint.max.y() {
201            size.set_y(constraint.max.y());
202        }
203
204        if let Some(scroll_state) = self.scroll_state.as_ref() {
205            scroll_state.update(cx, |scroll_state, _| {
206                if let Some(scroll_to) = scroll_state.scroll_to.take() {
207                    let visible_start = scroll_state.scroll_position;
208                    let visible_end = visible_start + size.along(self.axis);
209                    if let Some(child) = self.children.get(scroll_to) {
210                        let child_start: f32 = self.children[..scroll_to]
211                            .iter()
212                            .map(|c| c.size().along(self.axis))
213                            .sum();
214                        let child_end = child_start + child.size().along(self.axis);
215                        if child_start < visible_start {
216                            scroll_state.scroll_position = child_start;
217                        } else if child_end > visible_end {
218                            scroll_state.scroll_position = child_end - size.along(self.axis);
219                        }
220                    }
221                }
222
223                scroll_state.scroll_position =
224                    scroll_state.scroll_position.min(-remaining_space).max(0.);
225            });
226        }
227
228        (size, remaining_space)
229    }
230
231    fn paint(
232        &mut self,
233        bounds: RectF,
234        visible_bounds: RectF,
235        remaining_space: &mut Self::LayoutState,
236        cx: &mut PaintContext,
237    ) -> Self::PaintState {
238        let mut remaining_space = *remaining_space;
239
240        let overflowing = remaining_space < 0.;
241        if overflowing {
242            cx.scene.push_layer(Some(bounds));
243        }
244
245        let mut child_origin = bounds.origin();
246        if let Some(scroll_state) = self.scroll_state.as_ref() {
247            let scroll_position = scroll_state.read(cx).scroll_position;
248            match self.axis {
249                Axis::Horizontal => child_origin.set_x(child_origin.x() - scroll_position),
250                Axis::Vertical => child_origin.set_y(child_origin.y() - scroll_position),
251            }
252        }
253
254        for child in &mut self.children {
255            if remaining_space > 0. {
256                if let Some(metadata) = child.metadata::<FlexParentData>() {
257                    if metadata.float {
258                        match self.axis {
259                            Axis::Horizontal => child_origin += vec2f(remaining_space, 0.0),
260                            Axis::Vertical => child_origin += vec2f(0.0, remaining_space),
261                        }
262                        remaining_space = 0.;
263                    }
264                }
265            }
266            child.paint(child_origin, visible_bounds, cx);
267            match self.axis {
268                Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
269                Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
270            }
271        }
272
273        if overflowing {
274            cx.scene.pop_layer();
275        }
276    }
277
278    fn dispatch_event(
279        &mut self,
280        event: &Event,
281        bounds: RectF,
282        _: RectF,
283        remaining_space: &mut Self::LayoutState,
284        _: &mut Self::PaintState,
285        cx: &mut EventContext,
286    ) -> bool {
287        let mut handled = false;
288        for child in &mut self.children {
289            handled = child.dispatch_event(event, cx) || handled;
290        }
291        if !handled {
292            if let &Event::ScrollWheel(ScrollWheelEvent {
293                position,
294                delta,
295                precise,
296            }) = event
297            {
298                if *remaining_space < 0. && bounds.contains_point(position) {
299                    if let Some(scroll_state) = self.scroll_state.as_ref() {
300                        scroll_state.update(cx, |scroll_state, cx| {
301                            let mut delta = match self.axis {
302                                Axis::Horizontal => {
303                                    if delta.x() != 0. {
304                                        delta.x()
305                                    } else {
306                                        delta.y()
307                                    }
308                                }
309                                Axis::Vertical => delta.y(),
310                            };
311                            if !precise {
312                                delta *= 20.;
313                            }
314
315                            scroll_state.scroll_position -= delta;
316
317                            handled = true;
318                            cx.notify();
319                        });
320                    }
321                }
322            }
323        }
324
325        if !handled {
326            if let &Event::MouseMoved(MouseMovedEvent { position, .. }) = event {
327                // If this is a scrollable flex, and the mouse is over it, eat the scroll event to prevent
328                // propogating it to the element below.
329                if self.scroll_state.is_some() && bounds.contains_point(position) {
330                    handled = true;
331                }
332            }
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}