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}