Skip to content

Commit dc1f070

Browse files
authored
Add VelocityTrackingAnimation (#458)
1 parent 4946205 commit dc1f070

File tree

1 file changed

+69
-4
lines changed

1 file changed

+69
-4
lines changed

Sources/OpenSwiftUICore/Animation/Animation/VelocityTrackingAnimation.swift

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@
22
// VelocityTrackingAnimation.swift
33
// OpenSwiftUICore
44
//
5-
// Audited for iOS 18.0
6-
// Status: WIP
5+
// Audited for 6.5.4
6+
// Status: Complete
77
// ID: FD9125BC1E04E33D1D7BE4A31225AA98 (SwiftUICore)
88

9+
import Foundation
10+
911
// MARK: - TracksVelocityKey
1012

1113
private struct TracksVelocityKey: TransactionKey {
1214
static var defaultValue: Bool { false }
1315
}
1416

17+
@available(OpenSwiftUI_v5_0, *)
1518
extension Transaction {
1619
/// Whether this transaction will track the velocity of any animatable
1720
/// properties that change.
@@ -50,6 +53,68 @@ extension Transaction {
5053
}
5154

5255
extension Animation {
53-
// FIXME: VelocityTrackingAnimation
54-
static let velocityTracking: Animation = .default
56+
static let velocityTracking: Animation = Animation(VelocityTrackingAnimation())
57+
}
58+
59+
private struct VelocityTrackingAnimation: CustomAnimation {
60+
nonisolated func animate<V>(
61+
value: V,
62+
time: TimeInterval,
63+
context: inout AnimationContext<V>
64+
) -> V? where V : VectorArithmetic {
65+
var sampler = context.velocityState.sampler
66+
if sampler.isEmpty { // FIXME: Verify this logic
67+
sampler.addSample(value, time: .init(seconds: time))
68+
context.velocityState = .init(sampler: sampler)
69+
}
70+
let newTime = (sampler.lastTime?.seconds ?? .zero) + 2.0
71+
let velocity = velocity(
72+
value: value,
73+
time: time,
74+
context: context
75+
)
76+
if let velocity, velocity == .zero {
77+
return nil
78+
}
79+
guard newTime > time else {
80+
return nil
81+
}
82+
return value
83+
}
84+
85+
86+
nonisolated func velocity<V>(
87+
value: V,
88+
time: TimeInterval,
89+
context: AnimationContext<V>
90+
) -> V? where V : VectorArithmetic {
91+
let timeDiff = time - (context.velocityState.sampler.lastTime?.seconds ?? .zero)
92+
let scale = pow(0.998, timeDiff * 1000)
93+
return context.velocityState.sampler.velocity.scaled(by: scale).valuePerSecond
94+
}
95+
96+
nonisolated func shouldMerge<V>(
97+
previous: Animation,
98+
value: V,
99+
time: TimeInterval,
100+
context: inout AnimationContext<V>
101+
) -> Bool where V: VectorArithmetic {
102+
context.velocityState.sampler.addSample(value, time: .init(seconds: time))
103+
return true
104+
}
105+
}
106+
107+
extension AnimationContext {
108+
fileprivate var velocityState: VelocityState<Value> {
109+
get { state[VelocityState<Value>.self] }
110+
set { state[VelocityState<Value>.self] = newValue }
111+
}
112+
}
113+
114+
private struct VelocityState<Value>: AnimationStateKey where Value: VectorArithmetic {
115+
static var defaultValue: VelocityState {
116+
VelocityState(sampler: .init())
117+
}
118+
119+
var sampler: VelocitySampler<Value>
55120
}

0 commit comments

Comments
 (0)