diff --git a/Source/animation/types/animation_generators/Cache/AnimationCache.swift b/Source/animation/types/animation_generators/Cache/AnimationCache.swift index 0dbcb49b..0a746d99 100644 --- a/Source/animation/types/animation_generators/Cache/AnimationCache.swift +++ b/Source/animation/types/animation_generators/Cache/AnimationCache.swift @@ -8,7 +8,7 @@ import AppKit class AnimationUtils { - class func layerForNodeRenderer(_ renderer: NodeRenderer, animation: Animation, customBounds: Rect? = .none, shouldRenderContent: Bool = true) -> ShapeLayer { + class func layerForNodeRenderer(_ renderer: NodeRenderer, animation: Animation, customBounds: Rect? = .none, shouldRenderContent: Bool = true, isGradient: Bool = false) -> LayerProtocol { let node = renderer.node if let cachedLayer = renderer.layer { @@ -18,10 +18,10 @@ class AnimationUtils { } // 'sublayer' is for actual CAAnimations, and 'layer' is for manual transforming and hierarchy changes - let sublayer = ShapeLayer() + let sublayer: LayerProtocol = isGradient ? GradientLayer () : ShapeLayer() sublayer.shouldRenderContent = shouldRenderContent - let layer = ShapeLayer() + let layer: LayerProtocol = isGradient ? GradientLayer () : ShapeLayer() layer.addSublayer(sublayer) layer.masksToBounds = false diff --git a/Source/animation/types/animation_generators/MorphingGenerator.swift b/Source/animation/types/animation_generators/MorphingGenerator.swift index 34c64e85..c3a7c060 100644 --- a/Source/animation/types/animation_generators/MorphingGenerator.swift +++ b/Source/animation/types/animation_generators/MorphingGenerator.swift @@ -67,9 +67,15 @@ func addMorphingAnimation(_ animation: BasicAnimation, _ context: AnimationConte completion() } - - layer.path = fromLocus.toCGPath() - layer.setupStrokeAndFill(shape) + + if let layer = layer as? ShapeLayer { + layer.path = fromLocus.toCGPath() + layer.setupStrokeAndFill(shape) + } else if let layer = layer as? GradientLayer { + let maskLayer = CAShapeLayer() + maskLayer.path = fromLocus.toCGPath() + layer.mask = maskLayer + } let animationId = animation.ID layer.add(generatedAnimation, forKey: animationId) diff --git a/Source/animation/types/animation_generators/PathAnimationGenerator.swift b/Source/animation/types/animation_generators/PathAnimationGenerator.swift index c7bd052e..377f2c0b 100644 --- a/Source/animation/types/animation_generators/PathAnimationGenerator.swift +++ b/Source/animation/types/animation_generators/PathAnimationGenerator.swift @@ -19,7 +19,7 @@ func addPathAnimation(_ animation: BasicAnimation, _ context: AnimationContext, return } - let layer = AnimationUtils.layerForNodeRenderer(renderer, animation: animation, shouldRenderContent: false) + let layer = AnimationUtils.layerForNodeRenderer(renderer, animation: animation, shouldRenderContent: false, isGradient: shape.fill is LinearGradient) // Creating proper animation let generatedAnim = generatePathAnimation( @@ -52,9 +52,11 @@ func addPathAnimation(_ animation: BasicAnimation, _ context: AnimationContext, completion() } - //layer.path = RenderUtils.toCGPath(shape.form).copy(using: &layer.transform) - layer.path = shape.form.toCGPath() - layer.setupStrokeAndFill(shape) + if let layer = layer as? ShapeLayer { + //layer.path = RenderUtils.toCGPath(shape.form).copy(using: &layer.transform) + layer.path = shape.form.toCGPath() + layer.setupStrokeAndFill(shape) + } layer.add(generatedAnim, forKey: animation.ID) animation.removeFunc = { [weak layer] in diff --git a/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift b/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift index 8d567b3f..b4b08447 100644 --- a/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift +++ b/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift @@ -32,7 +32,7 @@ func addShapeAnimation(_ animation: BasicAnimation, _ context: AnimationContext, let toShape = shapeAnimation.getVFunc()(animation.autoreverses ? 0.5 : 1.0) let duration = animation.autoreverses ? animation.getDuration() / 2.0 : animation.getDuration() - let layer = AnimationUtils.layerForNodeRenderer(renderer, animation: animation, shouldRenderContent: false) + let layer = AnimationUtils.layerForNodeRenderer(renderer, animation: animation, shouldRenderContent: false, isGradient: shape.fill is LinearGradient) // Creating proper animation let generatedAnimation = generateShapeAnimation(context, @@ -89,8 +89,22 @@ func addShapeAnimation(_ animation: BasicAnimation, _ context: AnimationContext, completion() } - layer.path = fromShape.form.toCGPath() - layer.setupStrokeAndFill(fromShape) + if let layer = layer as? ShapeLayer { + layer.path = fromShape.form.toCGPath() + layer.setupStrokeAndFill(fromShape) + } else if let layer = layer as? GradientLayer { + let maskLayer = CAShapeLayer() + maskLayer.path = fromShape.form.toCGPath() + layer.mask = maskLayer + + if let color = shape.fill as? LinearGradient { + let properties = color.toCG() + layer.colors = properties.colors + layer.locations = properties.locations + layer.startPoint = CGPoint(x: color.x1, y: color.y1) + layer.endPoint = CGPoint(x: color.x2, y: color.y2) + } + } let animationId = animation.ID layer.add(generatedAnimation, forKey: animationId) @@ -127,16 +141,25 @@ fileprivate func generateShapeAnimation(_ context: AnimationContext, from: Shape group.animations?.append(transformAnimation) // Fill - let fromFillColor = from.fill as? Color ?? Color.clear - let toFillColor = to.fill as? Color ?? Color.clear + + if from.fill != to.fill { + if let fromFillColor = from.fill as? Color, let toFillColor = to.fill as? Color { + let fillAnimation = CABasicAnimation(keyPath: "fillColor") + fillAnimation.fromValue = fromFillColor.toCG() + fillAnimation.toValue = toFillColor.toCG() + fillAnimation.duration = duration - if fromFillColor != toFillColor { - let fillAnimation = CABasicAnimation(keyPath: "fillColor") - fillAnimation.fromValue = fromFillColor.toCG() - fillAnimation.toValue = toFillColor.toCG() - fillAnimation.duration = duration + group.animations?.append(fillAnimation) + } else if let fromFillColor = from.fill as? LinearGradient, let toFillColor = to.fill as? LinearGradient { + let fillAnimation = CABasicAnimation(keyPath: "colors") + let fromProperties = fromFillColor.toCG() + let toProperties = toFillColor.toCG() + fillAnimation.fromValue = fromProperties.colors + fillAnimation.toValue = toProperties.colors + fillAnimation.duration = duration - group.animations?.append(fillAnimation) + group.animations?.append(fillAnimation) + } } // Stroke diff --git a/Source/animation/types/animation_generators/TransformGenerator.swift b/Source/animation/types/animation_generators/TransformGenerator.swift index 5e45377d..d4ee1dcd 100644 --- a/Source/animation/types/animation_generators/TransformGenerator.swift +++ b/Source/animation/types/animation_generators/TransformGenerator.swift @@ -22,7 +22,7 @@ func addTransformAnimation(_ animation: BasicAnimation, _ context: AnimationCont let transactionsDisabled = CATransaction.disableActions() CATransaction.setDisableActions(true) - let layer = AnimationUtils.layerForNodeRenderer(renderer, animation: animation, shouldRenderContent: true) + let layer = AnimationUtils.layerForNodeRenderer(renderer, animation: animation, shouldRenderContent: true, isGradient: (node as? Shape)?.fill is LinearGradient) // Creating proper animation let generatedAnimation = transformAnimationByFunc(transformAnimation, diff --git a/Source/model/draw/Gradient.swift b/Source/model/draw/Gradient.swift index 75cbe937..fdd09fdc 100644 --- a/Source/model/draw/Gradient.swift +++ b/Source/model/draw/Gradient.swift @@ -1,3 +1,5 @@ +import UIKit + open class Gradient: Fill { public let userSpace: Bool @@ -19,4 +21,15 @@ open class Gradient: Fill { return stops.elementsEqual(other.stops) } + + public func toCG() -> (colors: Array, locations: Array) { + var colors: Array = [] + var locations: Array = [] + for stop in stops { + colors += [stop.color.toCG()] + locations += [NSNumber(value: stop.offset)] + } + + return (colors, locations) + } } diff --git a/Source/render/NodeRenderer.swift b/Source/render/NodeRenderer.swift index 268b7552..1fb5d3c2 100644 --- a/Source/render/NodeRenderer.swift +++ b/Source/render/NodeRenderer.swift @@ -11,10 +11,10 @@ enum ColoringMode { } class CachedLayer { - let rootLayer: ShapeLayer - let animationLayer: ShapeLayer + let rootLayer: LayerProtocol + let animationLayer: LayerProtocol - required init(rootLayer: ShapeLayer, animationLayer: ShapeLayer) { + required init(rootLayer: LayerProtocol, animationLayer: LayerProtocol) { self.rootLayer = rootLayer self.animationLayer = animationLayer } diff --git a/Source/views/ShapeLayer.swift b/Source/views/ShapeLayer.swift index 82c22ecf..84bcbc17 100644 --- a/Source/views/ShapeLayer.swift +++ b/Source/views/ShapeLayer.swift @@ -6,10 +6,32 @@ import UIKit import AppKit #endif -class ShapeLayer: CAShapeLayer { +protocol LayerProtocol: CALayer { + var renderer: NodeRenderer? { get set } + var shouldRenderContent: Bool { get set } + var isForceRenderingEnabled: Bool { get set } + func draw(in ctx: CGContext) +} + +class ShapeLayer: CAShapeLayer, LayerProtocol { weak var renderer: NodeRenderer? - var shouldRenderContent = true - var isForceRenderingEnabled = true + var shouldRenderContent: Bool = true + var isForceRenderingEnabled: Bool = true + + override func draw(in ctx: CGContext) { + if !shouldRenderContent { + super.draw(in: ctx) + return + } + + renderer?.directRender(in: ctx, force: isForceRenderingEnabled) + } +} + +class GradientLayer: CAGradientLayer, LayerProtocol { + weak var renderer: NodeRenderer? + var shouldRenderContent: Bool = true + var isForceRenderingEnabled: Bool = true override func draw(in ctx: CGContext) { if !shouldRenderContent {