use cranpose_ui_layout::{
Axis, Constraints, IntrinsicSize, MeasurePolicy, MeasureResult, MeasureScope, Placement,
};
use smallvec::SmallVec;
use crate::layout::core::Measurable;
#[derive(Clone, Debug, PartialEq)]
pub struct FlowMeasurePolicy {
pub axis: Axis,
pub main_axis_spacing: f32,
pub cross_axis_spacing: f32,
pub layout_direction: crate::LayoutDirection,
}
impl FlowMeasurePolicy {
pub fn new(axis: Axis, main_axis_spacing: f32, cross_axis_spacing: f32) -> Self {
Self {
axis,
main_axis_spacing: main_axis_spacing.max(0.0),
cross_axis_spacing: cross_axis_spacing.max(0.0),
layout_direction: crate::LayoutDirection::Ltr,
}
}
fn along(&self, width: f32, height: f32) -> (f32, f32) {
match self.axis {
Axis::Horizontal => (width, height),
Axis::Vertical => (height, width),
}
}
fn main_intrinsic(&self, measurable: &dyn Measurable, cross: f32, size: IntrinsicSize) -> f32 {
match (self.axis, size) {
(Axis::Horizontal, IntrinsicSize::Min) => measurable.min_intrinsic_width(cross),
(Axis::Horizontal, IntrinsicSize::Max) => measurable.max_intrinsic_width(cross),
(Axis::Vertical, IntrinsicSize::Min) => measurable.min_intrinsic_height(cross),
(Axis::Vertical, IntrinsicSize::Max) => measurable.max_intrinsic_height(cross),
}
}
fn cross_intrinsic(&self, measurable: &dyn Measurable, main: f32, size: IntrinsicSize) -> f32 {
match (self.axis, size) {
(Axis::Horizontal, IntrinsicSize::Min) => measurable.min_intrinsic_height(main),
(Axis::Horizontal, IntrinsicSize::Max) => measurable.max_intrinsic_height(main),
(Axis::Vertical, IntrinsicSize::Min) => measurable.min_intrinsic_width(main),
(Axis::Vertical, IntrinsicSize::Max) => measurable.max_intrinsic_width(main),
}
}
fn wrapped_cross(
&self,
measurables: &[Box<dyn Measurable>],
available_main: f32,
size: IntrinsicSize,
) -> f32 {
let mut cursor = 0.0_f32;
let mut line_start = 0.0_f32;
let mut line_cross = 0.0_f32;
for measurable in measurables {
let child_main =
self.main_intrinsic(measurable.as_ref(), f32::INFINITY, IntrinsicSize::Max);
let child_cross = self.cross_intrinsic(measurable.as_ref(), child_main, size);
if cursor > 0.0 && cursor + self.main_axis_spacing + child_main > available_main {
line_start += line_cross + self.cross_axis_spacing;
cursor = 0.0;
line_cross = 0.0;
}
cursor += if cursor > 0.0 {
self.main_axis_spacing + child_main
} else {
child_main
};
line_cross = line_cross.max(child_cross);
}
line_start + line_cross
}
fn unwrapped_main(&self, measurables: &[Box<dyn Measurable>], cross: f32) -> f32 {
let total_spacing = self.main_axis_spacing * measurables.len().saturating_sub(1) as f32;
measurables
.iter()
.map(|m| self.main_intrinsic(m.as_ref(), cross, IntrinsicSize::Max))
.sum::<f32>()
+ total_spacing
}
fn narrowest_main(&self, measurables: &[Box<dyn Measurable>], cross: f32) -> f32 {
measurables
.iter()
.map(|m| self.main_intrinsic(m.as_ref(), cross, IntrinsicSize::Min))
.fold(0.0, f32::max)
}
}
impl MeasurePolicy for FlowMeasurePolicy {
fn measure(
&self,
scope: &dyn MeasureScope,
measurables: &[Box<dyn Measurable>],
constraints: Constraints,
) -> MeasureResult {
let mut placements = Vec::new();
let measurement = self.measure_into(scope, measurables, constraints, &mut placements);
MeasureResult::new(measurement, placements)
}
fn measure_into(
&self,
_scope: &dyn MeasureScope,
measurables: &[Box<dyn Measurable>],
constraints: Constraints,
placements: &mut Vec<Placement>,
) -> cranpose_ui_layout::Measurement {
placements.clear();
if measurables.is_empty() {
let (width, height) = constraints.constrain(0.0, 0.0);
return crate::modifier::Size { width, height }.into();
}
let child_constraints = Constraints {
min_width: 0.0,
max_width: constraints.max_width,
min_height: 0.0,
max_height: constraints.max_height,
};
let placeables: SmallVec<[cranpose_ui_layout::Placeable; 8]> = measurables
.iter()
.map(|measurable| measurable.measure(child_constraints))
.collect();
let (max_main, _) = self.along(constraints.max_width, constraints.max_height);
let mut cursor = 0.0_f32;
let mut line_start = 0.0_f32;
let mut line_cross = 0.0_f32;
let mut longest_line = 0.0_f32;
placements.reserve(placeables.len());
for placeable in &placeables {
let (child_main, child_cross) = self.along(placeable.width(), placeable.height());
if cursor > 0.0 && cursor + self.main_axis_spacing + child_main > max_main {
longest_line = longest_line.max(cursor);
line_start += line_cross + self.cross_axis_spacing;
cursor = 0.0;
line_cross = 0.0;
}
let main = if cursor > 0.0 {
cursor + self.main_axis_spacing
} else {
0.0
};
let (x, y) = self.along(main, line_start);
if !self.layout_direction.is_rtl() {
placeable.place(x, y);
}
placements.push(Placement::new(placeable.node_id(), x, y, 0.0));
cursor = main + child_main;
line_cross = line_cross.max(child_cross);
}
longest_line = longest_line.max(cursor);
let (width, height) = self.along(longest_line, line_start + line_cross);
let width = width.clamp(constraints.min_width, constraints.max_width);
let height = height.clamp(constraints.min_height, constraints.max_height);
if self.layout_direction.is_rtl() {
for (placement, placeable) in placements.iter_mut().zip(&placeables) {
placement.x = self
.layout_direction
.place_x(placement.x, width, placeable.width());
placeable.place(placement.x, placement.y);
}
}
crate::modifier::Size { width, height }.into()
}
fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
match self.axis {
Axis::Horizontal => self.narrowest_main(measurables, height),
Axis::Vertical => self.wrapped_cross(measurables, height, IntrinsicSize::Min),
}
}
fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
match self.axis {
Axis::Horizontal => self.unwrapped_main(measurables, height),
Axis::Vertical => self.wrapped_cross(measurables, height, IntrinsicSize::Max),
}
}
fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
match self.axis {
Axis::Horizontal => self.wrapped_cross(measurables, width, IntrinsicSize::Min),
Axis::Vertical => self.narrowest_main(measurables, width),
}
}
fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
match self.axis {
Axis::Horizontal => self.wrapped_cross(measurables, width, IntrinsicSize::Max),
Axis::Vertical => self.unwrapped_main(measurables, width),
}
}
}