jtrivedi / Wave

Wave is a spring-based animation engine for iOS and macOS that makes it easy to create fluid, interruptible animations that feel great.
https://jtrivedi.github.io/Wave/
MIT License
2.04k stars 55 forks source link

Extended animation support #36

Closed flocked closed 11 months ago

flocked commented 1 year ago

Extended animation support

Spring

SpringAnimator

/// A protocol that describes an animatable value type.
public protocol AnimatableData: Equatable, Comparable {
    /// The type defining the data to animate.
    associatedtype AnimatableData: VectorArithmetic = Self
    /// The data to animate.
    var animatableData: AnimatableData { get }
    /// Initializes with animatable data.
    init(_ animatableData: AnimatableData)
    /// Scaled integral representation of the value.
    var scaledIntegral: Self { get }
    static var zero: Self { get }
}

Double, Float, CGFloat and Arrays containing these confirm to VectorArithmetic (typealias: AnimatableVector) and can be used as animatable data. Apple also uses VectorArithmetic for its SwiftUI Animatable protocol to animate. It allows very quick calculations. I use vDSP which calculates many values parallel in one cpu cycle.

 public struct SomeStruct {
    let value1: CGFloat
    let value2: CGFloat
 }

 extension SomeStruct: AnimatableData {
    public var animatableData: AnimatableVector {
        [value1, value2]
    }

    public init(_ animatableData: AnimatableVector) {
        self.value1 = animatableData[0]
        self.value1 = animatableData[1]
    }

    public static var zero: Self = SomeStruct(value1: 0, value2: 0)
 }

AnimatablePropertyProvider protocol

Extending a class with AnimatablePropertyProvider automatically adds animator: PropertyAnimator<Class>.

public protocol AnimatablePropertyProvider: AnyObject {
    /// Use this property to set any animatable properties in an ``Wave/animate(withSpring:delay:gestureVelocity:animations:completion:)`` animation block.
    var animator: PropertyAnimator<Self> { get }
}

To set/get a property animated use the keyPath of a property to animate (that conforms to AnimatableData) on the animator.

extension NSView: AnimatablePropertyProvider { }

Wave.animate(withSpring: .bouncy) {
        myView.animator[\.frame] = newFrame // sets a new frame animated.
        let currentFrame = myView.animator[\.frame] // current frame (either the target of the spring animation or the frame)
}

This allows modularity. E.g.

extension NSView: AnimatablePropertyProvider { }

extension PropertyAnimator<NSView> {
    var frame: CGRect {
        get { self[\.frame] }
        set { self[\.frame] = newValue }
    }
}

extension CALayer: AnimatablePropertyProvider { }

extension PropertyAnimator<CALayer> {
    var opacity: CGFloat {
        get { self[\.opacity] }
        set { self[\.opacity] = newValue }
    }
}

Wave.animate(withSpring: .bouncy) {
    myView.animator.frame = newFrame
    myLayer.animator.opacity = 0.5
}

The current animation velocity can always be accessed via animators animationVelocity and the keyPath of the animated property.

let currentVelocity = myView.animator.animationVelocity[\.frame]

Spring + CAKeyframeAnimation

Generate a CAKeyframeAnimation from a Spring.

let animation = Spring.bouncy.keyframeAnimation()
animation.keyPath = "frame"
layer.add(animation, forKey: "MyAnimation")

Wave

jtrivedi commented 11 months ago

Hi -- I appreciate the work you've put in here, but Wave is unfortunately not open to contributions, per the Contributing.md file found here.