Rob Napier is correct, but unfortunately, most people don't know what he knows. So, as the other Rob said in the comments for his answer, a lot of APIs just use CGPoint
for every floating point 2-tuple. So practically, you're going to want all of those from Apple to be synesthetic (i.e. dimensions of different quantities become tied together).
import CoreGraphics
import simd
public extension SIMD2 {
init(_ scalars: (Scalar, Scalar)) {
self.init(scalars.0, scalars.1)
}
init<Float2: CommonVectorOperable>(_ float2: Float2)
where Float2.Operand == Self {
self = float2.convertedToOperand
}
}
public extension SIMD2 where Scalar == CGFloat.NativeType {
// MARK: - Initializers
init(_ x: CGFloat, _ y: CGFloat) {
self.init(x.native, y.native)
}
}
import CoreGraphics
import simd
public protocol CGFloat2: CommonVectorOperable
where Operand == SIMD2<CGFloat.NativeType> {
init(_: CGFloat.NativeType, _: CGFloat.NativeType)
}
// MARK: CommonOperable
public extension CGFloat2 {
init(_ simd: Operand) {
self.init(simd.x, simd.y)
}
}
// MARK: CommonVectorOperable
public extension CGFloat2 {
static var convertToOperandScalar: (CGFloat) -> Operand.Scalar { \.native }
}
extension CGPoint: CGFloat2 {
public init(_ x: CGFloat.NativeType, _ y: CGFloat.NativeType) {
self.init(x: x, y: y)
}
public var convertedToOperand: SIMD2<CGFloat.NativeType> { .init(x, y) }
}
extension CGSize: CGFloat2 {
public init(_ width: CGFloat.NativeType, _ height: CGFloat.NativeType) {
self.init(width: width, height: height)
}
public var convertedToOperand: SIMD2<CGFloat.NativeType> { .init(width, height) }
}
extension CGVector: CGFloat2 {
public init(_ dx: CGFloat.NativeType, _ dy: CGFloat.NativeType) {
self.init(dx: dx, dy: dy)
}
public var convertedToOperand: Operand { .init(dx, dy) }
}
/// A type that can operate with other types via intermediate conversion.
public protocol CommonOperable {
/// The type to be converted to, for interoperability.
associatedtype Operand
init(_: Operand)
var convertedToOperand: Operand { get }
}
public extension CommonOperable {
init<Operable: CommonOperable>(_ operable: Operable)
where Operand == Operable.Operand {
self.init(operable.convertedToOperand)
}
}
// MARK: internal
extension CommonOperable {
/// Forwards operators to converted operands.
static func operate<Operable1: CommonOperable, Result: CommonOperable>(
_ operable0: Self,
_ operate: (Operand, Operand) -> Operand,
_ operable1: Operable1
) -> Result
where Operand == Operable1.Operand, Operand == Result.Operand {
Result(
operate(
operable0.convertedToOperand,
operable1.convertedToOperand
)
)
}
/// Forwards `Operand` methods to converted operands.
func performMethod<Parameters, Result>(
_ method: (Operand) -> (Parameters) -> Result,
_ parameters: Parameters
) -> Result {
method(self.convertedToOperand)(parameters)
}
/// Forwards `Operand` methods to converted operands.
/// - Returns: A converted result.
func performMethod<Parameters, Result: CommonOperable>(
_ method: (Operand) -> (Parameters) -> Operand,
_ parameters: Parameters
) -> Result
where Operand == Result.Operand {
Result(performMethod(method, parameters))
}
}
/// A vector type that can operate with other types via intermediate conversion.
public protocol CommonVectorOperable: CommonOperable where Operand: SIMD {
associatedtype Scalar
static var convertToOperandScalar: (Scalar) -> Operand.Scalar { get }
}
public extension CommonVectorOperable {
/// Forwards operators to converted operands.
static func operate(
_ vector: Self,
_ operate: (Operand, Operand.Scalar) -> Operand,
_ scalar: Scalar
) -> Self {
Self(
operate(
vector.convertedToOperand,
convertToOperandScalar(scalar)
)
)
}
}
public extension CommonOperable
where Operand: SIMD, Operand.Scalar: FloatingPoint {
static func + <Operable1: CommonOperable, Result: CommonOperable>
(operable0: Self, operable1: Operable1) -> Result
where Operand == Operable1.Operand, Operand == Result.Operand {
operate(operable0, +, operable1)
}
static func + (operable0: Self, operable1: Self) -> Self {
operate(operable0, +, operable1)
}
static func += <Operable1: CommonOperable>
(operable0: inout Self, operable1: Operable1)
where Operand == Operable1.Operand {
operable0 = operable0 + operable1
}
// MARK: -
static func - <Operable1: CommonOperable, Result: CommonOperable>
(operable0: Self, operable1: Operable1) -> Result
where Operand == Operable1.Operand, Operand == Result.Operand {
operate(operable0, -, operable1)
}
static func - (operable0: Self, operable1: Self) -> Self {
operate(operable0, -, operable1)
}
static func -= <Operable1: CommonOperable>
(operable0: inout Self, operable1: Operable1)
where Operand == Operable1.Operand {
operable0 = operable0 - operable1
}
// MARK: -
static func / <Divisor: CommonOperable, Result: CommonOperable>
(dividend: Self, divisor: Divisor) -> Result
where Operand == Divisor.Operand, Operand == Result.Operand {
operate(dividend, /, divisor)
}
static func / (dividend: Self, divisor: Self) -> Self {
operate(dividend, /, divisor)
}
static func /= <Divisor: CommonOperable>
(dividend: inout Self, divisor: Divisor)
where Operand == Divisor.Operand {
dividend = dividend / divisor
}
}
public extension CommonOperable where Operand == SIMD2<CGFloat.NativeType> {
func clamped<Result: CommonOperable>(within bounds: CGRect) -> Result
where Operand == Result.Operand {
performMethod(Operand.clamped(within:), bounds)
}
}
public extension CommonVectorOperable where Operand.Scalar: FloatingPoint {
static func * (vector: Self, scalar: Scalar) -> Self {
operate(vector, *, scalar)
}
static func / (dividend: Self, divisor: Scalar) -> Self {
operate(dividend, /, divisor)
}
}