1use crate::{WindowsUtilsError, WindowsUtilsResult};
9use core::fmt;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub struct Point {
15 pub x: i32,
17 pub y: i32,
19}
20
21impl Point {
22 pub const fn new(x: i32, y: i32) -> Self {
24 Self { x, y }
25 }
26
27 pub const ORIGIN: Self = Self::new(0, 0);
29
30 pub fn is_valid(&self) -> bool {
32 self.x >= 0 && self.y >= 0
33 }
34
35 pub fn translate(&self, dx: i32, dy: i32) -> Self {
37 Self::new(self.x + dx, self.y + dy)
38 }
39
40 pub fn distance_to(&self, other: Point) -> f64 {
42 let dx = (other.x - self.x) as f64;
43 let dy = (other.y - self.y) as f64;
44 (dx * dx + dy * dy).sqrt()
45 }
46}
47
48impl fmt::Display for Point {
49 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50 write!(f, "({}, {})", self.x, self.y)
51 }
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
56#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
57pub struct Rect {
58 pub origin: Point,
60 pub width: i32,
62 pub height: i32,
64}
65
66impl Rect {
67 pub const fn new(x: i32, y: i32, width: i32, height: i32) -> Self {
69 Self {
70 origin: Point::new(x, y),
71 width,
72 height,
73 }
74 }
75
76 pub fn from_points(top_left: Point, bottom_right: Point) -> Self {
78 Self {
79 origin: top_left,
80 width: bottom_right.x - top_left.x,
81 height: bottom_right.y - top_left.y,
82 }
83 }
84
85 pub fn left(&self) -> i32 {
87 self.origin.x
88 }
89
90 pub fn top(&self) -> i32 {
92 self.origin.y
93 }
94
95 pub fn right(&self) -> i32 {
97 self.origin.x + self.width
98 }
99
100 pub fn bottom(&self) -> i32 {
102 self.origin.y + self.height
103 }
104
105 pub fn center(&self) -> Point {
107 Point::new(
108 self.origin.x + self.width / 2,
109 self.origin.y + self.height / 2,
110 )
111 }
112
113 pub fn contains(&self, point: Point) -> bool {
115 point.x >= self.left()
116 && point.x <= self.right()
117 && point.y >= self.top()
118 && point.y <= self.bottom()
119 }
120
121 pub fn area(&self) -> i32 {
123 self.width * self.height
124 }
125
126 pub fn intersects(&self, other: &Rect) -> bool {
128 self.left() <= other.right()
129 && self.right() >= other.left()
130 && self.top() <= other.bottom()
131 && self.bottom() >= other.top()
132 }
133}
134
135#[derive(Debug, Clone, Copy, PartialEq)]
137#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
138pub struct WindowStyle {
139 pub has_border: bool,
141 pub has_title_bar: bool,
143 pub border_width: i32,
145 pub title_bar_height: i32,
147}
148
149impl Default for WindowStyle {
150 fn default() -> Self {
151 Self {
152 has_border: true,
153 has_title_bar: true,
154 border_width: 8,
155 title_bar_height: 30,
156 }
157 }
158}
159
160#[derive(Debug, Clone)]
162#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
163pub struct Window {
164 handle: isize,
166 window_rect: Rect,
168 style: WindowStyle,
170}
171
172impl Window {
173 pub fn new(handle: isize, window_rect: Rect, style: WindowStyle) -> Self {
175 Self {
176 handle,
177 window_rect,
178 style,
179 }
180 }
181
182 pub fn client_rect(&self) -> Rect {
184 let mut client = self.window_rect;
185
186 if self.style.has_title_bar {
187 client.origin.y += self.style.title_bar_height;
188 client.height -= self.style.title_bar_height;
189 }
190
191 if self.style.has_border {
192 client.origin.x += self.style.border_width;
193 client.origin.y += self.style.border_width;
194 client.width -= self.style.border_width * 2;
195 client.height -= self.style.border_width * 2;
196 }
197
198 if client.width < 0 {
200 client.width = 0;
201 }
202 if client.height < 0 {
203 client.height = 0;
204 }
205
206 client
207 }
208
209 pub fn handle(&self) -> isize {
211 self.handle
212 }
213
214 pub fn window_rect(&self) -> Rect {
216 self.window_rect
217 }
218
219 pub fn style(&self) -> WindowStyle {
221 self.style
222 }
223
224 pub fn move_to(&mut self, new_position: Point) {
226 self.window_rect.origin = new_position;
227 }
228
229 pub fn resize(&mut self, new_width: i32, new_height: i32) {
231 self.window_rect.width = new_width;
232 self.window_rect.height = new_height;
233 }
234
235 pub fn set_style(&mut self, style: WindowStyle) {
237 self.style = style;
238 }
239}
240
241pub trait CoordinateTransformer {
243 fn client_to_screen(&self, point: Point) -> WindowsUtilsResult<Point>;
245
246 fn screen_to_client(&self, point: Point) -> WindowsUtilsResult<Point>;
248
249 fn client_rect(&self) -> Rect;
251
252 fn window_rect(&self) -> Rect;
254
255 fn is_point_in_client(&self, point: Point) -> bool;
257}
258
259impl CoordinateTransformer for Window {
260 fn client_to_screen(&self, point: Point) -> WindowsUtilsResult<Point> {
261 if !point.is_valid() {
262 return Err(WindowsUtilsError::InvalidCoordinates {
263 x: point.x,
264 y: point.y,
265 });
266 }
267
268 let client_rect = self.client_rect();
269
270 Ok(Point::new(
273 client_rect.left() + point.x,
274 client_rect.top() + point.y,
275 ))
276 }
277
278 fn screen_to_client(&self, point: Point) -> WindowsUtilsResult<Point> {
279 if !point.is_valid() {
280 return Err(WindowsUtilsError::InvalidCoordinates {
281 x: point.x,
282 y: point.y,
283 });
284 }
285
286 let client_rect = self.client_rect();
287
288 let client_point = Point::new(point.x - client_rect.left(), point.y - client_rect.top());
291
292 if client_point.x < 0
294 || client_point.y < 0
295 || client_point.x >= client_rect.width
296 || client_point.y >= client_rect.height
297 {
298 return Err(WindowsUtilsError::OutOfBounds {
299 x: point.x,
300 y: point.y,
301 });
302 }
303
304 Ok(client_point)
305 }
306
307 fn client_rect(&self) -> Rect {
308 self.client_rect()
309 }
310
311 fn window_rect(&self) -> Rect {
312 self.window_rect
313 }
314
315 fn is_point_in_client(&self, point: Point) -> bool {
316 let client_rect = self.client_rect();
317 point.x >= 0 && point.y >= 0 && point.x < client_rect.width && point.y < client_rect.height
318 }
319}