use cranpose_ui_layout::{
Axis, Constraints, MeasurePolicy, MeasureResult, MeasureScope, ParentData, Placement,
bias_offset,
};
use smallvec::SmallVec;
use crate::layout::core::{
Alignment, Arrangement, HorizontalAlignment, LinearArrangement, Measurable, VerticalAlignment,
};
#[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,
scope: &dyn MeasureScope,
measurables: &[Box<dyn Measurable>],
constraints: Constraints,
) -> MeasureResult {
let mut placements = Vec::new();
let size = self.measure_into(scope, measurables, constraints, &mut placements);
MeasureResult::new(size, placements)
}
fn measure_into(
&self,
scope: &dyn MeasureScope,
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 = alignment
.horizontal
.align(width, child_width, scope.density());
let y = alignment
.vertical
.align(height, child_height, scope.density());
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,
pub density: f32,
}
pub(crate) struct WeightShares {
density: f32,
unit_px: f32,
remainder_px: f32,
}
impl WeightShares {
pub(crate) fn new(
remaining: f32,
weights: impl Iterator<Item = f32> + Clone,
density: f32,
) -> Self {
let density = if density > 0.0 && density.is_finite() {
density
} else {
1.0
};
let total_weight: f32 = weights.clone().sum();
let remaining_px = (remaining * density).round();
let unit_px = if total_weight > 0.0 {
remaining_px / total_weight
} else {
0.0
};
let rounded_px: f32 = weights.map(|weight| (unit_px * weight + 0.5).floor()).sum();
Self {
density,
unit_px,
remainder_px: remaining_px - rounded_px,
}
}
pub(crate) fn next_share(&mut self, weight: f32) -> f32 {
let step = if self.remainder_px > 0.0 {
1.0
} else if self.remainder_px < 0.0 {
-1.0
} else {
0.0
};
self.remainder_px -= step;
((self.unit_px * weight + 0.5).floor() + step).max(0.0) / self.density
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CrossAxisAlignment {
Start,
Center,
End,
}
impl CrossAxisAlignment {
fn align(&self, available: f32, child: f32, density: f32) -> f32 {
let bias = match self {
CrossAxisAlignment::Start => -1.0,
CrossAxisAlignment::Center => 0.0,
CrossAxisAlignment::End => 1.0,
};
bias_offset(bias, available, child, density)
}
}
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,
density: f32,
) -> Self {
Self {
axis,
main_axis_arrangement,
cross_axis_alignment,
density,
}
}
pub fn row(
horizontal_arrangement: LinearArrangement,
vertical_alignment: VerticalAlignment,
density: f32,
) -> Self {
Self::new(
Axis::Horizontal,
horizontal_arrangement,
vertical_alignment.into(),
density,
)
}
pub fn column(
vertical_arrangement: LinearArrangement,
horizontal_alignment: HorizontalAlignment,
density: f32,
) -> Self {
Self::new(
Axis::Vertical,
vertical_arrangement,
horizontal_alignment.into(),
density,
)
}
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 measure_fixed_children(
&self,
measurables: &[Box<dyn Measurable>],
fixed_children: &[usize],
(max_main, max_cross): (f32, f32),
spacing: f32,
placeables: &mut [Option<cranpose_ui_layout::Placeable>],
) -> (f32, f32) {
let main_axis_bounded = max_main.is_finite();
let mut fixed_space = 0.0_f32;
let mut max_cross_size = 0.0_f32;
for &idx in fixed_children {
let available_main = if main_axis_bounded {
(max_main - fixed_space).max(0.0)
} else {
max_main
};
let placeable = measurables[idx].measure(self.make_constraints(
0.0,
available_main,
0.0,
max_cross,
));
let main_size = self.get_main_axis_size(placeable.width(), placeable.height());
let spacing_after = if main_axis_bounded {
spacing.min(available_main - main_size).max(0.0)
} else {
spacing
};
fixed_space += main_size + spacing_after;
max_cross_size =
max_cross_size.max(self.get_cross_axis_size(placeable.width(), placeable.height()));
placeables[idx] = Some(placeable);
}
(fixed_space, max_cross_size)
}
fn get_spacing(&self) -> f32 {
self.main_axis_arrangement.spacing(self.density)
}
}
impl MeasurePolicy for FlexMeasurePolicy {
fn measure(
&self,
scope: &dyn MeasureScope,
measurables: &[Box<dyn Measurable>],
constraints: Constraints,
) -> MeasureResult {
let mut placements = Vec::new();
let size = self.measure_into(scope, measurables, constraints, &mut placements);
MeasureResult::new(size, placements)
}
fn measure_into(
&self,
_scope: &dyn MeasureScope,
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 (fixed_space, mut max_cross_size) = self.measure_fixed_children(
measurables,
&fixed_children,
(max_main, max_cross),
spacing,
&mut placeables,
);
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 weighted_spacing = spacing * (weighted_children.len() - 1) as f32;
let remaining_main = (max_main - fixed_space - weighted_spacing).max(0.0);
let mut shares = WeightShares::new(
remaining_main,
weighted_children.iter().map(|(_, data)| data.weight),
self.density,
);
for &(idx, parent_data) in &weighted_children {
let measurable = &measurables[idx];
let allocated = shares.next_share(parent_data.weight);
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(
self.density,
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_or(self.cross_axis_alignment, Into::into),
Axis::Vertical => parent_data[idx]
.column_alignment
.map_or(self.cross_axis_alignment, Into::into),
};
let cross_pos = cross_axis_alignment.align(container_cross, child_cross, self.density);
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,
scope: &dyn MeasureScope,
measurables: &[Box<dyn Measurable>],
constraints: Constraints,
) -> MeasureResult {
let mut placements = Vec::new();
let size = self.measure_into(scope, measurables, constraints, &mut placements);
MeasureResult::new(size, placements)
}
fn measure_into(
&self,
_scope: &dyn MeasureScope,
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,
scope: &dyn MeasureScope,
_measurables: &[Box<dyn Measurable>],
constraints: Constraints,
) -> MeasureResult {
let mut placements = Vec::new();
let size = self.measure_into(scope, &[], constraints, &mut placements);
MeasureResult::new(size, placements)
}
fn measure_into(
&self,
_scope: &dyn MeasureScope,
_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,
scope: &dyn MeasureScope,
_measurables: &[Box<dyn Measurable>],
constraints: Constraints,
) -> MeasureResult {
let mut placements = Vec::new();
let size = self.measure_into(scope, &[], constraints, &mut placements);
MeasureResult::new(size, placements)
}
fn measure_into(
&self,
_scope: &dyn MeasureScope,
_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;