diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index b3207ffd..02ca1de2 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -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 = ""; }; 57AF398B1E67E9DB00F0BFE2 /* EventsExampleController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventsExampleController.swift; sourceTree = ""; }; 58E4D50B1D841C6E00EC8815 /* TransformExampleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformExampleView.swift; sourceTree = ""; }; + 5B195EAB2276D5C40008AE8B /* AnimationsHierarchyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationsHierarchyViewController.swift; sourceTree = ""; }; 5BAE3CB020C54E3D006BEF51 /* FiltersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersViewController.swift; sourceTree = ""; }; 66AE19DA1CC8CB3C00B78B5E /* tiger.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = tiger.svg; path = Example/Assets/SVG/tiger.svg; sourceTree = ""; }; 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 = ""; }; + 5BE9F15D226EEAE500030967 /* AnimationsHierarchy */ = { + isa = PBXGroup; + children = ( + 5B195EAB2276D5C40008AE8B /* AnimationsHierarchyViewController.swift */, + ); + path = AnimationsHierarchy; + sourceTree = ""; + }; 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 */, diff --git a/Example/Example/Base.lproj/Main.storyboard b/Example/Example/Base.lproj/Main.storyboard index c93f9d0a..7c03ebd3 100644 --- a/Example/Example/Base.lproj/Main.storyboard +++ b/Example/Example/Base.lproj/Main.storyboard @@ -383,5 +383,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/Example/Examples/AnimationsHierarchy/AnimationsHierarchyViewController.swift b/Example/Example/Examples/AnimationsHierarchy/AnimationsHierarchyViewController.swift new file mode 100644 index 00000000..394c4ebc --- /dev/null +++ b/Example/Example/Examples/AnimationsHierarchy/AnimationsHierarchyViewController.swift @@ -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 + } + +} diff --git a/Example/Example/MenuViewController.swift b/Example/Example/MenuViewController.swift index e0c55692..686c3cf2 100644 --- a/Example/Example/MenuViewController.swift +++ b/Example/Example/MenuViewController.swift @@ -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 } diff --git a/Source/animation/AnimationProducer.swift b/Source/animation/AnimationProducer.swift index 8ecac9a9..a72b94a7 100644 --- a/Source/animation/AnimationProducer.swift +++ b/Source/animation/AnimationProducer.swift @@ -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() - } } } } diff --git a/Source/animation/AnimationUtils.swift b/Source/animation/AnimationUtils.swift index 7185201c..d1588940 100644 --- a/Source/animation/AnimationUtils.swift +++ b/Source/animation/AnimationUtils.swift @@ -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 - } } diff --git a/Source/animation/Easing.swift b/Source/animation/Easing.swift index 18bbc19a..77fb16b7 100644 --- a/Source/animation/Easing.swift +++ b/Source/animation/Easing.swift @@ -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) } diff --git a/Source/animation/types/ShapeAnimation.swift b/Source/animation/types/ShapeAnimation.swift index ead3f510..23391ddd 100644 --- a/Source/animation/types/ShapeAnimation.swift +++ b/Source/animation/types/ShapeAnimation.swift @@ -7,9 +7,10 @@ // class ShapeAnimation: AnimationImpl { - 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 { 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(animatedNode), valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps) type = .shape node = animatedNode @@ -33,8 +35,9 @@ class ShapeAnimation: AnimationImpl { } } - 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(animatedNode), factory: factory, animationDuration: animationDuration, delay: delay, fps: fps) type = .shape node = animatedNode diff --git a/Source/animation/types/TransformAnimation.swift b/Source/animation/types/TransformAnimation.swift index 052af1c9..1e0606d6 100644 --- a/Source/animation/types/TransformAnimation.swift +++ b/Source/animation/types/TransformAnimation.swift @@ -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) } diff --git a/Source/animation/types/animation_generators/Cache/AnimationCache.swift b/Source/animation/types/animation_generators/Cache/AnimationCache.swift index 6f464247..c967536e 100644 --- a/Source/animation/types/animation_generators/Cache/AnimationCache.swift +++ b/Source/animation/types/animation_generators/Cache/AnimationCache.swift @@ -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) - } } diff --git a/Source/animation/types/animation_generators/CombinationAnimationGenerator.swift b/Source/animation/types/animation_generators/CombinationAnimationGenerator.swift index ed17f0b6..0a22547c 100644 --- a/Source/animation/types/animation_generators/CombinationAnimationGenerator.swift +++ b/Source/animation/types/animation_generators/CombinationAnimationGenerator.swift @@ -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) diff --git a/Source/animation/types/animation_generators/MorphingGenerator.swift b/Source/animation/types/animation_generators/MorphingGenerator.swift index 036241c6..99d4e0c7 100644 --- a/Source/animation/types/animation_generators/MorphingGenerator.swift +++ b/Source/animation/types/animation_generators/MorphingGenerator.swift @@ -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 diff --git a/Source/animation/types/animation_generators/OpacityGenerator.swift b/Source/animation/types/animation_generators/OpacityGenerator.swift index 6c1a4cc6..6b4b6d5e 100644 --- a/Source/animation/types/animation_generators/OpacityGenerator.swift +++ b/Source/animation/types/animation_generators/OpacityGenerator.swift @@ -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 { diff --git a/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift b/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift index 8a38ca85..9f703093 100644 --- a/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift +++ b/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift @@ -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 diff --git a/Source/animation/types/animation_generators/TransformGenerator.swift b/Source/animation/types/animation_generators/TransformGenerator.swift index 8a1e6277..98778f89 100644 --- a/Source/animation/types/animation_generators/TransformGenerator.swift +++ b/Source/animation/types/animation_generators/TransformGenerator.swift @@ -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) } diff --git a/Source/events/TouchEvent.swift b/Source/events/TouchEvent.swift index 2eb2248f..8eadc4fa 100644 --- a/Source/events/TouchEvent.swift +++ b/Source/events/TouchEvent.swift @@ -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 } } } diff --git a/Source/model/geom2d/Transform.swift b/Source/model/geom2d/Transform.swift index 8f731b6c..38dc516c 100644 --- a/Source/model/geom2d/Transform.swift +++ b/Source/model/geom2d/Transform.swift @@ -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 diff --git a/Source/model/scene/SceneUtils.swift b/Source/model/scene/SceneUtils.swift index ae183e56..4c05f1bb 100644 --- a/Source/model/scene/SceneUtils.swift +++ b/Source/model/scene/SceneUtils.swift @@ -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 } } diff --git a/Source/render/GroupRenderer.swift b/Source/render/GroupRenderer.swift index c3f0e989..4aca8301 100644 --- a/Source/render/GroupRenderer.swift +++ b/Source/render/GroupRenderer.swift @@ -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 - } - } } diff --git a/Source/render/ImageRenderer.swift b/Source/render/ImageRenderer.swift index 4d52cf89..24f24b84 100644 --- a/Source/render/ImageRenderer.swift +++ b/Source/render/ImageRenderer.swift @@ -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 - } - } } diff --git a/Source/render/NodeRenderer.swift b/Source/render/NodeRenderer.swift index 8bb770d7..23d3cd72 100644 --- a/Source/render/NodeRenderer.swift +++ b/Source/render/NodeRenderer.swift @@ -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(_ v: Variable) { @@ -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] { diff --git a/Source/render/RenderUtils.swift b/Source/render/RenderUtils.swift index 3d559893..c8fb310b 100644 --- a/Source/render/RenderUtils.swift +++ b/Source/render/RenderUtils.swift @@ -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)") } diff --git a/Source/render/ShapeRenderer.swift b/Source/render/ShapeRenderer.swift index 22242651..cfddc19a 100644 --- a/Source/render/ShapeRenderer.swift +++ b/Source/render/ShapeRenderer.swift @@ -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 { diff --git a/Source/render/TextRenderer.swift b/Source/render/TextRenderer.swift index 8883647a..61467074 100644 --- a/Source/render/TextRenderer.swift +++ b/Source/render/TextRenderer.swift @@ -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 - } - } } diff --git a/Source/views/MacawView.swift b/Source/views/MacawView.swift index 1c21593a..3eb9ad71 100644 --- a/Source/views/MacawView.swift +++ b/Source/views/MacawView.swift @@ -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() diff --git a/Source/views/ShapeLayer.swift b/Source/views/ShapeLayer.swift index 08f187c5..74693b63 100644 --- a/Source/views/ShapeLayer.swift +++ b/Source/views/ShapeLayer.swift @@ -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) } }