mirror of
https://github.com/exyte/Macaw.git
synced 2024-10-11 03:49:05 +03:00
Animation play/stop
This commit is contained in:
parent
9dc0761d1d
commit
3d0317e9ee
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10116" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="8Ku-fM-19f">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10116" systemVersion="15E65" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="8Ku-fM-19f">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
@ -77,7 +77,27 @@
|
||||
<view key="view" contentMode="scaleToFill" id="y1S-j5-FMS" customClass="ThirdPageCustomView" customModule="Example" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="myZ-CN-l6C">
|
||||
<rect key="frame" x="276" y="544" width="48" height="22"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="48" id="Jge-rD-lfB"/>
|
||||
<constraint firstAttribute="height" constant="22" id="LHt-QV-KDv"/>
|
||||
</constraints>
|
||||
<state key="normal" title="Play"/>
|
||||
<connections>
|
||||
<action selector="onPlayerBtnClicked" destination="y1S-j5-FMS" eventType="touchUpInside" id="69e-Z7-WVS"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="myZ-CN-l6C" firstAttribute="centerX" secondItem="y1S-j5-FMS" secondAttribute="centerX" id="iiN-tB-5cf"/>
|
||||
<constraint firstItem="1ea-S4-NTm" firstAttribute="top" secondItem="myZ-CN-l6C" secondAttribute="bottom" constant="34" id="sUz-cy-GZo"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="playerBtn" destination="myZ-CN-l6C" id="mBO-we-5tC"/>
|
||||
</connections>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="asF-Da-v23" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
|
@ -5,6 +5,23 @@ import Macaw
|
||||
|
||||
class ThirdPageCustomView: MacawView {
|
||||
|
||||
@IBOutlet var playerBtn: UIButton?
|
||||
|
||||
let animations: [Animatable]
|
||||
var paused = true
|
||||
|
||||
@IBAction func onPlayerBtnClicked() {
|
||||
paused = !paused
|
||||
|
||||
if paused {
|
||||
playerBtn?.setTitle("Play", forState: .Normal)
|
||||
animations.pause()
|
||||
} else {
|
||||
playerBtn?.setTitle("Pause", forState: .Normal)
|
||||
animations.play()
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
|
||||
func cloudExample() -> Group {
|
||||
@ -131,8 +148,6 @@ class ThirdPageCustomView: MacawView {
|
||||
pos: Transform().move(-80, my: -100)
|
||||
)
|
||||
|
||||
super.init(node: group, coder: aDecoder)
|
||||
|
||||
// Animation 1
|
||||
// let cloud1ShapeAnimation = Animation(observableValue: cloud1.posProperty,
|
||||
// finalValue: Transform.move(120, my: 320).scale(0.3, sy: 0.3),
|
||||
@ -152,24 +167,29 @@ class ThirdPageCustomView: MacawView {
|
||||
let cloud3ShapeAnimation = PathAnimation(observableValue: cloud3.posProperty,
|
||||
path: path,
|
||||
animationDuration: 3.0)
|
||||
let animation = [
|
||||
let animation1 = [
|
||||
// cloud1ShapeAnimation,
|
||||
cloud2ShapeAnimation.easeIn(),
|
||||
cloud3ShapeAnimation.easeOut()
|
||||
].animationSequence().looped().infiniteLoop()
|
||||
super.addAnimation(animation)
|
||||
|
||||
// Animation 2
|
||||
|
||||
let cloud4ShapeAnimation = PathAnimation(observableValue: cloud4.posProperty,
|
||||
let animation2 = PathAnimation(observableValue: cloud4.posProperty,
|
||||
function: { t in
|
||||
|
||||
return Transform.move(50.0 + 300 * t, my: 100 + 50 * sin(M_PI * t * 6.0)).scale(0.15, sy: 0.15)
|
||||
}, animationDuration: 3.0).looped().infiniteLoop()
|
||||
super.addAnimation(cloud4ShapeAnimation)
|
||||
|
||||
animations = [animation1, animation2]
|
||||
|
||||
super.init(node: group, coder: aDecoder)
|
||||
super.addAnimation(animation1, autoPlay: false)
|
||||
super.addAnimation(animation2, autoPlay: false)
|
||||
}
|
||||
|
||||
required init?(node: Node, coder aDecoder: NSCoder) {
|
||||
animations = []
|
||||
super.init(node: node, coder: aDecoder)
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +1,64 @@
|
||||
import Foundation
|
||||
|
||||
public extension SequenceType where Generator.Element: Animatable {
|
||||
func play() {
|
||||
self.forEach { animation in
|
||||
animation.play()
|
||||
}
|
||||
}
|
||||
|
||||
func pause() {
|
||||
self.forEach { animation in
|
||||
animation.pause()
|
||||
}
|
||||
}
|
||||
|
||||
func remove() {
|
||||
self.forEach { animation in
|
||||
animation.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Animatable {
|
||||
func animate(progress: Double) {}
|
||||
func getDuration() -> Double{ return 0}
|
||||
|
||||
var shouldBeRemoved = false
|
||||
var paused = false
|
||||
public var currentProgress: Double?
|
||||
|
||||
func animate(progress: Double) { }
|
||||
func getDuration() -> Double { return 0 }
|
||||
|
||||
func play() { paused = false }
|
||||
func pause() { paused = true }
|
||||
func remove() { shouldBeRemoved = true }
|
||||
}
|
||||
|
||||
public class Animation<T: Interpolable>: Animatable {
|
||||
|
||||
let value: ObservableValue<T>
|
||||
let value: ObservableValue<T>
|
||||
|
||||
let start: T
|
||||
let final: T
|
||||
let duration: Double
|
||||
let start: T
|
||||
let final: T
|
||||
let duration: Double
|
||||
|
||||
public required init(observableValue: ObservableValue<T>, startValue: T, finalValue: T, animationDuration: Double) {
|
||||
value = observableValue
|
||||
start = startValue
|
||||
final = finalValue
|
||||
duration = animationDuration
|
||||
public required init(observableValue: ObservableValue<T>, startValue: T, finalValue: T, animationDuration: Double) {
|
||||
value = observableValue
|
||||
start = startValue
|
||||
final = finalValue
|
||||
duration = animationDuration
|
||||
}
|
||||
|
||||
}
|
||||
public convenience init(observableValue: ObservableValue<T>, finalValue: T, animationDuration: Double) {
|
||||
self.init(observableValue: observableValue, startValue: observableValue.get(), finalValue: finalValue, animationDuration: animationDuration)
|
||||
}
|
||||
|
||||
public convenience init(observableValue: ObservableValue<T>, finalValue: T, animationDuration: Double) {
|
||||
self.init(observableValue: observableValue, startValue: observableValue.get(), finalValue: finalValue, animationDuration: animationDuration )
|
||||
}
|
||||
public override func animate(progress: Double) {
|
||||
|
||||
public override func animate(progress: Double) {
|
||||
value.set(start.interpolate(final, progress: progress))
|
||||
}
|
||||
|
||||
value.set(start.interpolate(final, progress: progress))
|
||||
}
|
||||
|
||||
public override func getDuration() -> Double {
|
||||
return duration
|
||||
}
|
||||
public override func getDuration() -> Double {
|
||||
return duration
|
||||
}
|
||||
}
|
||||
|
@ -3,48 +3,68 @@ import QuartzCore
|
||||
|
||||
class AnimationLoop {
|
||||
|
||||
var displayLink: CADisplayLink?
|
||||
var displayLink: CADisplayLink?
|
||||
|
||||
var animationSubscriptions: [AnimationSubscription] = []
|
||||
var rendererCall: (()->())?
|
||||
var animationSubscriptions: [AnimationSubscription] = []
|
||||
var rendererCall: (() -> ())?
|
||||
|
||||
init() {
|
||||
displayLink = CADisplayLink(target: self, selector: #selector(onFrameUpdate(_:)))
|
||||
displayLink?.paused = false
|
||||
displayLink?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)
|
||||
}
|
||||
init() {
|
||||
displayLink = CADisplayLink(target: self, selector: #selector(onFrameUpdate(_:)))
|
||||
displayLink?.paused = false
|
||||
displayLink?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)
|
||||
}
|
||||
|
||||
dynamic private func onFrameUpdate(displayLink: CADisplayLink) {
|
||||
dynamic private func onFrameUpdate(displayLink: CADisplayLink) {
|
||||
|
||||
var toRemove = [AnimationSubscription]()
|
||||
var toRemove = [AnimationSubscription]()
|
||||
|
||||
animationSubscriptions.forEach { subscription in
|
||||
animationSubscriptions.forEach { subscription in
|
||||
|
||||
if subscription.startTime == .None {
|
||||
subscription.startTime = displayLink.timestamp
|
||||
}
|
||||
// Handle manually set position
|
||||
if !subscription.anim.paused {
|
||||
if let pausedPosition = subscription.anim.currentProgress {
|
||||
subscription.anim.currentProgress = .None
|
||||
subscription.startTime = displayLink.timestamp - subscription.anim.getDuration() * pausedPosition
|
||||
subscription.moveToTimeFrame(pausedPosition)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
guard let startTime = subscription.startTime else {
|
||||
return
|
||||
}
|
||||
// Calculating current position
|
||||
if subscription.startTime == .None {
|
||||
subscription.startTime = displayLink.timestamp
|
||||
}
|
||||
|
||||
let timePosition = displayLink.timestamp - startTime
|
||||
let position = timePosition / subscription.anim.getDuration()
|
||||
guard let startTime = subscription.startTime else {
|
||||
return
|
||||
}
|
||||
|
||||
if position > 1.0 {
|
||||
toRemove.append(subscription)
|
||||
}
|
||||
let timePosition = displayLink.timestamp - startTime
|
||||
let position = timePosition / subscription.anim.getDuration()
|
||||
|
||||
subscription.moveToTimeFrame(position, advance: 0)
|
||||
}
|
||||
if position > 1.0 || subscription.anim.shouldBeRemoved {
|
||||
toRemove.append(subscription)
|
||||
}
|
||||
|
||||
rendererCall?()
|
||||
// Saving paused position
|
||||
if subscription.anim.paused {
|
||||
if subscription.anim.currentProgress == .None {
|
||||
subscription.anim.currentProgress = position
|
||||
}
|
||||
|
||||
// Removing
|
||||
toRemove.forEach { subsription in
|
||||
if let index = animationSubscriptions.indexOf ({ $0 === subsription }) {
|
||||
animationSubscriptions.removeAtIndex(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
subscription.moveToTimeFrame(position)
|
||||
}
|
||||
|
||||
rendererCall?()
|
||||
|
||||
// Removing
|
||||
toRemove.forEach { subsription in
|
||||
if let index = animationSubscriptions.indexOf({ $0 === subsription }) {
|
||||
animationSubscriptions.removeAtIndex(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,16 @@ import Foundation
|
||||
|
||||
class AnimationSubscription {
|
||||
|
||||
let anim: Animatable
|
||||
let anim: Animatable
|
||||
|
||||
var startTime: CFTimeInterval?
|
||||
var startTime: CFTimeInterval?
|
||||
|
||||
init( animation: Animatable ) {
|
||||
anim = animation
|
||||
}
|
||||
init(animation: Animatable, paused: Bool = false) {
|
||||
anim = animation
|
||||
anim.paused = paused
|
||||
}
|
||||
|
||||
func moveToTimeFrame(position: Double, advance: Double) {
|
||||
anim.animate(position)
|
||||
}
|
||||
func moveToTimeFrame(position: Double) {
|
||||
anim.animate(position)
|
||||
}
|
||||
}
|
||||
|
@ -3,35 +3,34 @@ import UIKit
|
||||
|
||||
public class MacawView: UIView {
|
||||
|
||||
var node: Node!
|
||||
var context: RenderContext!
|
||||
var renderer: NodeRenderer!
|
||||
var loop: AnimationLoop?
|
||||
var node: Node!
|
||||
var context: RenderContext!
|
||||
var renderer: NodeRenderer!
|
||||
var loop: AnimationLoop?
|
||||
|
||||
public required init?(node: Node, coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
public required init?(node: Node, coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
self.node = node
|
||||
self.context = RenderContext(view: self)
|
||||
self.renderer = RenderUtils.createNodeRenderer(node, context: context)!
|
||||
self.loop = AnimationLoop()
|
||||
self.loop?.rendererCall = {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
self.node = node
|
||||
self.context = RenderContext(view: self)
|
||||
self.renderer = RenderUtils.createNodeRenderer(node, context: context)!
|
||||
self.loop = AnimationLoop()
|
||||
self.loop?.rendererCall = {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public convenience required init?(coder aDecoder: NSCoder) {
|
||||
self.init(node: Group(pos: Transform()), coder: aDecoder)
|
||||
}
|
||||
|
||||
public convenience required init?(coder aDecoder: NSCoder) {
|
||||
self.init(node: Group(pos: Transform()), coder: aDecoder)
|
||||
}
|
||||
override public func drawRect(rect: CGRect) {
|
||||
self.context.cgContext = UIGraphicsGetCurrentContext()
|
||||
renderer.render()
|
||||
}
|
||||
|
||||
override public func drawRect(rect: CGRect) {
|
||||
self.context.cgContext = UIGraphicsGetCurrentContext()
|
||||
renderer.render()
|
||||
}
|
||||
|
||||
public func addAnimation(animation: Animatable) {
|
||||
let subscription = AnimationSubscription(animation: animation)
|
||||
self.loop?.animationSubscriptions.append(subscription)
|
||||
}
|
||||
public func addAnimation(animation: Animatable, autoPlay: Bool = true) {
|
||||
let subscription = AnimationSubscription(animation: animation, paused: !autoPlay)
|
||||
self.loop?.animationSubscriptions.append(subscription)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user