Skip to main content

anathema_geometry/
position.rs

1use std::ops::{Add, AddAssign, Mul, Sub, SubAssign};
2
3use crate::Size;
4
5// -----------------------------------------------------------------------------
6//   - Generic position -
7// -----------------------------------------------------------------------------
8
9/// A position in global space.
10/// Can contain negative coordinates
11#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd)]
12pub struct Pos {
13    /// X coordinate
14    pub x: i32,
15    /// Y coordinate
16    pub y: i32,
17}
18
19impl Pos {
20    /// Zero
21    pub const ZERO: Self = Self::new(0, 0);
22
23    /// Create a new instance with the given x and y coordinates
24    pub const fn new(x: i32, y: i32) -> Self {
25        Self { x, y }
26    }
27}
28
29impl Default for Pos {
30    fn default() -> Self {
31        Self::ZERO
32    }
33}
34
35impl From<(i32, i32)> for Pos {
36    fn from(val: (i32, i32)) -> Self {
37        Self::new(val.0, val.1)
38    }
39}
40
41impl From<(u16, u16)> for Pos {
42    fn from(val: (u16, u16)) -> Self {
43        Self::new(val.0 as i32, val.1 as i32)
44    }
45}
46
47impl From<LocalPos> for Pos {
48    fn from(LocalPos { x, y }: LocalPos) -> Self {
49        Self::new(x as i32, y as i32)
50    }
51}
52
53impl From<(usize, usize)> for Pos {
54    fn from(val: (usize, usize)) -> Self {
55        Self::new(val.0 as i32, val.1 as i32)
56    }
57}
58
59impl Add for Pos {
60    type Output = Self;
61
62    fn add(self, rhs: Self) -> Self::Output {
63        Pos::new(self.x + rhs.x, self.y + rhs.y)
64    }
65}
66
67impl Add<Size> for Pos {
68    type Output = Self;
69
70    fn add(self, rhs: Size) -> Self::Output {
71        Pos::new(self.x + rhs.width as i32, self.y + rhs.height as i32)
72    }
73}
74
75impl Add<LocalPos> for Pos {
76    type Output = Self;
77
78    fn add(self, rhs: LocalPos) -> Self::Output {
79        Pos::new(self.x + rhs.x as i32, self.y + rhs.y as i32)
80    }
81}
82
83impl Sub<LocalPos> for Pos {
84    type Output = Self;
85
86    fn sub(self, rhs: LocalPos) -> Self::Output {
87        Pos::new(self.x - rhs.x as i32, self.y - rhs.y as i32)
88    }
89}
90
91impl Mul<f32> for Pos {
92    type Output = Self;
93
94    fn mul(self, rhs: f32) -> Self::Output {
95        Self {
96            x: (self.x as f32 * rhs).round() as i32,
97            y: (self.y as f32 * rhs).round() as i32,
98        }
99    }
100}
101
102impl AddAssign for Pos {
103    fn add_assign(&mut self, rhs: Pos) {
104        self.x += rhs.x;
105        self.y += rhs.y;
106    }
107}
108
109impl Sub for Pos {
110    type Output = Self;
111
112    fn sub(self, rhs: Self) -> Self::Output {
113        Pos::new(self.x - rhs.x, self.y - rhs.y)
114    }
115}
116
117impl SubAssign for Pos {
118    fn sub_assign(&mut self, rhs: Pos) {
119        self.x -= rhs.x;
120        self.y -= rhs.y;
121    }
122}
123
124// -----------------------------------------------------------------------------
125//   - Local position -
126// -----------------------------------------------------------------------------
127
128/// Positions in a local space.
129/// These coordinates can not be negative.
130/// `0, 0` refers to top left.
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
132pub struct LocalPos {
133    /// X coordinate
134    pub x: u16,
135    /// Y coordinate
136    pub y: u16,
137}
138
139impl LocalPos {
140    /// Zero...
141    pub const ZERO: Self = Self::new(0, 0);
142
143    /// Create a new set of coordinates in local space
144    pub const fn new(x: u16, y: u16) -> Self {
145        Self { x, y }
146    }
147
148    pub const fn to_index(self, width: u16) -> usize {
149        (self.y * width + self.x) as usize
150    }
151
152    pub const fn saturating_sub(mut self, other: Self) -> Self {
153        self.x = self.x.saturating_sub(other.x);
154        self.y = self.y.saturating_sub(other.y);
155        self
156    }
157}
158
159impl From<(u16, u16)> for LocalPos {
160    fn from((x, y): (u16, u16)) -> Self {
161        Self { x, y }
162    }
163}
164
165impl From<(i32, i32)> for LocalPos {
166    fn from((x, y): (i32, i32)) -> Self {
167        Self {
168            x: x as u16,
169            y: y as u16,
170        }
171    }
172}
173
174impl From<(usize, usize)> for LocalPos {
175    fn from((x, y): (usize, usize)) -> Self {
176        Self {
177            x: x as u16,
178            y: y as u16,
179        }
180    }
181}
182
183impl TryFrom<Pos> for LocalPos {
184    type Error = ();
185
186    fn try_from(value: Pos) -> Result<Self, Self::Error> {
187        if value.x < 0 || value.y < 0 {
188            return Err(());
189        }
190
191        if value.x > u16::MAX as i32 || value.y > u16::MAX as i32 {
192            return Err(());
193        }
194
195        Ok(Self {
196            x: value.x as u16,
197            y: value.y as u16,
198        })
199    }
200}
201
202impl Add for LocalPos {
203    type Output = Self;
204
205    fn add(self, rhs: Self) -> Self::Output {
206        LocalPos {
207            x: self.x + rhs.x,
208            y: self.y + rhs.y,
209        }
210    }
211}
212
213impl AddAssign for LocalPos {
214    fn add_assign(&mut self, rhs: Self) {
215        self.x += rhs.x;
216        self.y += rhs.y;
217    }
218}
219
220#[cfg(test)]
221mod test {
222    use super::*;
223
224    #[test]
225    fn index_from_coords() {
226        let width = 20;
227
228        let actual = LocalPos::new(0, 0).to_index(width);
229        let expected = 0;
230        assert_eq!(expected, actual);
231
232        let actual = LocalPos::new(10, 0).to_index(width);
233        let expected = 10;
234        assert_eq!(expected, actual);
235
236        let actual = LocalPos::new(4, 20).to_index(width);
237        let expected = (width * width) as usize + 4;
238        assert_eq!(expected, actual);
239    }
240}