diff --git a/Sources/SkipLib/CoreGraphics.swift b/Sources/SkipLib/CoreGraphics.swift index ac5a4d0..be1972a 100644 --- a/Sources/SkipLib/CoreGraphics.swift +++ b/Sources/SkipLib/CoreGraphics.swift @@ -16,6 +16,12 @@ public struct CGPoint: Hashable { self.x = x self.y = y } + + public func applying(_ transform: CGAffineTransform) -> CGPoint { + let px = transform.a * x + transform.c * y + transform.tx + let py = transform.b * x + transform.d * y + transform.ty + return CGPoint(x: px, y: py) + } } public struct CGSize: Hashable { @@ -27,10 +33,18 @@ public struct CGSize: Hashable { self.width = width self.height = height } + + public func applying(_ transform: CGAffineTransform) -> CGSize { + let swidth = transform.a * width + transform.c * height + let sheight = transform.b * width + transform.d * height + return CGSize(width: swidth, height: sheight) + } } public struct CGRect: Hashable { public static let zero = CGRect() + public static let null = CGRect(x: .infinity, y: .infinity, width: 0.0, height: 0.0) + public static let infinite = CGRect(x: -Double.MAX_VALUE / 2.0, y: -Double.MAX_VALUE / 2.0, width: Double.MAX_VALUE, height: Double.MAX_VALUE) public var origin: CGPoint public var size: CGSize @@ -42,5 +56,241 @@ public struct CGRect: Hashable { public init(x: Double, y: Double, width: Double, height: Double) { self.init(origin: CGPoint(x: x, y: y), size: CGSize(width: width, height: height)) } + + public init(x: Int, y: Int, width: Int, height: Int) { + self.init(origin: CGPoint(x: Double(x), y: Double(y)), size: CGSize(width: Double(width), height: Double(height))) + } + + public var height: CGFloat { + get { + return size.height + } + set { + size.height = newValue + } + } + + public var width: CGFloat { + get { + return size.width + } + set { + size.width = newValue + } + } + + public var minX: CGFloat { + return width >= 0.0 ? origin.x : origin.x + width + } + + public var midX: CGFloat { + return (minX + maxX) / 2.0 + } + + public var maxX: CGFloat { + return width >= 0.0 ? origin.x + width : origin.x + } + + public var minY: CGFloat { + return height >= 0.0 ? origin.y : origin.y + height + } + + public var midY: CGFloat { + return (minY + maxY) / 2.0 + } + + public var maxY: CGFloat { + return height >= 0.0 ? origin.y + height : origin.y + } + + public var standardized: CGRect { + return CGRect(x: minX, y: minY, width: abs(width), height: abs(height)) + } + + public var integral: CGRect { + if isInfinite || isNull { + return self + } + let rect = standardized + return CGRect(x: floor(rect.minX), y: floor(rect.minY), width: ceil(rect.width), height: ceil(rect.height)) + } + + public func applying(_ transform: CGAffineTransform) -> CGRect { + if isInfinite || isNull { + return self + } + return CGRect(origin: origin.applying(transform), size: size.applying(transform)) + } + + public func insetBy(dx: CGFloat, dy: CGFloat) -> CGRect { + if isInfinite || isNull { + return self + } + let rect = standardized + return CGRect(x: rect.minX + dx, y: rect.minY + dy, width: rect.width - dx * 2.0, height: rect.height - dy * 2.0) + } + + public func offsetBy(dx: CGFloat, dy: CGFloat) -> CGRect { + if isInfinite || isNull { + return self + } + let rect = standardized + return CGRect(x: rect.minX + dx, y: rect.minY + dy, width: rect.width, height: rect.height) + } + + public func union(_ other: CGRect) -> CGRect { + if other.isEmpty { + return self + } else if isEmpty { + return other + } + if other.isInfinite || isInfinite { + return .infinite + } + let rect1 = standardized + let rect2 = other.standardized + let minX = min(rect1.minX, rect2.minX) + let maxX = max(rect1.maxX, rect2.maxX) + let minY = min(rect1.minY, rect2.minY) + let maxY = max(rect1.maxY, rect2.maxY) + return CGRect(x: minX, y: minY, width: maxX - minX, height: maxY - minY) + } + + public func intersection(_ other: CGRect) -> CGRect { + if other.isEmpty || isEmpty { + return .null + } + if other.isInfinite { + return self + } else if isInfinite { + return other + } + let rect1 = standardized + let rect2 = other.standardized + let minX = max(rect1.minX, rect2.minX) + let maxX = min(rect1.maxX, rect2.maxX) + let minY = max(rect1.minY, rect2.minY) + let maxY = min(rect1.maxY, rect2.maxY) + return CGRect(x: minX, y: minY, width: max(0.0, maxX - minX), height: max(0.0, maxY - minY)) + } + + func intersects(_ other: CGRect) -> Bool { + return !intersection(other).isEmpty + } + + func contains(_ point: CGPoint) -> Bool { + let rect = standardized + return point.x >= rect.minX && point.x <= rect.maxX && point.y >= rect.minY && point.y <= rect.maxY + } + + func contains(_ rect: CGRect) -> Bool { + return intersection(rect) == rect + } + + var isEmpty: Bool { + return width == 0.0 && height == 0.0 + } + + var isInfinite: Bool { + return self == .infinite + } + + var isNull: Bool { + return self == .null + } +} + +public struct CGAffineTransform: Codable, Equatable { + public var a = 1.0 + public var b = 0.0 + public var c = 0.0 + public var d = 1.0 + public var tx = 0.0 + public var ty = 0.0 + + public static let identity = CGAffineTransform() + + public init() { + } + + public init(a: Double, b: Double, c: Double, d: Double, tx: Double, ty: Double) { + self.a = a + self.b = b + self.c = c + self.d = d + self.tx = tx + self.ty = ty + } + + public init(a: Float, b: Float, c: Float, d: Float, tx: Float, ty: Float) { + self.a = Double(a) + self.b = Double(b) + self.c = Double(c) + self.d = Double(d) + self.tx = Double(tx) + self.ty = Double(ty) + } + + public init(rotationAngle: CGFloat) { + let sinus = sin(rotationAngle) + let cosinus = cos(rotationAngle) + a = cosinus + b = sinus + c = -sinus + d = cosinus + } + + public init(scaleX: CGFloat, y: CGFloat) { + a = scaleX + d = y + } + + public init(translationX: CGFloat, y: CGFloat) { + tx = translationX + ty = y + } + + public var isIdentity: Bool { + return self == Self.identity + } + + public func concatenating(_ transform: CGAffineTransform) -> CGAffineTransform { + var result = CGAffineTransform() + result.a = transform.a * a + transform.b * c + result.b = transform.a * b + transform.b * d + result.c = transform.c * a + transform.d * c + result.d = transform.c * b + transform.d * d + result.tx = transform.tx * a + transform.ty * c + tx + result.ty = transform.tx * b + transform.ty * d + ty + return result + } + + public func inverted() -> CGAffineTransform { + let determinant = a * d - c * b + if determinant == 0.0 { + return self + } + + var result = CGAffineTransform() + result.a = d / determinant + result.b = -b / determinant + result.c = -c / determinant + result.d = a / determinant + result.tx = (-d * tx + c * ty) / determinant; + result.ty = (b * tx - a * ty) / determinant; + return result + } + + public func rotated(by angle: CGFloat) -> CGAffineTransform { + return concatenating(CGAffineTransform(rotationAngle: angle)) + } + + public func scaledBy(x: CGFloat, y: CGFloat) -> CGAffineTransform { + return concatenating(CGAffineTransform(scaleX: x, y: y)) + } + + public func translatedBy(x: CGFloat, y: CGFloat) -> CGAffineTransform { + return concatenating(CGAffineTransform(translationX: x, y: y)) + } } #endif diff --git a/Sources/SkipLib/Skip/Collections.kt b/Sources/SkipLib/Skip/Collections.kt index c03f969..ea47df4 100644 --- a/Sources/SkipLib/Skip/Collections.kt +++ b/Sources/SkipLib/Skip/Collections.kt @@ -758,4 +758,4 @@ val IntRange.upperBound: Int get() = if (endInclusive == Int.MAX_VALUE) Int.MAX_VALUE else endInclusive + 1 val IntRange.lowerBound: Int get() = start -// IntRange.isEmpty, IntRange.contains can be used as-is +// IntRange.isEmpty, IntRange.contains can be used as-is \ No newline at end of file