use crate::layout::core::{
Alignment, Arrangement, HorizontalAlignment, LinearArrangement, Measurable, VerticalAlignment,
};
use cranpose_ui_layout::{
Axis, Constraints, FlexParentData, MeasurePolicy, MeasureResult, Placement,
};
use smallvec::SmallVec;
#[derive(Clone, Debug, PartialEq)]
pub struct BoxMeasurePolicy {
pub content_alignment: Alignment,
pub propagate_min_constraints: bool,
}
impl BoxMeasurePolicy {
pub fn new(content_alignment: Alignment, propagate_min_constraints: bool) -> Self {
Self {
content_alignment,
propagate_min_constraints,
}
}
}
impl MeasurePolicy for BoxMeasurePolicy {
fn measure(
&self,
measurables: &[Box<dyn Measurable>],
constraints: Constraints,
) -> MeasureResult {
let child_constraints = if self.propagate_min_constraints {
constraints
} else {
Constraints {
min_width: 0.0,
max_width: constraints.max_width,
min_height: 0.0,
max_height: constraints.max_height,
}
};
let mut max_width = 0.0_f32;
let mut max_height = 0.0_f32;
let mut placeables = Vec::with_capacity(measurables.len());
for measurable in measurables {
let placeable = measurable.measure(child_constraints);
max_width = max_width.max(placeable.width());
max_height = max_height.max(placeable.height());
placeables.push(placeable);
}
let width = max_width.clamp(constraints.min_width, constraints.max_width);
let height = max_height.clamp(constraints.min_height, constraints.max_height);
let mut placements = Vec::with_capacity(placeables.len());
for placeable in placeables {
let child_width = placeable.width();
let child_height = placeable.height();
let x = match self.content_alignment.horizontal {
HorizontalAlignment::Start => 0.0,
HorizontalAlignment::CenterHorizontally => ((width - child_width) / 2.0).max(0.0),
HorizontalAlignment::End => (width - child_width).max(0.0),
};
let y = match self.content_alignment.vertical {
VerticalAlignment::Top => 0.0,
VerticalAlignment::CenterVertically => ((height - child_height) / 2.0).max(0.0),
VerticalAlignment::Bottom => (height - child_height).max(0.0),
};
placeable.place(x, y);
placements.push(Placement::new(placeable.node_id(), x, y, 0));
}
MeasureResult::new(crate::modifier::Size { width, height }, placements)
}
fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
measurables
.iter()
.map(|m| m.min_intrinsic_width(height))
.fold(0.0, f32::max)
}
fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
measurables
.iter()
.map(|m| m.max_intrinsic_width(height))
.fold(0.0, f32::max)
}
fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
measurables
.iter()
.map(|m| m.min_intrinsic_height(width))
.fold(0.0, f32::max)
}
fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
measurables
.iter()
.map(|m| m.max_intrinsic_height(width))
.fold(0.0, f32::max)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct FlexMeasurePolicy {
pub axis: Axis,
pub main_axis_arrangement: LinearArrangement,
pub cross_axis_alignment: CrossAxisAlignment,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CrossAxisAlignment {
Start,
Center,
End,
}
impl CrossAxisAlignment {
fn align(&self, available: f32, child: f32) -> f32 {
match self {
CrossAxisAlignment::Start => 0.0,
CrossAxisAlignment::Center => ((available - child) / 2.0).max(0.0),
CrossAxisAlignment::End => (available - child).max(0.0),
}
}
}
impl From<HorizontalAlignment> for CrossAxisAlignment {
fn from(alignment: HorizontalAlignment) -> Self {
match alignment {
HorizontalAlignment::Start => CrossAxisAlignment::Start,
HorizontalAlignment::CenterHorizontally => CrossAxisAlignment::Center,
HorizontalAlignment::End => CrossAxisAlignment::End,
}
}
}
impl From<VerticalAlignment> for CrossAxisAlignment {
fn from(alignment: VerticalAlignment) -> Self {
match alignment {
VerticalAlignment::Top => CrossAxisAlignment::Start,
VerticalAlignment::CenterVertically => CrossAxisAlignment::Center,
VerticalAlignment::Bottom => CrossAxisAlignment::End,
}
}
}
impl FlexMeasurePolicy {
pub fn new(
axis: Axis,
main_axis_arrangement: LinearArrangement,
cross_axis_alignment: CrossAxisAlignment,
) -> Self {
Self {
axis,
main_axis_arrangement,
cross_axis_alignment,
}
}
pub fn row(
horizontal_arrangement: LinearArrangement,
vertical_alignment: VerticalAlignment,
) -> Self {
Self::new(
Axis::Horizontal,
horizontal_arrangement,
vertical_alignment.into(),
)
}
pub fn column(
vertical_arrangement: LinearArrangement,
horizontal_alignment: HorizontalAlignment,
) -> Self {
Self::new(
Axis::Vertical,
vertical_arrangement,
horizontal_alignment.into(),
)
}
fn get_axis_constraints(&self, constraints: Constraints) -> (f32, f32, f32, f32) {
match self.axis {
Axis::Horizontal => (
constraints.min_width,
constraints.max_width,
constraints.min_height,
constraints.max_height,
),
Axis::Vertical => (
constraints.min_height,
constraints.max_height,
constraints.min_width,
constraints.max_width,
),
}
}
fn make_constraints(
&self,
min_main: f32,
max_main: f32,
min_cross: f32,
max_cross: f32,
) -> Constraints {
match self.axis {
Axis::Horizontal => Constraints {
min_width: min_main,
max_width: max_main,
min_height: min_cross,
max_height: max_cross,
},
Axis::Vertical => Constraints {
min_width: min_cross,
max_width: max_cross,
min_height: min_main,
max_height: max_main,
},
}
}
fn get_main_axis_size(&self, width: f32, height: f32) -> f32 {
match self.axis {
Axis::Horizontal => width,
Axis::Vertical => height,
}
}
fn get_cross_axis_size(&self, width: f32, height: f32) -> f32 {
match self.axis {
Axis::Horizontal => height,
Axis::Vertical => width,
}
}
fn get_spacing(&self) -> f32 {
match self.main_axis_arrangement {
LinearArrangement::SpacedBy(value) => value.max(0.0),
_ => 0.0,
}
}
}
impl MeasurePolicy for FlexMeasurePolicy {
fn measure(
&self,
measurables: &[Box<dyn Measurable>],
constraints: Constraints,
) -> MeasureResult {
if measurables.is_empty() {
let (width, height) = constraints.constrain(0.0, 0.0);
return MeasureResult::new(crate::modifier::Size { width, height }, vec![]);
}
let (min_main, max_main, min_cross, max_cross) = self.get_axis_constraints(constraints);
let main_axis_bounded = max_main.is_finite();
let spacing = self.get_spacing();
let mut fixed_children: SmallVec<[usize; 8]> = SmallVec::new();
let mut weighted_children: SmallVec<[(usize, FlexParentData); 8]> = SmallVec::new();
for (idx, measurable) in measurables.iter().enumerate() {
let parent_data = measurable.flex_parent_data().unwrap_or_default();
if parent_data.has_weight() {
weighted_children.push((idx, parent_data));
} else {
fixed_children.push(idx);
}
}
let child_constraints = self.make_constraints(0.0, max_main, 0.0, max_cross);
let mut placeables: SmallVec<[Option<cranpose_ui_layout::Placeable>; 8]> = SmallVec::new();
placeables.resize_with(measurables.len(), || None);
let mut fixed_main_size = 0.0_f32;
let mut max_cross_size = 0.0_f32;
for &idx in &fixed_children {
let measurable = &measurables[idx];
let placeable = measurable.measure(child_constraints);
let main_size = self.get_main_axis_size(placeable.width(), placeable.height());
let cross_size = self.get_cross_axis_size(placeable.width(), placeable.height());
fixed_main_size += main_size;
max_cross_size = max_cross_size.max(cross_size);
placeables[idx] = Some(placeable);
}
let num_children = measurables.len();
let total_spacing = if num_children > 1 {
spacing * (num_children - 1) as f32
} else {
0.0
};
if !weighted_children.is_empty() {
if main_axis_bounded {
let used_main = fixed_main_size + total_spacing;
let remaining_main = (max_main - used_main).max(0.0);
let total_weight: f32 = weighted_children.iter().map(|(_, data)| data.weight).sum();
for &(idx, parent_data) in &weighted_children {
let measurable = &measurables[idx];
let allocated = if total_weight > 0.0 {
remaining_main * (parent_data.weight / total_weight)
} else {
0.0
};
let weighted_constraints = if parent_data.fill {
self.make_constraints(allocated, allocated, 0.0, max_cross)
} else {
self.make_constraints(0.0, allocated, 0.0, max_cross)
};
let placeable = measurable.measure(weighted_constraints);
let cross_size =
self.get_cross_axis_size(placeable.width(), placeable.height());
max_cross_size = max_cross_size.max(cross_size);
placeables[idx] = Some(placeable);
}
} else {
for &(idx, _) in &weighted_children {
let measurable = &measurables[idx];
let placeable = measurable.measure(child_constraints);
let cross_size =
self.get_cross_axis_size(placeable.width(), placeable.height());
max_cross_size = max_cross_size.max(cross_size);
placeables[idx] = Some(placeable);
}
}
}
let placeables: SmallVec<[cranpose_ui_layout::Placeable; 8]> = placeables
.into_iter()
.map(|p| p.expect("placeable missing"))
.collect();
let total_main: f32 = placeables
.iter()
.map(|p| self.get_main_axis_size(p.width(), p.height()))
.sum::<f32>()
+ total_spacing;
let container_main = total_main.clamp(min_main, max_main);
let container_cross = max_cross_size.clamp(min_cross, max_cross);
let child_main_sizes: SmallVec<[f32; 8]> = placeables
.iter()
.map(|p| self.get_main_axis_size(p.width(), p.height()))
.collect();
let mut main_positions: SmallVec<[f32; 8]> =
SmallVec::with_capacity(child_main_sizes.len());
main_positions.resize(child_main_sizes.len(), 0.0);
let arrangement = if total_main > container_main {
LinearArrangement::Start
} else {
self.main_axis_arrangement
};
arrangement.arrange(container_main, &child_main_sizes, &mut main_positions);
let mut placements: SmallVec<[Placement; 8]> = SmallVec::with_capacity(placeables.len());
for (placeable, main_pos) in placeables.into_iter().zip(main_positions) {
let child_cross = self.get_cross_axis_size(placeable.width(), placeable.height());
let cross_pos = self
.cross_axis_alignment
.align(container_cross, child_cross);
let (x, y) = match self.axis {
Axis::Horizontal => (main_pos, cross_pos),
Axis::Vertical => (cross_pos, main_pos),
};
placeable.place(x, y);
placements.push(Placement::new(placeable.node_id(), x, y, 0));
}
let (width, height) = match self.axis {
Axis::Horizontal => (container_main, container_cross),
Axis::Vertical => (container_cross, container_main),
};
MeasureResult::new(
crate::modifier::Size { width, height },
placements.into_vec(),
)
}
fn min_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
let spacing = self.get_spacing();
let total_spacing = if measurables.len() > 1 {
spacing * (measurables.len() - 1) as f32
} else {
0.0
};
match self.axis {
Axis::Horizontal => {
measurables
.iter()
.map(|m| m.min_intrinsic_width(height))
.sum::<f32>()
+ total_spacing
}
Axis::Vertical => {
measurables
.iter()
.map(|m| m.min_intrinsic_width(height))
.fold(0.0, f32::max)
}
}
}
fn max_intrinsic_width(&self, measurables: &[Box<dyn Measurable>], height: f32) -> f32 {
let spacing = self.get_spacing();
let total_spacing = if measurables.len() > 1 {
spacing * (measurables.len() - 1) as f32
} else {
0.0
};
match self.axis {
Axis::Horizontal => {
measurables
.iter()
.map(|m| m.max_intrinsic_width(height))
.sum::<f32>()
+ total_spacing
}
Axis::Vertical => {
measurables
.iter()
.map(|m| m.max_intrinsic_width(height))
.fold(0.0, f32::max)
}
}
}
fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
let spacing = self.get_spacing();
let total_spacing = if measurables.len() > 1 {
spacing * (measurables.len() - 1) as f32
} else {
0.0
};
match self.axis {
Axis::Horizontal => {
measurables
.iter()
.map(|m| m.min_intrinsic_height(width))
.fold(0.0, f32::max)
}
Axis::Vertical => {
measurables
.iter()
.map(|m| m.min_intrinsic_height(width))
.sum::<f32>()
+ total_spacing
}
}
}
fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
let spacing = self.get_spacing();
let total_spacing = if measurables.len() > 1 {
spacing * (measurables.len() - 1) as f32
} else {
0.0
};
match self.axis {
Axis::Horizontal => {
measurables
.iter()
.map(|m| m.max_intrinsic_height(width))
.fold(0.0, f32::max)
}
Axis::Vertical => {
measurables
.iter()
.map(|m| m.max_intrinsic_height(width))
.sum::<f32>()
+ total_spacing
}
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct LeafMeasurePolicy {
pub intrinsic_size: crate::modifier::Size,
}
impl LeafMeasurePolicy {
pub fn new(intrinsic_size: crate::modifier::Size) -> Self {
Self { intrinsic_size }
}
}
impl MeasurePolicy for LeafMeasurePolicy {
fn measure(
&self,
_measurables: &[Box<dyn Measurable>],
constraints: Constraints,
) -> MeasureResult {
let (width, height) =
constraints.constrain(self.intrinsic_size.width, self.intrinsic_size.height);
MeasureResult::new(
crate::modifier::Size { width, height },
vec![], )
}
fn min_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
self.intrinsic_size.width
}
fn max_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
self.intrinsic_size.width
}
fn min_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
self.intrinsic_size.height
}
fn max_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
self.intrinsic_size.height
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct EmptyMeasurePolicy;
impl EmptyMeasurePolicy {
pub fn new() -> Self {
Self
}
}
impl Default for EmptyMeasurePolicy {
fn default() -> Self {
Self::new()
}
}
impl MeasurePolicy for EmptyMeasurePolicy {
fn measure(
&self,
_measurables: &[Box<dyn Measurable>],
constraints: Constraints,
) -> MeasureResult {
let (width, height) = constraints.constrain(0.0, 0.0);
MeasureResult::new(
crate::modifier::Size { width, height },
vec![], )
}
fn min_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
0.0
}
fn max_intrinsic_width(&self, _measurables: &[Box<dyn Measurable>], _height: f32) -> f32 {
0.0
}
fn min_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
0.0
}
fn max_intrinsic_height(&self, _measurables: &[Box<dyn Measurable>], _width: f32) -> f32 {
0.0
}
}
#[cfg(test)]
#[path = "tests/policies_tests.rs"]
mod tests;