260 lines
7.1 KiB
Rust
260 lines
7.1 KiB
Rust
use std::any::Any;
|
|
|
|
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<ElementBox>,
|
|
}
|
|
|
|
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<f32> {
|
|
child
|
|
.metadata()
|
|
.and_then(|d| d.downcast_ref::<FlexParentData>())
|
|
.map(|data| data.flex)
|
|
}
|
|
}
|
|
|
|
impl Extend<ElementBox> for Flex {
|
|
fn extend<T: IntoIterator<Item = ElementBox>>(&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 =
|
|
SizeConstraint::strict_along(cross_axis, constraint.max_along(cross_axis));
|
|
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 {
|
|
let space_per_flex = remaining_space / remaining_flex;
|
|
if let Some(flex) = Self::child_flex(&child) {
|
|
let child_max = space_per_flex * flex;
|
|
let child_constraint = match self.axis {
|
|
Axis::Horizontal => SizeConstraint::new(
|
|
vec2f(0.0, constraint.max.y()),
|
|
vec2f(child_max, constraint.max.y()),
|
|
),
|
|
Axis::Vertical => SizeConstraint::new(
|
|
vec2f(constraint.max.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::<Vec<json::Value>>()
|
|
})
|
|
}
|
|
}
|
|
|
|
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)
|
|
})
|
|
}
|
|
}
|