Permalink
Browse files

Animation refactor, all works

- Move responsibility back to StateMachine
- Add StateSwitchStop
- Clean up code
- Fix all tests
  • Loading branch information...
koenbok committed Sep 16, 2016
1 parent 23a313a commit 5b3372d8af0b4f5d42ac25f465be02d7a691c8ac
View
@@ -35,26 +35,59 @@ evaluateRelativeProperty = (target, k, v) ->
class exports.Animation extends BaseClass
- # 'properties' are the layer properties that will be animated
- # 'options' are the animationOptions for this animation
- # It's also possible to provide options through an 'options' key in the 'properties object'
- # or provide the options at top level and use 'properties' to specify the properties (old API)
- constructor: (properties={}, options={}) ->
- if properties.options?
- _.defaults(options, properties.options)
- delete properties.options
- else if properties.properties?
- #Support old properties: syntax
- layer = properties.layer
- props = properties.properties
- delete properties.properties
- delete properties.layer
- _.defaults(options, properties)
- properties = props
- properties.layer = layer
- @options = _.cloneDeep Defaults.getDefaults "Animation", options
- super properties
- @layer = properties.layer ? null
+ constructor: (args...) ->
+
+ # Old API detection
+
+ # animationA = new Animation
+ # layer: layerA
+ # properties:
+ # x: 100
+
+ # print args
+
+ layer = null
+ properties = {}
+ options = {}
+
+ if arguments.length is 3
+ layer = args[0]
+ properties = args[1]
+ options = args[2]
+
+ if arguments.length is 2
+ layer = args[0]
+ if args[1].properties
+ properties = args[1].properties
+ else
+ properties = args[1]
+ options = args[1].options if args[1].options
+
+
+ if arguments.length is 1
+ layer = args[0].layer
+ properties = args[0].properties
+ if args[0].options
+ options = args[0].options
+ else
+ options = args[0]
+
+ delete options.layer
+ delete options.properties
+ delete options.options
+
+
+ # print "Animation", layer, properties, options
+
+ @options = _.cloneDeep(Defaults.getDefaults("Animation", options))
+
+ super
+
+ @_layer = layer
+
+ unless layer instanceof Layer
+ throw Error("Animation: missing layer")
+
@properties = Animation.filterAnimatableProperties(properties)
if properties.origin
@@ -64,6 +97,9 @@ class exports.Animation extends BaseClass
@_originalState = @_currentState()
@_repeatCounter = @options.repeat
+ @define "layer",
+ get: -> @_layer
+
@define "isAnimating",
get: -> @ in @layer.context.animations
@@ -75,8 +111,6 @@ class exports.Animation extends BaseClass
@restart()
start: =>
- if @layer is null
- console.error "Animation: missing layer"
@_animator = @_createAnimator()
@_target = @layer
@@ -98,11 +132,11 @@ class exports.Animation extends BaseClass
if _.keys(@_stateA).length is 0
console.warn "Animation: nothing to animate, no animatable properties"
- return false
+ return @_noop()
if _.isEqual(@_stateA, @_stateB)
console.warn "Animation: nothing to animate, all properties are equal to what it is now"
- return false
+ return @_noop()
# If this animation wants to animate a property that is already being animated, it stops
# that currently running animation. If not, it allows them both to continue.
@@ -138,22 +172,28 @@ class exports.Animation extends BaseClass
@_repeatCounter--
# If animate is false we set everything immediately and skip the actual animation
start = @_start
- start = @_instant if @options.animate is false
+
+ # The option keywords animate and instant trigger an instant animation
+ if @options.animate is false or @options.instant is true
+ start = @_instant
# If we have a delay, we wait a bit for it to start
if @options.delay
@_delayTimer = Utils.delay(@options.delay, start)
else
start()
+
return true
- stop: (emit=true)->
+ stop: (emit=true) ->
+
if @_delayTimer?
Framer.CurrentContext.removeTimer(@_delayTimer)
@_delayTimer = null
+
@layer.context.removeAnimation(@)
- @emit("stop") if emit
+ @emit(Events.AnimationStop) if emit
Framer.Loop.off("update", @_update)
reverse: ->
@@ -192,15 +232,21 @@ class exports.Animation extends BaseClass
_.keys(@_stateA)
_instant: =>
- @emit("start")
+ @emit(Events.AnimationStart)
@_prepareUpdateValues()
@_updateValues(1)
- @emit("end")
- @emit("stop")
+ @emit(Events.AnimationStop)
+ @emit(Events.AnimationEnd)
+
+ _noop: =>
+ @emit(Events.AnimationStart)
+ @emit(Events.AnimationStop)
+ @emit(Events.AnimationEnd)
+ return false
_start: =>
@layer.context.addAnimation(@)
- @emit("start")
+ @emit(Events.AnimationStart)
Framer.Loop.on("update", @_update)
# Figure out what kind of values we have so we don't have to do it in
@@ -212,8 +258,8 @@ class exports.Animation extends BaseClass
if @_animator.finished()
@_updateValues(1)
@stop(emit=false)
- @emit("end")
- @emit("stop")
+ @emit(Events.AnimationStop)
+ @emit(Events.AnimationEnd)
else
@_updateValues(@_animator.next(delta))
View
@@ -38,13 +38,18 @@ Events.Click = Events.TouchEnd
Events.AnimationStart = "start"
Events.AnimationStop = "stop"
Events.AnimationEnd = "end"
-Events.AnimationDidStart = "start"
-Events.AnimationDidStop = "stop"
-Events.AnimationDidEnd = "end"
+
+Events.AnimationDidStart = Events.AnimationStart # Deprecated
+Events.AnimationDidStop = Events.AnimationStop # Deprecated
+Events.AnimationDidEnd = Events.AnimationEnd # Deprecated
# State events
-Events.StateWillSwitch = "willSwitch"
-Events.StateDidSwitch = "didSwitch"
+Events.StateSwitchStart = "stateswitchstart"
+Events.StateSwitchStop = "stateswitchstop"
+Events.StateSwitchEnd = "stateswitchend"
+
+Events.StateWillSwitch = Events.StateSwitchStart # Deprecated
+Events.StateDidSwitch = Events.StateSwitchEnd # Deprecated
# Scroll events
Events.Scroll = "scroll"
View
@@ -888,76 +888,45 @@ class exports.Layer extends BaseClass
# Used to animate to a state with a specific name
# We lookup the stateName and call 'animate' with the properties of the state
animateToState: (stateName, options={}) ->
- properties = @_stateMachine.switchTo stateName
- if @_stateMachine.previousName is @_stateMachine.currentName
- shouldChange = false
- for property, value of properties
- if @[property] isnt value
- shouldChange = true
- break
- if not shouldChange
- return null
- finished = options.completion
- options.completion = =>
- @_stateMachine.emit(Events.StateDidSwitch, @_stateMachine.previousName, @_stateMachine.currentName, @)
- finished?()
- @animate properties, options
+ return @_stateMachine.switchTo(stateName, options)
animate: (properties, options={}) ->
- if typeof properties == "string"
- stateName = properties
- return @animateToState stateName, options
- #Support the old properties syntax
- if properties.properties?
- # console.warn "Using Layer.animate with 'properties' key is deprecated: please provide properties directly and use the 'options' key to provide animation options instead"
+ # print "layer.animate", properties, options
+
+ # If the properties are a string, we assume it's a state name
+ if _.isString(properties)
+ return @animateToState(properties, options)
+
+ # Support the old properties syntax, we add all properties top level and
+ # move the options into an options property.
+ if properties.hasOwnProperty("properties")
options = properties
properties = options.properties
delete options.properties
- _.defaults(options, properties.options, @animationOptions)
- delete properties.options
-
- animatableProperties = Animation.filterAnimatableProperties(properties)
- nonAnimatableProperties = _.omit(_.clone(properties), _.keys(animatableProperties))
-
- start = options.start
- start ?= true
- delete options.start
- instant = options.instant ? false
- if instant
- options.animate = false
- delete options.instant
- parameters = animatableProperties
- parameters.layer = @
-
- animation = new Animation parameters, options
- animationFinished = =>
- for k, v of nonAnimatableProperties
- @[k] = v
- options.completion?()
-
- animation.once Events.AnimationStop, animationFinished
- started = animation.start() if start
- if not started
- # If the animation didn't start (e.g. because there are no properties to animate), call the finished handler ourselves
- animationFinished()
- animation
+ # With the new api we treat the properties as animatable properties, and use
+ # the special options keyword for animation options.
+ if properties.hasOwnProperty("options")
+ options = _.defaults(properties.options, options)
+ delete properties.options
+
+ # Merge the animation options with the default animation options for this layer
+ options = _.defaults(options, @animationOptions)
+ options.start ?= true
+
+ animation = new Animation(@, properties, options)
+ animation.start() if options.start
+
+ return animation
switchInstant: (properties, options={}) ->
- options = _.defaults({instant:true}, options)
- @animate properties, options
-
- animateToNextState: (stateNames=[], options) ->
- if not Array.isArray(stateNames)
- if not options? and typeof stateNames is "object"
- options = stateNames
- stateNames = []
- else
- stateNames = Utils.arrayFromArguments arguments
- options = {}
- nextState = @_stateMachine.next(stateNames)
- @animate nextState, options
+ @animate(properties, _.merge(options, {instant: true}))
+
+ animateToNextState: (args..., options={}) ->
+ states = []
+ states = _.flatten(args) if args.length
+ @animate(@_stateMachine.next(states), options)
animations: ->
# Current running animations on this layer
@@ -1196,8 +1165,12 @@ class exports.Layer extends BaseClass
onDragAnimationEnd: (cb) -> @on(Events.DragAnimationEnd, cb)
onDirectionLockStart: (cb) -> @on(Events.DirectionLockStart, cb)
- onStateDidSwitch: (cb) -> @on(Events.StateDidSwitch, cb)
- onStateWillSwitch: (cb) -> @on(Events.StateWillSwitch, cb)
+ onStateSwitchStart: (cb) -> @on(Events.StateSwitchStart, cb)
+ onStateSwitchStop: (cb) -> @on(Events.StateSwitchStop, cb)
+ onStateSwitchEnd: (cb) -> @on(Events.StateSwitchEnd, cb)
+
+ onStateWillSwitch: (cb) -> @on(Events.StateSwitchStart, cb) # Deprecated
+ onStateDidSwitch: (cb) -> @on(Events.StateSwitchEnd, cb) # Deprecated
# Gestures
Oops, something went wrong.

0 comments on commit 5b3372d

Please sign in to comment.