use crate::layout::core::{
Alignment, Arrangement, HorizontalAlignment, LinearArrangement, Measurable, VerticalAlignment,
};
use cranpose_ui_layout::{Axis, Constraints, MeasurePolicy, MeasureResult, ParentData, 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 mut placements = Vec::new();
let size = self.measure_into(measurables, constraints, &mut placements);
MeasureResult::new(size, placements)
}
fn measure_into(
&self,
measurables: &[Box<dyn Measurable>],
constraints: Constraints,
placements: &mut Vec<Placement>,
) -> crate::modifier::Size {
placements.clear();
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: SmallVec<[(cranpose_ui_layout::Placeable, Alignment); 8]> =
SmallVec::new();
for measurable in measurables {
let placeable = measurable.measure(child_constraints);
max_width = max_width.max(placeable.width());
max_height = max_height.max(placeable.height());
let alignment = measurable
.parent_data()
.box_alignment
.unwrap_or(self.content_alignment);
placeables.push((placeable, alignment));
}
let width = max_width.clamp(constraints.min_width, constraints.max_width);
let height = max_height.clamp(constraints.min_height, constraints.max_height);
placements.reserve(placeables.len());
for (placeable, alignment) in placeables {
let child_width = placeable.width();
let child_height = placeable.height();
let x = match 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 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));
}
crate::modifier::Size { width, height }
}
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 {
let mut placements = Vec::new();
let size = self.measure_into(measurables, constraints, &mut placements);
MeasureResult::new(size, placements)
}
fn measure_into(
&self,
measurables: &[Box<dyn Measurable>],
constraints: Constraints,
placements: &mut Vec<Placement>,
) -> crate::modifier::Size {
placements.clear();
if measurables.is_empty() {
let (width, height) = constraints.constrain(0.0, 0.0);
return crate::modifier::Size { width, height };
}
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 parent_data: SmallVec<[ParentData; 8]> = measurables
.iter()
.map(|child| child.parent_data())
.collect();
let mut weighted_children: SmallVec<[(usize, ParentData); 8]> = SmallVec::new();
for (idx, data) in parent_data.iter().copied().enumerate() {
if data.has_weight() {
weighted_children.push((idx, 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()
.enumerate()
.map(|(idx, placeable)| {
placeable.unwrap_or_else(|| measurables[idx].measure(child_constraints))
})
.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
&& !matches!(self.main_axis_arrangement, LinearArrangement::SpacedBy(_))
{
LinearArrangement::Start
} else {
self.main_axis_arrangement
};
arrangement.arrange(container_main, &child_main_sizes, &mut main_positions);
placements.reserve(placeables.len());
for (idx, (placeable, main_pos)) in placeables.into_iter().zip(main_positions).enumerate() {
let child_cross = self.get_cross_axis_size(placeable.width(), placeable.height());
let cross_axis_alignment = match self.axis {
Axis::Horizontal => parent_data[idx]
.row_alignment
.map(Into::into)
.unwrap_or(self.cross_axis_alignment),
Axis::Vertical => parent_data[idx]
.column_alignment
.map(Into::into)
.unwrap_or(self.cross_axis_alignment),
};
let cross_pos = 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),
};
crate::modifier::Size { width, height }
}
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 FlowRowMeasurePolicy {
pub main_axis_spacing: f32,
pub cross_axis_spacing: f32,
}
impl FlowRowMeasurePolicy {
pub fn new(main_axis_spacing: f32, cross_axis_spacing: f32) -> Self {
Self {
main_axis_spacing: main_axis_spacing.max(0.0),
cross_axis_spacing: cross_axis_spacing.max(0.0),
}
}
fn wrapped_intrinsic_height(
&self,
measurables: &[Box<dyn Measurable>],
available_width: f32,
use_min_height: bool,
) -> f32 {
let mut cursor_x = 0.0_f32;
let mut line_top = 0.0_f32;
let mut line_height = 0.0_f32;
for measurable in measurables {
let child_width = measurable.max_intrinsic_width(f32::INFINITY);
let child_height = if use_min_height {
measurable.min_intrinsic_height(child_width)
} else {
measurable.max_intrinsic_height(child_width)
};
if cursor_x > 0.0 && cursor_x + self.main_axis_spacing + child_width > available_width {
line_top += line_height + self.cross_axis_spacing;
cursor_x = 0.0;
line_height = 0.0;
}
cursor_x += if cursor_x > 0.0 {
self.main_axis_spacing + child_width
} else {
child_width
};
line_height = line_height.max(child_height);
}
line_top + line_height
}
}
impl MeasurePolicy for FlowRowMeasurePolicy {
fn measure(
&self,
measurables: &[Box<dyn Measurable>],
constraints: Constraints,
) -> MeasureResult {
let mut placements = Vec::new();
let size = self.measure_into(measurables, constraints, &mut placements);
MeasureResult::new(size, placements)
}
fn measure_into(
&self,
measurables: &[Box<dyn Measurable>],
constraints: Constraints,
placements: &mut Vec<Placement>,
) -> crate::modifier::Size {
placements.clear();
if measurables.is_empty() {
let (width, height) = constraints.constrain(0.0, 0.0);
return crate::modifier::Size { width, height };
}
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 mut cursor_x = 0.0_f32; let mut line_top = 0.0_f32; let mut line_height = 0.0_f32;
let mut max_line_width = 0.0_f32;
placements.reserve(placeables.len());
for placeable in placeables {
let child_width = placeable.width();
let child_height = placeable.height();
if cursor_x > 0.0
&& cursor_x + self.main_axis_spacing + child_width > constraints.max_width
{
max_line_width = max_line_width.max(cursor_x);
line_top += line_height + self.cross_axis_spacing;
cursor_x = 0.0;
line_height = 0.0;
}
let x = if cursor_x > 0.0 {
cursor_x + self.main_axis_spacing
} else {
0.0
};
placeable.place(x, line_top);
placements.push(Placement::new(placeable.node_id(), x, line_top, 0));
cursor_x = x + child_width;
line_height = line_height.max(child_height);
}
max_line_width = max_line_width.max(cursor_x);
let width = max_line_width.clamp(constraints.min_width, constraints.max_width);
let height = (line_top + line_height).clamp(constraints.min_height, constraints.max_height);
crate::modifier::Size { width, height }
}
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 {
let total_spacing = if measurables.len() > 1 {
self.main_axis_spacing * (measurables.len() - 1) as f32
} else {
0.0
};
measurables
.iter()
.map(|m| m.max_intrinsic_width(height))
.sum::<f32>()
+ total_spacing
}
fn min_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
self.wrapped_intrinsic_height(measurables, width, true)
}
fn max_intrinsic_height(&self, measurables: &[Box<dyn Measurable>], width: f32) -> f32 {
self.wrapped_intrinsic_height(measurables, width, false)
}
}
#[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 mut placements = Vec::new();
let size = self.measure_into(&[], constraints, &mut placements);
MeasureResult::new(size, placements)
}
fn measure_into(
&self,
_measurables: &[Box<dyn Measurable>],
constraints: Constraints,
placements: &mut Vec<Placement>,
) -> crate::modifier::Size {
placements.clear();
let (width, height) =
constraints.constrain(self.intrinsic_size.width, self.intrinsic_size.height);
crate::modifier::Size { width, height }
}
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 mut placements = Vec::new();
let size = self.measure_into(&[], constraints, &mut placements);
MeasureResult::new(size, placements)
}
fn measure_into(
&self,
_measurables: &[Box<dyn Measurable>],
constraints: Constraints,
placements: &mut Vec<Placement>,
) -> crate::modifier::Size {
placements.clear();
let (width, height) = constraints.constrain(0.0, 0.0);
crate::modifier::Size { width, height }
}
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;