Skip to content

Commit

Permalink
Merge pull request #524 from koenbok/feature/gradient-tests
Browse files Browse the repository at this point in the history
Gradient Tests
  • Loading branch information
eelco committed Jun 30, 2017
2 parents 635adc9 + b1d2e85 commit 3430da7
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 88 deletions.
16 changes: 12 additions & 4 deletions framer/Animation.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ class exports.Animation extends BaseClass
@_valueUpdaters[k] = @_updateColorValue
else if Gradient.isGradient(v) or Gradient.isGradient(@_stateA[k])
@_valueUpdaters[k] = @_updateGradientValue
# If the begin state is not set, animate from the same state but with alpha 0
@_stateA[k] ?= Gradient.multiplyAlpha(v, 0)
else
@_valueUpdaters[k] = @_updateNumberValue

Expand All @@ -294,8 +296,14 @@ class exports.Animation extends BaseClass
@_target[key] = Color.mix(@_stateA[key], @_stateB[key], value, false, @options.colorModel)

_updateGradientValue: (key, value) =>
if not @_stateB[key] and value is 1
@_target[key] = @_stateB[key]
return

gradientA = Gradient._asPlainObject(@_stateA[key])
gradientB = Gradient._asPlainObject(@_stateB[key])
# If the end state is not set, animate to the same state but with alpha 0
gradientB = Gradient._asPlainObject(@_stateB[key] ? Gradient.multiplyAlpha(gradientA, 0))

@_target[key] = Gradient.mix(
_.defaults(gradientA, gradientB)
_.defaults(gradientB, gradientA)
Expand All @@ -307,7 +315,7 @@ class exports.Animation extends BaseClass
return _.pick(@layer, _.keys(@properties))

@isAnimatable = (v) ->
_.isNumber(v) or _.isFunction(v) or isRelativeProperty(v) or Color.isColorObject(v) or Gradient.isGradient(v)
_.isNumber(v) or _.isFunction(v) or isRelativeProperty(v) or Color.isColorObject(v) or Gradient.isGradientObject(v)

@filterAnimatableProperties = (properties) ->
# Function to filter only animatable properties out of a given set
Expand All @@ -330,8 +338,8 @@ class exports.Animation extends BaseClass
animatableProperties[k] = v
else if Color.isValidColorProperty(k, v)
animatableProperties[k] = new Color(v)
else if k is "gradient" and not _.isEmpty(Gradient._asPlainObject(v))
animatableProperties[k] = new Gradient(v)
else if k is "gradient"
animatableProperties[k] = v

return animatableProperties

Expand Down
13 changes: 10 additions & 3 deletions framer/Gradient.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
class exports.Gradient extends BaseClass
constructor: (options = {}) ->

if options instanceof Gradient then return options

options.start ?= "black"
options.end ?= "white"
options.angle ?= 0
Expand Down Expand Up @@ -64,7 +62,9 @@ class exports.Gradient extends BaseClass
end: colorB
angle: Math.round(Math.random() * 360)

@isGradient: (gradient) -> return gradient instanceof Gradient
@isGradient: (gradient) -> return not _.isEmpty(@_asPlainObject(gradient))

@isGradientObject: (gradient) -> return gradient instanceof Gradient

@equal: (gradientA, gradientB) ->
return false unless Gradient.isGradient(gradientA)
Expand All @@ -74,5 +74,12 @@ class exports.Gradient extends BaseClass
equalEnd = Color.equal(gradientA.end, gradientB.end)
return equalAngle and equalStart and equalEnd

@multiplyAlpha: (gradient, alpha) ->
gradient = new Gradient(gradient) if not @isGradientObject(gradient)
return new Gradient
start: gradient.start.multiplyAlpha(alpha)
end: gradient.end.multiplyAlpha(alpha)
angle: gradient.angle

@_asPlainObject: (gradient) ->
_.pick(gradient, ["start", "end", "angle"])
44 changes: 22 additions & 22 deletions framer/Layer.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Utils = require "./Utils"
{LayerDraggable} = require "./LayerDraggable"
{LayerPinchable} = require "./LayerPinchable"
{Gestures} = require "./Gestures"
{LayerPropertyProxy} = require "./LayerPropertyProxy"

NoCacheDateKey = Date.now()

Expand Down Expand Up @@ -79,14 +80,14 @@ layerProperty = (obj, name, cssProperty, fallback, validator, transformer, optio

exports.layerProperty = layerProperty

# Use this to wrap property values in a Proxy so setting sub-properties
# will also trigger updates on the layer.
# Because we’re not fully on ES6, we can’t use Proxy, so use our own wrapper.
layerProxiedValue = (value, layer, property) ->
return value unless window.Proxy and _.isObject(value)
new Proxy value,
set: (proxiedValue, subProperty, subValue) ->
clone = Object.assign(new proxiedValue.constructor, proxiedValue)
clone[subProperty] = subValue
layer[property] = clone
return true
return value unless _.isObject(value)
new LayerPropertyProxy value, (proxiedValue, subProperty, subValue) ->
proxiedValue[subProperty] = subValue
layer[property] = proxiedValue

layerPropertyPointTransformer = (value, layer, property) ->
if _.isFunction(value)
Expand Down Expand Up @@ -115,20 +116,21 @@ asBorderRadius = (value) ->
return 0 if not _.isObject(value)

result = {}
isValidObject = false
for key in ["topLeft", "topRight", "bottomRight", "bottomLeft"]
# TODO: Also support percentages?
if _.has(value, key) and _.isNumber(value[key])
result[key] = value[key]
return if _.isEmpty(result) then 0 else result
isValidObject ||= _.has(value, key)
result[key] = value[key] ? 0
return if not isValidObject then 0 else result

asBorderWidth = (value) ->
return value if _.isNumber(value)
return 0 if not _.isObject(value)
result = {}
isValidObject = false
for key in ["left", "right", "bottom", "top"]
if _.has(value, key) and _.isNumber(value[key])
result[key] = value[key]
return if _.isEmpty(result) then 0 else result
isValidObject ||= _.has(value, key)
result[key] = value[key] ? 0
return if not isValidObject then 0 else result

parentOrContext = (layerOrContext) ->
if layerOrContext.parent?
Expand Down Expand Up @@ -902,7 +904,7 @@ class exports.Layer extends BaseClass
defaults = Defaults.getDefaults "Layer", {}
isBackgroundColorDefault = @backgroundColor?.isEqual(defaults.backgroundColor)

if Gradient.isGradient(value)
if Gradient.isGradientObject(value)
@emit("change:gradient", value, currentValue)
@emit("change:image", value, currentValue)
@_setPropertyValue("image", value)
Expand Down Expand Up @@ -970,15 +972,13 @@ class exports.Layer extends BaseClass

@define "gradient",
get: ->
return layerProxiedValue(@image, @, "gradient") if Gradient.isGradient(@image)
return layerProxiedValue(@image, @, "gradient") if Gradient.isGradientObject(@image)
return null
set: (value) ->
set: (value) -> # Copy semantics!
if Gradient.isGradient(value)
@image = value
else
gradientOptions = Gradient._asPlainObject(value)
if not _.isEmpty(gradientOptions)
@image = new Gradient(gradientOptions)
@image = new Gradient(value)
else if not value and Gradient.isGradientObject(@image)
@image = null

##############################################################
## HIERARCHY
Expand Down
19 changes: 19 additions & 0 deletions framer/LayerPropertyProxy.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# This is a subset polyfill for ES6’s Proxy
# Because we only use it for setters, the only callback available is when
# a (sub)property is set.

class exports.LayerPropertyProxy
constructor: (target, callback) ->
proxy = @
getter = (prop) ->
@[prop]
setter = (prop, value) ->
callback(@, prop, value, proxy)
for prop in Object.getOwnPropertyNames(target)
targetDesc = Object.getOwnPropertyDescriptor(target, prop)
desc =
enumerable: target.enumerable
get: getter.bind(target, prop)
set: setter.bind(target, prop)
Object.defineProperty(proxy, prop, desc)
proxy.__proto__ = target.__proto__
4 changes: 2 additions & 2 deletions test/tests/GradientTest.coffee
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
describe "Linear Gradient", ->
describe "Gradient", ->

it "should allow input as object", ->

Expand All @@ -14,7 +14,7 @@ describe "Linear Gradient", ->
gradient.start.isEqual(start).should.be.true
gradient.end.isEqual(end).should.be.true
gradient.angle.should.equal angle

it "should compare for equality", ->

gradient = new Gradient
Expand Down
38 changes: 38 additions & 0 deletions test/tests/LayerAnimationTest.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -841,3 +841,41 @@ describe "LayerAnimation", ->
animation.on Events.AnimationEnd, ->
layer.x.should.equal 10
done()

describe "Gradients", ->

it "should animate only the subproperties", (done) ->
layer = new Layer
layer.gradient =
start: "blue"
layer.states.test =
gradient:
end: "red"
animation = layer.animate "test"
animation.on Events.AnimationEnd, ->
layer.gradient.start.isEqual("blue").should.be.true
layer.gradient.end.isEqual("red").should.be.true
done()

it "should animate if no gradient is set yet", (done) ->
layer = new Layer
layer.on "change:gradient", ->
if layer.gradient
layer.gradient.start.b.should.equal 255
layer.on Events.AnimationEnd, ->
layer.gradient.start.isEqual("blue").should.be.true
done()
layer.animate
gradient:
start: "blue"

it "should animate to a null gradient", (done) ->
layer = new Layer
gradient:
start: "blue"
end: "red"
layer.on Events.AnimationEnd, ->
assert.equal layer.gradient, null
done()
layer.animate
gradient: null
Loading

0 comments on commit 3430da7

Please sign in to comment.