objc2_core_foundation/geometry.rs
1#[cfg(feature = "objc2")]
2use objc2::encode::{Encode, Encoding, RefEncode};
3
4use crate::{CGAffineTransform, CGVector};
5
6#[cfg(target_pointer_width = "64")]
7type InnerFloat = f64;
8#[cfg(not(target_pointer_width = "64"))]
9type InnerFloat = f32;
10
11/// The basic type for all floating-point values.
12///
13/// This is [`f32`] on 32-bit platforms and [`f64`] on 64-bit platforms.
14///
15/// See [Apple's documentation](https://developer.apple.com/documentation/corefoundation/cgfloat?language=objc).
16// Defined in CoreGraphics/CGBase.h and CoreFoundation/CFCGTypes.h
17// TODO: Use a newtype here?
18pub type CGFloat = InnerFloat;
19
20// NSGeometry types are aliases to CGGeometry types on iOS, tvOS, watchOS and
21// macOS 64bit (and hence their Objective-C encodings are different).
22//
23// TODO: Adjust `objc2-encode` so that this is handled there, and so that we
24// can effectively forget about it and use `NS` and `CG` types equally.
25#[cfg(not(any(
26 not(target_vendor = "apple"),
27 all(target_os = "macos", target_pointer_width = "32")
28)))]
29#[cfg(feature = "objc2")]
30mod names {
31 pub(super) const POINT: &str = "CGPoint";
32 pub(super) const SIZE: &str = "CGSize";
33 pub(super) const RECT: &str = "CGRect";
34}
35
36#[cfg(any(
37 not(target_vendor = "apple"),
38 all(target_os = "macos", target_pointer_width = "32")
39))]
40#[cfg(feature = "objc2")]
41mod names {
42 pub(super) const POINT: &str = "_NSPoint";
43 pub(super) const SIZE: &str = "_NSSize";
44 pub(super) const RECT: &str = "_NSRect";
45}
46
47/// A point in a two-dimensional coordinate system.
48///
49/// See [Apple's documentation](https://developer.apple.com/documentation/corefoundation/cgpoint?language=objc).
50#[repr(C)]
51#[derive(Clone, Copy, Debug, PartialEq, Default)]
52pub struct CGPoint {
53 /// The x-coordinate of the point.
54 pub x: CGFloat,
55 /// The y-coordinate of the point.
56 pub y: CGFloat,
57}
58
59#[cfg(feature = "objc2")]
60unsafe impl Encode for CGPoint {
61 const ENCODING: Encoding =
62 Encoding::Struct(names::POINT, &[CGFloat::ENCODING, CGFloat::ENCODING]);
63}
64
65#[cfg(feature = "objc2")]
66unsafe impl RefEncode for CGPoint {
67 const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
68}
69
70impl CGPoint {
71 /// Create a new point with the given coordinates.
72 ///
73 ///
74 /// # Examples
75 ///
76 /// ```
77 /// use objc2_core_foundation::CGPoint;
78 /// assert_eq!(CGPoint::new(10.0, -2.3), CGPoint { x: 10.0, y: -2.3 });
79 /// ```
80 #[inline]
81 #[doc(alias = "NSMakePoint")]
82 #[doc(alias = "CGPointMake")]
83 pub const fn new(x: CGFloat, y: CGFloat) -> Self {
84 Self { x, y }
85 }
86
87 /// A point with both coordinates set to `0.0`.
88 ///
89 ///
90 /// # Examples
91 ///
92 /// ```
93 /// use objc2_core_foundation::CGPoint;
94 /// assert_eq!(CGPoint::ZERO, CGPoint { x: 0.0, y: 0.0 });
95 /// ```
96 #[doc(alias = "NSZeroPoint")]
97 #[doc(alias = "CGPointZero")]
98 #[doc(alias = "ORIGIN")]
99 pub const ZERO: Self = Self::new(0.0, 0.0);
100}
101
102/// A two-dimensional size.
103///
104/// As this is sometimes used to represent a distance vector, rather than a
105/// physical size, the width and height are _not_ guaranteed to be
106/// non-negative! Methods that expect that must use one of [`CGSize::abs`] or
107/// [`CGRect::standardize`].
108///
109/// See [Apple's documentation](https://developer.apple.com/documentation/corefoundation/cgsize?language=objc).
110#[repr(C)]
111#[derive(Clone, Copy, Debug, PartialEq, Default)]
112pub struct CGSize {
113 /// The dimensions along the x-axis.
114 pub width: CGFloat,
115 /// The dimensions along the y-axis.
116 pub height: CGFloat,
117}
118
119#[cfg(feature = "objc2")]
120unsafe impl Encode for CGSize {
121 const ENCODING: Encoding =
122 Encoding::Struct(names::SIZE, &[CGFloat::ENCODING, CGFloat::ENCODING]);
123}
124
125#[cfg(feature = "objc2")]
126unsafe impl RefEncode for CGSize {
127 const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
128}
129
130impl CGSize {
131 /// Create a new size with the given dimensions.
132 ///
133 ///
134 /// # Examples
135 ///
136 /// ```
137 /// use objc2_core_foundation::CGSize;
138 /// let size = CGSize::new(10.0, 2.3);
139 /// assert_eq!(size.width, 10.0);
140 /// assert_eq!(size.height, 2.3);
141 /// ```
142 ///
143 /// Negative values are allowed (though often undesired).
144 ///
145 /// ```
146 /// use objc2_core_foundation::CGSize;
147 /// let size = CGSize::new(-1.0, 0.0);
148 /// assert_eq!(size.width, -1.0);
149 /// ```
150 #[inline]
151 #[doc(alias = "NSMakeSize")]
152 #[doc(alias = "CGSizeMake")]
153 pub const fn new(width: CGFloat, height: CGFloat) -> Self {
154 // The documentation for NSSize explicitly says:
155 // > If the value of width or height is negative, however, the
156 // > behavior of some methods may be undefined.
157 //
158 // But since this type can come from FFI, we'll leave it up to the
159 // user to ensure that it is used safely.
160 Self { width, height }
161 }
162
163 /// Convert the size to a non-negative size.
164 ///
165 /// This can be used to convert the size to a safe value.
166 ///
167 ///
168 /// # Examples
169 ///
170 /// ```
171 /// use objc2_core_foundation::CGSize;
172 /// assert_eq!(CGSize::new(-1.0, 1.0).abs(), CGSize::new(1.0, 1.0));
173 /// ```
174 #[inline]
175 #[cfg(feature = "std")] // Only available in core since Rust 1.85
176 pub fn abs(self) -> Self {
177 Self::new(self.width.abs(), self.height.abs())
178 }
179
180 /// A size that is 0.0 in both dimensions.
181 ///
182 ///
183 /// # Examples
184 ///
185 /// ```
186 /// use objc2_core_foundation::CGSize;
187 /// assert_eq!(CGSize::ZERO, CGSize { width: 0.0, height: 0.0 });
188 /// ```
189 #[doc(alias = "NSZeroSize")]
190 #[doc(alias = "CGSizeZero")]
191 pub const ZERO: Self = Self::new(0.0, 0.0);
192}
193
194/// The location and dimensions of a rectangle.
195///
196/// In the default Core Graphics coordinate space (macOS), the origin is
197/// located in the lower-left corner of the rectangle and the rectangle
198/// extends towards the upper-right corner.
199///
200/// If the context has a flipped coordinate space (iOS, tvOS, watchOS) the
201/// origin is in the upper-left corner and the rectangle extends towards the
202/// lower-right corner.
203///
204/// See [Apple's documentation](https://developer.apple.com/documentation/corefoundation/cgrect?language=objc).
205#[repr(C)]
206#[derive(Clone, Copy, Debug, PartialEq, Default)]
207pub struct CGRect {
208 /// The coordinates of the rectangle’s origin.
209 pub origin: CGPoint,
210 /// The dimensions of the rectangle.
211 pub size: CGSize,
212}
213
214#[cfg(feature = "objc2")]
215unsafe impl Encode for CGRect {
216 const ENCODING: Encoding =
217 Encoding::Struct(names::RECT, &[CGPoint::ENCODING, CGSize::ENCODING]);
218}
219
220#[cfg(feature = "objc2")]
221unsafe impl RefEncode for CGRect {
222 const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
223}
224
225impl CGRect {
226 /// Create a new rectangle with the given origin and dimensions.
227 ///
228 ///
229 /// # Examples
230 ///
231 /// ```
232 /// use objc2_core_foundation::{CGPoint, CGRect, CGSize};
233 /// let origin = CGPoint::new(10.0, -2.3);
234 /// let size = CGSize::new(5.0, 0.0);
235 /// let rect = CGRect::new(origin, size);
236 /// ```
237 #[inline]
238 #[doc(alias = "NSMakeRect")]
239 #[doc(alias = "CGRectMake")]
240 pub const fn new(origin: CGPoint, size: CGSize) -> Self {
241 Self { origin, size }
242 }
243
244 /// A rectangle with origin (0.0, 0.0) and zero width and height.
245 #[doc(alias = "NSZeroRect")]
246 #[doc(alias = "CGRectZero")]
247 pub const ZERO: Self = Self::new(CGPoint::ZERO, CGSize::ZERO);
248
249 /// Returns a rectangle with a positive width and height.
250 ///
251 ///
252 /// # Examples
253 ///
254 /// ```
255 /// use objc2_core_foundation::{CGPoint, CGRect, CGSize};
256 /// let origin = CGPoint::new(1.0, 1.0);
257 /// let size = CGSize::new(-5.0, -2.0);
258 /// let rect = CGRect::new(origin, size);
259 /// assert_eq!(rect.standardize().size, CGSize::new(5.0, 2.0));
260 /// ```
261 #[inline]
262 #[doc(alias = "CGRectStandardize")]
263 #[cfg(feature = "std")] // `abs` only available in core since Rust 1.85
264 pub fn standardize(self) -> Self {
265 Self::new(self.origin, self.size.abs())
266 }
267
268 /// The smallest coordinate of the rectangle.
269 #[inline]
270 #[doc(alias = "CGRectGetMinX")]
271 #[doc(alias = "CGRectGetMinY")]
272 #[doc(alias = "NSMinX")]
273 #[doc(alias = "NSMinY")]
274 pub fn min(self) -> CGPoint {
275 self.origin
276 }
277
278 /// The center point of the rectangle.
279 #[inline]
280 #[doc(alias = "CGRectGetMidX")]
281 #[doc(alias = "CGRectGetMidY")]
282 #[doc(alias = "NSMidX")]
283 #[doc(alias = "NSMidY")]
284 pub fn mid(self) -> CGPoint {
285 CGPoint::new(
286 self.origin.x + (self.size.width * 0.5),
287 self.origin.y + (self.size.height * 0.5),
288 )
289 }
290
291 /// The largest coordinate of the rectangle.
292 #[inline]
293 #[doc(alias = "CGRectGetMaxX")]
294 #[doc(alias = "CGRectGetMaxY")]
295 #[doc(alias = "NSMaxX")]
296 #[doc(alias = "NSMaxY")]
297 pub fn max(self) -> CGPoint {
298 CGPoint::new(
299 self.origin.x + self.size.width,
300 self.origin.y + self.size.height,
301 )
302 }
303
304 /// Returns whether a rectangle has zero width or height.
305 ///
306 ///
307 /// # Examples
308 ///
309 /// ```
310 /// use objc2_core_foundation::{CGPoint, CGRect, CGSize};
311 /// assert!(CGRect::ZERO.is_empty());
312 /// let point = CGPoint::new(1.0, 2.0);
313 /// assert!(CGRect::new(point, CGSize::ZERO).is_empty());
314 /// assert!(!CGRect::new(point, CGSize::new(1.0, 1.0)).is_empty());
315 /// ```
316 #[inline]
317 #[doc(alias = "CGRectIsEmpty")]
318 pub fn is_empty(self) -> bool {
319 !(self.size.width > 0.0 && self.size.height > 0.0)
320 // TODO: NaN handling?
321 // self.size.width <= 0.0 || self.size.height <= 0.0
322 }
323
324 // TODO: NSContainsRect / CGRectContainsRect
325 // TODO: NSDivideRect / CGRectDivide
326 // TODO: NSInsetRect / CGRectInset
327 // TODO: NSIntegralRect / CGRectIntegral
328 // TODO: NSIntersectionRect / CGRectIntersection
329 // TODO: NSUnionRect / CGRectUnion
330 // TODO: NSIntersectsRect / CGRectIntersectsRect
331 // TODO: NSMouseInRect
332 // TODO: NSMouseInRect
333 // TODO: NSPointInRect / CGRectContainsPoint
334 // TODO: NSOffsetRect / CGRectOffset
335
336 // TODO: CGRectIsNull
337 // TODO: CGRectIsInfinite
338 // TODO: CGRectInfinite
339 // TODO: CGRectNull
340
341 // TODO: NSHeight / CGRectGetHeight (standardized)
342 // TODO: NSWidth / CGRectGetWidth (standardized)
343}
344
345// TODO: Derive this
346impl Default for CGVector {
347 fn default() -> Self {
348 Self { dx: 0.0, dy: 0.0 }
349 }
350}
351
352impl CGVector {
353 #[inline]
354 #[doc(alias = "CGVectorMake")]
355 pub const fn new(dx: CGFloat, dy: CGFloat) -> Self {
356 Self { dx, dy }
357 }
358}
359
360// TODO: Derive this
361impl Default for CGAffineTransform {
362 fn default() -> Self {
363 Self {
364 a: 0.0,
365 b: 0.0,
366 c: 0.0,
367 d: 0.0,
368 tx: 0.0,
369 ty: 0.0,
370 }
371 }
372}
373
374#[cfg(test)]
375mod tests {
376 use super::*;
377
378 #[test]
379 fn test_cgsize_new() {
380 CGSize::new(1.0, 1.0);
381 CGSize::new(0.0, -0.0);
382 CGSize::new(-0.0, 0.0);
383 CGSize::new(-0.0, -0.0);
384 CGSize::new(-1.0, -1.0);
385 CGSize::new(-1.0, 1.0);
386 CGSize::new(1.0, -1.0);
387 }
388}