use std::ops::Mul;
#[allow(unused)]
use pax_runtime_api::math::Generic;
use pax_runtime_api::math::{Point2, Space, TransformParts};
use pax_runtime_api::{Interpolatable, LocalProperty, Percent, Rotation, Window};
use crate::api::math::{Transform2, Vector2};
use crate::api::{Axis, LayoutRole, Size, Transform2D};
use crate::node_interface::NodeLocal;
use crate::ContainerFrame;
pub fn compute_tab(
layout_properties: LocalProperty<LayoutProperties>,
extra_transform: LocalProperty<Option<Transform2D>>,
container_transform_and_bounds: LocalProperty<TransformAndBounds<NodeLocal, Window>>,
) -> LocalProperty<TransformAndBounds<NodeLocal, Window>> {
let deps = [
layout_properties.untyped(),
container_transform_and_bounds.untyped(),
extra_transform.untyped(),
];
LocalProperty::computed(
move || {
let container_t_and_b = container_transform_and_bounds.get();
layout_properties.read(|layout_properties| {
let transform_and_bounds =
calculate_transform_and_bounds(&layout_properties, container_t_and_b.clone());
let extra_transform = extra_transform.get();
if let Some(transform) = extra_transform {
transform.apply(transform_and_bounds)
} else {
transform_and_bounds
}
})
},
&deps,
)
}
pub fn apply_container_frame(
container_transform_and_bounds: TransformAndBounds<NodeLocal, Window>,
container_frame: Option<ContainerFrame>,
) -> TransformAndBounds<NodeLocal, Window> {
match container_frame {
Some(frame) => TransformAndBounds {
transform: container_transform_and_bounds.transform * frame.transform,
bounds: frame.bounds,
},
None => container_transform_and_bounds,
}
}
pub fn apply_padding_frame(
container_transform_and_bounds: TransformAndBounds<NodeLocal, Window>,
padding_x: Option<Size>,
padding_y: Option<Size>,
) -> TransformAndBounds<NodeLocal, Window> {
let padding_x = padding_x
.map(|padding| {
padding
.evaluate(container_transform_and_bounds.bounds, Axis::X)
.max(0.0)
})
.unwrap_or(0.0);
let padding_y = padding_y
.map(|padding| {
padding
.evaluate(container_transform_and_bounds.bounds, Axis::Y)
.max(0.0)
})
.unwrap_or(0.0);
if padding_x == 0.0 && padding_y == 0.0 {
return container_transform_and_bounds;
}
TransformAndBounds {
transform: container_transform_and_bounds.transform
* Transform2::translate(Vector2::new(padding_x, padding_y)),
bounds: (
(container_transform_and_bounds.bounds.0 - (2.0 * padding_x)).max(0.0),
(container_transform_and_bounds.bounds.1 - (2.0 * padding_y)).max(0.0),
),
}
}
pub fn resolve_padded_autosize_axis(content_extent: f64, padding: Option<Size>) -> Option<f64> {
let Some(padding) = padding else {
return Some(content_extent.max(0.0));
};
let (pixel_component, percent_component) = match padding {
Size::Pixels(pixels) => (pixels.to_float().max(0.0), 0.0),
Size::Percent(percent) => (0.0, percent.to_float().max(0.0) / 100.0),
Size::Combined(pixels, percent) => (
pixels.to_float().max(0.0),
percent.to_float().max(0.0) / 100.0,
),
};
let denominator = 1.0 - (2.0 * percent_component);
(denominator > f64::EPSILON)
.then_some(((content_extent.max(0.0) + (2.0 * pixel_component)) / denominator).max(0.0))
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct LayoutHull {
pub min_x: f64,
pub max_x: f64,
pub min_y: f64,
pub max_y: f64,
pub valid_x: bool,
pub valid_y: bool,
}
impl Interpolatable for LayoutHull {}
const LAYOUT_MEASUREMENT_EPSILON: f64 = 1e-9;
pub(crate) fn layout_hulls_equivalent(previous: &LayoutHull, candidate: &LayoutHull) -> bool {
previous.valid_x == candidate.valid_x
&& previous.valid_y == candidate.valid_y
&& layout_measurements_equivalent(previous.min_x, candidate.min_x)
&& layout_measurements_equivalent(previous.max_x, candidate.max_x)
&& layout_measurements_equivalent(previous.min_y, candidate.min_y)
&& layout_measurements_equivalent(previous.max_y, candidate.max_y)
}
fn layout_measurements_equivalent(previous: f64, candidate: f64) -> bool {
if previous == candidate {
return true;
}
if !previous.is_finite() || !candidate.is_finite() {
return previous.to_bits() == candidate.to_bits();
}
(previous - candidate).abs() <= LAYOUT_MEASUREMENT_EPSILON
}
impl LayoutHull {
pub fn from_bounds(bounds: (f64, f64)) -> Self {
Self::from_axis_ranges(Some((0.0, bounds.0)), Some((0.0, bounds.1)))
}
pub fn from_axis_ranges(x: Option<(f64, f64)>, y: Option<(f64, f64)>) -> Self {
let (min_x, max_x, valid_x) = match x {
Some((min_x, max_x)) => {
let (min_x, max_x) = order_pair(min_x, max_x);
(min_x, max_x, true)
}
None => (0.0, 0.0, false),
};
let (min_y, max_y, valid_y) = match y {
Some((min_y, max_y)) => {
let (min_y, max_y) = order_pair(min_y, max_y);
(min_y, max_y, true)
}
None => (0.0, 0.0, false),
};
Self {
min_x,
max_x,
min_y,
max_y,
valid_x,
valid_y,
}
}
pub fn x_range(&self) -> Option<(f64, f64)> {
self.valid_x.then_some((self.min_x, self.max_x))
}
pub fn y_range(&self) -> Option<(f64, f64)> {
self.valid_y.then_some((self.min_y, self.max_y))
}
pub fn union(self, other: Self) -> Self {
Self::from_axis_ranges(
union_axis_ranges(self.x_range(), other.x_range()),
union_axis_ranges(self.y_range(), other.y_range()),
)
}
pub fn forward_extent_x(&self) -> Option<f64> {
self.x_range().map(|(_, max_x)| max_x.max(0.0))
}
pub fn forward_extent_y(&self) -> Option<f64> {
self.y_range().map(|(_, max_y)| max_y.max(0.0))
}
}
pub fn project_layout_hull<F: Space, T: Space>(
transform: Transform2<F, T>,
hull: LayoutHull,
) -> LayoutHull {
let mixes_axes = transform.m[1].abs() > f64::EPSILON || transform.m[2].abs() > f64::EPSILON;
if hull.valid_x && hull.valid_y {
let corners = [
transform * Point2::new(hull.min_x, hull.min_y),
transform * Point2::new(hull.min_x, hull.max_y),
transform * Point2::new(hull.max_x, hull.min_y),
transform * Point2::new(hull.max_x, hull.max_y),
];
let (min_x, max_x) = corners.iter().fold(
(f64::INFINITY, f64::NEG_INFINITY),
|(min_x, max_x), point| (min_x.min(point.x), max_x.max(point.x)),
);
let (min_y, max_y) = corners.iter().fold(
(f64::INFINITY, f64::NEG_INFINITY),
|(min_y, max_y), point| (min_y.min(point.y), max_y.max(point.y)),
);
return LayoutHull::from_axis_ranges(Some((min_x, max_x)), Some((min_y, max_y)));
}
if mixes_axes {
return LayoutHull::default();
}
let x = hull
.x_range()
.map(|(min_x, max_x)| project_axis_range(min_x, max_x, transform.m[0], transform.m[4]));
let y = hull
.y_range()
.map(|(min_y, max_y)| project_axis_range(min_y, max_y, transform.m[3], transform.m[5]));
LayoutHull::from_axis_ranges(x, y)
}
pub fn project_child_layout_hull_to_parent_space(
parent: TransformAndBounds<NodeLocal, Window>,
child: TransformAndBounds<NodeLocal, Window>,
child_hull: LayoutHull,
) -> LayoutHull {
let relative_transform = parent.transform.inverse() * child.transform;
let layout_transform: Transform2<NodeLocal, NodeLocal> = Transform2::translate(Vector2::new(
relative_transform.m[4],
relative_transform.m[5],
));
project_layout_hull(layout_transform, child_hull)
}
pub(crate) fn projected_child_layout_hull_property(
parent: LocalProperty<TransformAndBounds<NodeLocal, Window>>,
padding_x: LocalProperty<Option<Size>>,
padding_y: LocalProperty<Option<Size>>,
child: LocalProperty<TransformAndBounds<NodeLocal, Window>>,
child_hull: LocalProperty<LayoutHull>,
child_layout_role: LocalProperty<Option<LayoutRole>>,
name: &str,
) -> LocalProperty<LayoutHull> {
let deps = [
parent.untyped(),
padding_x.untyped(),
padding_y.untyped(),
child.untyped(),
child_hull.untyped(),
child_layout_role.untyped(),
];
LocalProperty::computed_with_cutoff_and_name(
move || {
if child_layout_role.get() == Some(LayoutRole::Breakout) {
return LayoutHull::default();
}
let parent = apply_padding_frame(parent.get(), padding_x.get(), padding_y.get());
project_child_layout_hull_to_parent_space(parent, child.get(), child_hull.get())
},
&deps,
layout_hulls_equivalent,
name,
)
}
pub fn add_symmetric_padding_to_content_layout_hull(
hull: LayoutHull,
padding_x: Option<Size>,
padding_y: Option<Size>,
) -> LayoutHull {
LayoutHull::from_axis_ranges(
hull.x_range().and_then(|(min_x, max_x)| {
resolve_symmetric_padded_axis_range(min_x, max_x, padding_x)
}),
hull.y_range().and_then(|(min_y, max_y)| {
resolve_symmetric_padded_axis_range(min_y, max_y, padding_y)
}),
)
}
fn resolve_symmetric_padded_axis_range(
min_extent: f64,
max_extent: f64,
padding: Option<Size>,
) -> Option<(f64, f64)> {
let Some(padding) = padding else {
return Some((min_extent, max_extent));
};
let outer_extent = resolve_padded_autosize_axis(max_extent.max(0.0), Some(padding))?;
let leading_padding = evaluate_padding_against_resolved_extent(padding, outer_extent)?;
Some((
min_extent + leading_padding,
outer_extent.max(max_extent + leading_padding),
))
}
fn evaluate_padding_against_resolved_extent(padding: Size, outer_extent: f64) -> Option<f64> {
let (pixel_component, percent_component) = padding_components(padding);
let padding = pixel_component + (percent_component * outer_extent.max(0.0));
padding.is_finite().then_some(padding.max(0.0))
}
fn padding_components(padding: Size) -> (f64, f64) {
match padding {
Size::Pixels(pixels) => (pixels.to_float().max(0.0), 0.0),
Size::Percent(percent) => (0.0, percent.to_float().max(0.0) / 100.0),
Size::Combined(pixels, percent) => (
pixels.to_float().max(0.0),
percent.to_float().max(0.0) / 100.0,
),
}
}
fn order_pair(a: f64, b: f64) -> (f64, f64) {
if a <= b {
(a, b)
} else {
(b, a)
}
}
fn union_axis_ranges(lhs: Option<(f64, f64)>, rhs: Option<(f64, f64)>) -> Option<(f64, f64)> {
match (lhs, rhs) {
(Some((lhs_min, lhs_max)), Some((rhs_min, rhs_max))) => {
Some((lhs_min.min(rhs_min), lhs_max.max(rhs_max)))
}
(Some(range), None) | (None, Some(range)) => Some(range),
(None, None) => None,
}
}
fn project_axis_range(min: f64, max: f64, scale: f64, translate: f64) -> (f64, f64) {
order_pair(scale * min + translate, scale * max + translate)
}
pub fn calculate_transform_and_bounds(
LayoutProperties {
width,
height,
anchor_x,
anchor_y,
x,
y,
rotate,
scale_x,
scale_y,
skew_x,
skew_y,
}: &LayoutProperties,
TransformAndBounds {
transform: container_transform,
bounds: container_bounds,
}: TransformAndBounds<NodeLocal, Window>,
) -> TransformAndBounds<NodeLocal, Window> {
let x = x.unwrap_or(Size::ZERO());
let y = y.unwrap_or(Size::ZERO());
let width = width
.map(|v| v.evaluate(container_bounds, Axis::X))
.unwrap_or(container_bounds.0);
let height = height
.map(|v| v.evaluate(container_bounds, Axis::Y))
.unwrap_or(container_bounds.1);
let origin = Vector2::new(
x.evaluate(container_bounds, Axis::X),
y.evaluate(container_bounds, Axis::Y),
);
let bounds = (width, height);
let anchor_x = anchor_x.unwrap_or_else(|| match x {
Size::Pixels(_) => Size::ZERO(),
Size::Combined(_, per) | Size::Percent(per) => {
if per.to_float() > 100.0 {
Size::default()
} else if per.to_float() < 0.0 {
Size::ZERO()
} else {
Size::Percent(per)
}
}
});
let anchor_y = anchor_y.unwrap_or_else(|| match y {
Size::Pixels(_) => Size::ZERO(),
Size::Combined(_, per) | Size::Percent(per) => {
if per.to_float() > 100.0 {
Size::default()
} else if per.to_float() < 0.0 {
Size::ZERO()
} else {
Size::Percent(per)
}
}
});
let anchor_transform = Transform2::translate(Vector2::new(
-anchor_x.evaluate(bounds, Axis::X),
-anchor_y.evaluate(bounds, Axis::Y),
));
let scale = Vector2::new(
scale_x
.as_ref()
.map(|s| s.0.to_float() / 100.0)
.unwrap_or(1.0),
scale_y
.as_ref()
.map(|s| s.0.to_float() / 100.0)
.unwrap_or(1.0),
);
let skew = Vector2::new(
skew_x.map(|s| s.get_as_radians()).unwrap_or(0.0),
skew_y.map(|s| s.get_as_radians()).unwrap_or(0.0),
);
let rotation = rotate.map(|s| s.get_as_radians()).unwrap_or(0.0);
let parts = TransformParts {
origin,
scale,
skew,
rotation,
};
let combined_transform: Transform2<NodeLocal, NodeLocal> = parts.into();
TransformAndBounds {
transform: container_transform * combined_transform * anchor_transform,
bounds,
}
}
impl<F: Space, T: Space> Interpolatable for TransformAndBounds<F, T> {
fn interpolate(&self, other: &Self, t: f64) -> Self {
TransformAndBounds {
transform: self.transform.interpolate(&other.transform, t),
bounds: (
self.bounds.0 + (other.bounds.0 - self.bounds.0) * t,
self.bounds.1 + (other.bounds.1 - self.bounds.1) * t,
),
}
}
}
#[derive(PartialEq)]
pub struct TransformAndBounds<F, T = F> {
pub transform: Transform2<F, T>,
pub bounds: (f64, f64),
}
impl<F: Space, T: Space> std::fmt::Debug for TransformAndBounds<F, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TransformAndBounds")
.field("transform", &self.transform)
.field("bounds", &self.bounds)
.finish()
}
}
impl PartialEq for TransformAndBounds<NodeLocal, Window> {
fn eq(&self, other: &Self) -> bool {
self.transform == other.transform && self.bounds == other.bounds
}
}
impl<F: Space, T: Space> Default for TransformAndBounds<F, T> {
fn default() -> Self {
Self {
transform: Default::default(),
bounds: (100.0, 100.0),
}
}
}
impl<F, T> Clone for TransformAndBounds<F, T> {
fn clone(&self) -> Self {
Self {
transform: self.transform.clone(),
bounds: self.bounds.clone(),
}
}
}
impl<F, T> Copy for TransformAndBounds<F, T> {}
impl<W1: Space, W2: Space, W3: Space> Mul<TransformAndBounds<W1, W2>>
for TransformAndBounds<W2, W3>
{
type Output = TransformAndBounds<W1, W3>;
fn mul(self, rhs: TransformAndBounds<W1, W2>) -> Self::Output {
let s_s = Transform2::scale_sep(Vector2::new(self.bounds.0, self.bounds.1));
let r_s = Transform2::scale_sep(Vector2::new(rhs.bounds.0, rhs.bounds.1));
let s_t = self.transform * s_s;
let r_t = rhs.transform * r_s;
let res = s_t * r_t * s_s.inverse() * r_s.inverse();
TransformAndBounds {
transform: res,
bounds: (self.bounds.0 * rhs.bounds.0, self.bounds.1 * rhs.bounds.1),
}
}
}
impl<F: Space, T: Space> TransformAndBounds<F, T> {
pub fn center(&self) -> Point2<T> {
let (o, u, v) = self.transform.decompose();
let u = u * self.bounds.0;
let v = v * self.bounds.1;
o + v / 2.0 + u / 2.0
}
pub fn corners(&self) -> [Point2<T>; 4] {
let (o, u, v) = self.transform.decompose();
let u = u * self.bounds.0;
let v = v * self.bounds.1;
[o, o + v, o + u + v, o + u]
}
pub fn contains_point(&self, point: Point2<T>) -> bool {
self.as_transform().contains_point(point)
}
pub fn as_pure_size(self) -> Self {
let mut parts: TransformParts = self.transform.into();
let bounds_x = std::mem::replace(&mut parts.scale.x, 1.0);
let bounds_y = std::mem::replace(&mut parts.scale.y, 1.0);
TransformAndBounds {
transform: parts.into(),
bounds: (self.bounds.0 * bounds_x, self.bounds.1 * bounds_y),
}
}
pub fn as_pure_scale(self) -> Self {
TransformAndBounds {
transform: self.transform
* Transform2::scale_sep(Vector2::new(self.bounds.0, self.bounds.1)),
bounds: (1.0, 1.0),
}
}
pub fn cast_spaces<A: Space, B: Space>(self) -> TransformAndBounds<A, B> {
TransformAndBounds {
transform: self.transform.cast_spaces(),
bounds: self.bounds,
}
}
pub fn as_transform(&self) -> Transform2<F, T> {
self.transform * Transform2::scale_sep(Vector2::new(self.bounds.0, self.bounds.1))
}
}
#[test]
fn test_transform_and_bounds_mult() {
let dvx = 0.6;
let dvy = 0.3;
let t_and_b_with_scale = TransformAndBounds::<Generic> {
transform: Transform2::new([1.5, 1.1, 2.3, 3.2, 1.2, 1.0])
* Transform2::scale_sep(Vector2::<Generic>::new(dvx, dvy)),
bounds: (2.1, 1.2),
};
let t_and_b_with_size = TransformAndBounds::<Generic> {
transform: Transform2::new([1.5, 1.1, 2.3, 3.2, 1.2, 1.0]),
bounds: (2.1 * dvx, 1.2 * dvy),
};
let some_other_transform = TransformAndBounds::<Generic> {
transform: Transform2::new([1.1, 1.2, 5.3, 9.2, 1.0, 2.0]),
bounds: (1.9, 4.5),
};
let res_scale = t_and_b_with_scale * some_other_transform;
let res_size = t_and_b_with_size * some_other_transform;
let t_scale = res_scale.transform
* Transform2::<Generic>::scale_sep(Vector2::new(res_scale.bounds.0, res_scale.bounds.1));
let t_scale_c = t_scale.coeffs();
let t_size = res_size.transform
* Transform2::<Generic>::scale_sep(Vector2::new(res_size.bounds.0, res_size.bounds.1));
let t_size_c = t_size.coeffs();
let diff_sum = t_scale_c
.iter()
.zip(t_size_c)
.map(|(a, b)| (a - b).abs())
.sum::<f64>();
assert!(diff_sum < 1e-4);
let res_scale_other_way = some_other_transform * t_and_b_with_scale;
let res_size_other_way = some_other_transform * t_and_b_with_size;
let t_scale = res_scale_other_way.transform
* Transform2::<Generic>::scale_sep(Vector2::new(
res_scale_other_way.bounds.0,
res_scale_other_way.bounds.1,
));
let t_scale_c = t_scale.coeffs();
let t_size = res_size_other_way.transform
* Transform2::<Generic>::scale_sep(Vector2::new(
res_size_other_way.bounds.0,
res_size_other_way.bounds.1,
));
let t_size_c = t_size.coeffs();
let diff_sum = t_scale_c
.iter()
.zip(t_size_c)
.map(|(a, b)| (a - b).abs())
.sum::<f64>();
assert!(diff_sum < 1e-4);
}
#[test]
fn test_apply_container_frame_uses_assigned_bounds() {
let parent = TransformAndBounds::<NodeLocal, Window> {
transform: Transform2::translate(Vector2::new(50.0, 60.0)),
bounds: (100.0, 100.0),
};
let frame = ContainerFrame {
transform: Transform2::translate(Vector2::new(10.0, 20.0)),
bounds: (30.0, 40.0),
};
let result = apply_container_frame(parent, Some(frame));
assert_eq!(result.bounds, (30.0, 40.0));
assert_eq!(result.transform.m[4], 60.0);
assert_eq!(result.transform.m[5], 80.0);
}
#[test]
fn test_apply_padding_frame_shrinks_and_offsets_child_bounds() {
let parent = TransformAndBounds::<NodeLocal, Window> {
transform: Transform2::translate(Vector2::new(50.0, 60.0)),
bounds: (200.0, 100.0),
};
let result = apply_padding_frame(
parent,
Some(Size::Pixels(10.into())),
Some(Size::Percent(20.into())),
);
assert_eq!(result.bounds, (180.0, 60.0));
assert_eq!(result.transform.m[4], 60.0);
assert_eq!(result.transform.m[5], 80.0);
}
#[test]
fn test_resolve_padded_autosize_axis_solves_percent_padding() {
let resolved = resolve_padded_autosize_axis(60.0, Some(Size::Percent(20.into()))).unwrap();
assert!((resolved - 100.0).abs() < 1e-9);
}
#[test]
fn test_add_symmetric_padding_to_content_layout_hull_solves_from_content() {
let hull = LayoutHull::from_axis_ranges(Some((0.0, 50.0)), Some((4.0, 84.0)));
let padded = add_symmetric_padding_to_content_layout_hull(
hull,
Some(Size::Pixels(5.into())),
Some(Size::Percent(10.into())),
);
assert_eq!(padded.x_range(), Some((5.0, 60.0)));
assert!((padded.y_range().unwrap().0 - 14.5).abs() < 1e-9);
assert!((padded.forward_extent_y().unwrap() - 105.0).abs() < 1e-9);
}
#[test]
fn test_project_layout_hull_with_translation_preserves_forward_extents() {
let hull = LayoutHull::from_axis_ranges(Some((-10.0, 40.0)), Some((0.0, 20.0)));
let projected = project_layout_hull(
Transform2::<Generic>::translate(Vector2::new(30.0, -5.0)),
hull,
);
assert_eq!(projected.x_range(), Some((20.0, 70.0)));
assert_eq!(projected.y_range(), Some((-5.0, 15.0)));
assert_eq!(projected.forward_extent_x(), Some(70.0));
assert_eq!(projected.forward_extent_y(), Some(15.0));
}
#[test]
fn test_project_layout_hull_keeps_axis_independence_without_cross_axis_mixing() {
let hull = LayoutHull::from_axis_ranges(Some((10.0, 30.0)), None);
let projected = project_layout_hull(
Transform2::<Generic>::new([-2.0, 0.0, 0.0, 3.0, 5.0, 7.0]),
hull,
);
assert_eq!(projected.x_range(), Some((-55.0, -15.0)));
assert_eq!(projected.y_range(), None);
}
#[test]
fn test_project_layout_hull_invalidates_partial_axes_when_transform_mixes_axes() {
let hull = LayoutHull::from_axis_ranges(Some((0.0, 20.0)), None);
let projected = project_layout_hull(
Transform2::<Generic>::rotate(std::f64::consts::FRAC_PI_4),
hull,
);
assert_eq!(projected, LayoutHull::default());
}
#[test]
fn test_project_child_layout_hull_ignores_rotation_for_flow_measurement() {
let parent = TransformAndBounds::<NodeLocal, Window> {
transform: Transform2::identity(),
bounds: (100.0, 100.0),
};
let child = TransformAndBounds::<NodeLocal, Window> {
transform: Transform2::<NodeLocal, Window>::translate(Vector2::new(10.0, 20.0))
* Transform2::<NodeLocal, NodeLocal>::rotate(std::f64::consts::FRAC_PI_4),
bounds: (100.0, 100.0),
};
let hull = LayoutHull::from_axis_ranges(None, Some((0.0, 50.0)));
let projected = project_child_layout_hull_to_parent_space(parent, child, hull);
assert_eq!(projected.x_range(), None);
assert_eq!(projected.y_range(), Some((20.0, 70.0)));
}
#[test]
fn test_layout_hull_equivalence_ignores_numeric_jitter() {
let previous = LayoutHull::from_axis_ranges(Some((10.0, 30.0)), Some((20.0, 40.0000000000001)));
let equivalent =
LayoutHull::from_axis_ranges(Some((10.0, 30.0000000000001)), Some((20.0, 40.0)));
let changed = LayoutHull::from_axis_ranges(Some((10.0, 30.001)), Some((20.0, 40.0)));
assert!(layout_hulls_equivalent(&previous, &equivalent));
assert!(!layout_hulls_equivalent(&previous, &changed));
}
impl Interpolatable for LayoutProperties {}
#[derive(Debug, Default, Clone)]
pub struct LayoutProperties {
pub x: Option<Size>,
pub y: Option<Size>,
pub width: Option<Size>,
pub height: Option<Size>,
pub rotate: Option<Rotation>,
pub scale_x: Option<Percent>,
pub scale_y: Option<Percent>,
pub anchor_x: Option<Size>,
pub anchor_y: Option<Size>,
pub skew_x: Option<Rotation>,
pub skew_y: Option<Rotation>,
}
impl LayoutProperties {
pub fn fill() -> Self {
Self {
x: Some(Size::ZERO()),
y: Some(Size::ZERO()),
width: Some(Size::default()),
height: Some(Size::default()),
rotate: Some(Rotation::ZERO()),
scale_x: Some(Percent(100.into())),
scale_y: Some(Percent(100.into())),
anchor_x: None,
anchor_y: None,
skew_x: Some(Rotation::ZERO()),
skew_y: Some(Rotation::ZERO()),
}
}
}
impl<F: Space, T: Space> TransformAndBounds<F, T> {
pub fn inverse(&self) -> TransformAndBounds<T, F> {
let t_inv = self.transform.inverse();
let b_inv = (1.0 / self.bounds.0, 1.0 / self.bounds.1);
TransformAndBounds {
transform: t_inv,
bounds: b_inv,
}
}
pub fn intersects(&self, other: &Self) -> bool {
let corners_self = self.corners();
let corners_other = other.corners();
for i in 0..2 {
let axis = (corners_self[i] - corners_self[(i + 1) % 4]).normal();
let self_projections: Vec<_> = corners_self
.iter()
.map(|&p| p.to_vector().project_onto(axis).length())
.collect();
let other_projections: Vec<_> = corners_other
.iter()
.map(|&p| p.to_vector().project_onto(axis).length())
.collect();
let (min_self, max_self) = min_max_projections(&self_projections);
let (min_other, max_other) = min_max_projections(&other_projections);
if max_self < min_other || max_other < min_self {
return false;
}
}
true
}
}
fn min_max_projections(projections: &[f64]) -> (f64, f64) {
let min_projection = *projections.iter().min_by(|a, b| a.total_cmp(b)).unwrap();
let max_projection = *projections.iter().max_by(|a, b| a.total_cmp(b)).unwrap();
(min_projection, max_projection)
}
pub trait ComputableTransform<F, T> {
fn apply(&self, bounds: TransformAndBounds<F, T>) -> TransformAndBounds<F, T>;
}
impl ComputableTransform<NodeLocal, Window> for Transform2D {
fn apply(
&self,
bounds: TransformAndBounds<NodeLocal, Window>,
) -> TransformAndBounds<NodeLocal, Window> {
let layout_properties = LayoutProperties {
x: self.translate.map(|v| v[0]),
y: self.translate.map(|v| v[1]),
width: Some(Size::Pixels(bounds.bounds.0.into())),
height: Some(Size::Pixels(bounds.bounds.1.into())),
rotate: self.rotate.clone(),
scale_x: self
.scale
.as_ref()
.map(|v| Percent((100.0 * v[0].clone().expect_percent()).into())),
scale_y: self
.scale
.as_ref()
.map(|v| Percent((100.0 * v[1].clone().expect_percent()).into())),
anchor_x: self.anchor.map(|v| v[0]),
anchor_y: self.anchor.map(|v| v[1]),
skew_x: self.skew.as_ref().map(|v| v[0].clone()),
skew_y: self.skew.as_ref().map(|v| v[1].clone()),
};
let curr = calculate_transform_and_bounds(&layout_properties, bounds.clone());
match &self.previous {
Some(previous) => (*previous).apply(curr),
None => curr,
}
}
}