Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Merge pull request #558 from koenbok/feature/path-targeting
Feature/path targeting
- Loading branch information
Showing
with
518 additions
and 150 deletions.
- +9 −7 framer/Animation.coffee
- +11 −1 framer/BaseClass.coffee
- +2 −0 framer/Framer.coffee
- +32 −10 framer/Layer.coffee
- +5 −1 framer/LayerStyle.coffee
- +60 −68 framer/SVG.coffee
- +84 −0 framer/SVGBaseLayer.coffee
- +68 −0 framer/SVGGroup.coffee
- +19 −60 framer/SVGLayer.coffee
- +82 −0 framer/SVGPath.coffee
- +7 −0 framer/Utils.coffee
- +3 −1 package.json
- +1 −0 test/tests.coffee
- +38 −0 test/tests/BaseClassTest.coffee
- +24 −1 test/tests/LayerAnimationTest.coffee
- +3 −1 test/tests/LayerTest.coffee
- +27 −0 test/tests/SVGPathTest.coffee
- +43 −0 test/tests/UtilsTest.coffee
@@ -1,70 +1,62 @@ | ||
{BaseClass} = require "./BaseClass" | ||
|
||
class SVG | ||
@isSVG: (svg) -> | ||
svg instanceof SVGElement | ||
|
||
|
||
class SVGPath extends BaseClass | ||
|
||
@define "length", | ||
get: -> | ||
@_length | ||
|
||
|
||
@define "start", | ||
get: -> | ||
@pointAtFraction(0) | ||
|
||
@define "end", | ||
get: -> | ||
@pointAtFraction(1) | ||
|
||
@define "path", @simpleProperty("path", null) | ||
|
||
constructor: (path) -> | ||
return null if not SVGPath.isPath(path) | ||
super | ||
if path instanceof SVGPath | ||
path = path.path | ||
@path = path | ||
@_length = path.getTotalLength() | ||
|
||
pointAtFraction: (fraction) -> | ||
@path.getPointAtLength(@length * fraction) | ||
|
||
valueUpdater: (axis, target, offset) => | ||
switch axis | ||
when "horizontal" | ||
offset -= @start.x | ||
return (key, value) => | ||
target[key] = offset + @pointAtFraction(value).x | ||
when "vertical" | ||
offset -= @start.y | ||
return (key, value) => | ||
target[key] = offset + @pointAtFraction(value).y | ||
when "angle" | ||
return (key, value, delta = 0) => | ||
return if delta is 0 | ||
fromPoint = @pointAtFraction(Math.max(value - delta, 0)) | ||
toPoint = @pointAtFraction(Math.min(value + delta, 1)) | ||
angle = Math.atan2(fromPoint.y - toPoint.y, fromPoint.x - toPoint.x) * 180 / Math.PI - 90 | ||
target[key] = angle | ||
{_} = require "./Underscore" | ||
{Color} = require "./Color" | ||
|
||
class exports.SVG | ||
|
||
@validFill = (value) -> | ||
Color.validColorValue(value) or _.startsWith(value, "url(") | ||
|
||
@toFill = (value) -> | ||
if _.startsWith(value, "url(") | ||
return value | ||
else | ||
Color.toColor(value) | ||
|
||
@updateGradientSVG: (svgLayer) -> | ||
return if svgLayer.__constructor | ||
if not Gradient.isGradient(svgLayer.gradient) | ||
svgLayer._elementGradientSVG?.innerHTML = "" | ||
return | ||
|
||
if not svgLayer._elementGradientSVG | ||
svgLayer._elementGradientSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg") | ||
svgLayer._element.appendChild svgLayer._elementGradientSVG | ||
|
||
id = "#{svgLayer.id}-gradient" | ||
svgLayer._elementGradientSVG.innerHTML = """ | ||
<linearGradient id='#{id}' gradientTransform='rotate(#{svgLayer.gradient.angle - 90}, 0.5, 0.5)' > | ||
<stop offset="0" stop-color='##{svgLayer.gradient.start.toHex()}' stop-opacity='#{svgLayer.gradient.start.a}' /> | ||
<stop offset="1" stop-color='##{svgLayer.gradient.end.toHex()}' stop-opacity='#{svgLayer.gradient.end.a}' /> | ||
</linearGradient> | ||
""" | ||
svgLayer.fill = "url(##{id})" | ||
|
||
@constructSVGElements: (root, elements, PathClass, GroupClass) -> | ||
|
||
targets = {} | ||
children = [] | ||
|
||
if elements? | ||
for element in elements | ||
|
||
isTarget = element.id? | ||
|
||
options = {} | ||
options.name = element.id if isTarget | ||
options.parent = root | ||
|
||
if element instanceof SVGGElement | ||
group = new GroupClass(element, options) | ||
children.push(group) | ||
_.extend(targets, group.elements) | ||
if isTarget then targets[element.id] = group | ||
continue | ||
if element instanceof SVGPathElement | ||
path = new PathClass(element, options) | ||
children.push(path) | ||
if isTarget then targets[element.id] = path | ||
continue | ||
return {targets, children} | ||
|
||
@isPath: (path) -> | ||
path instanceof SVGPathElement or path instanceof SVGPath | ||
|
||
@getStart: (path) -> | ||
@getPointAtFraction(path, 0) | ||
|
||
@getPointAtFraction: (path, fraction) -> | ||
return null if not @isPath(path) | ||
length = path.getTotalLength() * fraction | ||
path.getPointAtLength(length) | ||
|
||
@getEnd: (path) -> | ||
@getPointAtFraction(path, 1) | ||
|
||
|
||
exports.SVG = SVG | ||
exports.SVGPath = SVGPath | ||
path instanceof Framer.SVGPath |
@@ -0,0 +1,84 @@ | ||
{Layer, layerProperty} = require "./Layer" | ||
{Color} = require "./Color" | ||
|
||
class exports.SVGBaseLayer extends Layer | ||
# Overridden Layer properties | ||
|
||
@define "parent", | ||
enumerable: false | ||
exportable: false | ||
importable: false | ||
get: -> | ||
@_parent or null | ||
@define "html", get: -> @_element.outerHTML or "" | ||
@define "width", get: -> @_width | ||
@define "height", get: -> @_height | ||
|
||
# Disabled properties | ||
@undefine ["label", "blending", "image"] | ||
@undefine ["blur", "brightness", "saturate", "hueRotate", "contrast", "invert", "grayscale", "sepia"] # webkitFilter properties | ||
@undefine ["backgroundBlur", "backgroundBrightness", "backgroundSaturate", "backgroundHueRotate", "backgroundContrast", "backgroundInvert", "backgroundGrayscale", "backgroundSepia"] # webkitBackdropFilter properties | ||
for i in [0..8] | ||
do (i) => | ||
@undefine "shadow#{i+1}" | ||
@undefine "shadows" | ||
@undefine ["borderRadius", "cornerRadius", "borderStyle"] | ||
@undefine ["constraintValues", "htmlIntrinsicSize"] | ||
|
||
# Proxied helpers | ||
@proxy = (propertyName, proxiedName) -> | ||
@define propertyName, | ||
get: -> | ||
@[proxiedName] | ||
set: (value) -> | ||
return if @__applyingDefaults | ||
@[proxiedName] = value | ||
|
||
@proxy "borderColor", "stroke" | ||
@proxy "strokeColor", "stroke" | ||
@proxy "borderWidth", "strokeWidth" | ||
|
||
# Overridden functions from Layer | ||
_insertElement: -> | ||
updateForSizeChange: -> | ||
updateForDevicePixelRatioChange: => | ||
for cssProperty in ["width", "height", "webkitTransform"] | ||
@_element.style[cssProperty] = LayerStyle[cssProperty](@) | ||
copy: undefined | ||
copySingle: undefined | ||
addChild: undefined | ||
removeChild: undefined | ||
addSubLayer: undefined | ||
removeSubLayer: undefined | ||
bringToFront: undefined | ||
sendToBack: undefined | ||
placeBefore: undefined | ||
placeBehind: undefined | ||
|
||
@attributesFromElement: (attributes, element) -> | ||
options = {} | ||
for attribute in attributes | ||
key = _.camelCase attribute | ||
options[key] = element.getAttribute(attribute) | ||
return options | ||
|
||
constructor: (options) -> | ||
element = options.element | ||
@_element = element | ||
@_elementBorder = element | ||
@_elementHTML = element | ||
@_parent = options.parent | ||
delete options.parent | ||
delete options.element | ||
|
||
pathProperties = ["fill", "stroke", "stroke-width", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-dasharray", "stroke-dashoffset"] | ||
_.defaults options, @constructor.attributesFromElement(pathProperties, element) | ||
|
||
super(options) | ||
|
||
@define "gradient", | ||
get: -> | ||
console.warn "The gradient property is currently not supported on shapes" | ||
return undefined | ||
set: (value) -> | ||
console.warn "The gradient property is currently not supported on shapes" |
Oops, something went wrong.