Permalink
Browse files

Merge pull request #537 from koenbok/feature/multiple-shadows

Feature/multiple shadows
  • Loading branch information...
nvh committed Aug 23, 2017
2 parents ee120e5 + bcef2c9 commit a9c2efe7295af4dfbd853456696adb5befbb7dc3
Showing with 228 additions and 41 deletions.
  1. +9 −3 framer/Animation.coffee
  2. +8 −0 framer/Defaults.coffee
  3. +45 −10 framer/Layer.coffee
  4. +28 −26 framer/LayerStyle.coffee
  5. +1 −1 framer/TextLayer.coffee
  6. +137 −1 test/tests/LayerTest.coffee
View
@@ -291,6 +291,8 @@ class exports.Animation extends BaseClass
@_valueUpdaters[k] = @_updateNumericObjectValue.bind(this, ["topLeft", "topRight", "bottomRight", "bottomLeft"])
else if k is "template"
@_valueUpdaters[k] = @_updateTemplateValue
else if /^shadow[1-9]$/.test(k)
@_valueUpdaters[k] = @_updateShadowValue
else
@_valueUpdaters[k] = @_updateNumberValue
@@ -301,7 +303,7 @@ class exports.Animation extends BaseClass
_updateNumberValue: (key, value) =>
@_target[key] = Utils.mapRange(value, 0, 1, @_stateA[key], @_stateB[key])
_updateNumericObjectValue: (propKeys, key, value) =>
_updateNumericObjectValue: (propKeys, key, value, flatten=true) =>
valueA = @_stateA[key]
valueB = @_stateB[key]
@@ -316,7 +318,7 @@ class exports.Animation extends BaseClass
result[propKey] = Utils.mapRange(value, 0, 1, keyValueA, keyValueB)
# Flatten to a single number if all properties have the same value
if _.uniq(_.values(result)).length is 1
if flatten and _.uniq(_.values(result)).length is 1
result = result[propKeys[0]]
@_target[key] = result
@@ -340,6 +342,10 @@ class exports.Animation extends BaseClass
@options.colorModel
)
_updateShadowValue: (key, value) =>
@_updateNumericObjectValue(["x", "y", "blur", "spread"], key, value, false)
@_target[key].color = Color.mix(@_stateA[key].color, @_stateB[key].color, value, false, @options.colorModel)
# shallow mix all end state `{key: value}`s if `value` is a number, otherwise just takes `value`
_updateTemplateValue: (key, value) =>
fromData = @_stateA[key]
@@ -374,7 +380,7 @@ class exports.Animation extends BaseClass
# Special cases that animate with different types of objects
@isAnimatableKey = (k) ->
k in ["gradient", "borderWidth", "borderRadius", "template"]
k in ["gradient", "borderWidth", "borderRadius", "template"] or /^shadow[1-9]$/.test(k)
@filterAnimatableProperties = (properties) ->
# Function to filter only animatable properties out of a given set
View
@@ -88,6 +88,14 @@ Originals =
backgroundColor: null
Hints:
color: "rgba(144, 19, 254, 0.8)"
Shadow:
x: 0
y: 0
color: "rgba(123, 123, 123, 0.5)"
type: "box"
blur: 0
spread: 0
exports.Defaults =
View
@@ -139,10 +139,19 @@ parentOrContext = (layerOrContext) ->
else
return layerOrContext.context
exports.updateShadow = (layer, value) ->
layer._element.style.boxShadow = LayerStyle["boxShadow"](layer)
layer._element.style.textShadow = LayerStyle["textShadow"](layer)
layer._element.style.webkitFilter = LayerStyle["webkitFilter"](layer)
proxiedShadowValue = (layer, value, index = 0) ->
v = _.defaults _.clone(value), Framer.Defaults.Shadow
v?.color = new Color(v.color)
layerProxiedValue(v, layer, "shadow#{index+1}")
updateShadowsProperty = (prop) ->
(layer, value) ->
layer.shadows ?= []
if (layer.shadows.filter (s) -> s isnt null).length is 0
layer.shadows[0] = proxiedShadowValue(layer, Framer.Defaults.Shadow)
for shadow in layer.shadows
shadow?[prop] = value
layer.updateShadowStyle()
class exports.Layer extends BaseClass
@@ -330,13 +339,39 @@ class exports.Layer extends BaseClass
@define "grayscale", layerProperty(@, "grayscale", "webkitFilter", 0, _.isNumber)
@define "sepia", layerProperty(@, "sepia", "webkitFilter", 0, _.isNumber)
for i in [0...8]
do (i) =>
@define "shadow#{i+1}",
depends: ["shadowX", "shadowY", "shadowBlur", "shadowSpread", "shadowColor", "shadowType"]
get: ->
@shadows?[i]
set: (value) ->
@shadows ?= []
@shadows[i] = proxiedShadowValue(@, value, i)
@updateShadowStyle()
# Shadow properties
@define "shadowX", layerProperty(@, "shadowX", null, 0, _.isNumber, null, {}, exports.updateShadow)
@define "shadowY", layerProperty(@, "shadowY", null, 0, _.isNumber, null, {}, exports.updateShadow)
@define "shadowBlur", layerProperty(@, "shadowBlur", null, 0, _.isNumber, null, {}, exports.updateShadow)
@define "shadowSpread", layerProperty(@, "shadowSpread", null, 0, _.isNumber, null, {}, exports.updateShadow)
@define "shadowColor", layerProperty(@, "shadowColor", null, "", Color.validColorValue, Color.toColor, {}, exports.updateShadow)
@define "shadowType", layerProperty(@, "shadowType", null, "box", null, null, {}, exports.updateShadow)
@define "shadowX", layerProperty(@, "shadowX", null, 0, _.isNumber, null, {}, updateShadowsProperty("x"))
@define "shadowY", layerProperty(@, "shadowY", null, 0, _.isNumber, null, {}, updateShadowsProperty("y"))
@define "shadowBlur", layerProperty(@, "shadowBlur", null, 0, _.isNumber, null, {}, updateShadowsProperty("blur"))
@define "shadowSpread", layerProperty(@, "shadowSpread", null, 0, _.isNumber, null, {}, updateShadowsProperty("spread"))
@define "shadowColor", layerProperty(@, "shadowColor", null, "", Color.validColorValue, Color.toColor, {}, updateShadowsProperty("color"))
@define "shadowType", layerProperty(@, "shadowType", null, "box", null, null, {}, updateShadowsProperty("type"))
@define "shadows",
default: null
get: ->
@_getPropertyValue("shadows")
set: (value) ->
shadows = []
for shadow, index in value
shadows.push proxiedShadowValue(@, shadow, index)
@_setPropertyValue("shadows", shadows)
@updateShadowStyle()
updateShadowStyle: ->
@_element.style.boxShadow = LayerStyle["boxShadow"](@)
@_element.style.textShadow = LayerStyle["textShadow"](@)
@_element.style.webkitFilter = LayerStyle["webkitFilter"](@)
# Color properties
@define "backgroundColor", layerProperty(@, "backgroundColor", "backgroundColor", null, Color.validColorValue, Color.toColor)
View
@@ -28,6 +28,21 @@ _Force2DProperties =
"rotationX": 0
"rotationY": 0
getShadowStrings = (layer, types, createString) ->
if not _.isArray(types)
types = [types]
result = []
if layer.shadows?
for shadow in layer.shadows
if shadow is null or not (shadow.type in types)
continue
shadow = _.defaults _.clone(shadow), Framer.Defaults.Shadow
if shadow.x is 0 and shadow.y is 0 and shadow.blur is 0 and shadow.spread is 0
continue
dropShadow = createString(shadow, layer.context.pixelMultiplier)
result.push(dropShadow)
return result
exports.LayerStyle =
width: (layer) ->
@@ -95,10 +110,11 @@ exports.LayerStyle =
css.push("#{cssName}(#{filterFormat(layer[layerName], unit)})")
# filter shadow
if layer._properties and layer._properties.shadowType is "drop" and layer._properties.shadowColor
dropShadow = "drop-shadow(#{layer._properties.shadowX * layer.context.pixelMultiplier}px #{layer._properties.shadowY * layer.context.pixelMultiplier}px #{layer._properties.shadowBlur * layer.context.pixelMultiplier}px #{layer._properties.shadowColor})"
css.push(dropShadow)
shadowStrings = getShadowStrings(layer, "drop", (shadow, pixelMultiplier) ->
"drop-shadow(#{shadow.x * pixelMultiplier}px #{shadow.y * pixelMultiplier}px #{shadow.blur * pixelMultiplier}px #{shadow.color})"
)
css = css.concat shadowStrings
return css.join(" ")
webkitTransform: (layer) ->
@@ -168,33 +184,19 @@ exports.LayerStyle =
return "auto"
boxShadow: (layer) ->
shadowStrings = getShadowStrings(layer, ["box", "inset"], (shadow, pixelMultiplier) ->
insetString = if shadow.type is "inset" then "inset " else ""
"#{insetString}#{shadow.x * pixelMultiplier}px #{shadow.y * pixelMultiplier}px #{shadow.blur * pixelMultiplier}px #{shadow.spread * pixelMultiplier}px #{shadow.color}"
)
props = layer._properties
if not props and props.shadowColor
return ""
else if not (props.shadowType is "box" or props.shadowType is "inset")
return ""
else if props.shadowX is 0 and props.shadowY is 0 and props.shadowBlur is 0 and props.shadowSpread is 0
return ""
insetString = ""
insetString = "inset " if props.shadowType is "inset"
return "#{insetString} #{layer._properties.shadowX * layer.context.pixelMultiplier}px #{layer._properties.shadowY * layer.context.pixelMultiplier}px #{layer._properties.shadowBlur * layer.context.pixelMultiplier}px #{layer._properties.shadowSpread * layer.context.pixelMultiplier}px #{layer._properties.shadowColor}"
return shadowStrings.join(", ")
textShadow: (layer) ->
props = layer._properties
if not props and props.shadowColor
return ""
else if props.shadowType isnt "text"
return ""
else if props.shadowX is 0 and props.shadowY is 0 and props.shadowBlur is 0 and props.shadowSpread is 0
return ""
return "#{layer._properties.shadowX * layer.context.pixelMultiplier}px #{layer._properties.shadowY * layer.context.pixelMultiplier}px #{layer._properties.shadowBlur * layer.context.pixelMultiplier}px #{layer._properties.shadowColor}"
shadowStrings = getShadowStrings(layer, "text", (shadow, pixelMultiplier) ->
"#{shadow.x * pixelMultiplier}px #{shadow.y * pixelMultiplier}px #{shadow.blur * pixelMultiplier}px #{shadow.color}"
)
return shadowStrings.join(", ")
backgroundColor: (layer) ->
return layer._properties.backgroundColor
View
@@ -1,4 +1,4 @@
{Layer, layerProperty, updateShadow} = require "./Layer"
{Layer, layerProperty} = require "./Layer"
{LayerStyle} = require "./LayerStyle"
{StyledText} = require "./StyledText"
View
@@ -732,7 +732,6 @@ describe "Layer", ->
layer.shadowY.should.equal 10
layer.shadowBlur.should.equal 10
layer.shadowSpread.should.equal 10
layer.style.boxShadow.should.equal "rgba(123, 123, 123, 0.498039) 10px 10px 10px 10px"
# Only after we set a color a shadow should be drawn
@@ -748,6 +747,143 @@ describe "Layer", ->
layer.shadowColor = null
layer.style.boxShadow.should.equal "rgba(0, 0, 0, 0) 10px 10px 10px 10px"
it "should add multiple shadows by passing an array into the shadows property", ->
l = new Layer
shadows: [{blur: 10, color: "red"}, {x: 1, color: "blue"}, {y: 10, color: "green", type: "inset"}]
l.style.boxShadow.should.equal "rgb(255, 0, 0) 0px 0px 10px 0px, rgb(0, 0, 255) 1px 0px 0px 0px, rgb(0, 128, 0) 0px 10px 0px 0px inset"
it "should be able to access shadow properties through properties", ->
l = new Layer
l.shadow1 = x: 10
l.style.boxShadow.should.equal "rgba(123, 123, 123, 0.498039) 10px 0px 0px 0px"
it "should change the shadow when a shadow property is changed", ->
l = new Layer
l.shadow2 = x: 10
l.shadow2.x = 5
l.style.boxShadow.should.equal "rgba(123, 123, 123, 0.498039) 5px 0px 0px 0px"
it "should change the shadow when another shadow property is changed", ->
l = new Layer
l.shadow2 = x: 10
l.shadow2.y = 5
l.style.boxShadow.should.equal "rgba(123, 123, 123, 0.498039) 10px 5px 0px 0px"
it "should get the same shadow that is set", ->
l = new Layer
l.shadow1 = x: 10
l.shadow1.x.should.equal 10
it "should let the shadow be set in the constructor", ->
l = new Layer
shadow1:
x: 10
color: "red"
l.shadow1.x.should.equal 10
l.shadow1.color.toString().should.equal "rgb(255, 0, 0)"
it "should convert color strings to color objects when set via shadows", ->
l = new Layer
shadows: [{blur: 10, color: "red"}]
l.style.boxShadow.should.equal "rgb(255, 0, 0) 0px 0px 10px 0px"
it "should change the first shadow when a shadow property is changed", ->
l = new Layer
l.shadow1.x = 10
l.style.boxShadow.should.equal "rgba(123, 123, 123, 0.498039) 10px 0px 0px 0px"
it "should animate shadows through a shadow property", (done) ->
l = new Layer
l.animate
shadow1:
x: 20
options:
time: 0.2
l.onAnimationEnd ->
l.shadow1.x.should.equal 20
done()
it "should animate shadow colors", (done) ->
l = new Layer
shadow1:
color: "red"
l.animate
shadow1:
color: "blue"
options:
time: 0.2
l.onAnimationEnd ->
l.shadow1.color.toString().should.equal "rgb(0, 0, 255)"
done()
it "should remove a shadow when a shadow property is set to null", ->
l = new Layer
l.shadow1 = x: 10
l.shadow2 = y: 10
l.shadow3 = blur: 10
l.shadow2 = null
l.style.boxShadow.should.equal "rgba(123, 123, 123, 0.498039) 10px 0px 0px 0px, rgba(123, 123, 123, 0.498039) 0px 0px 10px 0px"
it "should should change all shadows when shadowColor, shadowX, shadowY are changed", ->
l = new Layer
shadows: [{blur: 10, color: "red"}, {x: 1, color: "blue"}, {y: 10, color: "green", type: "inset"}]
l.shadowColor = "yellow"
l.shadowX = 10
l.shadowY = 20
l.shadowBlur = 30
l.shadowSpread = 40
l.style.boxShadow.should.equal "rgb(255, 255, 0) 10px 20px 30px 40px, rgb(255, 255, 0) 10px 20px 30px 40px, rgb(255, 255, 0) 10px 20px 30px 40px inset"
it "should create a shadow if a shadowColor is set when there aren't any shadows", ->
l = new Layer
l.shadows = []
l.shadowColor = "yellow"
l.shadows.length.should.equal 1
Color.equal(l.shadows[0].color, "yellow").should.be.true
it "should copy shadows if you copy a layer", ->
l = new Layer
shadows: [{blur: 10, color: "red"}, {x: 1, color: "blue"}, {y: 10, color: "green", type: "inset"}]
l2 = l.copy()
l2.style.boxShadow.should.equal "rgb(255, 0, 0) 0px 0px 10px 0px, rgb(0, 0, 255) 1px 0px 0px 0px, rgb(0, 128, 0) 0px 10px 0px 0px inset"
l2.shadows.should.eql l.shadows
l2.shadows.should.not.equal l.shadows
it "should not change shadows after copying a layer", ->
l = new Layer
shadows: [{blur: 10, color: "red"}, {x: 1, color: "blue"}, {y: 10, color: "green", type: "inset"}]
l2 = l.copy()
l.shadow1.x = 100
l2.shadow1.should.not.equal l.shadow1
l2.shadow1.x.should.not.equal 100
it "should be able to handle more then 10 shadows", ->
shadows = []
result = []
for i in [1...16]
shadows.push {x: i}
result.push "rgba(123, 123, 123, 0.498039) #{i}px 0px 0px 0px"
l = new Layer
shadows: shadows
l.style.boxShadow.should.equal result.join(", ")
l.shadows.length.should.equal 15
l.shadows[14].y = 10
l.updateShadowStyle()
result[14] = "rgba(123, 123, 123, 0.498039) 15px 10px 0px 0px"
l.style.boxShadow.should.equal result.join(", ")
it "should handle multiple shadow types", ->
l = new Layer
shadows: [{type: "box", x: 10}, {type: "text", x: 5}, {type: "inset", y: 3}, {type: "drop", y: 15}]
l.style.boxShadow.should.equal "rgba(123, 123, 123, 0.498039) 10px 0px 0px 0px, rgba(123, 123, 123, 0.498039) 0px 3px 0px 0px inset"
l.style.textShadow.should.equal "rgba(123, 123, 123, 0.498039) 5px 0px 0px"
l.style.webkitFilter.should.equal "drop-shadow(rgba(123, 123, 123, 0.498039) 0px 15px 0px)"
describe "Events", ->
it "should remove all events", ->
layerA = new Layer
handler = -> console.log "hello"

0 comments on commit a9c2efe

Please sign in to comment.