use std::ops::{Add, Sub};
use crate::Keyable;
#[derive(Debug, Clone, Copy, Keyable)]
pub struct Vec2(pub f32, pub f32);
impl Vec2 {
pub const ZERO: Self = Self(0.0, 0.0);
pub fn anchored(self, anchor: Anchor) -> AnchoredSize {
AnchoredSize { size: self, anchor }
}
}
impl Add for Vec2 {
type Output = Vec2;
fn add(self, rhs: Vec2) -> Vec2 {
Vec2(self.0 + rhs.0, self.1 + rhs.1)
}
}
impl Sub for Vec2 {
type Output = Vec2;
fn sub(self, rhs: Vec2) -> Vec2 {
Vec2(self.0 - rhs.0, self.1 - rhs.1)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Rect {
pub origin: Vec2,
pub size: Vec2,
}
#[derive(Debug, Clone, Copy, Keyable)]
pub struct Transform {
pub a: f32,
pub b: f32,
pub c: f32,
pub d: f32,
pub tx: f32,
pub ty: f32,
}
impl Transform {
pub const IDENTITY: Self = Self {
a: 1.0,
b: 0.0,
c: 0.0,
d: 1.0,
tx: 0.0,
ty: 0.0,
};
pub const fn translate(offset: Vec2) -> Self {
Self {
a: 1.0,
b: 0.0,
c: 0.0,
d: 1.0,
tx: offset.0,
ty: offset.1,
}
}
pub const fn scale(scale: Vec2) -> Self {
Self {
a: scale.0,
b: 0.0,
c: 0.0,
d: scale.1,
tx: 0.0,
ty: 0.0,
}
}
pub fn rotate(radians: f32) -> Self {
let cos = radians.cos();
let sin = radians.sin();
Self {
a: cos,
b: sin,
c: -sin,
d: cos,
tx: 0.0,
ty: 0.0,
}
}
pub fn concat(self, child: Self) -> Self {
Self {
a: self.a * child.a + self.c * child.b,
b: self.b * child.a + self.d * child.b,
c: self.a * child.c + self.c * child.d,
d: self.b * child.c + self.d * child.d,
tx: self.a * child.tx + self.c * child.ty + self.tx,
ty: self.b * child.tx + self.d * child.ty + self.ty,
}
}
pub fn then(self, next: Self) -> Self {
next.concat(self)
}
pub fn around_point(point: Vec2, transform: Self) -> Self {
Self::translate(point)
.concat(transform)
.concat(Self::translate(Vec2(-point.0, -point.1)))
}
pub fn transform_point(self, point: Vec2) -> Vec2 {
Vec2(
self.a * point.0 + self.c * point.1 + self.tx,
self.b * point.0 + self.d * point.1 + self.ty,
)
}
pub fn transform_rect(self, rect: Rect) -> Rect {
let p0 = self.transform_point(rect.origin);
let p1 = self.transform_point(Vec2(rect.origin.0 + rect.size.0, rect.origin.1));
let p2 = self.transform_point(Vec2(rect.origin.0, rect.origin.1 + rect.size.1));
let p3 = self.transform_point(Vec2(
rect.origin.0 + rect.size.0,
rect.origin.1 + rect.size.1,
));
let min_x = p0.0.min(p1.0).min(p2.0).min(p3.0);
let max_x = p0.0.max(p1.0).max(p2.0).max(p3.0);
let min_y = p0.1.min(p1.1).min(p2.1).min(p3.1);
let max_y = p0.1.max(p1.1).max(p2.1).max(p3.1);
Rect {
origin: Vec2(min_x, min_y),
size: Vec2(max_x - min_x, max_y - min_y),
}
}
}
#[derive(Debug, Clone, Copy, Keyable)]
pub struct Anchor {
pub rx: f32,
pub ry: f32,
}
impl Anchor {
pub const TOP_LEFT: Self = Self::new(0.0, 0.0);
pub const TOP_CENTER: Self = Self::new(0.5, 0.0);
pub const TOP_RIGHT: Self = Self::new(1.0, 0.0);
pub const CENTER_LEFT: Self = Self::new(0.0, 0.5);
pub const CENTER: Self = Self::new(0.5, 0.5);
pub const CENTER_RIGHT: Self = Self::new(1.0, 0.5);
pub const BOTTOM_LEFT: Self = Self::new(0.0, 1.0);
pub const BOTTOM_CENTER: Self = Self::new(0.5, 1.0);
pub const BOTTOM_RIGHT: Self = Self::new(1.0, 1.0);
pub const fn new(rx: f32, ry: f32) -> Self {
Self { rx, ry }
}
pub fn point(self, size: Vec2) -> Vec2 {
Vec2(size.0 * self.rx, size.1 * self.ry)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AnchoredSize {
pub size: Vec2,
pub anchor: Anchor,
}
impl AnchoredSize {
pub fn snap_to(self, target_point: Vec2) -> Vec2 {
target_point - self.anchor.point(self.size)
}
}
#[derive(Debug, Clone, Copy, Keyable)]
pub struct EdgeInsets {
pub left: f32,
pub top: f32,
pub right: f32,
pub bottom: f32,
}
impl EdgeInsets {
pub const ZERO: Self = Self {
left: 0.0,
top: 0.0,
right: 0.0,
bottom: 0.0,
};
pub const fn all(value: f32) -> Self {
Self {
left: value,
top: value,
right: value,
bottom: value,
}
}
pub const fn symmetric(horizontal: f32, vertical: f32) -> Self {
Self {
left: horizontal,
top: vertical,
right: horizontal,
bottom: vertical,
}
}
pub const fn only(left: f32, top: f32, right: f32, bottom: f32) -> Self {
Self {
left,
top,
right,
bottom,
}
}
pub fn horizontal(&self) -> f32 {
self.left + self.right
}
pub fn vertical(&self) -> f32 {
self.top + self.bottom
}
pub fn top_left(&self) -> Vec2 {
Vec2(self.left, self.top)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Constraints {
pub min: Vec2,
pub max: Vec2,
}
impl Constraints {
pub const UNBOUNDED: Self = Self {
min: Vec2::ZERO,
max: Vec2(f32::INFINITY, f32::INFINITY),
};
pub const fn tight(size: Vec2) -> Self {
Self {
min: size,
max: size,
}
}
pub const fn loose(max: Vec2) -> Self {
Self {
min: Vec2::ZERO,
max,
}
}
pub fn constrain(&self, size: Vec2) -> Vec2 {
Vec2(
size.0.clamp(self.min.0, self.max.0),
size.1.clamp(self.min.1, self.max.1),
)
}
pub(crate) fn fill_size(&self) -> Vec2 {
let finite_or_zero = |axis: f32| if axis.is_finite() { axis } else { 0.0 };
self.constrain(Vec2(finite_or_zero(self.max.0), finite_or_zero(self.max.1)))
}
pub fn with_max(&self, max: Vec2) -> Self {
let new_max = Vec2(max.0.min(self.max.0), max.1.min(self.max.1));
Self {
min: Vec2(self.min.0.min(new_max.0), self.min.1.min(new_max.1)),
max: new_max,
}
}
pub fn shrink(&self, by: Vec2) -> Self {
let new_max = Vec2((self.max.0 - by.0).max(0.0), (self.max.1 - by.1).max(0.0));
Self {
min: Vec2((self.min.0 - by.0).max(0.0), (self.min.1 - by.1).max(0.0)),
max: new_max,
}
}
pub fn tighten_cross(&self, axis: Axis, value: f32) -> Self {
match axis {
Axis::Horizontal => Self {
min: Vec2(self.min.0, value),
max: Vec2(self.max.0, value),
},
Axis::Vertical => Self {
min: Vec2(value, self.min.1),
max: Vec2(value, self.max.1),
},
}
}
pub fn tighten_main(&self, axis: Axis, value: f32) -> Self {
match axis {
Axis::Horizontal => Self {
min: Vec2(value, self.min.1),
max: Vec2(value, self.max.1),
},
Axis::Vertical => Self {
min: Vec2(self.min.0, value),
max: Vec2(self.max.0, value),
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Axis {
Horizontal,
Vertical,
}
#[cfg(test)]
mod tests {
use super::*;
use std::f32::consts::FRAC_PI_2;
fn assert_near(actual: f32, expected: f32) {
assert!(
(actual - expected).abs() < 1e-5,
"expected {expected}, got {actual}"
);
}
fn assert_vec_near(actual: Vec2, expected: Vec2) {
assert_near(actual.0, expected.0);
assert_near(actual.1, expected.1);
}
#[test]
fn then_applies_transforms_in_reading_order() {
let transform = Transform::scale(Vec2(2.0, 3.0)).then(Transform::rotate(FRAC_PI_2));
assert_vec_near(transform.transform_point(Vec2(1.0, 0.0)), Vec2(0.0, 2.0));
}
#[test]
fn around_point_keeps_pivot_fixed() {
let pivot = Vec2(10.0, 20.0);
let transform = Transform::around_point(pivot, Transform::scale(Vec2(2.0, 3.0)));
assert_vec_near(transform.transform_point(pivot), pivot);
assert_vec_near(
transform.transform_point(Vec2(11.0, 21.0)),
Vec2(12.0, 23.0),
);
}
#[test]
fn transform_rect_returns_axis_aligned_bounds() {
let rect = Rect {
origin: Vec2(0.0, 0.0),
size: Vec2(2.0, 4.0),
};
let bounds = Transform::rotate(FRAC_PI_2).transform_rect(rect);
assert_vec_near(bounds.origin, Vec2(-4.0, 0.0));
assert_vec_near(bounds.size, Vec2(4.0, 2.0));
}
}