1use egui::{Pos2, Rect, Vec2, pos2, vec2};
9use serde::{Deserialize, Serialize};
10
11use crate::grid::{grid_i32, grid_u32_ceil, px, px_u};
12
13#[derive(
14 Copy, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
15)]
16pub struct GridPos {
17 pub x: i32,
18 pub y: i32,
19}
20
21#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
22pub struct GridSize {
23 pub w: u32,
24 pub h: u32,
25}
26
27#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
28pub struct GridVec {
29 pub dx: i32,
30 pub dy: i32,
31}
32
33#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
34pub struct GridRect {
35 pub min: GridPos,
36 pub size: GridSize,
37}
38
39impl GridPos {
40 pub const ZERO: GridPos = GridPos { x: 0, y: 0 };
41
42 pub const fn new(x: i32, y: i32) -> Self {
43 Self { x, y }
44 }
45}
46
47impl GridSize {
48 pub const ZERO: GridSize = GridSize { w: 0, h: 0 };
49
50 pub const fn new(w: u32, h: u32) -> Self {
51 Self { w, h }
52 }
53}
54
55impl GridVec {
56 pub const ZERO: GridVec = GridVec { dx: 0, dy: 0 };
57
58 pub const fn new(dx: i32, dy: i32) -> Self {
59 Self { dx, dy }
60 }
61}
62
63impl GridRect {
64 pub const fn new(min: GridPos, size: GridSize) -> Self {
65 Self { min, size }
66 }
67
68 pub fn from_min_size(min: GridPos, size: GridSize) -> Self {
69 Self { min, size }
70 }
71
72 pub fn from_two_pos(a: GridPos, b: GridPos) -> Self {
73 let min_x = a.x.min(b.x);
74 let min_y = a.y.min(b.y);
75 let max_x = a.x.max(b.x);
76 let max_y = a.y.max(b.y);
77 Self {
78 min: GridPos::new(min_x, min_y),
79 size: GridSize::new((max_x - min_x) as u32, (max_y - min_y) as u32),
80 }
81 }
82
83 pub fn max(self) -> GridPos {
84 GridPos::new(
85 self.min.x + self.size.w as i32,
86 self.min.y + self.size.h as i32,
87 )
88 }
89
90 pub fn width(self) -> u32 {
91 self.size.w
92 }
93
94 pub fn height(self) -> u32 {
95 self.size.h
96 }
97
98 pub fn left(self) -> i32 {
99 self.min.x
100 }
101
102 pub fn top(self) -> i32 {
103 self.min.y
104 }
105
106 pub fn right(self) -> i32 {
107 self.min.x + self.size.w as i32
108 }
109
110 pub fn bottom(self) -> i32 {
111 self.min.y + self.size.h as i32
112 }
113
114 pub fn left_top(self) -> GridPos {
115 self.min
116 }
117
118 pub fn right_top(self) -> GridPos {
119 GridPos::new(self.right(), self.top())
120 }
121
122 pub fn left_bottom(self) -> GridPos {
123 GridPos::new(self.left(), self.bottom())
124 }
125
126 pub fn right_bottom(self) -> GridPos {
127 self.max()
128 }
129
130 pub fn contains(self, p: GridPos) -> bool {
131 p.x >= self.left() && p.x <= self.right() && p.y >= self.top() && p.y <= self.bottom()
132 }
133
134 pub fn intersects(self, other: GridRect) -> bool {
135 self.left() < other.right()
136 && other.left() < self.right()
137 && self.top() < other.bottom()
138 && other.top() < self.bottom()
139 }
140
141 pub fn translate(self, delta: GridVec) -> Self {
142 Self {
143 min: GridPos::new(self.min.x + delta.dx, self.min.y + delta.dy),
144 size: self.size,
145 }
146 }
147}
148
149impl std::ops::Add<GridVec> for GridPos {
150 type Output = GridPos;
151 fn add(self, rhs: GridVec) -> GridPos {
152 GridPos::new(self.x + rhs.dx, self.y + rhs.dy)
153 }
154}
155
156impl std::ops::Sub<GridPos> for GridPos {
157 type Output = GridVec;
158 fn sub(self, rhs: GridPos) -> GridVec {
159 GridVec::new(self.x - rhs.x, self.y - rhs.y)
160 }
161}
162
163pub trait GridPosExt {
165 fn from_pos2_snapped(p: Pos2) -> Self;
167 fn to_pos2(self) -> Pos2;
169}
170
171impl GridPosExt for GridPos {
172 fn from_pos2_snapped(p: Pos2) -> Self {
173 Self::new(grid_i32(p.x), grid_i32(p.y))
174 }
175
176 fn to_pos2(self) -> Pos2 {
177 pos2(px(self.x), px(self.y))
178 }
179}
180
181pub trait GridSizeExt {
183 fn from_vec2_ceil(v: Vec2) -> Self;
187}
188
189impl GridSizeExt for GridSize {
190 fn from_vec2_ceil(v: Vec2) -> Self {
191 Self::new(grid_u32_ceil(v.x), grid_u32_ceil(v.y))
192 }
193}
194
195pub trait GridRectExt {
197 fn from_rect_snapped(r: Rect) -> Self;
199 fn to_rect(self) -> Rect;
201}
202
203impl GridRectExt for GridRect {
204 fn from_rect_snapped(r: Rect) -> Self {
205 let min = GridPos::from_pos2_snapped(r.min);
206 let max = GridPos::from_pos2_snapped(r.max);
207 Self::from_two_pos(min, max)
208 }
209
210 fn to_rect(self) -> Rect {
211 Rect::from_min_size(
212 self.min.to_pos2(),
213 vec2(px_u(self.size.w), px_u(self.size.h)),
214 )
215 }
216}
217
218impl From<GridPos> for Pos2 {
219 fn from(p: GridPos) -> Self {
220 p.to_pos2()
221 }
222}
223
224impl From<blockworx_doc::geometry::GridPoint> for GridPos {
229 fn from(p: blockworx_doc::geometry::GridPoint) -> Self {
230 Self::new(p.x, p.y)
231 }
232}
233
234impl From<GridPos> for blockworx_doc::geometry::GridPoint {
235 fn from(p: GridPos) -> Self {
236 Self { x: p.x, y: p.y }
237 }
238}
239
240impl From<blockworx_doc::geometry::GridVec> for GridVec {
241 fn from(v: blockworx_doc::geometry::GridVec) -> Self {
242 Self::new(v.dx, v.dy)
243 }
244}
245
246impl From<GridVec> for blockworx_doc::geometry::GridVec {
247 fn from(v: GridVec) -> Self {
248 Self::new(v.dx, v.dy)
249 }
250}
251
252impl From<GridVec> for Vec2 {
253 fn from(v: GridVec) -> Self {
254 vec2(px(v.dx), px(v.dy))
255 }
256}
257
258impl From<GridRect> for Rect {
259 fn from(r: GridRect) -> Self {
260 r.to_rect()
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn gridpos_pos2_roundtrip() {
270 for &(x, y) in &[(0, 0), (1, 1), (-3, 7), (100, -50)] {
271 let g = GridPos::new(x, y);
272 let p: Pos2 = g.into();
273 let g2 = GridPos::from_pos2_snapped(p);
274 assert_eq!(g, g2);
275 }
276 }
277
278 #[test]
279 fn gridrect_rect_roundtrip() {
280 let r = GridRect::new(GridPos::new(-2, 3), GridSize::new(7, 4));
281 let r2 = GridRect::from_rect_snapped(r.to_rect());
282 assert_eq!(r, r2);
283 }
284
285 #[test]
286 fn gridrect_intersects_matches_rect_semantics() {
287 let a = GridRect::new(GridPos::new(0, 0), GridSize::new(2, 2));
288 let b = GridRect::new(GridPos::new(2, 0), GridSize::new(2, 2));
289 assert!(!a.intersects(b));
291 let c = GridRect::new(GridPos::new(1, 0), GridSize::new(2, 2));
292 assert!(a.intersects(c));
293 }
294}