|
| 1 | +// Copyright © 2025 Saleem Abdulrasool <compnerd@compnerd.org> |
| 2 | +// SPDX-License-Identifier: BSD-3-Clause |
| 3 | + |
| 4 | +@frozen |
| 5 | +public struct Rect { |
| 6 | + public let origin: Point |
| 7 | + public let size: Size |
| 8 | + |
| 9 | + public init(origin: Point, size: Size) { |
| 10 | + self.origin = origin |
| 11 | + self.size = size |
| 12 | + } |
| 13 | +} |
| 14 | + |
| 15 | +extension Rect: AdditiveArithmetic { |
| 16 | + public static var zero: Rect { |
| 17 | + Rect(origin: .zero, size: .zero) |
| 18 | + } |
| 19 | + |
| 20 | + public static func + (_ lhs: Rect, _ rhs: Rect) -> Rect { |
| 21 | + Rect(origin: lhs.origin + rhs.origin, size: lhs.size + rhs.size) |
| 22 | + } |
| 23 | + |
| 24 | + public static func - (_ lhs: Rect, _ rhs: Rect) -> Rect { |
| 25 | + Rect(origin: lhs.origin - rhs.origin, size: lhs.size - rhs.size) |
| 26 | + } |
| 27 | +} |
| 28 | + |
| 29 | +extension Rect: Codable { } |
| 30 | + |
| 31 | +extension Rect: CustomStringConvertible { |
| 32 | + public var description: String { |
| 33 | + "{\(origin), \(size)}" |
| 34 | + } |
| 35 | +} |
| 36 | + |
| 37 | +extension Rect: Equatable { } |
| 38 | + |
| 39 | +extension Rect: Hashable { } |
| 40 | + |
| 41 | +extension Rect: Sendable { } |
| 42 | + |
| 43 | +extension Rect { |
| 44 | + public var isEmpty: Bool { |
| 45 | + size.width <= 0 || size.height <= 0 |
| 46 | + } |
| 47 | +} |
| 48 | + |
| 49 | +extension Rect { |
| 50 | + public var center: Point { |
| 51 | + Point(x: origin.x + size.width / 2, y: origin.y + size.height / 2) |
| 52 | + } |
| 53 | +} |
| 54 | + |
| 55 | +extension Rect { |
| 56 | + public func contains(_ point: Point) -> Bool { |
| 57 | + return point.x >= origin.x && point.x < (origin.x + size.width) && |
| 58 | + point.y >= origin.y && point.y < (origin.y + size.height) |
| 59 | + } |
| 60 | + |
| 61 | + public func contains(_ rect: Rect) -> Bool { |
| 62 | + return rect.origin.x >= origin.x && |
| 63 | + (rect.origin.x + rect.size.width) <= (origin.x + size.width) && |
| 64 | + rect.origin.y >= origin.y && |
| 65 | + (rect.origin.y + rect.size.height) <= (origin.y + size.height) |
| 66 | + } |
| 67 | +} |
| 68 | + |
| 69 | +extension Rect { |
| 70 | + public func intersects(_ rect: Rect) -> Bool { |
| 71 | + return (origin.x + size.width) > rect.origin.x && |
| 72 | + origin.x < (rect.origin.x + rect.size.width) && |
| 73 | + (origin.y + size.height) > rect.origin.y && |
| 74 | + origin.y < (rect.origin.y + rect.size.height) |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +extension Rect { |
| 79 | + public func union(_ rect: Rect) -> Rect { |
| 80 | + let x = (min: min(origin.x, rect.origin.x), |
| 81 | + max: max(origin.x + size.width, rect.origin.x + rect.size.width)) |
| 82 | + let y = (min: min(origin.y, rect.origin.y), |
| 83 | + max: max(origin.y + size.height, rect.origin.y + rect.size.height)) |
| 84 | + |
| 85 | + return Rect(origin: Point(x: x.min, y: y.min), |
| 86 | + size: Size(width: x.max - x.min, height: y.max - y.min)) |
| 87 | + } |
| 88 | +} |
| 89 | + |
| 90 | +extension Rect { |
| 91 | + public func intersection(with rect: Rect) -> Rect? { |
| 92 | + let x = (min: max(origin.x, rect.origin.x), |
| 93 | + max: min(origin.x + size.width, rect.origin.x + rect.size.width)) |
| 94 | + let y = (min: max(origin.y, rect.origin.y), |
| 95 | + max: min(origin.y + size.height, rect.origin.y + rect.size.height)) |
| 96 | + |
| 97 | + guard x.min < x.max, y.min < y.max else { return nil } |
| 98 | + return Rect(origin: Point(x: x.min, y: y.min), |
| 99 | + size: Size(width: x.max - x.min, height: y.max - y.min)) |
| 100 | + } |
| 101 | +} |
| 102 | + |
| 103 | +extension Rect { |
| 104 | + public func offset(by point: Point) -> Rect { |
| 105 | + return Rect(origin: origin + point, size: size) |
| 106 | + } |
| 107 | +} |
0 commit comments