Skip to content
Merged
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 @@ -2,16 +2,19 @@
// VelocityTrackingAnimation.swift
// OpenSwiftUICore
//
// Audited for iOS 18.0
// Status: WIP
// Audited for 6.5.4
// Status: Complete
// ID: FD9125BC1E04E33D1D7BE4A31225AA98 (SwiftUICore)

import Foundation

// MARK: - TracksVelocityKey

private struct TracksVelocityKey: TransactionKey {
static var defaultValue: Bool { false }
}

@available(OpenSwiftUI_v5_0, *)
extension Transaction {
/// Whether this transaction will track the velocity of any animatable
/// properties that change.
Expand Down Expand Up @@ -50,6 +53,68 @@ extension Transaction {
}

extension Animation {
// FIXME: VelocityTrackingAnimation
static let velocityTracking: Animation = .default
static let velocityTracking: Animation = Animation(VelocityTrackingAnimation())
}

private struct VelocityTrackingAnimation: CustomAnimation {
nonisolated func animate<V>(
value: V,
time: TimeInterval,
context: inout AnimationContext<V>
) -> V? where V : VectorArithmetic {
var sampler = context.velocityState.sampler
if sampler.isEmpty { // FIXME: Verify this logic
sampler.addSample(value, time: .init(seconds: time))
context.velocityState = .init(sampler: sampler)
}
let newTime = (sampler.lastTime?.seconds ?? .zero) + 2.0
let velocity = velocity(
value: value,
time: time,
context: context
)
if let velocity, velocity == .zero {
return nil
}
guard newTime > time else {
return nil
}
return value
}


nonisolated func velocity<V>(
value: V,
time: TimeInterval,
context: AnimationContext<V>
) -> V? where V : VectorArithmetic {
let timeDiff = time - (context.velocityState.sampler.lastTime?.seconds ?? .zero)
let scale = pow(0.998, timeDiff * 1000)
return context.velocityState.sampler.velocity.scaled(by: scale).valuePerSecond
}

nonisolated func shouldMerge<V>(
previous: Animation,
value: V,
time: TimeInterval,
context: inout AnimationContext<V>
) -> Bool where V: VectorArithmetic {
context.velocityState.sampler.addSample(value, time: .init(seconds: time))
return true
}
}

extension AnimationContext {
fileprivate var velocityState: VelocityState<Value> {
get { state[VelocityState<Value>.self] }
set { state[VelocityState<Value>.self] = newValue }
}
}

private struct VelocityState<Value>: AnimationStateKey where Value: VectorArithmetic {
static var defaultValue: VelocityState {
VelocityState(sampler: .init())
}

var sampler: VelocitySampler<Value>
}