Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 47 additions & 6 deletions Sources/OpenCoreGraphics/CGRect+Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,71 @@ extension CGRect {
}

public var width: CGFloat {
return size.width
return abs(size.width)
}

public var height: CGFloat {
return size.height
return abs(size.height)
}

public var minX: CGFloat {
return origin.x
return min(origin.x, origin.x + size.width)
}

public var minY: CGFloat {
return origin.y
return min(origin.y, origin.y + size.height)
}

public var maxX: CGFloat {
return origin.x + size.width
return max(origin.x, origin.x + size.width)
}

public var maxY: CGFloat {
return origin.y + size.height
return max(origin.y, origin.y + size.height)
}

public var midX: CGFloat {
return origin.x + size.width / 2
}

public var midY: CGFloat {
return origin.y + size.height / 2
}

public var isNull: Bool {
return origin.x == .infinity || origin.y == .infinity
}

public var standardized: CGRect {
guard !isNull else { return .null }
return CGRect(x: minX, y: minY, width: width, height: height)
}

public func contains(_ point: CGPoint) -> Bool {
return !isNull && point.x >= minX && point.x < maxX
&& point.y >= minY && point.y < maxY
}

public func intersects(_ rect: CGRect) -> Bool {
guard !isNull && !rect.isNull else { return false }
func overlaps(_ start: CGFloat, _ end: CGFloat, _ otherStart: CGFloat, _ otherEnd: CGFloat) -> Bool {
if start == otherStart { return true }
return start < otherStart ? otherStart < end : start < otherEnd
}
return overlaps(minX, maxX, rect.minX, rect.maxX)
&& overlaps(minY, maxY, rect.minY, rect.maxY)
}

public func union(_ rect: CGRect) -> CGRect {
if isNull { return rect }
if rect.isNull { return self }
let x = min(minX, rect.minX)
let y = min(minY, rect.minY)
return CGRect(x: x, y: y, width: max(maxX, rect.maxX) - x, height: max(maxY, rect.maxY) - y)
}

public static let zero = CGRect(origin: .zero, size: .zero)
public static let null = CGRect(x: CGFloat.infinity, y: CGFloat.infinity, width: 0, height: 0)
}

extension CGRect: Swift.Hashable {
Expand Down
99 changes: 99 additions & 0 deletions Tests/OpenCoreGraphicsShimsTests/CGRectTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//
// CGRectTests.swift
// OpenCoreGraphicsShimsTests

import OpenCoreGraphicsShims
import Testing

struct CGRectTests {
@Test
func negativeSizeBounds() {
let rect = CGRect(x: 11, y: 22, width: -10, height: -20)
#expect(rect.width == 10)
#expect(rect.height == 20)
#expect(rect.minX == 1)
#expect(rect.midX == 6)
#expect(rect.maxX == 11)
#expect(rect.minY == 2)
#expect(rect.midY == 12)
#expect(rect.maxY == 22)
#expect(rect.standardized == CGRect(x: 1, y: 2, width: 10, height: 20))
}

@Test
func nullRectangles() {
#expect(CGRect.null.isNull)
#expect(!CGRect.zero.isNull)
#expect(CGRect.null.standardized == .null)
let rect = CGRect(x: CGFloat.infinity, y: 2, width: 10, height: 20)
#expect(rect.isNull)
#expect(rect.standardized == .null)
#expect(!CGRect(x: -CGFloat.infinity, y: 2, width: 10, height: 20).isNull)
}

@Test(arguments: [
(CGPoint(x: 0, y: 0), true),
(CGPoint(x: 5, y: 5), true),
(CGPoint(x: 10, y: 5), false),
(CGPoint(x: 5, y: 10), false),
(CGPoint(x: -1, y: 5), false),
])
func containsPoint(point: CGPoint, expected: Bool) {
let rect = CGRect(x: 10, y: 10, width: -10, height: -10)
#expect(rect.contains(point) == expected)
#expect(!CGRect.null.contains(point))
#expect(!CGRect.zero.contains(point))
}

@Test(arguments: [
(CGRect(x: 5, y: 5, width: 10, height: 10), true),
(CGRect(x: 11, y: 0, width: 10, height: 10), false),
(CGRect(x: 5, y: 5, width: 0, height: 0), true),
(CGRect(x: 5, y: 5, width: 0, height: 2), true),
(CGRect.zero, true),
(CGRect.null, false),
])
func intersectionBounds(other: CGRect, expected: Bool) {
let rect = CGRect(x: 10, y: 10, width: -10, height: -10)
#expect(rect.intersects(other) == expected)
#expect(other.intersects(rect) == expected)
#expect(!CGRect.null.intersects(other))
}

@Test(arguments: [
CGRect(x: 10, y: 0, width: 10, height: 10),
CGRect(x: 10, y: 5, width: 0, height: 0),
])
func maximumEdgeIntersection(other: CGRect) {
let rect = CGRect(x: 10, y: 10, width: -10, height: -10)
// OpenCoreGraphics follows Darwin CoreGraphics for boundary intersections.
// Linux currently uses Swift Foundation's CGRect, which includes these
// boundary contacts. The Linux expectation records this platform difference.
#if canImport(Darwin)
let expected = false
#else
let expected = true
#endif
#expect(rect.intersects(other) == expected)
#expect(other.intersects(rect) == expected)
}

@Test
func unionIncludesDegenerateBounds() {
let rect = CGRect(x: 10, y: 10, width: -10, height: -10)
#expect(rect.union(.null) == rect)
#expect(CGRect.null.union(rect) == rect)
#expect(CGRect.null.union(.null) == .null)
let point = CGRect(x: 15, y: 20, width: 0, height: 0)
#expect(CGRect.zero.union(point) == CGRect(x: 0, y: 0, width: 15, height: 20))
#expect(rect.union(point) == CGRect(x: 0, y: 0, width: 15, height: 20))
}

@Test
func coincidentDegenerateIntersections() {
#expect(CGRect.zero.intersects(.zero))
let line = CGRect(x: 0, y: 0, width: 0, height: 10)
#expect(line.intersects(CGRect(x: 0, y: 2, width: 0, height: 4)))
#expect(!line.intersects(CGRect(x: 1, y: 2, width: 0, height: 4)))
}
}
Loading