1
1
mirror of https://github.com/exyte/Macaw.git synced 2024-08-15 16:10:39 +03:00

Add animation layer hierarchy

This commit is contained in:
Alisa Mylnikova 2019-04-30 15:13:39 +07:00
parent 916240f118
commit 44dfb89d16
26 changed files with 573 additions and 561 deletions

View File

@ -16,6 +16,7 @@
575129B61CBD14AF00BD3C2E /* AnimationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575129B51CBD14AF00BD3C2E /* AnimationsView.swift */; };
57AF398C1E67E9DB00F0BFE2 /* EventsExampleController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57AF398B1E67E9DB00F0BFE2 /* EventsExampleController.swift */; };
58E4D50C1D841C6E00EC8815 /* TransformExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E4D50B1D841C6E00EC8815 /* TransformExampleView.swift */; };
5B195EAD2276D5C40008AE8B /* AnimationsHierarchyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B195EAB2276D5C40008AE8B /* AnimationsHierarchyViewController.swift */; };
5BAE3CB120C54E3D006BEF51 /* FiltersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAE3CB020C54E3D006BEF51 /* FiltersViewController.swift */; };
66AE19DB1CC8CB3C00B78B5E /* tiger.svg in Resources */ = {isa = PBXBuildFile; fileRef = 66AE19DA1CC8CB3C00B78B5E /* tiger.svg */; };
B02E75F11C16104900D1971D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B02E75F01C16104900D1971D /* AppDelegate.swift */; };
@ -50,6 +51,7 @@
575129B51CBD14AF00BD3C2E /* AnimationsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationsView.swift; sourceTree = "<group>"; };
57AF398B1E67E9DB00F0BFE2 /* EventsExampleController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventsExampleController.swift; sourceTree = "<group>"; };
58E4D50B1D841C6E00EC8815 /* TransformExampleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformExampleView.swift; sourceTree = "<group>"; };
5B195EAB2276D5C40008AE8B /* AnimationsHierarchyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationsHierarchyViewController.swift; sourceTree = "<group>"; };
5BAE3CB020C54E3D006BEF51 /* FiltersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersViewController.swift; sourceTree = "<group>"; };
66AE19DA1CC8CB3C00B78B5E /* tiger.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = tiger.svg; path = Example/Assets/SVG/tiger.svg; sourceTree = "<group>"; };
B02E75ED1C16104900D1971D /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -111,6 +113,7 @@
5747F9BB1E38B660004E338F /* Morphing */,
57AF398A1E67E86400F0BFE2 /* Events */,
5BAE3CAF20C54DA5006BEF51 /* Filters */,
5BE9F15D226EEAE500030967 /* AnimationsHierarchy */,
574EC4401CB7E2440063F317 /* MenuViewController.swift */,
);
path = Examples;
@ -158,6 +161,14 @@
path = Filters;
sourceTree = "<group>";
};
5BE9F15D226EEAE500030967 /* AnimationsHierarchy */ = {
isa = PBXGroup;
children = (
5B195EAB2276D5C40008AE8B /* AnimationsHierarchyViewController.swift */,
);
path = AnimationsHierarchy;
sourceTree = "<group>";
};
65CBD13FE29C7F0265D0E051 /* Frameworks */ = {
isa = PBXGroup;
children = (
@ -360,6 +371,7 @@
574EC4441CBB60AF0063F317 /* AnimationsExampleController.swift in Sources */,
5747F9BD1E38B683004E338F /* MorphingView.swift in Sources */,
575129B61CBD14AF00BD3C2E /* AnimationsView.swift in Sources */,
5B195EAD2276D5C40008AE8B /* AnimationsHierarchyViewController.swift in Sources */,
58E4D50C1D841C6E00EC8815 /* TransformExampleView.swift in Sources */,
B02E75F11C16104900D1971D /* AppDelegate.swift in Sources */,
5BAE3CB120C54E3D006BEF51 /* FiltersViewController.swift in Sources */,

View File

@ -383,5 +383,38 @@
</objects>
<point key="canvasLocation" x="-113" y="1196"/>
</scene>
<!--AnimationsHierarchy-->
<scene sceneID="8bU-7m-OgE">
<objects>
<viewController storyboardIdentifier="AnimationsHierarchyViewController" title="Animations Hierarchy" id="T46-H6-N3f" userLabel="AnimationsHierarchy" customClass="AnimationsHierarchyViewController" customModule="Example" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="F0r-c7-NMQ"/>
<viewControllerLayoutGuide type="bottom" id="MiP-PT-hkh"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="ONf-4d-IZ5">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="WpG-iW-4sM" customClass="MacawView" customModule="Macaw">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="WpG-iW-4sM" firstAttribute="top" secondItem="F0r-c7-NMQ" secondAttribute="bottom" id="IyG-gg-H9z"/>
<constraint firstAttribute="trailing" secondItem="WpG-iW-4sM" secondAttribute="trailing" id="Zx6-cu-1ui"/>
<constraint firstItem="WpG-iW-4sM" firstAttribute="leading" secondItem="ONf-4d-IZ5" secondAttribute="leading" id="gPb-ic-ege"/>
<constraint firstItem="MiP-PT-hkh" firstAttribute="top" secondItem="WpG-iW-4sM" secondAttribute="bottom" id="iOg-aV-zFW"/>
</constraints>
</view>
<connections>
<outlet property="animView" destination="WpG-iW-4sM" id="a3r-wa-eYi"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="t7f-Bd-Zcu" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="598" y="1194"/>
</scene>
</scenes>
</document>

View File

@ -0,0 +1,64 @@
//
// AnimationsHierarchyViewController.swift
// Example
//
// Created by Alisa Mylnikova on 29/04/2019.
// Copyright © 2019 Exyte. All rights reserved.
//
import UIKit
import Macaw
class AnimationsHierarchyViewController: UIViewController {
@IBOutlet weak var animView: MacawView!
override func viewDidLoad() {
super.viewDidLoad()
animView.node = createTree(height: 3)
animView.zoom.enable()
}
func createTree(height: Int) -> Node {
let rect = Rect(w: 10, h: 10)
let root = createLeaf(childForm: rect, xDelta: Double(UIScreen.main.bounds.width/2), yDelta: 20)
createLeavesRecursive(root, maxLevel: height, childForm: rect)
return root
}
func createLeavesRecursive(_ root: Group, maxLevel: Int, _ level: Int = 0, childForm: Locus) {
if level == maxLevel {
return
}
let (left, right) = createLeaves(root, childForm: childForm, level: level)
createLeavesRecursive(left, maxLevel: maxLevel, level + 1, childForm: childForm)
createLeavesRecursive(right, maxLevel: maxLevel, level + 1, childForm: childForm)
}
func createLeaves(_ root: Group, childForm: Locus, level: Int) -> (Group, Group) {
let delta = Double(90/(level+1))
let height = Double(50)
let inset = childForm.bounds().w / 2
let left = createLeaf(childForm: childForm, xDelta: -delta, yDelta: height)
let right = createLeaf(childForm: childForm, xDelta: delta, yDelta: height)
let leftBranch = Shape(form: Line(x1: 0, y1: inset, x2: -delta, y2: height - inset), stroke: Stroke(fill: Color.purple, width: 2))
let rightBranch = Shape(form: Line(x1: 0, y1: inset, x2: delta, y2: height - inset), stroke: Stroke(fill: Color.purple, width: 2))
root.contents.append(contentsOf: [leftBranch, rightBranch, left, right])
return (left, right)
}
func createLeaf(childForm: Locus, xDelta: Double, yDelta: Double) -> Group {
let inset = childForm.bounds().w / 2
let leaf = Shape(form: childForm, fill: Color.teal, place: .move(dx: -inset, dy: -inset))
leaf.placeVar.animation(angle: 2 * .pi, during: 5).cycle().play()
let leafGroup = [leaf].group(place: .move(dx: xDelta, dy: yDelta))
leaf.onTap { _ in
leafGroup.placeVar.animation(angle: .pi / 4, x: leaf.place.dx, y: leaf.place.dy, during: 2).easing(.elasticOut).autoreversed().play()
}
return leafGroup
}
}

View File

@ -2,7 +2,7 @@ import UIKit
open class MenuViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
@IBOutlet var tableView: UITableView?
@IBOutlet var tableView: UITableView!
fileprivate var viewControllers = [
"FirstPageViewController",
@ -13,7 +13,8 @@ open class MenuViewController: UIViewController, UITableViewDataSource, UITableV
"MorphingExampleController",
"EventsExampleController",
"FiltersViewController",
"TextsViewController"
"TextsViewController",
"AnimationsHierarchyViewController"
].map {
UIStoryboard(name: "Main", bundle: .none).instantiateViewController(withIdentifier: $0)
}
@ -24,10 +25,6 @@ open class MenuViewController: UIViewController, UITableViewDataSource, UITableV
tableView?.reloadData()
}
open func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewControllers.count
}

View File

@ -16,9 +16,7 @@ class AnimationProducer {
struct ContentAnimationDesc {
let animation: ContentsAnimation
let layer: CALayer
weak var cache: AnimationCache?
let topLayers: [ShapeLayer]
let startDate: Date
let finishDate: Date
let completion: (() -> Void)?
@ -92,11 +90,7 @@ class AnimationProducer {
return
}
guard let layer = macawView.mLayer else {
return
}
guard let cache = macawView.animationCache else {
guard let layer = macawView.mLayer, let cache = macawView.animationCache else {
return
}
@ -256,39 +250,9 @@ class AnimationProducer {
unionBounds = unionBounds?.union(rect: contentsAnimation.getVFunc()(t).group().bounds!)
}
guard let renderer = animation.nodeRenderer, let layer = cache?.layerForNodeRenderer(renderer, context, animation: contentsAnimation, customBounds: unionBounds) else {
return
}
var rootRenderer: NodeRenderer? = renderer
while rootRenderer?.parentRenderer != nil {
rootRenderer = rootRenderer?.parentRenderer
}
let allRenderers = rootRenderer?.getAllChildrenRecursive()
var animationRenderers = [NodeRenderer]()
if let groupRenderer = renderer as? GroupRenderer {
animationRenderers.append(contentsOf: groupRenderer.getAllChildrenRecursive())
}
let bottomRenderer = animationRenderers.min { $0.zPosition < $1.zPosition }
var topLayers = [ShapeLayer]()
if let bottomRenderer = bottomRenderer, let allRenderers = allRenderers {
for renderer in allRenderers
where !(renderer is GroupRenderer)
&& renderer.zPosition > bottomRenderer.zPosition
&& !animationRenderers.contains(renderer) {
if let layer = cache?.layerForNodeRenderer(renderer, context, animation: contentsAnimation) {
topLayers.append(layer)
}
}
}
let animationDesc = ContentAnimationDesc(
animation: contentsAnimation,
layer: layer,
cache: cache,
topLayers: topLayers,
startDate: Date(),
finishDate: Date(timeInterval: contentsAnimation.duration, since: startDate),
completion: completion
@ -322,11 +286,6 @@ class AnimationProducer {
continue
}
defer {
animationDesc.layer.setNeedsDisplay()
animationDesc.layer.displayIfNeeded()
}
let progress = currentDate.timeIntervalSince(animationDesc.startDate) / animation.duration + animation.pausedProgress
// Completion
@ -345,9 +304,6 @@ class AnimationProducer {
contentsAnimations.remove(at: count - 1 - index)
animationDesc.cache?.freeLayer(renderer)
animationDesc.completion?()
for layer in animationDesc.topLayers {
animationDesc.cache?.freeLayer(layer: layer)
}
continue
}
@ -369,11 +325,6 @@ class AnimationProducer {
animation.pausedProgress = progress
}
}
for layer in animationDesc.topLayers {
layer.setNeedsDisplay()
layer.displayIfNeeded()
}
}
}
}

View File

@ -3,14 +3,14 @@ import Foundation
class AnimationUtils {
class func absolutePosition(_ nodeRenderer: NodeRenderer?, _ context: AnimationContext) -> Transform {
return AnimationUtils.absoluteTransform(nodeRenderer, context, pos: nodeRenderer?.node()?.place ?? .identity)
return AnimationUtils.absoluteTransform(nodeRenderer, context, pos: nodeRenderer?.node.place ?? .identity)
}
class func absoluteTransform(_ nodeRenderer: NodeRenderer?, _ context: AnimationContext, pos: Transform) -> Transform {
var transform = pos
var parentRenderer = nodeRenderer?.parentRenderer
while parentRenderer != nil {
if let node = parentRenderer?.node() {
if let node = parentRenderer?.node {
transform = node.place.concat(with: transform)
}
parentRenderer = parentRenderer?.parentRenderer
@ -20,14 +20,14 @@ class AnimationUtils {
class func absoluteClip(_ nodeRenderer: NodeRenderer?) -> Locus? {
// shouldn't this be a superposition of all parents' clips?
let node = nodeRenderer?.node()
let node = nodeRenderer?.node
if let nodeClip = node?.clip {
return nodeClip
}
var parentRenderer = nodeRenderer?.parentRenderer
while parentRenderer != nil {
if let parentClip = parentRenderer?.node()?.clip {
if let parentClip = parentRenderer?.node.clip {
return parentClip
}
@ -38,22 +38,4 @@ class AnimationUtils {
}
private static var indexCache = [Node: Int]()
class func animatedNodes(root: Node, animationCache: AnimationCache) -> [Node] {
if animationCache.isAnimating(root) {
return [root]
}
guard let rootGroup = root as? Group else {
return []
}
var result = [Node]()
rootGroup.contents.forEach { child in
let childAnimatedNodes = animatedNodes(root: child, animationCache: animationCache)
result.append(contentsOf: childAnimatedNodes)
}
return result
}
}

View File

@ -19,7 +19,12 @@ open class Easing {
public static let easeIn: Easing = EaseIn()
public static let easeOut: Easing = EaseOut()
public static let easeInOut: Easing = EaseInOut()
public static let elasticOut: Easing = ElasticOut()
public static let elasticInOut: Easing = ElasticInOut()
public static func elasticOut(elasticity: Double = 10.0) -> ElasticOut {
return ElasticOut(elasticity: elasticity)
}
public static func elasticInOut(elasticity: Double = 10.0) -> ElasticInOut {
return ElasticInOut(elasticity: elasticity)
}
@ -51,6 +56,29 @@ private class EaseInOut: Easing {
}
}
public class ElasticOut: Easing {
let elasticity: Double
init(elasticity: Double = 10.0) { // less elasticity means more springy effect
self.elasticity = elasticity
}
override open func progressFor(time: Double) -> Double {
if time == 0 {
return 0
}
let t = time / 0.5
if t == 2 {
return 1
}
let p = 0.3
let s = p / 4
let postFix = pow(2, -elasticity * t)
return (postFix * sin((t - s) * (2 * .pi) / p ) + 1)
}
}
public class ElasticInOut: Easing {
let elasticity: Double
@ -62,19 +90,17 @@ public class ElasticInOut: Easing {
if time == 0 {
return 0
}
var t = time / 0.5
if t == 2 {
let t = time / 0.5 - 1
if t == 1 {
return 1
}
let p = 0.3 * 1.5
let s = p / 4
if t < 1 {
t -= 1
if t < 0 {
let postFix = pow(2, elasticity * t)
return (-0.5 * (postFix * sin((t - s) * (2 * .pi) / p)))
}
t -= 1
let postFix = pow(2, -elasticity * t)
return (postFix * sin((t - s) * (2 * .pi) / p ) * 0.5 + 1)
}

View File

@ -7,9 +7,10 @@
//
class ShapeAnimation: AnimationImpl<Shape> {
let toParentGlobalTransfrom: Transform
let fromLayoutGlobalTransfrom: Transform
let toLayoutGlobalTransfrom: Transform
convenience init(animatedNode: Shape, finalValue: Shape, toParentGlobalTransfrom: Transform = .identity, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
convenience init(animatedNode: Shape, finalValue: Shape, fromLayoutGlobalTransfrom: Transform = .identity, toLayoutGlobalTransfrom: Transform = .identity, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
let interpolationFunc = { (t: Double) -> Shape in
if t == 0 {
@ -19,11 +20,12 @@ class ShapeAnimation: AnimationImpl<Shape> {
return finalValue
}
self.init(animatedNode: animatedNode, valueFunc: interpolationFunc, toParentGlobalTransfrom: toParentGlobalTransfrom, animationDuration: animationDuration, delay: delay, autostart: autostart, fps: fps)
self.init(animatedNode: animatedNode, valueFunc: interpolationFunc, fromLayoutGlobalTransfrom: fromLayoutGlobalTransfrom, toLayoutGlobalTransfrom: toLayoutGlobalTransfrom, animationDuration: animationDuration, delay: delay, autostart: autostart, fps: fps)
}
init(animatedNode: Shape, valueFunc: @escaping (Double) -> Shape, toParentGlobalTransfrom: Transform = .identity, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
self.toParentGlobalTransfrom = toParentGlobalTransfrom
init(animatedNode: Shape, valueFunc: @escaping (Double) -> Shape, fromLayoutGlobalTransfrom: Transform = .identity, toLayoutGlobalTransfrom: Transform = .identity, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
self.fromLayoutGlobalTransfrom = fromLayoutGlobalTransfrom
self.toLayoutGlobalTransfrom = toLayoutGlobalTransfrom
super.init(observableValue: AnimatableVariable<Shape>(animatedNode), valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps)
type = .shape
node = animatedNode
@ -33,8 +35,9 @@ class ShapeAnimation: AnimationImpl<Shape> {
}
}
init(animatedNode: Shape, factory: @escaping (() -> ((Double) -> Shape)), toParentGlobalTransfrom: Transform = .identity, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
self.toParentGlobalTransfrom = toParentGlobalTransfrom
init(animatedNode: Shape, factory: @escaping (() -> ((Double) -> Shape)), fromLayoutGlobalTransfrom: Transform = .identity, toLayoutGlobalTransfrom: Transform = .identity, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
self.fromLayoutGlobalTransfrom = fromLayoutGlobalTransfrom
self.toLayoutGlobalTransfrom = toLayoutGlobalTransfrom
super.init(observableValue: AnimatableVariable<Shape>(animatedNode), factory: factory, animationDuration: animationDuration, delay: delay, fps: fps)
type = .shape
node = animatedNode

View File

@ -104,35 +104,31 @@ public extension AnimatableVariable where T: TransformInterpolation {
func animation(angle: Double, x: Double? = .none, y: Double? = .none, during: Double = 1.0, delay: Double = 0.0) -> Animation {
let bounds = node!.bounds!
let place = node!.place
let factory = { () -> (Double) -> Transform in { t in
let asin = sin(angle * t); let acos = cos(angle * t)
let rotation = Transform(
m11: acos, m12: -asin,
m21: asin, m22: acos,
dx: 0.0, dy: 0.0
m21: asin, m22: acos
)
let move = Transform.move(
dx: x ?? bounds.w / 2.0,
dy: y ?? bounds.h / 2.0
dx: x ?? bounds.x + bounds.w / 2.0,
dy: y ?? bounds.y + bounds.h / 2.0
)
let t1 = move.concat(with: rotation)
let t2 = t1.concat(with: move.invert()!)
let result = t1.concat(with: t2)
return result
return place.concat(with: move).concat(with: rotation).concat(with: move.invert()!)
}
}
return TransformAnimation(animatedNode: self.node!, factory: factory, animationDuration: during, delay: delay)
return TransformAnimation(animatedNode: node!, factory: factory, animationDuration: during, delay: delay)
}
func animation(along path: Path, during: Double = 1.0, delay: Double = 0.0) -> Animation {
let factory = { () -> (Double) -> Transform in { (t: Double) in Transform.identity }
let factory = { () -> (Double) -> Transform in { (t: Double) in self.node?.place ?? .identity }
}
return TransformAnimation(animatedNode: self.node!, factory: factory, along: path, animationDuration: during, delay: delay)
}

View File

@ -9,18 +9,17 @@ import AppKit
class AnimationCache {
class CachedLayer {
let layer: ShapeLayer
let animation: Animation
var linksCounter = 1
let rootLayer: ShapeLayer
let animationLayer: ShapeLayer
required init(layer: ShapeLayer, animation: Animation) {
self.layer = layer
self.animation = animation
required init(rootLayer: ShapeLayer, animationLayer: ShapeLayer) {
self.rootLayer = rootLayer
self.animationLayer = animationLayer
}
}
weak var sceneLayer: CALayer?
var layerCache = [NodeRenderer: CachedLayer]()
var cache = [NodeRenderer: CachedLayer]()
required init(sceneLayer: CALayer) {
self.sceneLayer = sceneLayer
@ -28,44 +27,45 @@ class AnimationCache {
func layerForNodeRenderer(_ renderer: NodeRenderer, _ context: AnimationContext, animation: Animation, customBounds: Rect? = .none, shouldRenderContent: Bool = true) -> ShapeLayer {
guard let node = renderer.node() else {
return ShapeLayer()
let node = renderer.node
if let cachedLayer = cache[renderer] {
cachedLayer.rootLayer.transform = CATransform3DMakeAffineTransform(uncachedParentsPlace(renderer).toCG())
cachedLayer.animationLayer.opacity = Float(node.opacity)
return cachedLayer.animationLayer
}
if let cachedLayer = layerCache[renderer] {
cachedLayer.linksCounter += 1
return cachedLayer.layer
}
// 'sublayer' is for actual CAAnimations, and 'layer' is for manual transforming and hierarchy changes
let sublayer = ShapeLayer()
sublayer.shouldRenderContent = shouldRenderContent
sublayer.animationCache = self
let layer = ShapeLayer()
layer.shouldRenderContent = shouldRenderContent
layer.animationCache = self
layer.addSublayer(sublayer)
layer.masksToBounds = false
// Use to debug animation layers
// layer.backgroundColor = MColor.green.cgColor
// layer.borderWidth = 1.0
// layer.borderColor = MColor.blue.cgColor
// sublayer.backgroundColor = MColor.green.cgColor
// sublayer.borderWidth = 2.0
// sublayer.borderColor = MColor.red.cgColor
// layer.backgroundColor = MColor.blue.cgColor
// layer.borderWidth = 2.0
// layer.borderColor = MColor.cyan.cgColor
let calculatedBounds = customBounds ?? node.bounds
if let shapeBounds = calculatedBounds {
let cgRect = shapeBounds.toCG()
let origFrame = CGRect(x: 0.0, y: 0.0,
width: cgRect.width,
height: cgRect.height)
layer.bounds = origFrame
layer.anchorPoint = CGPoint(
let anchorPoint = CGPoint(
x: -1.0 * cgRect.origin.x / cgRect.width,
y: -1.0 * cgRect.origin.y / cgRect.height
)
layer.bounds = cgRect
sublayer.bounds = cgRect
layer.anchorPoint = anchorPoint
sublayer.anchorPoint = anchorPoint
layer.zPosition = CGFloat(renderer.zPosition)
layer.renderTransform = CGAffineTransform(translationX: -1.0 * cgRect.origin.x, y: -1.0 * cgRect.origin.y)
let nodeTransform = AnimationUtils.absolutePosition(renderer, context).toCG()
layer.transform = CATransform3DMakeAffineTransform(nodeTransform)
// Clip
if let clip = AnimationUtils.absoluteClip(renderer) {
let maskLayer = CAShapeLayer()
@ -77,18 +77,48 @@ class AnimationCache {
}
}
layer.opacity = Float(node.opacity)
layer.renderer = renderer
sublayer.opacity = Float(node.opacity)
sublayer.renderer = renderer
sublayer.contentsScale = calculateAnimationScale(animation: animation)
sublayer.setNeedsDisplay()
layer.contentsScale = calculateAnimationScale(animation: animation)
// find first parent with cached layer
var parent: NodeRenderer? = renderer.parentRenderer
var parentCachedLayer: CALayer? = sceneLayer
while parent != nil {
if let parent = parent {
if let cached = cache[parent] {
parentCachedLayer = cached.animationLayer
break
}
}
parent = parent?.parentRenderer
}
layer.transform = CATransform3DMakeAffineTransform(uncachedParentsPlace(renderer).toCG())
sublayer.transform = CATransform3DMakeAffineTransform(node.place.toCG())
parentCachedLayer?.addSublayer(layer)
parentCachedLayer?.setNeedsDisplay()
layer.setNeedsDisplay()
sceneLayer?.addSublayer(layer)
cache[renderer] = CachedLayer(rootLayer: layer, animationLayer: sublayer)
// move children to new layer
for child in renderer.getAllChildrenRecursive() {
if let cachedChildLayer = cache[child], let parentCachedLayer = parentCachedLayer {
parentCachedLayer.sublayers?.forEach { childLayer in
if childLayer === cachedChildLayer.rootLayer {
childLayer.removeFromSuperlayer()
childLayer.transform = CATransform3DMakeAffineTransform(uncachedParentsPlace(child).toCG())
sublayer.addSublayer(childLayer)
sublayer.setNeedsDisplay()
}
}
}
}
layerCache[renderer] = CachedLayer(layer: layer, animation: animation)
sceneLayer?.setNeedsDisplay()
return layer
return sublayer
}
private func calculateAnimationScale(animation: Animation) -> CGFloat {
@ -130,106 +160,73 @@ class AnimationCache {
}
func freeLayerHard(_ renderer: NodeRenderer) {
guard let cachedLayer = layerCache[renderer] else {
freeLayer(renderer)
}
func freeLayer(_ renderer: NodeRenderer?) {
guard let nodeRenderer = renderer, let layer = cache[nodeRenderer] else {
return
}
let layer = cachedLayer.layer
layerCache.removeValue(forKey: renderer)
sceneLayer?.setNeedsDisplay()
layer.removeFromSuperlayer()
}
cache.removeValue(forKey: nodeRenderer)
func freeLayer(layer: ShapeLayer) {
var cached: CachedLayer?
var renderer: NodeRenderer?
layerCache.forEach { key, value in
if value.layer === layer {
cached = value
renderer = key
// find first parent with cached layer
var parent: NodeRenderer? = nodeRenderer.parentRenderer
var parentCachedLayer: CALayer? = sceneLayer
while parent != nil {
if let parent = parent, let cached = cache[parent] {
parentCachedLayer = cached.animationLayer
break
}
parent = parent?.parentRenderer
}
// move children to closest parent layer
for child in nodeRenderer.getAllChildrenRecursive() {
if let cachedChildLayer = cache[child], let parentCachedLayer = parentCachedLayer {
layer.animationLayer.sublayers?.forEach { childLayer in
if childLayer === cachedChildLayer.rootLayer {
CATransaction.setValue(kCFBooleanTrue, forKey:kCATransactionDisableActions)
childLayer.removeFromSuperlayer()
childLayer.transform = CATransform3DMakeAffineTransform(uncachedParentsPlace(child).toCG())
parentCachedLayer.addSublayer(childLayer)
childLayer.setNeedsDisplay()
CATransaction.commit()
}
}
}
}
guard let cachedLayer = cached, let nodeRenderer = renderer else {
return
}
cachedLayer.linksCounter -= 1
if cachedLayer.linksCounter != 0 {
return
}
let layer = cachedLayer.layer
layerCache.removeValue(forKey: nodeRenderer)
layer.animationLayer.removeFromSuperlayer()
layer.rootLayer.removeFromSuperlayer()
parentCachedLayer?.setNeedsDisplay()
sceneLayer?.setNeedsDisplay()
layer.removeFromSuperlayer()
}
func freeLayer(_ renderer: NodeRenderer) {
guard let cachedLayer = layerCache[renderer] else {
return
}
cachedLayer.linksCounter -= 1
if cachedLayer.linksCounter != 0 {
return
}
let layer = cachedLayer.layer
layerCache.removeValue(forKey: renderer)
sceneLayer?.setNeedsDisplay()
layer.removeFromSuperlayer()
func isAnimating(_ nodeRenderer: NodeRenderer) -> Bool {
return cache[nodeRenderer] != nil
}
func isAnimating(_ node: Node) -> Bool {
let renderer = layerCache.keys.first { $0.node() === node }
if let renderer = renderer, let _ = layerCache[renderer] {
return true
if let renderer = cache.keys.first(where: { $0.node === node }) {
return isAnimating(renderer)
}
return false
}
func isChildrenAnimating(_ group: Group) -> Bool {
for child in group.contents {
if isAnimating(child) {
return true
}
if let childGroup = child as? Group {
return isChildrenAnimating(childGroup)
func uncachedParentsPlace(_ renderer: NodeRenderer) -> Transform {
var parent: NodeRenderer? = renderer.parentRenderer
var uncachedParentsPlace = Transform.identity
while parent != nil {
if let parent = parent {
if cache[parent] != nil {
break
}
uncachedParentsPlace = uncachedParentsPlace.concat(with: parent.node.place)
}
parent = parent?.parentRenderer
}
return false
return uncachedParentsPlace
}
func containsAnimation(_ node: Node) -> Bool {
if isAnimating(node) {
return true
}
if let group = node as? Group {
return isChildrenAnimating(group)
}
return false
}
func animations() -> [Animation] {
return layerCache.map { $0.1.animation }
}
func replace(original: NodeRenderer, replacement: NodeRenderer) {
guard let layer = layerCache[original] else {
return
}
layerCache[replacement] = layer
layerCache.removeValue(forKey: original)
}
}

View File

@ -13,19 +13,21 @@ import AppKit
extension AnimationProducer {
func createChildAnimations(_ combineAnimation: Animation, globalToPosition: Transform = .identity, animations: [Animation] = []) -> [Animation] {
func createChildAnimations(_ combineAnimation: Animation, fromLayoutGlobalTransfrom: Transform, toLayoutGlobalTransfrom: Transform, animations: [Animation] = []) -> [Animation] {
guard let combine = combineAnimation as? CombineAnimation else {
return animations
}
let globalToPosition = globalToPosition
let during = combine.duration
let delay = combine.delay
let fromNode = combine.node as! Group
let to = combine.toNodes
let fromContentsCopy = fromNode.contents.compactMap { SceneUtils.copyNode($0) }
fromNode.contents = fromContentsCopy
// Shapes on same hierarhy level
let fromShapes = fromNode.contents.compactMap { $0 as? Shape }
let fromShapes = fromContentsCopy.compactMap { $0 as? Shape }
let toShapes = to.compactMap { $0 as? Shape }
let minPathsNumber = min(fromShapes.count, toShapes.count)
@ -34,7 +36,7 @@ extension AnimationProducer {
let fromShape = fromShapes[i]
let toShape = toShapes[i]
let animation = ShapeAnimation(animatedNode: fromShape, finalValue: toShape, toParentGlobalTransfrom: globalToPosition, animationDuration: during, delay: delay)
let animation = ShapeAnimation(animatedNode: fromShape, finalValue: toShape, fromLayoutGlobalTransfrom: fromLayoutGlobalTransfrom, toLayoutGlobalTransfrom: toLayoutGlobalTransfrom, animationDuration: during, delay: delay)
animations.append(animation)
}
@ -66,7 +68,7 @@ extension AnimationProducer {
let toGroup = toGroups[i]
let groupAnimation = fromGroup.contentsVar.animation(to: toGroup.contents, during: during, delay: delay)
let groupAnimations = createChildAnimations(groupAnimation, globalToPosition: globalToPosition.concat(with: toGroup.place), animations: animations)
let groupAnimations = createChildAnimations(groupAnimation, fromLayoutGlobalTransfrom: fromLayoutGlobalTransfrom, toLayoutGlobalTransfrom: toLayoutGlobalTransfrom, animations: animations)
animations.append(contentsOf: groupAnimations)
}
@ -119,9 +121,10 @@ extension AnimationProducer {
}
var animations = combine.animations
if let toBounds = combine.toNodes.group().bounds {
let globalTransform = view.contentLayout.layout(rect: toBounds, into: view.frame.size.toMacaw())
let childAnimations = createChildAnimations(combine, globalToPosition: globalTransform) as! [BasicAnimation]
if let fromBounds = combine.node?.bounds, let toBounds = combine.toNodes.group().bounds {
let fromLayoutTransform = view.contentLayout.layout(rect: fromBounds, into: view.frame.size.toMacaw())
let toLayoutTransform = view.contentLayout.layout(rect: toBounds, into: view.frame.size.toMacaw())
let childAnimations = createChildAnimations(combine, fromLayoutGlobalTransfrom: fromLayoutTransform, toLayoutGlobalTransfrom: toLayoutTransform) as! [BasicAnimation]
animations.append(contentsOf: childAnimations)
}
@ -180,6 +183,12 @@ extension AnimationProducer {
}
}
CATransaction.setDisableActions(true)
defer {
CATransaction.commit()
//CATransaction.flush()
}
// Launching
animations.forEach { animation in
self.play(animation, context)

View File

@ -23,9 +23,8 @@ func addMorphingAnimation(_ animation: BasicAnimation, _ context: AnimationConte
return
}
let mutatingShape = SceneUtils.shapeCopy(from: shape)
renderer.replaceNode(with: mutatingShape)
animation.node = mutatingShape
let transactionsDisabled = CATransaction.disableActions()
CATransaction.setDisableActions(true)
let fromLocus = morphingAnimation.getVFunc()(0.0)
let toLocus = morphingAnimation.getVFunc()(animation.autoreverses ? 0.5 : 1.0)
@ -35,24 +34,29 @@ func addMorphingAnimation(_ animation: BasicAnimation, _ context: AnimationConte
return
}
// Creating proper animation
let generatedAnim = pathAnimation(
let generatedAnimation = pathAnimation(
from: fromLocus,
to: toLocus,
duration: duration,
renderTransform: layer.renderTransform!)
duration: duration)
generatedAnim.repeatCount = Float(animation.repeatCount)
generatedAnim.timingFunction = caTimingFunction(animation.easing)
generatedAnim.autoreverses = animation.autoreverses
generatedAnimation.repeatCount = Float(animation.repeatCount)
generatedAnimation.timingFunction = caTimingFunction(animation.easing)
generatedAnimation.autoreverses = animation.autoreverses
generatedAnim.completion = { finished in
generatedAnimation.progress = { progress in
let t = Double(progress)
animation.progress = t
animation.onProgressUpdate?(t)
}
generatedAnimation.completion = { finished in
if animation.manualStop {
animation.progress = 0.0
mutatingShape.form = morphingAnimation.getVFunc()(0.0)
shape.form = morphingAnimation.getVFunc()(0.0)
} else if finished {
animation.progress = 1.0
mutatingShape.form = morphingAnimation.getVFunc()(1.0)
shape.form = morphingAnimation.getVFunc()(1.0)
}
animationCache?.freeLayer(renderer)
@ -65,19 +69,10 @@ func addMorphingAnimation(_ animation: BasicAnimation, _ context: AnimationConte
completion()
}
generatedAnim.progress = { progress in
let t = Double(progress)
mutatingShape.form = morphingAnimation.getVFunc()(t)
animation.progress = t
animation.onProgressUpdate?(t)
}
layer.path = fromLocus.toCGPath()
// Stroke
if let stroke = mutatingShape.stroke {
if let stroke = shape.stroke {
if let color = stroke.fill as? Color {
layer.strokeColor = color.toCG()
} else {
@ -91,24 +86,27 @@ func addMorphingAnimation(_ animation: BasicAnimation, _ context: AnimationConte
}
// Fill
if let color = mutatingShape.fill as? Color {
if let color = shape.fill as? Color {
layer.fillColor = color.toCG()
} else {
layer.fillColor = MColor.clear.cgColor
}
let animationId = animation.ID
layer.add(generatedAnim, forKey: animationId)
layer.add(generatedAnimation, forKey: animationId)
animation.removeFunc = { [weak layer] in
layer?.removeAnimation(forKey: animationId)
}
if !transactionsDisabled {
CATransaction.commit()
}
}
fileprivate func pathAnimation(from: Locus, to: Locus, duration: Double, renderTransform: CGAffineTransform) -> CAAnimation {
fileprivate func pathAnimation(from: Locus, to: Locus, duration: Double) -> CAAnimation {
var transform = renderTransform
let fromPath = from.toCGPath().copy(using: &transform)
let toPath = to.toCGPath().copy(using: &transform)
let fromPath = from.toCGPath()
let toPath = to.toCGPath()
let animation = CABasicAnimation(keyPath: "path")
animation.fromValue = fromPath

View File

@ -15,6 +15,9 @@ func addOpacityAnimation(_ animation: BasicAnimation, _ context: AnimationContex
return
}
let transactionsDisabled = CATransaction.disableActions()
CATransaction.setDisableActions(true)
// Creating proper animation
let generatedAnimation = opacityAnimationByFunc(opacityAnimation.getVFunc(),
duration: animation.getDuration(),
@ -23,6 +26,12 @@ func addOpacityAnimation(_ animation: BasicAnimation, _ context: AnimationContex
generatedAnimation.repeatCount = Float(animation.repeatCount)
generatedAnimation.timingFunction = caTimingFunction(animation.easing)
generatedAnimation.progress = { progress in
let t = Double(progress)
animation.progress = t
animation.onProgressUpdate?(t)
}
generatedAnimation.completion = { finished in
animationCache?.freeLayer(renderer)
@ -49,22 +58,17 @@ func addOpacityAnimation(_ animation: BasicAnimation, _ context: AnimationContex
completion()
}
generatedAnimation.progress = { progress in
let t = Double(progress)
node.opacityVar.value = opacityAnimation.getVFunc()(t)
animation.progress = t
animation.onProgressUpdate?(t)
}
if let renderer = animation.nodeRenderer, let layer = animationCache?.layerForNodeRenderer(renderer, context, animation: animation) {
if let layer = animationCache?.layerForNodeRenderer(renderer, context, animation: animation) {
let animationId = animation.ID
layer.add(generatedAnimation, forKey: animationId)
animation.removeFunc = { [weak layer] in
layer?.removeAnimation(forKey: animationId)
}
}
if !transactionsDisabled {
CATransaction.commit()
}
}
func opacityAnimationByFunc(_ valueFunc: (Double) -> Double, duration: Double, offset: Double, fps: UInt) -> CAAnimation {

View File

@ -23,44 +23,49 @@ func addShapeAnimation(_ animation: BasicAnimation, _ context: AnimationContext,
return
}
let transactionsDisabled = CATransaction.disableActions()
CATransaction.setDisableActions(true)
let fromShape = shapeAnimation.getVFunc()(0.0)
let toShape = shapeAnimation.getVFunc()(animation.autoreverses ? 0.5 : 1.0)
let duration = animation.autoreverses ? animation.getDuration() / 2.0 : animation.getDuration()
let mutatingShape = SceneUtils.shapeCopy(from: fromShape)
renderer.replaceNode(with: mutatingShape)
guard let layer = animationCache?.layerForNodeRenderer(renderer, context, animation: animation, shouldRenderContent: false) else {
return
}
// Creating proper animation
let generatedAnim = generateShapeAnimation(context,
from: mutatingShape,
let generatedAnimation = generateShapeAnimation(context,
from: fromShape,
to: toShape,
animation: shapeAnimation,
duration: duration,
renderTransform: layer.renderTransform!)
duration: duration)
generatedAnim.repeatCount = Float(animation.repeatCount)
generatedAnim.timingFunction = caTimingFunction(animation.easing)
generatedAnim.autoreverses = animation.autoreverses
generatedAnimation.repeatCount = Float(animation.repeatCount)
generatedAnimation.timingFunction = caTimingFunction(animation.easing)
generatedAnimation.autoreverses = animation.autoreverses
generatedAnim.completion = { finished in
generatedAnimation.progress = { progress in
let t = Double(progress)
animation.progress = t
animation.onProgressUpdate?(t)
}
generatedAnimation.completion = { finished in
animation.progress = animation.manualStop ? 0.0 : 1.0
if !animation.autoreverses && finished {
mutatingShape.form = toShape.form
mutatingShape.stroke = toShape.stroke
mutatingShape.fill = toShape.fill
shape.form = toShape.form
shape.stroke = toShape.stroke
shape.fill = toShape.fill
}
if !finished {
animation.progress = 0.0
mutatingShape.form = fromShape.form
mutatingShape.stroke = fromShape.stroke
mutatingShape.fill = fromShape.fill
shape.form = fromShape.form
shape.stroke = fromShape.stroke
shape.fill = fromShape.fill
}
animationCache?.freeLayer(renderer)
@ -72,27 +77,6 @@ func addShapeAnimation(_ animation: BasicAnimation, _ context: AnimationContext,
completion()
}
generatedAnim.progress = { progress in
let t = Double(progress)
if !animation.autoreverses {
let currentShape = shapeAnimation.getVFunc()(t)
mutatingShape.place = currentShape.place
mutatingShape.opaque = currentShape.opaque
mutatingShape.opacity = currentShape.opacity
mutatingShape.clip = currentShape.clip
mutatingShape.mask = currentShape.mask
mutatingShape.effect = currentShape.effect
mutatingShape.form = currentShape.form
mutatingShape.stroke = currentShape.stroke
mutatingShape.fill = currentShape.fill
}
animation.progress = t
animation.onProgressUpdate?(t)
}
layer.path = fromShape.form.toCGPath()
// Stroke
@ -121,21 +105,23 @@ func addShapeAnimation(_ animation: BasicAnimation, _ context: AnimationContext,
}
let animationId = animation.ID
layer.add(generatedAnim, forKey: animationId)
layer.add(generatedAnimation, forKey: animationId)
animation.removeFunc = { [weak layer] in
layer?.removeAnimation(forKey: animationId)
}
if !transactionsDisabled {
CATransaction.commit()
}
}
fileprivate func generateShapeAnimation(_ context: AnimationContext, from: Shape, to: Shape, animation: ShapeAnimation, duration: Double, renderTransform: CGAffineTransform) -> CAAnimation {
fileprivate func generateShapeAnimation(_ context: AnimationContext, from: Shape, to: Shape, animation: ShapeAnimation, duration: Double) -> CAAnimation {
let group = CAAnimationGroup()
// Shape
// Path
var transform = renderTransform
let fromPath = from.form.toCGPath().copy(using: &transform)
let toPath = to.form.toCGPath().copy(using: &transform)
let fromPath = from.form.toCGPath()
let toPath = to.form.toCGPath()
let pathAnimation = CABasicAnimation(keyPath: "path")
pathAnimation.fromValue = fromPath
@ -145,16 +131,12 @@ fileprivate func generateShapeAnimation(_ context: AnimationContext, from: Shape
group.animations = [pathAnimation]
// Transform
let scaleAnimation = CABasicAnimation(keyPath: "transform")
scaleAnimation.duration = duration
let parentPos = AnimationUtils.absolutePosition(animation.nodeRenderer?.parentRenderer, context)
let fromPos = parentPos.concat(with: from.place)
let toParentPos = animation.toParentGlobalTransfrom
let toPos = toParentPos.concat(with: to.place)
scaleAnimation.fromValue = CATransform3DMakeAffineTransform(fromPos.toCG())
scaleAnimation.toValue = CATransform3DMakeAffineTransform(toPos.toCG())
let transformAnimation = CABasicAnimation(keyPath: "transform")
transformAnimation.duration = duration
transformAnimation.fromValue = CATransform3DMakeAffineTransform(animation.fromLayoutGlobalTransfrom.concat(with: from.place).toCG())
transformAnimation.toValue = CATransform3DMakeAffineTransform(animation.toLayoutGlobalTransfrom.concat(with: to.place).toCG())
group.animations?.append(scaleAnimation)
group.animations?.append(transformAnimation)
// Fill
let fromFillColor = from.fill as? Color ?? Color.clear

View File

@ -11,33 +11,37 @@ func addTransformAnimation(_ animation: BasicAnimation, _ context: AnimationCont
return
}
guard let node = animation.node, let renderer = animation.nodeRenderer else {
return
}
if transformAnimation.trajectory != nil && transformAnimation.easing === Easing.elasticInOut {
fatalError("Transform animation with trajectory can't have elastic easing, try using contentVar animation instead")
}
node.placeVar.value = transformAnimation.getVFunc()(0.0)
// Creating proper animation
var generatedAnimation: CAAnimation?
generatedAnimation = transformAnimationByFunc(transformAnimation,
context,
node: node,
duration: animation.getDuration(),
offset: animation.pausedProgress,
fps: transformAnimation.logicalFps)
guard let generatedAnim = generatedAnimation else {
guard let node = animation.node, let renderer = animation.nodeRenderer else {
return
}
generatedAnim.repeatCount = Float(animation.repeatCount)
let transactionsDisabled = CATransaction.disableActions()
CATransaction.setDisableActions(true)
generatedAnim.completion = { finished in
guard let layer = animationCache?.layerForNodeRenderer(renderer, context, animation: animation, shouldRenderContent: true) else {
return
}
// Creating proper animation
let generatedAnimation = transformAnimationByFunc(transformAnimation,
context,
duration: animation.getDuration(),
offset: animation.pausedProgress,
fps: transformAnimation.logicalFps)
generatedAnimation.repeatCount = Float(animation.repeatCount)
generatedAnimation.progress = { progress in
let t = Double(progress)
animation.progress = t
animation.onProgressUpdate?(t)
}
generatedAnimation.completion = { finished in
if animation.paused {
animation.pausedProgress += animation.progress
@ -63,25 +67,18 @@ func addTransformAnimation(_ animation: BasicAnimation, _ context: AnimationCont
completion()
}
generatedAnim.progress = { progress in
let t = Double(progress)
node.placeVar.value = transformAnimation.getVFunc()(t)
animation.progress = t
animation.onProgressUpdate?(t)
let animationId = animation.ID
layer.add(generatedAnimation, forKey: animationId)
animation.removeFunc = { [weak layer] in
layer?.removeAnimation(forKey: animationId)
}
if let renderer = animation.nodeRenderer, let layer = animationCache?.layerForNodeRenderer(renderer, context, animation: animation) {
let animationId = animation.ID
layer.add(generatedAnim, forKey: animationId)
animation.removeFunc = { [weak layer] in
layer?.removeAnimation(forKey: animationId)
}
if !transactionsDisabled {
CATransaction.commit()
}
}
func transformAnimationByFunc(_ animation: TransformAnimation, _ context: AnimationContext, node: Node, duration: Double, offset: Double, fps: UInt) -> CAAnimation {
func transformAnimationByFunc(_ animation: TransformAnimation, _ context: AnimationContext, duration: Double, offset: Double, fps: UInt) -> CAAnimation {
let valueFunc = animation.getVFunc()
@ -90,8 +87,6 @@ func transformAnimationByFunc(_ animation: TransformAnimation, _ context: Animat
pathAnimation.timingFunction = caTimingFunction(animation.easing)
pathAnimation.duration = duration / 2
pathAnimation.autoreverses = animation.autoreverses
let value = AnimationUtils.absoluteTransform(animation.nodeRenderer, context, pos: valueFunc(0))
pathAnimation.values = [NSValue(caTransform3D: CATransform3DMakeAffineTransform(value.toCG()))]
pathAnimation.fillMode = MCAMediaTimingFillMode.forwards
pathAnimation.isRemovedOnCompletion = false
pathAnimation.path = trajectory.toCGPath()
@ -105,7 +100,7 @@ func transformAnimationByFunc(_ animation: TransformAnimation, _ context: Animat
tValue.append(1.0)
for t in tValue {
let progress = animation.easing.progressFor(time: t)
let value = AnimationUtils.absoluteTransform(animation.nodeRenderer, context, pos: valueFunc(offset + progress))
let value = valueFunc(offset + progress)
let cgValue = CATransform3DMakeAffineTransform(value.toCG())
transformValues.append(cgValue)
}

View File

@ -12,11 +12,6 @@ import UIKit
import AppKit
#endif
public enum Relativity {
case parent
case scene
}
class NodePath {
let node: Node
let location: CGPoint
@ -36,11 +31,13 @@ public struct TouchPoint {
private let absoluteLocation: Point
private let relativeLocation: Point // location inside the node
private let viewLocation: Point // location relative t
init(id: Int, location: Point, relativeLocation: Point) {
init(id: Int, location: Point, relativeToNodeLocation: Point, relativeToViewLocation: Point) {
self.id = id
self.absoluteLocation = location
self.relativeLocation = relativeLocation
self.relativeLocation = relativeToNodeLocation
self.viewLocation = relativeToViewLocation
}
public func location(in relativity: Relativity = .parent) -> Point {
@ -49,6 +46,8 @@ public struct TouchPoint {
return relativeLocation
case .scene:
return absoluteLocation
case .view:
return viewLocation
}
}
}

View File

@ -82,6 +82,10 @@ public final class Transform {
}
public func invert() -> Transform? {
if m11 == 1 && m12 == 0 && m21 == 0 && m22 == 1 {
return .move(dx: -dx, dy: -dy)
}
let det = self.m11 * self.m22 - self.m12 * self.m21
if det == 0 {
return nil

View File

@ -39,14 +39,16 @@ class SceneUtils {
let clip = referenceNode.clip
let tag = referenceNode.tag
var result: Node?
if let shape = referenceNode as? Shape {
return Shape(form: shape.form, fill: shape.fill, stroke: shape.stroke, place: pos, opaque: opaque, clip: clip, visible: visible, tag: tag)
result = Shape(form: shape.form, fill: shape.fill, stroke: shape.stroke, place: pos, opaque: opaque, clip: clip, visible: visible, tag: tag)
}
if let text = referenceNode as? Text {
return Text(text: text.text, font: text.font, fill: text.fill, stroke: text.stroke, align: text.align, baseline: text.baseline, place: pos, opaque: opaque, clip: clip, visible: visible, tag: tag)
result = Text(text: text.text, font: text.font, fill: text.fill, stroke: text.stroke, align: text.align, baseline: text.baseline, place: pos, opaque: opaque, clip: clip, visible: visible, tag: tag)
}
if let image = referenceNode as? Image {
return Image(src: image.src, xAlign: image.xAlign, yAlign: image.yAlign, aspectRatio: image.aspectRatio, w: image.w, h: image.h, place: pos, opaque: opaque, clip: clip, visible: visible, tag: tag)
result = Image(src: image.src, xAlign: image.xAlign, yAlign: image.yAlign, aspectRatio: image.aspectRatio, w: image.w, h: image.h, place: pos, opaque: opaque, clip: clip, visible: visible, tag: tag)
}
if let group = referenceNode as? Group {
var contents = [Node]()
@ -55,8 +57,19 @@ class SceneUtils {
contents.append(copy)
}
}
return Group(contents: contents, place: pos, opaque: opaque, clip: clip, visible: visible, tag: tag)
result = Group(contents: contents, place: pos, opaque: opaque, clip: clip, visible: visible, tag: tag)
}
return .none
result?.touchPressedHandlers = referenceNode.touchPressedHandlers
result?.touchMovedHandlers = referenceNode.touchMovedHandlers
result?.touchReleasedHandlers = referenceNode.touchReleasedHandlers
result?.tapHandlers = referenceNode.tapHandlers
result?.longTapHandlers = referenceNode.longTapHandlers
result?.panHandlers = referenceNode.panHandlers
result?.rotateHandlers = referenceNode.rotateHandlers
result?.pinchHandlers = referenceNode.pinchHandlers
return result
}
}

View File

@ -6,12 +6,16 @@ import UIKit
class GroupRenderer: NodeRenderer {
weak var group: Group?
var group: Group
var renderers: [NodeRenderer] = []
init(group: Group, view: MacawView?, animationCache: AnimationCache?) {
override var node: Node {
return group
}
init(group: Group, view: MacawView?, animationCache: AnimationCache?, parentRenderer: GroupRenderer? = nil) {
self.group = group
super.init(node: group, view: view, animationCache: animationCache)
super.init(node: group, view: view, animationCache: animationCache, parentRenderer: parentRenderer)
updateRenderers()
}
@ -22,23 +26,23 @@ class GroupRenderer: NodeRenderer {
override func doAddObservers() {
super.doAddObservers()
guard let group = group else {
return
}
group.contentsVar.onChange { [weak self] _ in
self?.updateRenderers()
}
observe(group.contentsVar)
}
override func node() -> Node? {
return group
override func freeCachedAbsPlace() {
for renderer in renderers {
renderer.freeCachedAbsPlace()
}
}
override func doRender(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
renderers.forEach { renderer in
renderer.render(in: context, force: force, opacity: opacity, coloringMode: coloringMode)
if !(animationCache?.isAnimating(renderer) ?? false) {
renderer.render(in: context, force: force, opacity: opacity, coloringMode: coloringMode)
}
}
}
@ -65,12 +69,8 @@ class GroupRenderer: NodeRenderer {
}
renderers.removeAll()
if let updatedRenderers = group?.contents.compactMap ({ child -> NodeRenderer? in
let childRenderer = RenderUtils.createNodeRenderer(child, view: view, animationCache: animationCache)
childRenderer.parentRenderer = self
return childRenderer
}) {
renderers = updatedRenderers
renderers = group.contents.compactMap { child -> NodeRenderer? in
return RenderUtils.createNodeRenderer(child, view: view, animationCache: animationCache, parentRenderer: self)
}
var parent: NodeRenderer = self
@ -79,12 +79,4 @@ class GroupRenderer: NodeRenderer {
}
parent.calculateZPositionRecursively()
}
override func replaceNode(with replacementNode: Node) {
super.replaceNode(with: replacementNode)
if let node = replacementNode as? Group {
group = node
}
}
}

View File

@ -9,30 +9,26 @@ import UIKit
#endif
class ImageRenderer: NodeRenderer {
weak var image: Image?
var image: Image
var renderedPaths: [CGPath] = [CGPath]()
init(image: Image, view: MacawView?, animationCache: AnimationCache?) {
override var node: Node {
return image
}
init(image: Image, view: MacawView?, animationCache: AnimationCache?, parentRenderer: GroupRenderer? = nil) {
self.image = image
super.init(node: image, view: view, animationCache: animationCache)
super.init(node: image, view: view, animationCache: animationCache, parentRenderer: parentRenderer)
}
deinit {
dispose()
}
override func node() -> Node? {
return image
}
override func doAddObservers() {
super.doAddObservers()
guard let image = image else {
return
}
observe(image.srcVar)
observe(image.xAlignVar)
observe(image.yAlignVar)
@ -42,9 +38,6 @@ class ImageRenderer: NodeRenderer {
}
override func doRender(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
guard let image = image else {
return
}
var mImage: MImage?
if image.src.contains("memory") {
@ -64,9 +57,6 @@ class ImageRenderer: NodeRenderer {
}
override func doFindNodeAt(path: NodePath, ctx: CGContext) -> NodePath? {
guard let image = image else {
return .none
}
if let mImage = MImage(named: image.src),
let rect = BoundsUtils.getRect(of: image, mImage: mImage) {
@ -77,12 +67,4 @@ class ImageRenderer: NodeRenderer {
}
return .none
}
override func replaceNode(with replacementNode: Node) {
super.replaceNode(with: replacementNode)
if let node = replacementNode as? Image {
image = node
}
}
}

View File

@ -6,6 +6,12 @@ import UIKit
import AppKit
#endif
public enum Relativity {
case parent
case scene
case view
}
enum ColoringMode {
case rgb, greyscale, alphaOnly
}
@ -13,17 +19,54 @@ enum ColoringMode {
class NodeRenderer {
weak var view: MacawView?
weak var parentRenderer: NodeRenderer?
internal var zPosition: Int = 0
let parentRenderer: GroupRenderer?
var zPosition: Int = 0
fileprivate let onNodeChange: () -> Void
fileprivate let disposables = GroupDisposable()
fileprivate var active = false
weak var animationCache: AnimationCache?
init(node: Node, view: MacawView?, animationCache: AnimationCache?) {
fileprivate var cachedAbsPlace: Transform?
fileprivate var absPlace: Transform {
if let place = cachedAbsPlace {
return place
}
if let place = parentRenderer?.absPlace.concat(with: node.place) {
cachedAbsPlace = place
return place
}
return node.place
}
func freeCachedAbsPlace() {
cachedAbsPlace = nil
}
public func place(in relativity: Relativity = .parent) -> Transform {
switch relativity {
case .parent:
return node.place
case .scene:
return absPlace
case .view:
if let viewPlace = view?.place {
return viewPlace.concat(with: absPlace)
}
return absPlace
}
}
open var node: Node {
fatalError("Unsupported")
}
init(node: Node, view: MacawView?, animationCache: AnimationCache?, parentRenderer: GroupRenderer? = nil) {
self.view = view
self.animationCache = animationCache
self.parentRenderer = parentRenderer
onNodeChange = { [unowned node, weak view] in
guard let isAnimating = animationCache?.isAnimating(node) else {
@ -45,10 +88,6 @@ class NodeRenderer {
}
func doAddObservers() {
guard let node = node() else {
return
}
observe(node.placeVar)
observe(node.opaqueVar)
observe(node.opacityVar)
@ -56,6 +95,10 @@ class NodeRenderer {
observe(node.effectVar)
node.animationObservers.append(self)
node.placeVar.onChange { [weak self] _ in
self?.freeCachedAbsPlace()
}
}
func observe<E>(_ v: Variable<E>) {
@ -72,11 +115,7 @@ class NodeRenderer {
open func dispose() {
removeObservers()
node()?.animationObservers = node()?.animationObservers.filter { !($0 as? NodeRenderer === self) } ?? []
}
open func node() -> Node? {
fatalError("Unsupported")
node.animationObservers = node.animationObservers.filter { !($0 as? NodeRenderer === self) }
}
final public func render(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
@ -84,9 +123,6 @@ class NodeRenderer {
defer {
context.restoreGState()
}
guard let node = node() else {
return
}
let newOpacity = node.opacity * opacity
context.concatenate(node.place.toCG())
@ -133,11 +169,7 @@ class NodeRenderer {
}
final func directRender(in context: CGContext, force: Bool = true, opacity: Double = 1.0, coloringMode: ColoringMode = .rgb) {
guard let node = node() else {
return
}
if let isAnimating = animationCache?.isAnimating(node), isAnimating {
if let isAnimating = animationCache?.isAnimating(self), isAnimating {
self.removeObservers()
if !force {
return
@ -166,7 +198,7 @@ class NodeRenderer {
}
fileprivate func applyEffects(_ effects: [Effect], context: CGContext, opacity: Double, coloringMode: ColoringMode = .rgb) {
guard let node = node(), let bounds = node.bounds else {
guard let bounds = node.bounds else {
return
}
var inset: Double = 0
@ -232,65 +264,51 @@ class NodeRenderer {
fatalError("Unsupported")
}
final func findNodeAt(parentNodePath: NodePath, ctx: CGContext) -> NodePath? {
guard let node = node() else {
final func findNodeAt(location: CGPoint, ctx: CGContext) -> NodePath? {
guard node.opaque, let inverted = node.place.invert() else {
return .none
}
if node.opaque {
let place = node.place
if let inverted = place.invert() {
ctx.saveGState()
defer {
ctx.restoreGState()
}
ctx.concatenate(place.toCG())
applyClip(in: ctx)
let loc = parentNodePath.location.applying(inverted.toCG())
let path = NodePath(node: node, location: loc, parent: parentNodePath)
let result = doFindNodeAt(path: path, ctx: ctx)
return result
}
ctx.saveGState()
defer {
ctx.restoreGState()
}
return .none
ctx.concatenate(node.place.toCG())
applyClip(in: ctx)
let loc = location.applying(inverted.toCG())
let path = NodePath(node: node, location: loc)
let result = doFindNodeAt(path: path, ctx: ctx)
return result
}
final func findNodeAt(parentNodePath: NodePath, ctx: CGContext) -> NodePath? {
guard node.opaque, let inverted = node.place.invert() else {
return .none
}
ctx.saveGState()
defer {
ctx.restoreGState()
}
ctx.concatenate(node.place.toCG())
applyClip(in: ctx)
let loc = parentNodePath.location.applying(inverted.toCG())
let path = NodePath(node: node, location: loc, parent: parentNodePath)
let result = doFindNodeAt(path: path, ctx: ctx)
return result
}
public func doFindNodeAt(path: NodePath, ctx: CGContext) -> NodePath? {
return nil
}
func replaceNode(with replacementNode: Node) {
guard let node = node() else {
return
}
if let groupRenderer = parentRenderer as? GroupRenderer, let group = groupRenderer.node() as? Group {
var contents = group.contents
var indexToInsert = 0
if let index = contents.firstIndex(of: node) {
contents.remove(at: index)
indexToInsert = index
}
contents.insert(replacementNode, at: indexToInsert)
group.contents = contents
}
if let hostingView = view, hostingView.node == node {
hostingView.node = replacementNode
}
}
func calculateZPositionRecursively() {
calculateZPosition(self)
}
private func applyClip(in context: CGContext) {
guard let node = node() else {
return
}
guard let clip = node.clip else {
return
}
@ -309,7 +327,7 @@ class NodeRenderer {
}
private func getMaskedImage(bounds: Rect) -> CGImage {
let mask = node()!.mask!
let mask = node.mask!
let image = renderToImage(bounds: bounds)
let nodeRenderer = RenderUtils.createNodeRenderer(mask, view: .none, animationCache: animationCache)
let maskImage = nodeRenderer.renderToImage(bounds: bounds, coloringMode: .greyscale)
@ -352,7 +370,11 @@ class NodeRenderer {
}
func getAllChildrenRecursive() -> [NodeRenderer] {
return getAllChildren(self)
var children = getAllChildren(self)
children.removeAll(where: { (r) -> Bool in
r === self
})
return children
}
private func getAllChildren(_ nodeRenderer: NodeRenderer) -> [NodeRenderer] {

View File

@ -16,19 +16,15 @@ class RenderUtils {
return p
}
class func createNodeRenderer(
_ node: Node,
view: MacawView?,
animationCache: AnimationCache?
) -> NodeRenderer {
class func createNodeRenderer(_ node: Node, view: MacawView?, animationCache: AnimationCache?, parentRenderer: GroupRenderer? = nil) -> NodeRenderer {
if let group = node as? Group {
return GroupRenderer(group: group, view: view, animationCache: animationCache)
return GroupRenderer(group: group, view: view, animationCache: animationCache, parentRenderer: parentRenderer)
} else if let shape = node as? Shape {
return ShapeRenderer(shape: shape, view: view, animationCache: animationCache)
return ShapeRenderer(shape: shape, view: view, animationCache: animationCache, parentRenderer: parentRenderer)
} else if let text = node as? Text {
return TextRenderer(text: text, view: view, animationCache: animationCache)
return TextRenderer(text: text, view: view, animationCache: animationCache, parentRenderer: parentRenderer)
} else if let image = node as? Image {
return ImageRenderer(image: image, view: view, animationCache: animationCache)
return ImageRenderer(image: image, view: view, animationCache: animationCache, parentRenderer: parentRenderer)
}
fatalError("Unsupported node: \(node)")
}

View File

@ -8,37 +8,30 @@ import AppKit
class ShapeRenderer: NodeRenderer {
weak var shape: Shape?
var shape: Shape
init(shape: Shape, view: MacawView?, animationCache: AnimationCache?) {
init(shape: Shape, view: MacawView?, animationCache: AnimationCache?, parentRenderer: GroupRenderer? = nil) {
self.shape = shape
super.init(node: shape, view: view, animationCache: animationCache)
super.init(node: shape, view: view, animationCache: animationCache, parentRenderer: parentRenderer)
}
deinit {
dispose()
}
override func node() -> Node? {
override var node: Node {
return shape
}
override func doAddObservers() {
super.doAddObservers()
guard let shape = shape else {
return
}
observe(shape.formVar)
observe(shape.fillVar)
observe(shape.strokeVar)
}
override func doRender(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
guard let shape = shape else {
return
}
if shape.fill == nil && shape.stroke == nil {
return
}
@ -61,10 +54,6 @@ class ShapeRenderer: NodeRenderer {
}
override func doFindNodeAt(path: NodePath, ctx: CGContext) -> NodePath? {
guard let shape = shape else {
return .none
}
RenderUtils.setGeometry(shape.form, ctx: ctx)
var drawingMode: CGPathDrawingMode?
if let stroke = shape.stroke {
@ -172,9 +161,6 @@ class ShapeRenderer: NodeRenderer {
}
fileprivate func drawPattern(_ pattern: Pattern, ctx: CGContext?, opacity: Double) {
guard let shape = shape else {
return
}
var patternNode = pattern.content
if !pattern.userSpace, let node = BoundsUtils.createNodeFromRespectiveCoords(respectiveNode: pattern.content, absoluteLocus: shape.form) {
patternNode = node
@ -239,14 +225,6 @@ class ShapeRenderer: NodeRenderer {
ctx!.restoreGState()
}
override func replaceNode(with replacementNode: Node) {
super.replaceNode(with: replacementNode)
if let node = replacementNode as? Shape {
shape = node
}
}
}
extension Stroke {

View File

@ -7,28 +7,24 @@ import AppKit
#endif
class TextRenderer: NodeRenderer {
weak var text: Text?
var text: Text
init(text: Text, view: MacawView?, animationCache: AnimationCache?) {
override var node: Node {
return text
}
init(text: Text, view: MacawView?, animationCache: AnimationCache?, parentRenderer: GroupRenderer? = nil) {
self.text = text
super.init(node: text, view: view, animationCache: animationCache)
super.init(node: text, view: view, animationCache: animationCache, parentRenderer: parentRenderer)
}
deinit {
dispose()
}
override func node() -> Node? {
return text
}
override func doAddObservers() {
super.doAddObservers()
guard let text = text else {
return
}
observe(text.textVar)
observe(text.fontVar)
observe(text.fillVar)
@ -39,9 +35,6 @@ class TextRenderer: NodeRenderer {
}
override func doRender(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
guard let text = text else {
return
}
let message = text.text
let font = getMFont()
@ -78,7 +71,7 @@ class TextRenderer: NodeRenderer {
}
override func doFindNodeAt(path: NodePath, ctx: CGContext) -> NodePath? {
guard let node = node(), let contains = node.bounds?.toCG().contains(path.location) else {
guard let contains = node.bounds?.toCG().contains(path.location) else {
return .none
}
@ -95,7 +88,7 @@ class TextRenderer: NodeRenderer {
// However it is needed for the Swift Package Manager to work accordingly.
return MFont()
}
guard let text = text, let textFont = text.font else {
guard let textFont = text.font else {
return MFont.systemFont(ofSize: MFont.mSystemFontSize)
}
@ -130,9 +123,6 @@ class TextRenderer: NodeRenderer {
}
fileprivate func getBounds(_ font: MFont) -> CGRect {
guard let text = text else {
return .zero
}
var textAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: font]
if text.kerning != 0.0 {
@ -182,12 +172,4 @@ class TextRenderer: NodeRenderer {
}
return MColor.black
}
override func replaceNode(with replacementNode: Node) {
super.replaceNode(with: replacementNode)
if let node = replacementNode as? Text {
text = node
}
}
}

View File

@ -230,14 +230,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
guard let renderer = renderer else {
return .none
}
ctx.saveGState()
defer {
ctx.restoreGState()
}
let transform = place.toCG()
ctx.concatenate(transform)
let loc = location.applying(transform.inverted())
return renderer.findNodeAt(parentNodePath: NodePath(node: Node(), location: loc), ctx: ctx)
return renderer.findNodeAt(location: location, ctx: ctx)
}
private func doFindNode(location: CGPoint) -> NodePath? {
@ -275,12 +268,15 @@ open class MacawView: MView, MGestureRecognizerDelegate {
let inverted = node.place.invert()!
let loc = location.applying(inverted.toCG())
let invertedViewPlace = self.place.invert()!
let relativeToView = location.applying(invertedViewPlace.toCG())
let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque())
while let current = nodePath {
let node = current.node
let relativeLocation = current.location
let point = TouchPoint(id: id, location: loc.toMacaw(), relativeLocation: relativeLocation.toMacaw())
let point = TouchPoint(id: id, location: loc.toMacaw(), relativeToNodeLocation: relativeLocation.toMacaw(), relativeToViewLocation: relativeToView.toMacaw())
let touchEvent = TouchEvent(node: node, points: [point])
if touchesOfNode[node] == nil {
@ -312,6 +308,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
return
}
let invertedViewPlace = self.place.invert()!
var points = [TouchPoint]()
for initialTouch in initialTouches {
guard let currentIndex = touchPoints.firstIndex(of: initialTouch) else {
@ -324,7 +321,8 @@ open class MacawView: MView, MGestureRecognizerDelegate {
let location = CGPoint(x: currentTouch.x, y: currentTouch.y)
let inverted = currentNode.place.invert()!
let loc = location.applying(inverted.toCG())
let point = TouchPoint(id: currentTouch.id, location: loc.toMacaw(), relativeLocation: nodePath.location.toMacaw())
let relativeToView = location.applying(invertedViewPlace.toCG())
let point = TouchPoint(id: currentTouch.id, location: loc.toMacaw(), relativeToNodeLocation: nodePath.location.toMacaw(), relativeToViewLocation: relativeToView.toMacaw())
points.append(point)
}
@ -355,6 +353,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
return
}
let invertedViewPlace = self.place.invert()!
let touchPoints = convert(touches: touches)
for touch in touchPoints {
@ -364,8 +363,9 @@ open class MacawView: MView, MGestureRecognizerDelegate {
let inverted = node.place.invert()!
let location = CGPoint(x: touch.x, y: touch.y)
let loc = location.applying(inverted.toCG())
let relativeToView = location.applying(invertedViewPlace.toCG())
let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque())
let point = TouchPoint(id: id, location: loc.toMacaw(), relativeLocation: nodePath.location.toMacaw())
let point = TouchPoint(id: id, location: loc.toMacaw(), relativeToNodeLocation: nodePath.location.toMacaw(), relativeToViewLocation: relativeToView.toMacaw())
let touchEvent = TouchEvent(node: node, points: [point])
node.handleTouchReleased(touchEvent)
@ -588,8 +588,8 @@ class LayoutHelper {
public func getTransform(_ nodeRenderer: NodeRenderer, _ layout: ContentLayout, _ size: Size) -> Transform {
setSize(size: size)
let node = nodeRenderer.node()
var rect = node?.bounds
let node = nodeRenderer.node
var rect = node.bounds
if let canvas = node as? SVGCanvas {
if let view = nodeRenderer.view {
rect = canvas.layout(size: view.bounds.size.toMacaw()).rect()

View File

@ -8,7 +8,6 @@ import AppKit
class ShapeLayer: CAShapeLayer {
weak var renderer: NodeRenderer?
var renderTransform: CGAffineTransform?
weak var animationCache: AnimationCache?
var shouldRenderContent = true
var isForceRenderingEnabled = true
@ -22,10 +21,6 @@ class ShapeLayer: CAShapeLayer {
let renderContext = RenderContext(view: .none)
renderContext.cgContext = ctx
if let renderTransform = renderTransform {
ctx.concatenate(renderTransform)
}
renderer?.directRender(in: ctx, force: isForceRenderingEnabled)
}
}