diff --git a/Sources/ChouTiUI/Universal/Layout/Geometry/CGSize+Extensions.swift b/Sources/ChouTiUI/Universal/Layout/Geometry/CGSize+Extensions.swift deleted file mode 100644 index dcfd148..0000000 --- a/Sources/ChouTiUI/Universal/Layout/Geometry/CGSize+Extensions.swift +++ /dev/null @@ -1,390 +0,0 @@ -// -// CGSize+Extensions.swift -// -// Created by Honghao Zhang on 11/10/20. -// Copyright © 2024 ChouTi. All rights reserved. -// - -import CoreGraphics -import ChouTi - -public extension CGSize { - - /// The greatest finite size. - static let greatestFiniteMagnitude = CGSize(.greatestFiniteMagnitude, .greatestFiniteMagnitude) - - /// Create a CGSize with the given width and height. - /// - /// - Parameters: - /// - width: The width of the size. - /// - height: The height of the size. - @inlinable - @inline(__always) - init(_ width: CGFloat, _ height: CGFloat) { - self.init(width: width, height: height) - } - - /// Create a CGSize with the given width and height. - /// - /// - Parameters: - /// - width: The width of the size. - /// - height: The height of the size. - @inlinable - @inline(__always) - init(_ width: Int, _ height: Int) { - self.init(width: width, height: height) - } - - /// Create a CGSize with the given size. - /// - /// - Parameters: - /// - size: The size of the size. - @inlinable - @inline(__always) - init(_ size: CGFloat) { - self.init(width: size, height: size) - } - - /// Create a CGSize with the given size. - /// - /// - Parameters: - /// - size: The size of the size. - @inlinable - @inline(__always) - init(_ size: Int) { - self.init(width: size, height: size) - } - - /// Create a CGSize with the given width and aspect ratio. - /// - /// - Parameters: - /// - width: The width of the size. - /// - aspectRatio: The aspect ratio of the size. - @inlinable - @inline(__always) - init(width: CGFloat, aspectRatio: CGFloat) { - self.init(width: width, height: width / aspectRatio) - } - - /// Create a CGSize with the given height and aspect ratio. - /// - /// - Parameters: - /// - height: The height of the size. - /// - aspectRatio: The aspect ratio of the size. - @inlinable - @inline(__always) - init(height: CGFloat, aspectRatio: CGFloat) { - self.init(width: height * aspectRatio, height: height) - } - - static prefix func - (value: Self) -> Self { - Self(-value.width, -value.height) - } - - static func + (left: CGSize, right: CGSize) -> CGSize { - CGSize(left.width + right.width, left.height + right.height) - } - - static func += (left: inout CGSize, right: CGSize) { - left.width += right.width - left.height += right.height - } - - static func - (left: CGSize, right: CGSize) -> CGSize { - CGSize(left.width - right.width, left.height - right.height) - } - - static func -= (left: inout CGSize, right: CGSize) { - left.width -= right.width - left.height -= right.height - } - - static func * (left: CGSize, right: CGSize) -> CGSize { - CGSize(left.width * right.width, left.height * right.height) - } - - static func *= (left: inout CGSize, right: CGSize) { - left.width *= right.width - left.height *= right.height - } - - static func / (left: CGSize, right: CGSize) -> CGSize { - CGSize(left.width / right.width, left.height / right.height) - } - - static func /= (left: inout CGSize, right: CGSize) { - left.width /= right.width - left.height /= right.height - } - - static func * (left: CGSize, right: CGFloat) -> CGSize { - CGSize(left.width * right, left.height * right) - } - - static func *= (left: inout CGSize, right: CGFloat) { - left.width *= right - left.height *= right - } - - static func / (left: CGSize, right: CGFloat) -> CGSize { - CGSize(left.width / right, left.height / right) - } - - static func /= (left: inout CGSize, right: CGFloat) { - left.width /= right - left.height /= right - } - - /// Add the given width and height to the size. - /// - /// - Parameters: - /// - width: The width to add to the size. - /// - height: The height to add to the size. - func adding(width: CGFloat = 0, height: CGFloat = 0) -> CGSize { - CGSize(width: self.width + width, height: self.height + height) - } - - /// Subtract the given width and height from the size. - /// - /// - Parameters: - /// - width: The width to subtract from the size. - /// - height: The height to subtract from the size. - func subtracting(width: CGFloat = 0, height: CGFloat = 0) -> CGSize { - CGSize(width: self.width - width, height: self.height - height) - } - - /// Multiply the size by the given factor. - /// - /// - Parameters: - /// - factor: The factor to multiply the size by. - func multiply(by factor: CGFloat) -> CGSize { - if factor == 1 { - return self - } - return CGSize(width * factor, height * factor) - } - - /// Inset the size by the given length. - /// - /// - Parameters: - /// - length: The length to inset the size by. Positive value decrements size in both sides. - func inset(_ length: CGFloat) -> CGSize { - if length == 0 { - return self - } - return subtracting(width: length * 2, height: length * 2) - } - - /// Inset the size by the given horizontal and vertical length. - /// - /// - Parameters: - /// - horizontal: The horizontal length to inset the size by. Positive value decrement size in both sides. - /// - vertical: The vertical length to inset the size by. Positive value decrement size in both sides. - func inset(horizontal: CGFloat = 0, vertical: CGFloat = 0) -> CGSize { - subtracting(width: horizontal * 2, height: vertical * 2) - } - - /// Inset the size by the given horizontal and vertical length. - /// - /// - Parameters: - /// - horizontal: The horizontal length to inset the size by. Positive value decrement size in both sides. - /// - vertical: The vertical length to inset the size by. Positive value decrement size in both sides. - func inset(_ horizontal: CGFloat, _ vertical: CGFloat) -> CGSize { - subtracting(width: horizontal * 2, height: vertical * 2) - } - - /// Get a new CGSize with new width. - /// - /// - Parameters: - /// - newWidth: The new width of the size. - func width(_ newWidth: CGFloat) -> CGSize { - CGSize(newWidth, height) - } - - /// Get a new CGSize with new height. - /// - /// - Parameters: - /// - newHeight: The new height of the size. - func height(_ newHeight: CGFloat) -> CGSize { - CGSize(width, newHeight) - } - - /// Check if the size is positive. - @inlinable - @inline(__always) - var isPositive: Bool { - width > 0 && height > 0 - } - - /// Check if the size is non-negative. - @inlinable - @inline(__always) - var isNonNegative: Bool { - width >= 0 && height >= 0 - } - - /// Get the area of the size. - @inlinable - @inline(__always) - var area: CGFloat { - width * height - } - - // MARK: - Shapes - - /// Get the aspect ratio (width / height). - @inlinable - @inline(__always) - var aspectRatio: CGFloat { - width / height - } - - /// Check if the size is a portrait size. - /// - /// Square shape is counted as portrait. - @inlinable - @inline(__always) - var isPortrait: Bool { - aspectRatio <= 1 - } - - /// Check if the size is a landscape size. - /// - /// Square shape is **NOT** counted as landscape. - @inlinable - @inline(__always) - var isLandScape: Bool { - aspectRatio > 1 - } - - /// Check if the size is a square. - @inlinable - @inline(__always) - var isSquare: Bool { - aspectRatio == 1 - } -} - -// MARK: - Rounding - -public extension CGSize { - - /// Round the size up to the nearest value that is a multiple of the given scale factor. - /// - /// This is useful for pixel-perfect UI rendering where the size needs to be a multiple of the device's scale factor. - /// - /// - Parameters: - /// - scaleFactor: The scale factor to round the size by. - mutating func roundUp(scaleFactor: CGFloat) { - guard scaleFactor > 0 else { - ChouTi.assertFailure("Scale factor must be positive.", metadata: [ - "scaleFactor": "\(scaleFactor)", - ]) - return - } - - let pixelWidth: CGFloat = 1 / scaleFactor - width = width.ceil(nearest: pixelWidth) - height = height.ceil(nearest: pixelWidth) - } - - /// Round the size up to the nearest value that is a multiple of the given scale factor. - /// - /// This is useful for pixel-perfect UI rendering where the size needs to be a multiple of the device's scale factor. - /// - /// - Parameters: - /// - scaleFactor: The scale factor to round the size by. - func roundedUp(scaleFactor: CGFloat) -> CGSize { - guard scaleFactor > 0 else { - ChouTi.assertFailure("Scale factor must be positive.", metadata: [ - "scaleFactor": "\(scaleFactor)", - ]) - return .zero - } - - let pixelWidth: CGFloat = 1 / scaleFactor - let width = width.ceil(nearest: pixelWidth) - let height = height.ceil(nearest: pixelWidth) - return CGSize(width: width, height: height) - } - - /// Round the size to the nearest value that is a multiple of the given scale factor. - /// - /// This is useful for pixel-perfect UI rendering where the size needs to be a multiple of the device's scale factor. - /// - /// - Parameters: - /// - scaleFactor: The scale factor to round the size by. - func rounded(scaleFactor: CGFloat) -> CGSize { - guard scaleFactor > 0 else { - ChouTi.assertFailure("Scale factor must be positive.", metadata: [ - "scaleFactor": "\(scaleFactor)", - ]) - return .zero - } - - let pixelWidth: CGFloat = 1 / scaleFactor - let width = width.round(nearest: pixelWidth) - let height = height.round(nearest: pixelWidth) - return CGSize(width: width, height: height) - } - - /// Round the size up to the nearest whole number. - mutating func roundUp() { - width = ceil(width) - height = ceil(height) - } - - /// Round the size up to the nearest whole number. - @inlinable - @inline(__always) - func roundedUp() -> CGSize { - CGSize(ceil(width), ceil(height)) - } -} - -public extension CGSize { - - /// Check if the size is approximately equal to the given size. - /// - /// - Parameters: - /// - other: The size to compare to. - /// - absoluteTolerance: The absolute tolerance to use for the comparison. - func isApproximatelyEqual(to other: Self, absoluteTolerance: CGFloat) -> Bool { - width.isApproximatelyEqual(to: other.width, absoluteTolerance: absoluteTolerance) && - height.isApproximatelyEqual(to: other.height, absoluteTolerance: absoluteTolerance) - } -} - -public extension CGSize { - - /// The rectangle orientation - enum Orientation { - case portrait - case landscape - } - - /// Get rectangle orientation. - /// - /// Square shape is counted as portrait. Zero size is also counted as portrait. - var orientation: Orientation { - guard area > 0, self != .zero else { - return .portrait // assume zero size is portrait - } - - if width <= height { - return .portrait - } else { - return .landscape - } - } -} - -// MARK: - Hashable - -extension CGSize: Hashable { - - public func hash(into hasher: inout Hasher) { - hasher.combine(width) - hasher.combine(height) - } -} diff --git a/Tests/ChouTiUITests/Universal/Layout/Geometry/CGSize+ExtensionsTests.swift b/Tests/ChouTiUITests/Universal/Layout/Geometry/CGSize+ExtensionsTests.swift deleted file mode 100644 index 1a638c6..0000000 --- a/Tests/ChouTiUITests/Universal/Layout/Geometry/CGSize+ExtensionsTests.swift +++ /dev/null @@ -1,241 +0,0 @@ -// -// CGSize+ExtensionsTests.swift -// -// Created by Honghao Zhang on 5/29/21. -// Copyright © 2024 ChouTi. All rights reserved. -// - -import ChouTiTest - -import ChouTi -import ChouTiUI - -class CGSize_ExtensionsTests: XCTestCase { - - func testInit() { - expect(CGSize(100, 200)) == CGSize(width: 100, height: 200) - expect(CGSize(100)) == CGSize(width: 100, height: 100) - expect(CGSize(100.2)) == CGSize(width: 100.2, height: 100.2) - - expect(CGSize(width: 100, aspectRatio: 2)) == CGSize(width: 100, height: 50) - expect(CGSize(height: 200, aspectRatio: 0.5)) == CGSize(width: 100, height: 200) - } - - func testOperators() { - expect(-CGSize(100, 200)) == CGSize(width: -100, height: -200) - - let size1 = CGSize(12.9, 23.3) - let size2 = CGSize(2.5, 3.8) - expect(size1 + size2) == CGSize(15.4, 27.1) - expect(size1 - size2) == CGSize(10.4, 19.5) - expect(size1 * size2) == CGSize(32.25, 88.53999999999999) - expect(size1 / size2) == CGSize(5.16, 6.131578947368421) - - var size = size1 - size += size2 - expect(size) == CGSize(15.4, 27.1) - size = size1 - size -= size2 - expect(size) == CGSize(10.4, 19.5) - size = size1 - size *= size2 - expect(size) == CGSize(32.25, 88.53999999999999) - size = size1 - size /= size2 - expect(size) == CGSize(5.16, 6.131578947368421) - - expect(size1 * 2) == CGSize(25.8, 46.6) - expect(size1 / 2) == CGSize(6.45, 11.65) - size = size1 - size *= 2 - expect(size) == CGSize(25.8, 46.6) - size = size1 - size /= 2 - expect(size) == CGSize(6.45, 11.65) - } - - func testAdding() { - let size = CGSize(12.9, 23.3) - expect(size.adding(width: 12.1, height: 26.7)) == CGSize(25, 50) - expect(size.adding(width: 12.1)) == CGSize(25, 23.3) - expect(size.adding(height: 26.7)) == CGSize(12.9, 50) - expect(size.adding()) == CGSize(12.9, 23.3) - } - - func testSubtracting() { - let size = CGSize(15, 20) - expect(size.subtracting(width: 10, height: 5.5)) == CGSize(5, 14.5) - expect(size.subtracting(width: 10)) == CGSize(5, 20) - expect(size.subtracting(height: 5.5)) == CGSize(15, 14.5) - expect(size.subtracting()) == CGSize(15, 20) - } - - func testMultiply() { - let size = CGSize(12.9, 23.3) - expect(size.multiply(by: 2)) == CGSize(25.8, 46.6) - expect(size.multiply(by: 0)) == CGSize.zero - expect(size.multiply(by: -1)) == CGSize(-12.9, -23.3) - expect(size.multiply(by: 0.5)) == CGSize(6.45, 11.65) - expect(size.multiply(by: 1)) == CGSize(12.9, 23.3) - } - - func testInset() { - let size = CGSize(12.9, 23.3) - expect(size.inset(2)) == CGSize(8.9, 19.3) - expect(size.inset(3, 6)) == CGSize(6.9, 11.3) - expect(size.inset(horizontal: 3, vertical: 6)) == CGSize(6.9, 11.3) - expect(size.inset(0)) == CGSize(12.9, 23.3) - expect(size.inset(-2)) == CGSize(16.9, 27.3) - } - - func testWithWidth() { - let size = CGSize(12.9, 23.3) - expect(size.width(10)) == CGSize(10, 23.3) - } - - func testWithHeight() { - let size = CGSize(12.9, 23.3) - expect(size.height(10)) == CGSize(12.9, 10) - } - - func testIsPositive() { - let size = CGSize(12.9, 23.3) - expect(size.isPositive) == true - - let size2 = CGSize(-12.9, 23.3) - expect(size2.isPositive) == false - } - - func testIsNonNegative() { - let size1 = CGSize(12.9, 23.3) - expect(size1.isNonNegative) == true - - let size2 = CGSize(-12.9, 23.3) - expect(size2.isNonNegative) == false - - let size3 = CGSize(-12.9, -23.3) - expect(size3.isNonNegative) == false - } - - func testArea() { - let size1 = CGSize(12, 23) - expect(size1.area) == 276 - } - - func testAspectRatio() { - let size1 = CGSize(12, 23) - expect(size1.aspectRatio) == 0.5217391304347826 - } - - func testShape() { - expect(CGSize(12, 23).isPortrait) == true - expect(CGSize(32, 23).isPortrait) == false - expect(CGSize(32, 32).isPortrait) == true - - expect(CGSize(12, 23).isLandScape) == false - expect(CGSize(32, 23).isLandScape) == true - expect(CGSize(32, 32).isLandScape) == false - - expect(CGSize(12, 23).isSquare) == false - expect(CGSize(32, 23).isSquare) == false - expect(CGSize(32, 32).isSquare) == true - } - - func testRoundUpWithScaleFactor() { - var size2 = CGSize(12.4, 23.7) - size2.roundUp(scaleFactor: 2) - expect(size2) == CGSize(12.5, 24) - size2.roundUp(scaleFactor: 2) - expect(size2) == CGSize(12.5, 24) - size2.roundUp(scaleFactor: 1) - expect(size2) == CGSize(13, 24) - - Assert.setTestAssertionFailureHandler { message, metadata, file, line, column in - expect(message) == "Scale factor must be positive." - expect(metadata) == ["scaleFactor": "0.0"] - } - - size2.roundUp(scaleFactor: 0) - - Assert.resetTestAssertionFailureHandler() - } - - func testRoundedUpWithScaleFactor() { - let size = CGSize(12.9, 23.3) - expect(size.roundedUp()) == CGSize(13, 24) - expect(size.roundedUp(scaleFactor: 2)) == CGSize(13, 23.5) - expect(size.roundedUp(scaleFactor: 3)) == CGSize(13, 23.333333333333332) - - Assert.setTestAssertionFailureHandler { message, metadata, file, line, column in - expect(message) == "Scale factor must be positive." - expect(metadata) == ["scaleFactor": "0.0"] - } - - expect(CGSize(12, 13).roundedUp(scaleFactor: 0)) == CGSize.zero - - Assert.resetTestAssertionFailureHandler() - } - - func testRounded() { - let size = CGSize(12.9, 23.3) - expect(size.rounded(scaleFactor: 2)) == CGSize(13, 23.5) - expect(size.rounded(scaleFactor: 3)) == CGSize(13, 23.333333333333332) - - Assert.setTestAssertionFailureHandler { message, metadata, file, line, column in - expect(message) == "Scale factor must be positive." - expect(metadata) == ["scaleFactor": "0.0"] - } - - expect(CGSize(12, 13).rounded(scaleFactor: 0)) == CGSize.zero - - Assert.resetTestAssertionFailureHandler() - } - - func testRoundUp() { - var size3 = CGSize(12.4, 23.7) - size3.roundUp() - expect(size3) == CGSize(13, 24) - } - - func testApproximatelyEqual() { - let pixelSize = 1 / CGFloat(3) - let absoluteTolerance = pixelSize + 1e-12 - - var size1 = CGSize(23.333333333333332, 0.5217391304347826) - var size2 = CGSize(23.333333333333331, 0.5217391304347826) - expect(size1) == size2 - expect(size1 == size2) == true - expect(size1.equalTo(size2)) == true - - size1 = CGSize(23.333333333333332, 0.5217391304347826) - size2 = CGSize(23.333333333333330, 0.5217391304347826) - expect(size1) != size2 - expect(size1 == size2) == false - expect(size1.equalTo(size2)) == false - expect(size1.isApproximatelyEqual(to: size2, absoluteTolerance: absoluteTolerance)) == true - - size1 = CGSize(187.33333333333331, 135.66666666666666) - size2 = CGSize(187.66666666666666, 135.66666666666666) - expect(size1) != size2 - expect(size1 == size2) == false - expect(size1.equalTo(size2)) == false - expect(size1.isApproximatelyEqual(to: size2, absoluteTolerance: absoluteTolerance)) == true - } - - func testOrientation() { - expect(CGSize(12, 23).orientation) == .portrait - expect(CGSize(23, 12).orientation) == .landscape - expect(CGSize(0, 0).orientation) == .portrait - expect(CGSize(12, 12).orientation) == .portrait - expect(CGSize(0, 12).orientation) == .portrait - expect(CGSize(12, 0).orientation) == .portrait - expect(CGSize(-10, 12).orientation) == .portrait - expect(CGSize(12, -10).orientation) == .portrait - } - - func testHashable() { - let size1 = CGSize(12, 23) - let size2 = CGSize(12, 23) - expect(size1.hashValue) == size2.hashValue - } -}