fastanim_core/
position.rs1use kurbo::{Affine, Point, Rect, Vec2};
5
6use crate::{FRAME_HEIGHT, FRAME_WIDTH};
7
8pub const DEFAULT_BUFF: f64 = 0.25;
10pub const EDGE_BUFF: f64 = 0.5;
12
13pub fn critical_point(b: Rect, dir: Vec2) -> Point {
16 let pick = |d: f64, lo: f64, mid: f64, hi: f64| {
18 if d > 0.0 {
19 hi
20 } else if d < 0.0 {
21 lo
22 } else {
23 mid
24 }
25 };
26 let c = b.center();
27 Point::new(pick(dir.x, b.x0, c.x, b.x1), pick(dir.y, b.y0, c.y, b.y1))
28}
29
30pub trait Position: Sized {
33 fn bbox(&self) -> Option<Rect>;
35
36 fn transform(self, a: Affine) -> Self;
38
39 fn next_to(self, other: &impl Position, dir: Vec2, buff: f64) -> Self {
43 let (Some(me), Some(them)) = (self.bbox(), other.bbox()) else {
44 return self;
45 };
46 let target = critical_point(them, dir) + dir * buff;
47 let v = target - critical_point(me, -dir);
48 self.transform(Affine::translate(v))
49 }
50
51 fn align_to(self, other: &impl Position, dir: Vec2) -> Self {
54 let (Some(me), Some(them)) = (self.bbox(), other.bbox()) else {
55 return self;
56 };
57 let mut v = critical_point(them, dir) - critical_point(me, dir);
58 if dir.x == 0.0 {
59 v.x = 0.0;
60 }
61 if dir.y == 0.0 {
62 v.y = 0.0;
63 }
64 self.transform(Affine::translate(v))
65 }
66
67 fn to_edge(self, dir: Vec2) -> Self {
70 let frame = Rect::new(
71 -FRAME_WIDTH / 2.0 + EDGE_BUFF,
72 -FRAME_HEIGHT / 2.0 + EDGE_BUFF,
73 FRAME_WIDTH / 2.0 - EDGE_BUFF,
74 FRAME_HEIGHT / 2.0 - EDGE_BUFF,
75 );
76 self.align_to(&frame, dir)
77 }
78}
79
80impl Position for Rect {
81 fn bbox(&self) -> Option<Rect> {
82 Some(*self)
83 }
84 fn transform(self, a: Affine) -> Self {
85 a.transform_rect_bbox(self)
86 }
87}
88
89impl Position for Point {
90 fn bbox(&self) -> Option<Rect> {
91 Some(Rect::from_points(*self, *self))
92 }
93 fn transform(self, a: Affine) -> Self {
94 a * self
95 }
96}
97
98pub fn arrange<T: Position>(items: Vec<T>, dir: Vec2, buff: f64) -> Vec<T> {
101 let before = union(&items);
102 let mut out: Vec<T> = Vec::with_capacity(items.len());
103 for item in items {
104 let placed = match out.iter().rev().find(|p| p.bbox().is_some()) {
105 Some(prev) => item.next_to(prev, dir, buff),
106 None => item,
107 };
108 out.push(placed);
109 }
110 let (Some(before), Some(after)) = (before, union(&out)) else {
111 return out;
112 };
113 let v = before.center() - after.center();
114 out.into_iter()
115 .map(|m| m.transform(Affine::translate(v)))
116 .collect()
117}
118
119fn union<T: Position>(items: &[T]) -> Option<Rect> {
120 items
121 .iter()
122 .filter_map(Position::bbox)
123 .reduce(|a, b| a.union(b))
124}