use kurbo::{Affine, Point, Rect, Vec2};
use crate::{FRAME_HEIGHT, FRAME_WIDTH};
pub const DEFAULT_BUFF: f64 = 0.25;
pub const EDGE_BUFF: f64 = 0.5;
pub fn critical_point(b: Rect, dir: Vec2) -> Point {
let pick = |d: f64, lo: f64, mid: f64, hi: f64| {
if d > 0.0 {
hi
} else if d < 0.0 {
lo
} else {
mid
}
};
let c = b.center();
Point::new(pick(dir.x, b.x0, c.x, b.x1), pick(dir.y, b.y0, c.y, b.y1))
}
pub trait Position: Sized {
fn bbox(&self) -> Option<Rect>;
fn transform(self, a: Affine) -> Self;
fn next_to(self, other: &impl Position, dir: Vec2, buff: f64) -> Self {
let (Some(me), Some(them)) = (self.bbox(), other.bbox()) else {
return self;
};
let target = critical_point(them, dir) + dir * buff;
let v = target - critical_point(me, -dir);
self.transform(Affine::translate(v))
}
fn align_to(self, other: &impl Position, dir: Vec2) -> Self {
let (Some(me), Some(them)) = (self.bbox(), other.bbox()) else {
return self;
};
let mut v = critical_point(them, dir) - critical_point(me, dir);
if dir.x == 0.0 {
v.x = 0.0;
}
if dir.y == 0.0 {
v.y = 0.0;
}
self.transform(Affine::translate(v))
}
fn to_edge(self, dir: Vec2) -> Self {
let frame = Rect::new(
-FRAME_WIDTH / 2.0 + EDGE_BUFF,
-FRAME_HEIGHT / 2.0 + EDGE_BUFF,
FRAME_WIDTH / 2.0 - EDGE_BUFF,
FRAME_HEIGHT / 2.0 - EDGE_BUFF,
);
self.align_to(&frame, dir)
}
}
impl Position for Rect {
fn bbox(&self) -> Option<Rect> {
Some(*self)
}
fn transform(self, a: Affine) -> Self {
a.transform_rect_bbox(self)
}
}
impl Position for Point {
fn bbox(&self) -> Option<Rect> {
Some(Rect::from_points(*self, *self))
}
fn transform(self, a: Affine) -> Self {
a * self
}
}
pub fn arrange<T: Position>(items: Vec<T>, dir: Vec2, buff: f64) -> Vec<T> {
let before = union(&items);
let mut out: Vec<T> = Vec::with_capacity(items.len());
for item in items {
let placed = match out.iter().rev().find(|p| p.bbox().is_some()) {
Some(prev) => item.next_to(prev, dir, buff),
None => item,
};
out.push(placed);
}
let (Some(before), Some(after)) = (before, union(&out)) else {
return out;
};
let v = before.center() - after.center();
out.into_iter()
.map(|m| m.transform(Affine::translate(v)))
.collect()
}
fn union<T: Position>(items: &[T]) -> Option<Rect> {
items
.iter()
.filter_map(Position::bbox)
.reduce(|a, b| a.union(b))
}