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.default_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                ..
297            }) = event
298            {
299                if *remaining_space < 0. && bounds.contains_point(position) {
300                    if let Some(scroll_state) = self.scroll_state.as_ref() {
301                        scroll_state.update(cx, |scroll_state, cx| {
302                            let mut delta = match self.axis {
303                                Axis::Horizontal => {
304                                    if delta.x() != 0. {
305                                        delta.x()
306                                    } else {
307                                        delta.y()
308                                    }
309                                }
310                                Axis::Vertical => delta.y(),
311                            };
312                            if !precise {
313                                delta *= 20.;
314                            }
315
316                            scroll_state.scroll_position -= delta;
317
318                            handled = true;
319                            cx.notify();
320                        });
321                    }
322                }
323            }
324        }
325
326        if !handled {
327            if let &Event::MouseMoved(MouseMovedEvent { position, .. }) = event {
328                // If this is a scrollable flex, and the mouse is over it, eat the scroll event to prevent
329                // propogating it to the element below.
330                if self.scroll_state.is_some() && bounds.contains_point(position) {
331                    handled = true;
332                }
333            }
334        }
335
336        handled
337    }
338
339    fn rect_for_text_range(
340        &self,
341        range_utf16: Range<usize>,
342        _: RectF,
343        _: RectF,
344        _: &Self::LayoutState,
345        _: &Self::PaintState,
346        cx: &MeasurementContext,
347    ) -> Option<RectF> {
348        self.children
349            .iter()
350            .find_map(|child| child.rect_for_text_range(range_utf16.clone(), cx))
351    }
352
353    fn debug(
354        &self,
355        bounds: RectF,
356        _: &Self::LayoutState,
357        _: &Self::PaintState,
358        cx: &DebugContext,
359    ) -> json::Value {
360        json!({
361            "type": "Flex",
362            "bounds": bounds.to_json(),
363            "axis": self.axis.to_json(),
364            "children": self.children.iter().map(|child| child.debug(cx)).collect::<Vec<json::Value>>()
365        })
366    }
367}
368
369struct FlexParentData {
370    flex: Option<(f32, bool)>,
371    float: bool,
372}
373
374pub struct FlexItem {
375    metadata: FlexParentData,
376    child: ElementBox,
377}
378
379impl FlexItem {
380    pub fn new(child: ElementBox) -> Self {
381        FlexItem {
382            metadata: FlexParentData {
383                flex: None,
384                float: false,
385            },
386            child,
387        }
388    }
389
390    pub fn flex(mut self, flex: f32, expanded: bool) -> Self {
391        self.metadata.flex = Some((flex, expanded));
392        self
393    }
394
395    pub fn float(mut self) -> Self {
396        self.metadata.float = true;
397        self
398    }
399}
400
401impl Element for FlexItem {
402    type LayoutState = ();
403    type PaintState = ();
404
405    fn layout(
406        &mut self,
407        constraint: SizeConstraint,
408        cx: &mut LayoutContext,
409    ) -> (Vector2F, Self::LayoutState) {
410        let size = self.child.layout(constraint, cx);
411        (size, ())
412    }
413
414    fn paint(
415        &mut self,
416        bounds: RectF,
417        visible_bounds: RectF,
418        _: &mut Self::LayoutState,
419        cx: &mut PaintContext,
420    ) -> Self::PaintState {
421        self.child.paint(bounds.origin(), visible_bounds, cx)
422    }
423
424    fn dispatch_event(
425        &mut self,
426        event: &Event,
427        _: RectF,
428        _: RectF,
429        _: &mut Self::LayoutState,
430        _: &mut Self::PaintState,
431        cx: &mut EventContext,
432    ) -> bool {
433        self.child.dispatch_event(event, cx)
434    }
435
436    fn rect_for_text_range(
437        &self,
438        range_utf16: Range<usize>,
439        _: RectF,
440        _: RectF,
441        _: &Self::LayoutState,
442        _: &Self::PaintState,
443        cx: &MeasurementContext,
444    ) -> Option<RectF> {
445        self.child.rect_for_text_range(range_utf16, cx)
446    }
447
448    fn metadata(&self) -> Option<&dyn Any> {
449        Some(&self.metadata)
450    }
451
452    fn debug(
453        &self,
454        _: RectF,
455        _: &Self::LayoutState,
456        _: &Self::PaintState,
457        cx: &DebugContext,
458    ) -> Value {
459        json!({
460            "type": "Flexible",
461            "flex": self.metadata.flex,
462            "child": self.child.debug(cx)
463        })
464    }
465}