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
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ extension Animation: CustomStringConvertible, CustomDebugStringConvertible, Cust
}

public var customMirror: Mirror {
Mirror(box, children: ["base": box.base])
Mirror(self, children: ["base": box.base])
}
}

Expand Down
14 changes: 7 additions & 7 deletions Sources/OpenSwiftUICore/Animation/Animation/UnitCurve.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public struct UnitCurve {
case .circularEaseIn:
return abs(progress / sqrt(1 - progress * progress))
case .circularEaseOut:
return abs((progress - 1.0) / sqrt(-(progress - 2.0) * (progress - 1.0)))
return abs((progress - 1.0) / sqrt(-(progress - 2.0) * progress))
case .circularEaseInOut:
if progress >= 0.5 {
return abs((progress + progress - 2) / sqrt(((progress * -4.0 + 8.0) * progress) - 3.0))
Expand Down Expand Up @@ -279,21 +279,21 @@ extension UnitCurve {
}

package func value(at time: Double) -> Double {
let t = solveX(time, epsilon: .ulpOfOne)
return round(t * (cy + t * (by + ay * t)) * pow(2, 20)) * .ulpOfOne
let t = solveX(time, epsilon: pow(2, -20))
return round(t * (cy + t * (by + ay * t)) * pow(2, 20)) * pow(2, -20)
}

package func velocity(at time: Double) -> Double {
let t = solveX(time, epsilon: .ulpOfOne)
let x = cx + (bx + bx) * t + (ax * 3 * t)
let y = cy + (by + by) * t + (ay * 3 * t)
let t = solveX(time, epsilon: pow(2, -20))
let x = cx + ((bx + bx) + (ax * 3 * t)) * t
let y = cy + ((by + by) + (ay * 3 * t)) * t
guard x != y else {
return 1.0
}
guard x != 0 else {
return y < 0 ? -.infinity : .infinity
}
return round((y / x) * pow(2, 20)) * .ulpOfOne
return round((y / x) * pow(2, 20)) * pow(2, -20)
}

// TODO: Implemented by Copilot. Verify this via unit test later
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// AnimationTests.swift
// OpenSwiftUICompatibilityTests

import Testing

struct AnimationCompatibilityTests {
@Test
func description() {
let animation = Animation.default
#expect(animation.description == "DefaultAnimation()")
#if OPENSWIFTUI_COMPATIBILITY_TEST
#expect(animation.debugDescription == "AnyAnimator(SwiftUI.DefaultAnimation())")
#else
#expect(animation.debugDescription == "AnyAnimator(OpenSwiftUICore.DefaultAnimation())")
#endif
#expect(animation.customMirror.description == "Mirror for Animation")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
//
// UnitCurveCompatibilityTests.swift
// OpenSwiftUICompatibilityTests

import Testing

// MARK: - UnitCurveTests

struct UnitCurveCompatibilityTests {
@Test
func linearCurve() {
let curve = UnitCurve.linear

#expect(curve.value(at: 0.0).isApproximatelyEqual(to: 0.0))
#expect(curve.value(at: 0.25).isApproximatelyEqual(to: 0.25))
#expect(curve.value(at: 0.5).isApproximatelyEqual(to: 0.5))
#expect(curve.value(at: 0.75).isApproximatelyEqual(to: 0.75))
#expect(curve.value(at: 1.0).isApproximatelyEqual(to: 1.0))

#expect(curve.velocity(at: 0.0).isApproximatelyEqual(to: 1.0))
#expect(curve.velocity(at: 0.25).isApproximatelyEqual(to: 1.0))
#expect(curve.velocity(at: 0.5).isApproximatelyEqual(to: 1.0))
#expect(curve.velocity(at: 0.75).isApproximatelyEqual(to: 1.0))
#expect(curve.velocity(at: 1.0).isApproximatelyEqual(to: 1.0))
}

@Test
func easeInCurve() {
let curve = UnitCurve.easeIn

#expect(curve.value(at: 0.0).isApproximatelyEqual(to: 0.0))
#expect(curve.value(at: 0.25).isApproximatelyEqual(to: 0.09, absoluteTolerance: 0.01))
#expect(curve.value(at: 0.5).isApproximatelyEqual(to: 0.32, absoluteTolerance: 0.01))
#expect(curve.value(at: 0.75).isApproximatelyEqual(to: 0.62, absoluteTolerance: 0.01))
#expect(curve.value(at: 1.0).isApproximatelyEqual(to: 1.0))

#expect(curve.velocity(at: 0.0).isApproximatelyEqual(to: 0.0))
#expect(curve.velocity(at: 0.25).isApproximatelyEqual(to: 0.67, absoluteTolerance: 0.01))
#expect(curve.velocity(at: 0.5).isApproximatelyEqual(to: 1.07, absoluteTolerance: 0.01))
#expect(curve.velocity(at: 0.75).isApproximatelyEqual(to: 1.37, absoluteTolerance: 0.01))
#expect(curve.velocity(at: 1.0).isApproximatelyEqual(to: 0.0))
}

@Test
func easeOutCurve() {
let curve = UnitCurve.easeOut

#expect(curve.value(at: 0.0).isApproximatelyEqual(to: 0.0))
#expect(curve.value(at: 0.25).isApproximatelyEqual(to: 0.38, absoluteTolerance: 0.01))
#expect(curve.value(at: 0.5).isApproximatelyEqual(to: 0.68, absoluteTolerance: 0.01))
#expect(curve.value(at: 0.75).isApproximatelyEqual(to: 0.91, absoluteTolerance: 0.01))
#expect(curve.value(at: 1.0).isApproximatelyEqual(to: 1.0))

#expect(curve.velocity(at: 0.0).isApproximatelyEqual(to: 1.0, absoluteTolerance: 0.01))
#expect(curve.velocity(at: 0.25).isApproximatelyEqual(to: 1.37, absoluteTolerance: 0.01))
#expect(curve.velocity(at: 0.5).isApproximatelyEqual(to: 1.07, absoluteTolerance: 0.01))
#expect(curve.velocity(at: 0.75).isApproximatelyEqual(to: 0.67, absoluteTolerance: 0.01))
#expect(curve.velocity(at: 1.0).isApproximatelyEqual(to: 0.0))
}

@Test
func easeInOutCurve() {
let curve = UnitCurve.easeInOut

#expect(curve.value(at: 0.0).isApproximatelyEqual(to: 0.0))
#expect(curve.value(at: 0.25).isApproximatelyEqual(to: 0.13, absoluteTolerance: 0.01))
#expect(curve.value(at: 0.5).isApproximatelyEqual(to: 0.5))
#expect(curve.value(at: 0.75).isApproximatelyEqual(to: 0.87, absoluteTolerance: 0.01))
#expect(curve.value(at: 1.0).isApproximatelyEqual(to: 1.0))

#expect(curve.velocity(at: 0.0).isApproximatelyEqual(to: 0.0))
#expect(curve.velocity(at: 0.25).isApproximatelyEqual(to: 1.06, absoluteTolerance: 0.01))
#expect(curve.velocity(at: 0.5).isApproximatelyEqual(to: 1.72, absoluteTolerance: 0.01))
#expect(curve.velocity(at: 0.75).isApproximatelyEqual(to: 1.06, absoluteTolerance: 0.01))
#expect(curve.velocity(at: 1.0).isApproximatelyEqual(to: 0.0))
}

@Test
func circularEaseInCurve() {
let curve = UnitCurve.circularEaseIn

#expect(curve.value(at: 0.0).isApproximatelyEqual(to: 0.0))
#expect(curve.value(at: 0.25).isApproximatelyEqual(to: 0.032, absoluteTolerance: 0.001))
#expect(curve.value(at: 0.5).isApproximatelyEqual(to: 0.134, absoluteTolerance: 0.001))
#expect(curve.value(at: 0.75).isApproximatelyEqual(to: 0.339, absoluteTolerance: 0.001))
#expect(curve.value(at: 1.0).isApproximatelyEqual(to: 1.0))

#expect(curve.velocity(at: 0.0).isApproximatelyEqual(to: 0.0))
#expect(curve.velocity(at: 0.25).isApproximatelyEqual(to: 0.258, absoluteTolerance: 0.001))
#expect(curve.velocity(at: 0.5).isApproximatelyEqual(to: 0.577, absoluteTolerance: 0.001))
#expect(curve.velocity(at: 0.75).isApproximatelyEqual(to: 1.134, absoluteTolerance: 0.001))
#expect(curve.velocity(at: 1.0).isApproximatelyEqual(to: .infinity))
}

@Test
func circularEaseOutCurve() {
let curve = UnitCurve.circularEaseOut

#expect(curve.value(at: 0.0).isApproximatelyEqual(to: 0.0))
#expect(curve.value(at: 0.25).isApproximatelyEqual(to: 0.661, absoluteTolerance: 0.001))
#expect(curve.value(at: 0.5).isApproximatelyEqual(to: 0.866, absoluteTolerance: 0.001))
#expect(curve.value(at: 0.75).isApproximatelyEqual(to: 0.968, absoluteTolerance: 0.001))
#expect(curve.value(at: 1.0).isApproximatelyEqual(to: 1.0))

#expect(curve.velocity(at: 0.0).isApproximatelyEqual(to: .infinity))
#expect(curve.velocity(at: 0.25).isApproximatelyEqual(to: 1.134, absoluteTolerance: 0.001))
#expect(curve.velocity(at: 0.5).isApproximatelyEqual(to: 0.577, absoluteTolerance: 0.001))
#expect(curve.velocity(at: 0.75).isApproximatelyEqual(to: 0.258, absoluteTolerance: 0.001))
#expect(curve.velocity(at: 1.0).isApproximatelyEqual(to: 0.0))
}

@Test
func circularEaseInOutCurve() {
let curve = UnitCurve.circularEaseInOut

#expect(curve.value(at: 0.0).isApproximatelyEqual(to: 0.0))
#expect(curve.value(at: 0.25).isApproximatelyEqual(to: 0.067, absoluteTolerance: 0.001))
#expect(curve.value(at: 0.5).isApproximatelyEqual(to: 0.5))
#expect(curve.value(at: 0.75).isApproximatelyEqual(to: 0.933, absoluteTolerance: 0.001))
#expect(curve.value(at: 1.0).isApproximatelyEqual(to: 1.0))

#expect(curve.velocity(at: 0.0).isApproximatelyEqual(to: 0.0))
#expect(curve.velocity(at: 0.25).isApproximatelyEqual(to: 0.577, absoluteTolerance: 0.001))
#expect(curve.velocity(at: 0.5).isApproximatelyEqual(to: .infinity, absoluteTolerance: 0.001))
#expect(curve.velocity(at: 0.75).isApproximatelyEqual(to: 0.577, absoluteTolerance: 0.001))
#expect(curve.velocity(at: 1.0).isApproximatelyEqual(to: 0.0))
}

// MARK: - Bezier Curve Creation

@Test
func bezierCurveCreation() {
let startPoint = UnitPoint(x: 0.25, y: 0.1)
let endPoint = UnitPoint(x: 0.75, y: 0.9)
let curve = UnitCurve.bezier(startControlPoint: startPoint, endControlPoint: endPoint)

#expect(curve.value(at: 0.0).isApproximatelyEqual(to: 0.0))
#expect(curve.value(at: 1.0).isApproximatelyEqual(to: 1.0))
#expect(curve.value(at: 0.5).isApproximatelyEqual(to: 0.5, absoluteTolerance: 0.01))
#expect(curve.velocity(at: 0.5).isApproximatelyEqual(to: 1.20, absoluteTolerance: 0.01))
}

@Test
func bezierCurveWithExtremeControlPoints() {
let startPoint = UnitPoint(x: -0.5, y: 2.0)
let endPoint = UnitPoint(x: 1.5, y: -1.0)
let curve = UnitCurve.bezier(startControlPoint: startPoint, endControlPoint: endPoint)

#expect(curve.value(at: 0.0).isApproximatelyEqual(to: 0.0))
#expect(curve.value(at: 1.0).isApproximatelyEqual(to: 1.0))
#expect(curve.value(at: 0.5).isApproximatelyEqual(to: 0.5))
#expect(curve.velocity(at: 0.5).isApproximatelyEqual(to: -1.0))
}

// MARK: - Value Function

@Test
func valueAtBoundaryConditions() {
let curve = UnitCurve.easeInOut

#expect(curve.value(at: -0.5).isApproximatelyEqual(to: 0.0))
#expect(curve.value(at: 0.0).isApproximatelyEqual(to: 0.0))
#expect(curve.value(at: 1.0).isApproximatelyEqual(to: 1.0))
#expect(curve.value(at: 1.5).isApproximatelyEqual(to: 1.0))
}

@Test
func valueAtVariousProgressPoints() {
let curve = UnitCurve.linear

for progress in stride(from: 0.0, through: 1.0, by: 0.1) {
let value = curve.value(at: progress)
#expect(value >= 0.0)
#expect(value <= 1.0)
}
}

// MARK: - Velocity Function

@Test
func velocityAtBoundaryConditions() {
let curve = UnitCurve.linear

#expect(curve.velocity(at: -0.5).isApproximatelyEqual(to: 1.0))
#expect(curve.velocity(at: 0.0).isApproximatelyEqual(to: 1.0))
#expect(curve.velocity(at: 1.0).isApproximatelyEqual(to: 1.0))
#expect(curve.velocity(at: 1.5).isApproximatelyEqual(to: 1.0))
}

@Test
func velocityForCircularCurves() {
let easeIn = UnitCurve.circularEaseIn
let easeOut = UnitCurve.circularEaseOut
let easeInOut = UnitCurve.circularEaseInOut

#expect(easeIn.velocity(at: 0.5) > 0.0)
#expect(easeOut.velocity(at: 0.5) > 0.0)
#expect(easeInOut.velocity(at: 0.5) > 0.0)
}

// MARK: - Inverse Property

@Test
func linearCurveInverse() {
let curve = UnitCurve.linear
let inverse = curve.inverse

#expect(inverse.value(at: 0.0).isApproximatelyEqual(to: 0.0))
#expect(inverse.value(at: 0.5).isApproximatelyEqual(to: 0.5))
#expect(inverse.value(at: 1.0).isApproximatelyEqual(to: 1.0))
}

@Test
func circularCurveInverse() {
let easeIn = UnitCurve.circularEaseIn
let easeOut = UnitCurve.circularEaseOut

#expect(easeIn.inverse.value(at: 0.3).isApproximatelyEqual(to: easeOut.value(at: 0.3)))
#expect(easeIn.inverse.value(at: 0.7).isApproximatelyEqual(to: easeOut.value(at: 0.7)))

#expect(easeIn.inverse.velocity(at: 0.3).isApproximatelyEqual(to: easeOut.velocity(at: 0.3)))
#expect(easeIn.inverse.velocity(at: 0.7).isApproximatelyEqual(to: easeOut.velocity(at: 0.7)))
}

@Test
func curveInverse() {
let easeIn = UnitCurve.easeIn
let easeOut = UnitCurve.easeOut

#expect(!easeIn.inverse.value(at: 0.3).isApproximatelyEqual(to: easeOut.value(at: 0.3)))
#expect(!easeIn.inverse.value(at: 0.7).isApproximatelyEqual(to: easeOut.value(at: 0.7)))

#expect(!easeIn.inverse.velocity(at: 0.3).isApproximatelyEqual(to: easeOut.velocity(at: 0.3)))
#expect(!easeIn.inverse.velocity(at: 0.7).isApproximatelyEqual(to: easeOut.velocity(at: 0.7)))
}

@Test
func bezierCurveInverse() {
let startPoint = UnitPoint(x: 0.25, y: 0.1)
let endPoint = UnitPoint(x: 0.75, y: 0.9)
let curve = UnitCurve.bezier(startControlPoint: startPoint, endControlPoint: endPoint)

let inverseStartPoint = UnitPoint(x: 0.1, y: 0.25)
let inverseEndPoint = UnitPoint(x: 0.9, y: 0.75)
let inverse = UnitCurve.bezier(startControlPoint: inverseStartPoint, endControlPoint: inverseEndPoint)

#expect(inverse.value(at: 0.3).isAlmostEqual(to: curve.inverse.value(at: 0.3)))
#expect(inverse.value(at: 0.7).isAlmostEqual(to: curve.inverse.value(at: 0.7)))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//
// UnitCurveTests.swift
// OpenSwiftUICoreTests

@testable import OpenSwiftUICore
import Testing

// MARK: - UnitCurveTests

struct UnitCurveTests {
// MARK: - CubicSolver

@Test
func cubicSolverInitialization() {
let startPoint = UnitPoint(x: 0.25, y: 0.1)
let endPoint = UnitPoint(x: 0.75, y: 0.9)
let solver = UnitCurve.CubicSolver(startControlPoint: startPoint, endControlPoint: endPoint)

#expect(solver.startControlPoint.x.isApproximatelyEqual(to: 0.25))
#expect(solver.startControlPoint.y.isApproximatelyEqual(to: 0.1))
#expect(solver.endControlPoint.x.isApproximatelyEqual(to: 0.75))
#expect(solver.endControlPoint.y.isApproximatelyEqual(to: 0.9))
}

@Test
func cubicSolverValueCalculation() {
let startPoint = UnitPoint(x: 0.25, y: 0.1)
let endPoint = UnitPoint(x: 0.75, y: 0.9)
let solver = UnitCurve.CubicSolver(startControlPoint: startPoint, endControlPoint: endPoint)

#expect(solver.value(at: 0.0).isApproximatelyEqual(to: 0.0))
#expect(solver.value(at: 1.0).isApproximatelyEqual(to: 1.0))
#expect(solver.value(at: 0.5).isApproximatelyEqual(to: 0.5))
}

@Test
func cubicSolverVelocityCalculation() {
let startPoint = UnitPoint(x: 0.25, y: 0.1)
let endPoint = UnitPoint(x: 0.75, y: 0.9)
let solver = UnitCurve.CubicSolver(startControlPoint: startPoint, endControlPoint: endPoint)
#expect(solver.velocity(at: 0.5).isApproximatelyEqual(to: 1.199, absoluteTolerance: 0.001))
}

// MARK: - Edge Cases

@Test
func extremeProgressValues() {
let curve = UnitCurve.easeInOut

#expect(curve.value(at: -1000.0).isApproximatelyEqual(to: 0.0))
#expect(curve.value(at: 1000.0).isApproximatelyEqual(to: 1.0))
#expect(curve.velocity(at: -1000.0).isApproximatelyEqual(to: 0.0))
#expect(curve.velocity(at: 1000.0).isApproximatelyEqual(to: 0.0))
}

@Test
func bezierWithIdenticalControlPoints() {
let point = UnitPoint(x: 0.5, y: 0.5)
let curve = UnitCurve.bezier(startControlPoint: point, endControlPoint: point)

#expect(curve.value(at: 0.0).isApproximatelyEqual(to: 0.0))
#expect(curve.value(at: 1.0).isApproximatelyEqual(to: 1.0))
#expect(curve.value(at: 0.5).isApproximatelyEqual(to: 0.5))
#expect(curve.velocity(at: 0.5).isApproximatelyEqual(to: 1.0))
}

@Test
func circularEaseInOutTransition() {
let curve = UnitCurve.circularEaseInOut

let belowMid = curve.value(at: 0.49)
let mid = curve.value(at: 0.5)
let aboveMid = curve.value(at: 0.51)

#expect(belowMid < mid)
#expect(mid < aboveMid)
#expect(mid.isApproximatelyEqual(to: 0.5))
}
}