use std::{any::Any, f32::INFINITY}; use crate::{ json::{self, ToJson, Value}, AfterLayoutContext, Axis, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, Vector2FExt, }; use pathfinder_geometry::{ rect::RectF, vector::{vec2f, Vector2F}, }; use serde_json::json; pub struct Flex { axis: Axis, children: Vec, } impl Flex { pub fn new(axis: Axis) -> Self { Self { axis, children: Default::default(), } } pub fn row() -> Self { Self::new(Axis::Horizontal) } pub fn column() -> Self { Self::new(Axis::Vertical) } fn child_flex<'b>(child: &ElementBox) -> Option { child .metadata() .and_then(|d| d.downcast_ref::()) .map(|data| data.flex) } } impl Extend for Flex { fn extend>(&mut self, children: T) { self.children.extend(children); } } impl Element for Flex { type LayoutState = (); type PaintState = (); fn layout( &mut self, constraint: SizeConstraint, ctx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let mut total_flex = 0.0; let mut fixed_space = 0.0; let cross_axis = self.axis.invert(); let mut cross_axis_max: f32 = 0.0; for child in &mut self.children { if let Some(flex) = Self::child_flex(&child) { total_flex += flex; } else { let child_constraint = match self.axis { Axis::Horizontal => SizeConstraint::new( vec2f(0.0, constraint.min.y()), vec2f(INFINITY, constraint.max.y()), ), Axis::Vertical => SizeConstraint::new( vec2f(constraint.min.x(), 0.0), vec2f(constraint.max.x(), INFINITY), ), }; let size = child.layout(child_constraint, ctx); fixed_space += size.along(self.axis); cross_axis_max = cross_axis_max.max(size.along(cross_axis)); } } let mut size = if total_flex > 0.0 { if constraint.max_along(self.axis).is_infinite() { panic!("flex contains flexible children but has an infinite constraint along the flex axis"); } let mut remaining_space = constraint.max_along(self.axis) - fixed_space; let mut remaining_flex = total_flex; for child in &mut self.children { if let Some(flex) = Self::child_flex(&child) { let child_max = if remaining_flex == 0.0 { remaining_space } else { let space_per_flex = remaining_space / remaining_flex; space_per_flex * flex }; let child_constraint = match self.axis { Axis::Horizontal => SizeConstraint::new( vec2f(0.0, constraint.min.y()), vec2f(child_max, constraint.max.y()), ), Axis::Vertical => SizeConstraint::new( vec2f(constraint.min.x(), 0.0), vec2f(constraint.max.x(), child_max), ), }; let child_size = child.layout(child_constraint, ctx); remaining_space -= child_size.along(self.axis); remaining_flex -= flex; cross_axis_max = cross_axis_max.max(child_size.along(cross_axis)); } } match self.axis { Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max), Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space), } } else { match self.axis { Axis::Horizontal => vec2f(fixed_space, cross_axis_max), Axis::Vertical => vec2f(cross_axis_max, fixed_space), } }; if constraint.min.x().is_finite() { size.set_x(size.x().max(constraint.min.x())); } if constraint.min.y().is_finite() { size.set_y(size.y().max(constraint.min.y())); } (size, ()) } fn after_layout( &mut self, _: Vector2F, _: &mut Self::LayoutState, ctx: &mut AfterLayoutContext, ) { for child in &mut self.children { child.after_layout(ctx); } } fn paint( &mut self, bounds: RectF, _: &mut Self::LayoutState, ctx: &mut PaintContext, ) -> Self::PaintState { let mut child_origin = bounds.origin(); for child in &mut self.children { child.paint(child_origin, ctx); match self.axis { Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0), Axis::Vertical => child_origin += vec2f(0.0, child.size().y()), } } } fn dispatch_event( &mut self, event: &Event, _: RectF, _: &mut Self::LayoutState, _: &mut Self::PaintState, ctx: &mut EventContext, ) -> bool { let mut handled = false; for child in &mut self.children { handled = child.dispatch_event(event, ctx) || handled; } handled } fn debug( &self, bounds: RectF, _: &Self::LayoutState, _: &Self::PaintState, ctx: &DebugContext, ) -> json::Value { json!({ "type": "Flex", "bounds": bounds.to_json(), "axis": self.axis.to_json(), "children": self.children.iter().map(|child| child.debug(ctx)).collect::>() }) } } struct FlexParentData { flex: f32, } pub struct Expanded { metadata: FlexParentData, child: ElementBox, } impl Expanded { pub fn new(flex: f32, child: ElementBox) -> Self { Expanded { metadata: FlexParentData { flex }, child, } } } impl Element for Expanded { type LayoutState = (); type PaintState = (); fn layout( &mut self, constraint: SizeConstraint, ctx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let size = self.child.layout(constraint, ctx); (size, ()) } fn after_layout( &mut self, _: Vector2F, _: &mut Self::LayoutState, ctx: &mut AfterLayoutContext, ) { self.child.after_layout(ctx); } fn paint( &mut self, bounds: RectF, _: &mut Self::LayoutState, ctx: &mut PaintContext, ) -> Self::PaintState { self.child.paint(bounds.origin(), ctx) } fn dispatch_event( &mut self, event: &Event, _: RectF, _: &mut Self::LayoutState, _: &mut Self::PaintState, ctx: &mut EventContext, ) -> bool { self.child.dispatch_event(event, ctx) } fn metadata(&self) -> Option<&dyn Any> { Some(&self.metadata) } fn debug( &self, _: RectF, _: &Self::LayoutState, _: &Self::PaintState, ctx: &DebugContext, ) -> Value { json!({ "type": "Expanded", "flex": self.metadata.flex, "child": self.child.debug(ctx) }) } }