From da870907a7135aba1c11aa5e18cafb592a348198 Mon Sep 17 00:00:00 2001 From: Alisa Mylnikova Date: Thu, 1 Nov 2018 17:30:16 +0700 Subject: [PATCH 1/5] Remove nodes map --- Example/Example.xcodeproj/project.pbxproj | 24 +- Example/Example/Base.lproj/Main.storyboard | 10 +- .../EasingExampleController.swift | 0 .../{Animations => Easing}/EasingView.swift | 0 Macaw.xcodeproj/project.pbxproj | 6 - Source/animation/AnimationImpl.swift | 3 +- Source/animation/AnimationProducer.swift | 44 ++-- Source/animation/AnimationUtils.swift | 86 ++------ .../animation/types/AnimationSequence.swift | 2 +- Source/animation/types/CombineAnimation.swift | 4 +- .../animation/types/ContentsAnimation.swift | 5 +- .../animation/types/MorphingAnimation.swift | 4 +- Source/animation/types/OpacityAnimation.swift | 5 +- Source/animation/types/ShapeAnimation.swift | 5 +- .../animation/types/TransformAnimation.swift | 7 +- .../Cache/AnimationCache.swift | 121 +++++----- .../Cache/NodeHashable.swift | 10 + .../MorphingGenerator.swift | 11 +- .../OpacityGenerator.swift | 6 +- .../ShapeAnimationGenerator.swift | 20 +- .../TransformGenerator.swift | 11 +- Source/model/scene/Group.swift | 44 ---- Source/model/scene/Node.swift | 34 +-- Source/render/GroupRenderer.swift | 39 ++-- Source/render/ImageRenderer.swift | 8 + Source/render/NodeRenderer.swift | 75 ++++++- Source/render/RenderUtils.swift | 5 +- Source/render/ShapeRenderer.swift | 8 + Source/render/TextRenderer.swift | 8 + Source/views/MacawView.swift | 208 ++++++++---------- Source/views/NodesMap.swift | 103 --------- Source/views/ShapeLayer.swift | 3 +- 32 files changed, 389 insertions(+), 530 deletions(-) rename Example/Example/Examples/{Animations => Easing}/EasingExampleController.swift (100%) rename Example/Example/Examples/{Animations => Easing}/EasingView.swift (100%) delete mode 100644 Source/views/NodesMap.swift diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index d5f44d66..19bcb20f 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -93,12 +93,13 @@ 574EC4271CB7DE7F0063F317 /* Examples */ = { isa = PBXGroup; children = ( - 5BAE3CAF20C54DA5006BEF51 /* Filters */, - 57AF398A1E67E86400F0BFE2 /* Events */, - 5747F9BB1E38B660004E338F /* Morphing */, + 574EC4331CB7DE7F0063F317 /* Shapes */, 6699B7CE1DFFE8B90072585E /* Transform */, 574EC4421CBB607E0063F317 /* Animations */, - 574EC4331CB7DE7F0063F317 /* Shapes */, + 5B08CF00218832A700D37662 /* Easing */, + 5747F9BB1E38B660004E338F /* Morphing */, + 57AF398A1E67E86400F0BFE2 /* Events */, + 5BAE3CAF20C54DA5006BEF51 /* Filters */, 574EC4401CB7E2440063F317 /* MenuViewController.swift */, ); path = Examples; @@ -117,8 +118,6 @@ children = ( 574EC4431CBB60AF0063F317 /* AnimationsExampleController.swift */, 575129B51CBD14AF00BD3C2E /* AnimationsView.swift */, - B04416F91E041A420016BC50 /* EasingView.swift */, - B04416FB1E04282A0016BC50 /* EasingExampleController.swift */, ); path = Animations; sourceTree = ""; @@ -131,6 +130,15 @@ path = Events; sourceTree = ""; }; + 5B08CF00218832A700D37662 /* Easing */ = { + isa = PBXGroup; + children = ( + B04416FB1E04282A0016BC50 /* EasingExampleController.swift */, + B04416F91E041A420016BC50 /* EasingView.swift */, + ); + path = Easing; + sourceTree = ""; + }; 5BAE3CAF20C54DA5006BEF51 /* Filters */ = { isa = PBXGroup; children = ( @@ -490,7 +498,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -508,7 +516,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.exyte.Example.Example; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/Example/Example/Base.lproj/Main.storyboard b/Example/Example/Base.lproj/Main.storyboard index b6f8dbd1..8059167b 100644 --- a/Example/Example/Base.lproj/Main.storyboard +++ b/Example/Example/Base.lproj/Main.storyboard @@ -1,12 +1,11 @@ - + - - + @@ -96,7 +95,7 @@ diff --git a/Example/Example/Examples/Animations/EasingExampleController.swift b/Example/Example/Examples/Easing/EasingExampleController.swift similarity index 100% rename from Example/Example/Examples/Animations/EasingExampleController.swift rename to Example/Example/Examples/Easing/EasingExampleController.swift diff --git a/Example/Example/Examples/Animations/EasingView.swift b/Example/Example/Examples/Easing/EasingView.swift similarity index 100% rename from Example/Example/Examples/Animations/EasingView.swift rename to Example/Example/Examples/Easing/EasingView.swift diff --git a/Macaw.xcodeproj/project.pbxproj b/Macaw.xcodeproj/project.pbxproj index befada5a..2e759cc7 100644 --- a/Macaw.xcodeproj/project.pbxproj +++ b/Macaw.xcodeproj/project.pbxproj @@ -33,7 +33,6 @@ 57614B031F83D15600875933 /* UIImage2Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57900FF81EA0DEBF00809FFB /* UIImage2Image.swift */; }; 57614B041F83D15600875933 /* SVGParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1471E3B393900D1CB28 /* SVGParser.swift */; }; 57614B051F83D15600875933 /* SWXMLHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 572CEFC61E2CED4B008C7C83 /* SWXMLHash.swift */; }; - 57614B061F83D15600875933 /* NodesMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1511E3B393900D1CB28 /* NodesMap.swift */; }; 57614B071F83D15600875933 /* RenderUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1421E3B393900D1CB28 /* RenderUtils.swift */; }; 57614B081F83D15600875933 /* FuncBounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E0F11E3B393900D1CB28 /* FuncBounds.swift */; }; 57614B091F83D15600875933 /* MView_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = A718CD461F45C28700966E06 /* MView_iOS.swift */; }; @@ -219,7 +218,6 @@ 57E5E1B01E3B393900D1CB28 /* CGFloat+Double.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E14D1E3B393900D1CB28 /* CGFloat+Double.swift */; }; 57E5E1B11E3B393900D1CB28 /* NSTimer+Closure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E14E1E3B393900D1CB28 /* NSTimer+Closure.swift */; }; 57E5E1B21E3B393900D1CB28 /* MacawView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1501E3B393900D1CB28 /* MacawView.swift */; }; - 57E5E1B31E3B393900D1CB28 /* NodesMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1511E3B393900D1CB28 /* NodesMap.swift */; }; 57E5E1B41E3B393900D1CB28 /* ShapeLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57E5E1521E3B393900D1CB28 /* ShapeLayer.swift */; }; 57F108741F502A3600DC365B /* Touchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57F108731F502A3600DC365B /* Touchable.swift */; }; 57F1087A1F53C92000DC365B /* MDisplayLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57F108791F53C92000DC365B /* MDisplayLink.swift */; }; @@ -657,7 +655,6 @@ 57E5E14D1E3B393900D1CB28 /* CGFloat+Double.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGFloat+Double.swift"; sourceTree = ""; }; 57E5E14E1E3B393900D1CB28 /* NSTimer+Closure.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSTimer+Closure.swift"; sourceTree = ""; }; 57E5E1501E3B393900D1CB28 /* MacawView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacawView.swift; sourceTree = ""; }; - 57E5E1511E3B393900D1CB28 /* NodesMap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NodesMap.swift; sourceTree = ""; }; 57E5E1521E3B393900D1CB28 /* ShapeLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShapeLayer.swift; sourceTree = ""; }; 57F108731F502A3600DC365B /* Touchable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Touchable.swift; sourceTree = ""; }; 57F108791F53C92000DC365B /* MDisplayLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MDisplayLink.swift; path = Source/platform/MDisplayLink.swift; sourceTree = SOURCE_ROOT; }; @@ -1285,7 +1282,6 @@ children = ( 57F108731F502A3600DC365B /* Touchable.swift */, 57E5E1501E3B393900D1CB28 /* MacawView.swift */, - 57E5E1511E3B393900D1CB28 /* NodesMap.swift */, 57E5E1521E3B393900D1CB28 /* ShapeLayer.swift */, ); path = views; @@ -2007,7 +2003,6 @@ 5B6E194020AC58F900454E7E /* Stroke.swift in Sources */, 57614B041F83D15600875933 /* SVGParser.swift in Sources */, 57614B051F83D15600875933 /* SWXMLHash.swift in Sources */, - 57614B061F83D15600875933 /* NodesMap.swift in Sources */, 57614B071F83D15600875933 /* RenderUtils.swift in Sources */, 57614B081F83D15600875933 /* FuncBounds.swift in Sources */, 57614B091F83D15600875933 /* MView_iOS.swift in Sources */, @@ -2146,7 +2141,6 @@ 57E5E1AB1E3B393900D1CB28 /* SVGParser.swift in Sources */, 5B6E193F20AC58F900454E7E /* Stroke.swift in Sources */, 572CEFC81E2CED4B008C7C83 /* SWXMLHash.swift in Sources */, - 57E5E1B31E3B393900D1CB28 /* NodesMap.swift in Sources */, 57E5E1A71E3B393900D1CB28 /* RenderUtils.swift in Sources */, 57E5E1601E3B393900D1CB28 /* FuncBounds.swift in Sources */, A718CD481F45C28700966E06 /* MView_iOS.swift in Sources */, diff --git a/Source/animation/AnimationImpl.swift b/Source/animation/AnimationImpl.swift index 2ba5bfb8..58a145aa 100644 --- a/Source/animation/AnimationImpl.swift +++ b/Source/animation/AnimationImpl.swift @@ -22,7 +22,8 @@ enum AnimationType { class BasicAnimation: Animation { - var nodeId: String? + weak var node: Node? + weak var nodeRenderer: NodeRenderer? var type = AnimationType.unknown let ID: String var next: BasicAnimation? diff --git a/Source/animation/AnimationProducer.swift b/Source/animation/AnimationProducer.swift index 23f8a734..111e8aaa 100644 --- a/Source/animation/AnimationProducer.swift +++ b/Source/animation/AnimationProducer.swift @@ -10,7 +10,7 @@ let animationProducer = AnimationProducer() class AnimationProducer { - var storedAnimations = [Node: BasicAnimation]() + var storedAnimations = [Node: BasicAnimation]() // is used to make sure node is in view hierarchy before actually creating the animation var delayedAnimations = [BasicAnimation: Timer]() var displayLink: MDisplayLinkProtocol? @@ -66,11 +66,27 @@ class AnimationProducer { } // General case - guard let nodeId = animation.nodeId, let node = Node.nodeBy(id: nodeId) else { + guard let node = animation.node else { return } + for observer in node.animationObservers { + observer.processAnimation(animation) + } + + switch animation.type { + case .unknown: + return + case .empty: + executeCompletion(animation) + case .sequence: + addAnimationSequence(animation) + case .combine: + addCombineAnimation(animation) + default: + break + } - guard let macawView = nodesMap.getView(node) else { + guard let macawView = animation.nodeRenderer?.view as? MacawView else { storedAnimations[node] = animation return } @@ -85,8 +101,6 @@ class AnimationProducer { // swiftlint:disable superfluous_disable_command switch_case_alignment switch animation.type { - case .unknown: - return case .affineTransformation: addTransformAnimation(animation, sceneLayer: layer, animationCache: cache, completion: { if let next = animation.next { @@ -99,10 +113,6 @@ class AnimationProducer { self.addAnimation(next) } }) - case .sequence: - addAnimationSequence(animation) - case .combine: - addCombineAnimation(animation) case .contents: addContentsAnimation(animation, cache: cache) { if let next = animation.next { @@ -121,8 +131,8 @@ class AnimationProducer { self.addAnimation(next) } } - case .empty: - executeCompletion(animation) + default: + break } // swiftlint:enable superfluous_disable_command switch_case_alignment } @@ -282,10 +292,6 @@ class AnimationProducer { return } - guard let nodeId = animation.nodeId, let node = Node.nodeBy(id: nodeId) else { - return - } - if animation.autoreverses { animation.autoreverses = false addAnimation([animation, animation.reverse()].sequence() as! BasicAnimation) @@ -311,7 +317,7 @@ class AnimationProducer { unionBounds = unionBounds?.union(rect: contentsAnimation.getVFunc()(t).group().bounds!) } - guard let layer = cache?.layerForNode(node, animation: contentsAnimation, customBounds: unionBounds) else { + guard let renderer = animation.nodeRenderer, let layer = cache?.layerForNodeRenderer(renderer, animation: contentsAnimation, customBounds: unionBounds) else { return } @@ -348,7 +354,7 @@ class AnimationProducer { for (index, animationDesc) in contentsAnimations.reversed().enumerated() { let animation = animationDesc.animation - guard let nodeId = animation.nodeId, let group = Node.nodeBy(id: nodeId) as? Group else { + guard let group = animation.node as? Group, let renderer = animation.nodeRenderer else { continue } @@ -373,7 +379,7 @@ class AnimationProducer { } contentsAnimations.remove(at: count - 1 - index) - animationDesc.cache?.freeLayer(group) + animationDesc.cache?.freeLayer(renderer) animationDesc.completion?() continue } @@ -386,7 +392,7 @@ class AnimationProducer { if animation.manualStop || animation.paused { defer { contentsAnimations.remove(at: count - 1 - index) - animationDesc.cache?.freeLayer(group) + animationDesc.cache?.freeLayer(renderer) } if animation.manualStop { diff --git a/Source/animation/AnimationUtils.swift b/Source/animation/AnimationUtils.swift index 2eb3d6e6..4faa20cb 100644 --- a/Source/animation/AnimationUtils.swift +++ b/Source/animation/AnimationUtils.swift @@ -1,43 +1,44 @@ import Foundation class AnimationUtils { - class func absolutePosition(_ node: Node, view: MacawView? = nil) -> Transform { - return AnimationUtils.absoluteTransform(node, pos: node.place, view: view) + class func absolutePosition(_ nodeRenderer: NodeRenderer?) -> Transform { + return AnimationUtils.absoluteTransform(nodeRenderer, pos: nodeRenderer?.node()?.place ?? .identity) } - class func absoluteTransform(_ node: Node, pos: Transform, view: MacawView? = nil) -> Transform { + class func absoluteTransform(_ nodeRenderer: NodeRenderer?, pos: Transform) -> Transform { var transform = pos - var parent = nodesMap.parents(node).first - while parent != .none { + var parentRenderer = nodeRenderer?.parentRenderer + while parentRenderer != nil { - if let canvas = parent as? SVGCanvas, - let view = nodesMap.getView(canvas) { + if let canvas = parentRenderer?.node() as? SVGCanvas, + let view = parentRenderer?.view as? MacawView { let rect = canvas.layout(size: view.bounds.size.toMacaw()).rect() let canvasTransform = view.contentLayout.layout(rect: rect, into: view.bounds.size.toMacaw()).move(dx: rect.x, dy: rect.y) transform = canvasTransform.concat(with: transform) - } else { - transform = parent!.place.concat(with: transform) + } else if let node = parentRenderer?.node() { + transform = node.place.concat(with: transform) } - parent = nodesMap.parents(parent!).first + parentRenderer = parentRenderer?.parentRenderer } return transform } - class func absoluteClip(node: Node) -> Locus? { - - if let _ = node.clip { - return node.clip + class func absoluteClip(_ nodeRenderer: NodeRenderer?) -> Locus? { + // shouldn't this be a superposition of all parents' clips? + let node = nodeRenderer?.node() + if let _ = node?.clip { + return node?.clip } - var parent = nodesMap.parents(node).first - while parent != .none { - if let _ = parent?.clip { - return parent?.clip + var parentRenderer = nodeRenderer?.parentRenderer + while parentRenderer != nil { + if let _ = parentRenderer?.node()?.clip { + return parentRenderer?.node()?.clip } - parent = nodesMap.parents(parent!).first + parentRenderer = parentRenderer?.parentRenderer } return .none @@ -45,53 +46,6 @@ class AnimationUtils { private static var indexCache = [Node: Int]() - class func absoluteIndex(_ node: Node, useCache: Bool = false) -> Int { - if useCache { - if let cachedIndex = indexCache[node] { - return cachedIndex - } - } else { - indexCache.removeAll() - } - - func childrenTotalCount(_ node: Node) -> Int { - guard let group = node as? Group else { - return 1 - } - - var count = 1 - for child in group.contents { - count += childrenTotalCount(child) - } - - return count - } - - var zIndex = 0 - var parent = nodesMap.parents(node).first - var currentNode = node - while parent != .none { - if let group = parent as? Group { - let localIndex = group.contents.index(of: currentNode) ?? group.contents.count - - for i in 0.. [Node] { if animationCache.isAnimating(root) { return [root] diff --git a/Source/animation/types/AnimationSequence.swift b/Source/animation/types/AnimationSequence.swift index c714d69a..63ae3595 100644 --- a/Source/animation/types/AnimationSequence.swift +++ b/Source/animation/types/AnimationSequence.swift @@ -10,7 +10,7 @@ internal class AnimationSequence: BasicAnimation { super.init() self.type = .sequence - self.nodeId = animations.first?.nodeId + self.node = animations.first?.node self.delay = delay } diff --git a/Source/animation/types/CombineAnimation.swift b/Source/animation/types/CombineAnimation.swift index 1e0191b6..a67bbe3f 100644 --- a/Source/animation/types/CombineAnimation.swift +++ b/Source/animation/types/CombineAnimation.swift @@ -10,7 +10,7 @@ internal class CombineAnimation: BasicAnimation { super.init() self.type = .combine - self.nodeId = nodeId ?? animations.first?.nodeId + self.node = node ?? animations.first?.node self.delay = delay } @@ -84,6 +84,6 @@ public extension Sequence where Iterator.Element: Animation { self.forEach { animation in toCombine.append(animation as! BasicAnimation) } - return CombineAnimation(animations: toCombine, delay: delay, node: node) + return CombineAnimation(animations: toCombine, delay: delay, node: node ?? toCombine.first?.node) } } diff --git a/Source/animation/types/ContentsAnimation.swift b/Source/animation/types/ContentsAnimation.swift index 37d35d35..c0103853 100644 --- a/Source/animation/types/ContentsAnimation.swift +++ b/Source/animation/types/ContentsAnimation.swift @@ -4,7 +4,7 @@ internal class ContentsAnimation: AnimationImpl<[Node]> { super.init(observableValue: animatedGroup.contentsVar, valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps) type = .contents - nodeId = animatedGroup.id + node = animatedGroup if autostart { self.play() @@ -14,7 +14,7 @@ internal class ContentsAnimation: AnimationImpl<[Node]> { init(animatedGroup: Group, factory: @escaping (() -> ((Double) -> [Node])), animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) { super.init(observableValue: animatedGroup.contentsVar, factory: factory, animationDuration: animationDuration, delay: delay, fps: fps) type = .contents - nodeId = animatedGroup.id + node = animatedGroup if autostart { self.play() @@ -29,7 +29,6 @@ internal class ContentsAnimation: AnimationImpl<[Node]> { } } - let node = Node.nodeBy(id: nodeId!) let reversedAnimation = ContentsAnimation(animatedGroup: node as! Group, factory: factory, animationDuration: duration, fps: logicalFps) reversedAnimation.progress = progress diff --git a/Source/animation/types/MorphingAnimation.swift b/Source/animation/types/MorphingAnimation.swift index d31971f2..8b025e3a 100644 --- a/Source/animation/types/MorphingAnimation.swift +++ b/Source/animation/types/MorphingAnimation.swift @@ -12,7 +12,7 @@ class MorphingAnimation: AnimationImpl { init(animatedNode: Shape, valueFunc: @escaping (Double) -> Locus, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) { super.init(observableValue: animatedNode.formVar, valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps) type = .morphing - nodeId = animatedNode.id + node = animatedNode if autostart { self.play() @@ -22,7 +22,7 @@ class MorphingAnimation: AnimationImpl { init(animatedNode: Shape, factory: @escaping (() -> ((Double) -> Locus)), animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) { super.init(observableValue: animatedNode.formVar, factory: factory, animationDuration: animationDuration, delay: delay, fps: fps) type = .morphing - nodeId = animatedNode.id + node = animatedNode if autostart { self.play() diff --git a/Source/animation/types/OpacityAnimation.swift b/Source/animation/types/OpacityAnimation.swift index 694bcf41..2d27d6cc 100644 --- a/Source/animation/types/OpacityAnimation.swift +++ b/Source/animation/types/OpacityAnimation.swift @@ -12,7 +12,7 @@ internal class OpacityAnimation: AnimationImpl { init(animatedNode: Node, valueFunc: @escaping (Double) -> Double, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) { super.init(observableValue: animatedNode.opacityVar, valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps) type = .opacity - nodeId = animatedNode.id + node = animatedNode if autostart { self.play() @@ -22,7 +22,7 @@ internal class OpacityAnimation: AnimationImpl { init(animatedNode: Node, factory: @escaping (() -> ((Double) -> Double)), animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) { super.init(observableValue: animatedNode.opacityVar, factory: factory, animationDuration: animationDuration, delay: delay, fps: fps) type = .opacity - nodeId = animatedNode.id + node = animatedNode if autostart { self.play() @@ -37,7 +37,6 @@ internal class OpacityAnimation: AnimationImpl { } } - let node = Node.nodeBy(id: nodeId!) let reversedAnimation = OpacityAnimation(animatedNode: node!, factory: factory, animationDuration: duration, fps: logicalFps) reversedAnimation.progress = progress diff --git a/Source/animation/types/ShapeAnimation.swift b/Source/animation/types/ShapeAnimation.swift index 7be33e38..ab4300b1 100644 --- a/Source/animation/types/ShapeAnimation.swift +++ b/Source/animation/types/ShapeAnimation.swift @@ -23,7 +23,7 @@ class ShapeAnimation: AnimationImpl { init(animatedNode: Shape, valueFunc: @escaping (Double) -> Shape, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) { super.init(observableValue: AnimatableVariable(animatedNode), valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps) type = .shape - nodeId = animatedNode.id + node = animatedNode if autostart { self.play() @@ -33,7 +33,7 @@ class ShapeAnimation: AnimationImpl { init(animatedNode: Shape, factory: @escaping (() -> ((Double) -> Shape)), animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) { super.init(observableValue: AnimatableVariable(animatedNode), factory: factory, animationDuration: animationDuration, delay: delay, fps: fps) type = .shape - nodeId = animatedNode.id + node = animatedNode if autostart { self.play() @@ -53,7 +53,6 @@ class ShapeAnimation: AnimationImpl { } } - let node = Node.nodeBy(id: nodeId!) let reversedAnimation = ShapeAnimation(animatedNode: node as! Shape, factory: factory, animationDuration: duration, diff --git a/Source/animation/types/TransformAnimation.swift b/Source/animation/types/TransformAnimation.swift index 79834be3..074b1386 100644 --- a/Source/animation/types/TransformAnimation.swift +++ b/Source/animation/types/TransformAnimation.swift @@ -16,7 +16,7 @@ internal class TransformAnimation: AnimationImpl { init(animatedNode: Node, valueFunc: @escaping (Double) -> Transform, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) { super.init(observableValue: animatedNode.placeVar, valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps) type = .affineTransformation - nodeId = animatedNode.id + node = animatedNode if autostart { self.play() @@ -26,7 +26,7 @@ internal class TransformAnimation: AnimationImpl { init(animatedNode: Node, factory: @escaping (() -> ((Double) -> Transform)), animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) { super.init(observableValue: animatedNode.placeVar, factory: factory, animationDuration: animationDuration, delay: delay, fps: fps) type = .affineTransformation - nodeId = animatedNode.id + node = animatedNode if autostart { self.play() @@ -36,7 +36,7 @@ internal class TransformAnimation: AnimationImpl { init(animatedNode: Node, factory: @escaping (() -> ((Double) -> Transform)), along path: Path, animationDuration: Double = 1.0, delay: Double = 0.0, autostart: Bool = false) { super.init(observableValue: animatedNode.placeVar, factory: factory, animationDuration: animationDuration, delay: delay) type = .affineTransformation - nodeId = animatedNode.id + node = animatedNode self.trajectory = path if autostart { @@ -53,7 +53,6 @@ internal class TransformAnimation: AnimationImpl { } } - let node = Node.nodeBy(id: nodeId!) let reversedAnimation = TransformAnimation(animatedNode: node!, factory: factory, animationDuration: duration, fps: logicalFps) reversedAnimation.progress = progress diff --git a/Source/animation/types/animation_generators/Cache/AnimationCache.swift b/Source/animation/types/animation_generators/Cache/AnimationCache.swift index 2fff8860..adc8ce89 100644 --- a/Source/animation/types/animation_generators/Cache/AnimationCache.swift +++ b/Source/animation/types/animation_generators/Cache/AnimationCache.swift @@ -20,71 +20,75 @@ class AnimationCache { } weak var sceneLayer: CALayer? - var layerCache = [Node: CachedLayer]() + var layerCache = [NodeRenderer: CachedLayer]() required init(sceneLayer: CALayer) { self.sceneLayer = sceneLayer } - func layerForNode(_ node: Node, animation: Animation, customBounds: Rect? = .none, shouldRenderContent: Bool = true) -> ShapeLayer { - guard let cachedLayer = layerCache[node] else { - let layer = ShapeLayer() - layer.shouldRenderContent = shouldRenderContent - layer.animationCache = self - - // Use to debug animation layers - // layer.backgroundColor = MColor.green.cgColor - // layer.borderWidth = 1.0 - // layer.borderColor = MColor.blue.cgColor - - let calculatedBounds = customBounds ?? node.bounds - if let shapeBounds = calculatedBounds { - let cgRect = shapeBounds.toCG() - - let origFrame = CGRect(x: 0.0, y: 0.0, - width: round(cgRect.width), - height: round(cgRect.height)) - - layer.bounds = origFrame - layer.anchorPoint = CGPoint( - x: -1.0 * cgRect.origin.x / cgRect.width, - y: -1.0 * cgRect.origin.y / cgRect.height - ) - layer.zPosition = CGFloat(AnimationUtils.absoluteIndex(node)) - - layer.renderTransform = CGAffineTransform(translationX: -1.0 * cgRect.origin.x, y: -1.0 * cgRect.origin.y) - - let nodeTransform = AnimationUtils.absolutePosition(node).toCG() - layer.transform = CATransform3DMakeAffineTransform(nodeTransform) - - // Clip - if let clip = AnimationUtils.absoluteClip(node: node) { - let maskLayer = CAShapeLayer() - let origPath = clip.toCGPath() - var offsetTransform = CGAffineTransform(translationX: -1.0 * cgRect.origin.x, y: -1.0 * cgRect.origin.y) - let clipPath = origPath.mutableCopy(using: &offsetTransform) - maskLayer.path = clipPath - layer.mask = maskLayer - } - } + func layerForNodeRenderer(_ renderer: NodeRenderer, animation: Animation, customBounds: Rect? = .none, shouldRenderContent: Bool = true) -> ShapeLayer { + + guard let node = renderer.node() else { + return ShapeLayer() + } - layer.opacity = Float(node.opacity) - layer.node = node + if let cachedLayer = layerCache[renderer] { + cachedLayer.linksCounter += 1 + return cachedLayer.layer + } - layer.contentsScale = calculateAnimationScale(animation: animation) + let layer = ShapeLayer() + layer.shouldRenderContent = shouldRenderContent + layer.animationCache = self + + // Use to debug animation layers + // layer.backgroundColor = MColor.green.cgColor + // layer.borderWidth = 1.0 + // layer.borderColor = MColor.blue.cgColor + + let calculatedBounds = customBounds ?? node.bounds + if let shapeBounds = calculatedBounds { + let cgRect = shapeBounds.toCG() + + let origFrame = CGRect(x: 0.0, y: 0.0, + width: round(cgRect.width), + height: round(cgRect.height)) + + layer.bounds = origFrame + layer.anchorPoint = CGPoint( + x: -1.0 * cgRect.origin.x / cgRect.width, + y: -1.0 * cgRect.origin.y / cgRect.height + ) + 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).toCG() + layer.transform = CATransform3DMakeAffineTransform(nodeTransform) + + // Clip + if let clip = AnimationUtils.absoluteClip(renderer) { + let maskLayer = CAShapeLayer() + let origPath = clip.toCGPath() + var offsetTransform = CGAffineTransform(translationX: -1.0 * cgRect.origin.x, y: -1.0 * cgRect.origin.y) + let clipPath = origPath.mutableCopy(using: &offsetTransform) + maskLayer.path = clipPath + layer.mask = maskLayer + } + } - layer.setNeedsDisplay() - sceneLayer?.addSublayer(layer) + layer.opacity = Float(node.opacity) + layer.node = node - layerCache[node] = CachedLayer(layer: layer, animation: animation) - sceneLayer?.setNeedsDisplay() + layer.contentsScale = calculateAnimationScale(animation: animation) - return layer - } + layer.setNeedsDisplay() + sceneLayer?.addSublayer(layer) - cachedLayer.linksCounter += 1 + layerCache[renderer] = CachedLayer(layer: layer, animation: animation) + sceneLayer?.setNeedsDisplay() - return cachedLayer.layer + return layer } private func calculateAnimationScale(animation: Animation) -> CGFloat { @@ -125,8 +129,8 @@ class AnimationCache { return defaultScale * CGFloat(sqrt(maxArea)) } - func freeLayer(_ node: Node) { - guard let cachedLayer = layerCache[node] else { + func freeLayer(_ renderer: NodeRenderer) { + guard let cachedLayer = layerCache[renderer] else { return } @@ -137,14 +141,15 @@ class AnimationCache { } let layer = cachedLayer.layer - layerCache.removeValue(forKey: node) + layerCache.removeValue(forKey: renderer) sceneLayer?.setNeedsDisplay() layer.removeFromSuperlayer() } func isAnimating(_ node: Node) -> Bool { - if let _ = layerCache[node] { + let renderer = layerCache.keys.filter{ $0.node() === node }.first + if let renderer = renderer, let _ = layerCache[renderer] { return true } @@ -183,7 +188,7 @@ class AnimationCache { return layerCache.map { $0.1.animation } } - func replace(original: Node, replacement: Node) { + func replace(original: NodeRenderer, replacement: NodeRenderer) { guard let layer = layerCache[original] else { return } diff --git a/Source/animation/types/animation_generators/Cache/NodeHashable.swift b/Source/animation/types/animation_generators/Cache/NodeHashable.swift index 6d1ae046..16b9a4e9 100644 --- a/Source/animation/types/animation_generators/Cache/NodeHashable.swift +++ b/Source/animation/types/animation_generators/Cache/NodeHashable.swift @@ -9,3 +9,13 @@ extension Node: Hashable { public func == (lhs: Node, rhs: Node) -> Bool { return lhs === rhs } + +extension NodeRenderer: Hashable { + public var hashValue: Int { + return Unmanaged.passUnretained(self).toOpaque().hashValue + } +} + +func == (lhs: NodeRenderer, rhs: NodeRenderer) -> Bool { + return lhs === rhs +} diff --git a/Source/animation/types/animation_generators/MorphingGenerator.swift b/Source/animation/types/animation_generators/MorphingGenerator.swift index dfe8275f..0d6e0608 100644 --- a/Source/animation/types/animation_generators/MorphingGenerator.swift +++ b/Source/animation/types/animation_generators/MorphingGenerator.swift @@ -19,20 +19,19 @@ func addMorphingAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, anim return } - guard let nodeId = animation.nodeId, let shape = Node.nodeBy(id: nodeId) as? Shape else { + guard let shape = animation.node as? Shape, let renderer = animation.nodeRenderer else { return } let mutatingShape = SceneUtils.shapeCopy(from: shape) - nodesMap.replace(node: shape, to: mutatingShape) - animationCache?.replace(original: shape, replacement: mutatingShape) - animation.nodeId = mutatingShape.id + renderer.replaceNode(with: mutatingShape) + animation.node = mutatingShape let fromLocus = morphingAnimation.getVFunc()(0.0) let toLocus = morphingAnimation.getVFunc()(animation.autoreverses ? 0.5 : 1.0) let duration = animation.autoreverses ? animation.getDuration() / 2.0 : animation.getDuration() - guard let layer = animationCache?.layerForNode(mutatingShape, animation: animation, shouldRenderContent: false) else { + guard let layer = animationCache?.layerForNodeRenderer(renderer, animation: animation, shouldRenderContent: false) else { return } // Creating proper animation @@ -56,7 +55,7 @@ func addMorphingAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, anim mutatingShape.form = morphingAnimation.getVFunc()(1.0) } - animationCache?.freeLayer(mutatingShape) + animationCache?.freeLayer(renderer) if !animation.cycled && !animation.manualStop { diff --git a/Source/animation/types/animation_generators/OpacityGenerator.swift b/Source/animation/types/animation_generators/OpacityGenerator.swift index feec5c42..a66e93a2 100644 --- a/Source/animation/types/animation_generators/OpacityGenerator.swift +++ b/Source/animation/types/animation_generators/OpacityGenerator.swift @@ -11,7 +11,7 @@ func addOpacityAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, anima return } - guard let nodeId = animation.nodeId, let node = Node.nodeBy(id: nodeId) else { + guard let node = animation.node, let renderer = animation.nodeRenderer else { return } @@ -25,7 +25,7 @@ func addOpacityAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, anima generatedAnimation.completion = { finished in - animationCache?.freeLayer(node) + animationCache?.freeLayer(renderer) if animation.paused { animation.pausedProgress += animation.progress @@ -58,7 +58,7 @@ func addOpacityAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, anima animation.onProgressUpdate?(t) } - if let layer = animationCache?.layerForNode(node, animation: animation) { + if let renderer = animation.nodeRenderer, let layer = animationCache?.layerForNodeRenderer(renderer, animation: animation) { let animationId = animation.ID layer.add(generatedAnimation, forKey: animationId) animation.removeFunc = { [weak layer] in diff --git a/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift b/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift index 940c7c72..b242b5c5 100644 --- a/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift +++ b/Source/animation/types/animation_generators/ShapeAnimationGenerator.swift @@ -19,7 +19,7 @@ func addShapeAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animati return } - guard let nodeId = animation.nodeId, let shape = Node.nodeBy(id: nodeId) as? Shape else { + guard let shape = animation.node as? Shape, let renderer = animation.nodeRenderer else { return } @@ -28,10 +28,9 @@ func addShapeAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animati let duration = animation.autoreverses ? animation.getDuration() / 2.0 : animation.getDuration() let mutatingShape = SceneUtils.shapeCopy(from: fromShape) - nodesMap.replace(node: shape, to: mutatingShape) - animationCache?.replace(original: shape, replacement: mutatingShape) + renderer.replaceNode(with: mutatingShape) - guard let layer = animationCache?.layerForNode(mutatingShape, animation: animation, shouldRenderContent: false) else { + guard let layer = animationCache?.layerForNodeRenderer(renderer, animation: animation, shouldRenderContent: false) else { return } @@ -39,6 +38,7 @@ func addShapeAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animati let generatedAnim = generateShapeAnimation( from: mutatingShape, to: toShape, + animation: shapeAnimation, duration: duration, renderTransform: layer.renderTransform!) @@ -63,7 +63,7 @@ func addShapeAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animati mutatingShape.fill = fromShape.fill } - animationCache?.freeLayer(mutatingShape) + animationCache?.freeLayer(renderer) if !animation.cycled && !animation.manualStop { animation.completion?() @@ -127,7 +127,7 @@ func addShapeAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, animati } } -fileprivate func generateShapeAnimation(from: Shape, to: Shape, duration: Double, renderTransform: CGAffineTransform) -> CAAnimation { +fileprivate func generateShapeAnimation(from: Shape, to: Shape, animation: ShapeAnimation, duration: Double, renderTransform: CGAffineTransform) -> CAAnimation { let group = CAAnimationGroup() @@ -147,9 +147,11 @@ fileprivate func generateShapeAnimation(from: Shape, to: Shape, duration: Double // Transform let scaleAnimation = CABasicAnimation(keyPath: "transform") scaleAnimation.duration = duration - let view = nodesMap.getView(from) - scaleAnimation.fromValue = CATransform3DMakeAffineTransform(AnimationUtils.absolutePosition(from, view: view).toCG()) - scaleAnimation.toValue = CATransform3DMakeAffineTransform(AnimationUtils.absolutePosition(to, view: view).toCG()) + let parentPos = AnimationUtils.absolutePosition(animation.nodeRenderer?.parentRenderer) + let fromPos = parentPos.concat(with: to.place) + let toPos = parentPos.concat(with: to.place) + scaleAnimation.fromValue = CATransform3DMakeAffineTransform(fromPos.toCG()) + scaleAnimation.toValue = CATransform3DMakeAffineTransform(toPos.toCG()) group.animations?.append(scaleAnimation) diff --git a/Source/animation/types/animation_generators/TransformGenerator.swift b/Source/animation/types/animation_generators/TransformGenerator.swift index 30aecc28..59d26cb7 100644 --- a/Source/animation/types/animation_generators/TransformGenerator.swift +++ b/Source/animation/types/animation_generators/TransformGenerator.swift @@ -11,7 +11,7 @@ func addTransformAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, ani return } - guard let nodeId = animation.nodeId, let node = Node.nodeBy(id: nodeId) else { + guard let node = animation.node, let renderer = animation.nodeRenderer else { return } @@ -51,7 +51,7 @@ func addTransformAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, ani node.placeVar.value = transformAnimation.getVFunc()(1.0) } - animationCache?.freeLayer(node) + animationCache?.freeLayer(renderer) if !animation.cycled && !animation.manualStop && @@ -71,7 +71,7 @@ func addTransformAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, ani animation.onProgressUpdate?(t) } - if let layer = animationCache?.layerForNode(node, animation: animation) { + if let renderer = animation.nodeRenderer, let layer = animationCache?.layerForNodeRenderer(renderer, animation: animation) { let animationId = animation.ID layer.add(generatedAnim, forKey: animationId) animation.removeFunc = { [weak layer] in @@ -83,14 +83,13 @@ func addTransformAnimation(_ animation: BasicAnimation, sceneLayer: CALayer, ani func transformAnimationByFunc(_ animation: TransformAnimation, node: Node, duration: Double, offset: Double, fps: UInt) -> CAAnimation { let valueFunc = animation.getVFunc() - let view = nodesMap.getView(node) if let trajectory = animation.trajectory { let pathAnimation = CAKeyframeAnimation(keyPath: "position") pathAnimation.timingFunction = caTimingFunction(animation.easing) pathAnimation.duration = duration / 2 pathAnimation.autoreverses = animation.autoreverses - let value = AnimationUtils.absoluteTransform(node, pos: valueFunc(0), view: view) + let value = AnimationUtils.absoluteTransform(animation.nodeRenderer, pos: valueFunc(0)) pathAnimation.values = [NSValue(caTransform3D: CATransform3DMakeAffineTransform(value.toCG()))] pathAnimation.fillMode = MCAMediaTimingFillMode.forwards pathAnimation.isRemovedOnCompletion = false @@ -105,7 +104,7 @@ func transformAnimationByFunc(_ animation: TransformAnimation, node: Node, durat tValue.append(1.0) for t in tValue { let progress = animation.easing.progressFor(time: t) - let value = AnimationUtils.absoluteTransform(node, pos: valueFunc(offset + progress), view: view) + let value = AnimationUtils.absoluteTransform(animation.nodeRenderer, pos: valueFunc(offset + progress)) let cgValue = CATransform3DMakeAffineTransform(value.toCG()) transformValues.append(cgValue) } diff --git a/Source/model/scene/Group.swift b/Source/model/scene/Group.swift index 4b16d40a..5b376565 100644 --- a/Source/model/scene/Group.swift +++ b/Source/model/scene/Group.swift @@ -4,22 +4,7 @@ open class Group: Node { open var contents: [Node] { get { return contentsVar.value } set(val) { - - contentsVar.value.forEach { subNode in - nodesMap.remove(subNode) - } - contentsVar.value = val - - if let view = nodesMap.getView(self) { - val.forEach { subNode in - nodesMap.add(subNode, view: view) - } - } - - val.forEach { subNode in - nodesMap.add(subNode, parent: self) - } } } @@ -40,35 +25,6 @@ open class Group: Node { self.contentsVar.node = self } - // Searching - - override public func nodeBy(tag: String) -> Node? { - if let node = super.nodeBy(tag: tag) { - return node - } - - for child in contents { - if let node = child.nodeBy(tag: tag) { - return node - } - } - - return .none - } - - override public func nodesBy(tag: String) -> [Node] { - var result = [Node]() - contents.forEach { child in - result.append(contentsOf: child.nodesBy(tag: tag)) - } - - if let node = super.nodeBy(tag: tag) { - result.append(node) - } - - return result - } - override open var bounds: Rect? { return BoundsUtils.getNodesBounds(contents) } diff --git a/Source/model/scene/Node.swift b/Source/model/scene/Node.swift index 45836d35..043def80 100644 --- a/Source/model/scene/Node.swift +++ b/Source/model/scene/Node.swift @@ -38,34 +38,7 @@ open class Node: Drawable { set(val) { effectVar.value = val } } - internal var id: String { - didSet { - Node.map.removeObject(forKey: id as NSString) - Node.map.setObject(self, forKey: id as NSString) - } - } - - // MARK: - ID map - private static let map = NSMapTable(keyOptions: NSMapTableStrongMemory, valueOptions: NSMapTableWeakMemory) - - public static func nodeBy(id: String) -> Node? { - return Node.map.object(forKey: id as NSString) - } - - // MARK: - Searching - public func nodeBy(tag: String) -> Node? { - if self.tag.contains(tag) { - return self - } - - return .none - } - - public func nodesBy(tag: String) -> [Node] { - return [nodeBy(tag: tag)].compactMap { $0 } - } - - // MARK: - Events + internal var animationObservers = [AnimationObserver]() var touchPressedHandlers = [ChangeHandler]() var touchMovedHandlers = [ChangeHandler]() @@ -94,7 +67,7 @@ open class Node: Drawable { } } - @discardableResult public func onTouchMoved (_ f: @escaping (TouchEvent) -> Void) -> Disposable { + @discardableResult public func onTouchMoved(_ f: @escaping (TouchEvent) -> Void) -> Disposable { let handler = ChangeHandler(f) touchMovedHandlers.append(handler) @@ -307,7 +280,6 @@ open class Node: Drawable { self.clipVar = Variable(clip) self.maskVar = Variable(mask) self.effectVar = Variable(effect) - self.id = NSUUID().uuidString super.init( visible: visible, @@ -315,8 +287,6 @@ open class Node: Drawable { ) self.placeVar.node = self self.opacityVar.node = self - - Node.map.setObject(self, forKey: self.id as NSString) } open var bounds: Rect? { diff --git a/Source/render/GroupRenderer.swift b/Source/render/GroupRenderer.swift index b5088fc8..80ed6598 100644 --- a/Source/render/GroupRenderer.swift +++ b/Source/render/GroupRenderer.swift @@ -7,13 +7,10 @@ import UIKit class GroupRenderer: NodeRenderer { weak var group: Group? + var renderers: [NodeRenderer] = [] - fileprivate var renderers: [NodeRenderer] = [] - let renderingInterval: RenderingInterval? - - init(group: Group, view: MView?, animationCache: AnimationCache?, interval: RenderingInterval? = .none) { + init(group: Group, view: MView?, animationCache: AnimationCache?) { self.group = group - self.renderingInterval = interval super.init(node: group, view: view, animationCache: animationCache) updateRenderers() } @@ -54,6 +51,16 @@ class GroupRenderer: NodeRenderer { return nil } + override func doFindAllNodesAt(location: CGPoint, ctx: CGContext) -> [Node]? { + for renderer in renderers.reversed() { + if var nodes = renderer.findAllNodesAt(location: location, ctx: ctx), let node = node() { + nodes.append(node) + return nodes + } + } + return nil + } + override func dispose() { super.dispose() renderers.forEach { renderer in renderer.dispose() } @@ -65,19 +72,19 @@ class GroupRenderer: NodeRenderer { renderers.removeAll() if let updatedRenderers = group?.contents.compactMap ({ child -> NodeRenderer? in - guard let interval = renderingInterval else { - return RenderUtils.createNodeRenderer(child, view: view, animationCache: animationCache) - } - - let index = AnimationUtils.absoluteIndex(child, useCache: true) - if index > interval.from && index < interval.to { - return RenderUtils.createNodeRenderer(child, view: view, animationCache: animationCache, interval: interval) - } - - return .none - + let childRenderer = RenderUtils.createNodeRenderer(child, view: view, animationCache: animationCache) + childRenderer.parentRenderer = self + return childRenderer }) { renderers = updatedRenderers } } + + 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 b81e9005..a66a195a 100644 --- a/Source/render/ImageRenderer.swift +++ b/Source/render/ImageRenderer.swift @@ -77,4 +77,12 @@ 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 b17287ab..c824bfc2 100644 --- a/Source/render/NodeRenderer.swift +++ b/Source/render/NodeRenderer.swift @@ -6,18 +6,15 @@ import UIKit import AppKit #endif -struct RenderingInterval { - let from: Int - let to: Int -} - enum ColoringMode { case rgb, greyscale, alphaOnly } -class NodeRenderer { +class NodeRenderer{ weak var view: MView? + weak var parentRenderer: NodeRenderer? + internal var zPosition: Int = 0 fileprivate let onNodeChange: () -> Void fileprivate let disposables = GroupDisposable() @@ -57,6 +54,8 @@ class NodeRenderer { observe(node.opacityVar) observe(node.clipVar) observe(node.effectVar) + + node.animationObservers.append(self) } func observe(_ v: Variable) { @@ -73,6 +72,7 @@ class NodeRenderer { open func dispose() { removeObservers() + node()?.animationObservers = node()?.animationObservers.filter{ !($0 as? NodeRenderer === self) } ?? [] } open func node() -> Node? { @@ -255,10 +255,62 @@ class NodeRenderer { return nil } + public final func findAllNodesAt(location: CGPoint, ctx: CGContext) -> [Node]? { + guard let node = node() 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 = location.applying(inverted.toCG()) + let result = doFindAllNodesAt(location: CGPoint(x: loc.x, y: loc.y), ctx: ctx) + return result + } + } + return nil + } + public func doFindNodeAt(location: CGPoint, ctx: CGContext) -> Node? { return nil } + public func doFindAllNodesAt(location: CGPoint, ctx: CGContext) -> [Node]? { + if let node = doFindNodeAt(location: location, ctx: ctx) { + return [node] + } + 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.index(of: node) { + contents.remove(at: index) + indexToInsert = index + } + + contents.insert(replacementNode, at: indexToInsert) + group.contents = contents + } + + if let hostingView = view as? MacawView, hostingView.node == node { + hostingView.node = replacementNode + } + } + private func applyClip(in context: CGContext) { guard let node = node() else { return @@ -324,3 +376,14 @@ class NodeRenderer { } } } + +protocol AnimationObserver { + func processAnimation(_ animation: BasicAnimation) +} + +extension NodeRenderer: AnimationObserver { + func processAnimation(_ animation: BasicAnimation) { + animation.nodeRenderer = self + } + +} diff --git a/Source/render/RenderUtils.swift b/Source/render/RenderUtils.swift index 1ba2fc6a..ac78baaa 100644 --- a/Source/render/RenderUtils.swift +++ b/Source/render/RenderUtils.swift @@ -19,11 +19,10 @@ class RenderUtils { class func createNodeRenderer( _ node: Node, view: MView?, - animationCache: AnimationCache?, - interval: RenderingInterval? = .none + animationCache: AnimationCache? ) -> NodeRenderer { if let group = node as? Group { - return GroupRenderer(group: group, view: view, animationCache: animationCache, interval: interval) + return GroupRenderer(group: group, view: view, animationCache: animationCache) } else if let shape = node as? Shape { return ShapeRenderer(shape: shape, view: view, animationCache: animationCache) } else if let text = node as? Text { diff --git a/Source/render/ShapeRenderer.swift b/Source/render/ShapeRenderer.swift index eba52fbf..d703b9ba 100644 --- a/Source/render/ShapeRenderer.swift +++ b/Source/render/ShapeRenderer.swift @@ -217,6 +217,14 @@ 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 30716354..6e34e88d 100644 --- a/Source/render/TextRenderer.swift +++ b/Source/render/TextRenderer.swift @@ -161,4 +161,12 @@ 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 94a43df3..a5a47fe5 100644 --- a/Source/views/MacawView.swift +++ b/Source/views/MacawView.swift @@ -13,13 +13,8 @@ open class MacawView: MView, MGestureRecognizerDelegate { /// Scene root node open var node: Node = Group() { - willSet { - nodesMap.remove(node) - } - didSet { layoutHelper.nodeChanged() - nodesMap.add(node, view: self) self.renderer?.dispose() if let cache = animationCache { self.renderer = RenderUtils.createNodeRenderer(node, view: self, animationCache: cache) @@ -115,7 +110,6 @@ open class MacawView: MView, MGestureRecognizerDelegate { initializeView() self.node = node - nodesMap.add(node, view: self) if let cache = self.animationCache { self.renderer = RenderUtils.createNodeRenderer(node, view: self, animationCache: cache) } @@ -126,7 +120,6 @@ open class MacawView: MView, MGestureRecognizerDelegate { self.init(frame: frame) self.node = node - nodesMap.add(node, view: self) if let cache = self.animationCache { self.renderer = RenderUtils.createNodeRenderer(node, view: self, animationCache: cache) } @@ -185,6 +178,8 @@ open class MacawView: MView, MGestureRecognizerDelegate { } override open func draw(_ rect: CGRect) { + calculateZPosition(renderer) + context.cgContext = MGraphicsGetCurrentContext() guard let ctx = context.cgContext else { return @@ -197,16 +192,17 @@ open class MacawView: MView, MGestureRecognizerDelegate { guard let renderer = renderer else { return } - ctx.concatenate(layoutHelper.getTransform(node, contentLayout, bounds.size.toMacaw())) + ctx.concatenate(layoutHelper.getTransform(renderer, contentLayout, bounds.size.toMacaw())) renderer.render(in: ctx, force: false, opacity: node.opacity) } - private func localContext( _ callback: (CGContext) -> Void) { - MGraphicsBeginImageContextWithOptions(self.bounds.size, false, 1.0) - if let ctx = MGraphicsGetCurrentContext() { - callback(ctx) + private func calculateZPosition(_ nodeRenderer: NodeRenderer?, currentIndex: Int = 0) { + nodeRenderer?.zPosition = currentIndex + if let groupRenderer = nodeRenderer as? GroupRenderer { + for (i, child) in groupRenderer.renderers.enumerated() { + calculateZPosition(child, currentIndex: currentIndex + i + 1) + } } - MGraphicsEndImageContext() } public final func findNodeAt(location: CGPoint) -> Node? { @@ -224,12 +220,26 @@ open class MacawView: MView, MGestureRecognizerDelegate { defer { ctx.restoreGState() } - let transform = layoutHelper.getTransform(node, contentLayout, bounds.size.toMacaw()) + let transform = layoutHelper.getTransform(renderer, contentLayout, bounds.size.toMacaw()) ctx.concatenate(transform) let loc = location.applying(transform.inverted()) return renderer.findNodeAt(location: loc, ctx: ctx) } + private func doFindAllNodes(location: CGPoint, ctx: CGContext) -> [Node]? { + guard let renderer = renderer else { + return .none + } + ctx.saveGState() + defer { + ctx.restoreGState() + } + let transform = layoutHelper.getTransform(renderer, contentLayout, bounds.size.toMacaw()) + ctx.concatenate(transform) + let loc = location.applying(transform.inverted()) + return renderer.findAllNodesAt(location: loc, ctx: ctx) + } + // MARK: - Touches override func mTouchesBegan(_ touches: [MTouchEvent]) { @@ -246,36 +256,29 @@ open class MacawView: MView, MGestureRecognizerDelegate { for touch in touches { let location = CGPoint(x: touch.x, y: touch.y) - var foundNode: Node? = .none - localContext { ctx in - foundNode = doFindNode(location: location, ctx: ctx) + guard let foundNodes = findTappedNodes(location: location) else { + return } - if let node = foundNode { - if touchesMap[touch] == nil { - touchesMap[touch] = [Node]() - } - - let inverted = node.place.invert()! - let loc = location.applying(inverted.toCG()) - - let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque()) - let point = TouchPoint(id: id, location: Point(x: Double(loc.x), y: Double(loc.y))) - let touchEvent = TouchEvent(node: node, points: [point]) + if touchesMap[touch] == nil { + touchesMap[touch] = [Node]() + } - var parent: Node? = node - while parent != .none { - let currentNode = parent! - if touchesOfNode[currentNode] == nil { - touchesOfNode[currentNode] = [MTouchEvent]() - } + let inverted = node.place.invert()! + let loc = location.applying(inverted.toCG()) - touchesMap[touch]?.append(currentNode) - touchesOfNode[currentNode]?.append(touch) - parent!.handleTouchPressed(touchEvent) + let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque()) + let point = TouchPoint(id: id, location: Point(x: Double(loc.x), y: Double(loc.y))) + let touchEvent = TouchEvent(node: node, points: [point]) - parent = nodesMap.parents(parent!).first + for foundNode in foundNodes { + if touchesOfNode[foundNode] == nil { + touchesOfNode[foundNode] = [MTouchEvent]() } + + touchesMap[touch]?.append(foundNode) + touchesOfNode[foundNode]?.append(touch) + foundNode.handleTouchPressed(touchEvent) } } } @@ -363,21 +366,8 @@ open class MacawView: MView, MGestureRecognizerDelegate { } let location = recognizer.location(in: self) - var foundNodes = [Node]() - - localContext { ctx in - guard let foundNode = doFindNode(location: location, ctx: ctx) else { - return - } - - var parent: Node? = foundNode - while parent != .none { - if parent!.shouldCheckForTap() { - foundNodes.append(parent!) - } - - parent = nodesMap.parents(parent!).first - } + guard let foundNodes = findTappedNodes(location: location) else { + return } foundNodes.forEach { node in @@ -400,21 +390,8 @@ open class MacawView: MView, MGestureRecognizerDelegate { } let location = recognizer.location(in: self) - var foundNodes = [Node]() - - localContext { ctx in - guard let foundNode = doFindNode(location: location, ctx: ctx) else { - return - } - - var parent: Node? = foundNode - while parent != .none { - if parent!.shouldCheckForTap() { - foundNodes.append(parent!) - } - - parent = nodesMap.parents(parent!).first - } + guard let foundNodes = findTappedNodes(location: location) else { + return } foundNodes.forEach { node in @@ -425,6 +402,18 @@ open class MacawView: MView, MGestureRecognizerDelegate { } } + func findTappedNodes(location: CGPoint) -> [Node]? { + MGraphicsBeginImageContextWithOptions(self.bounds.size, false, 1.0) + if let ctx = MGraphicsGetCurrentContext() { + guard let foundNodes = doFindAllNodes(location: location, ctx: ctx) else { + return nil + } + return foundNodes + } + MGraphicsEndImageContext() + return nil + } + // MARK: - Pan @objc func handlePan(recognizer: MPanGestureRecognizer) { @@ -438,23 +427,17 @@ open class MacawView: MView, MGestureRecognizerDelegate { if recognizer.state == .began { let location = recognizer.location(in: self) + guard let foundNodes = findTappedNodes(location: location) else { + return + } - localContext { ctx in - guard let foundNode = doFindNode(location: location, ctx: ctx) else { - return - } - - if self.recognizersMap[recognizer] == nil { - self.recognizersMap[recognizer] = [Node]() - } - - var parent: Node? = foundNode - while parent != .none { - if parent!.shouldCheckForPan() { - self.recognizersMap[recognizer]?.append(parent!) - } + if self.recognizersMap[recognizer] == nil { + self.recognizersMap[recognizer] = [Node]() + } - parent = nodesMap.parents(parent!).first + for foundNode in foundNodes { + if foundNode.shouldCheckForPan() { + self.recognizersMap[recognizer]?.append(foundNode) } } } @@ -492,23 +475,17 @@ open class MacawView: MView, MGestureRecognizerDelegate { if recognizer.state == .began { let location = recognizer.location(in: self) + guard let foundNodes = findTappedNodes(location: location) else { + return + } - localContext { ctx in - guard let foundNode = doFindNode(location: location, ctx: ctx) else { - return - } - - if self.recognizersMap[recognizer] == nil { - self.recognizersMap[recognizer] = [Node]() - } - - var parent: Node? = foundNode - while parent != .none { - if parent!.shouldCheckForRotate() { - self.recognizersMap[recognizer]?.append(parent!) - } + if self.recognizersMap[recognizer] == nil { + self.recognizersMap[recognizer] = [Node]() + } - parent = nodesMap.parents(parent!).first + for foundNode in foundNodes { + if foundNode.shouldCheckForRotate() { + self.recognizersMap[recognizer]?.append(foundNode) } } } @@ -539,23 +516,17 @@ open class MacawView: MView, MGestureRecognizerDelegate { if recognizer.state == .began { let location = recognizer.location(in: self) + guard let foundNodes = findTappedNodes(location: location) else { + return + } - localContext { ctx in - guard let foundNode = doFindNode(location: location, ctx: ctx) else { - return - } - - if self.recognizersMap[recognizer] == nil { - self.recognizersMap[recognizer] = [Node]() - } - - var parent: Node? = foundNode - while parent != .none { - if parent!.shouldCheckForPinch() { - self.recognizersMap[recognizer]?.append(parent!) - } + if self.recognizersMap[recognizer] == nil { + self.recognizersMap[recognizer] = [Node]() + } - parent = nodesMap.parents(parent!).first + for foundNode in foundNodes { + if foundNode.shouldCheckForPinch() { + self.recognizersMap[recognizer]?.append(foundNode) } } } @@ -573,10 +544,6 @@ open class MacawView: MView, MGestureRecognizerDelegate { } } - deinit { - nodesMap.remove(node) - } - // MARK: - MGestureRecognizerDelegate public func gestureRecognizer(_ gestureRecognizer: MGestureRecognizer, shouldReceive touch: MTouch) -> Bool { @@ -594,11 +561,12 @@ private class LayoutHelper { private var prevRect: Rect? private var prevTransform: CGAffineTransform? - public func getTransform(_ node: Node, _ layout: ContentLayout, _ size: Size) -> CGAffineTransform { + public func getTransform(_ nodeRenderer: NodeRenderer, _ layout: ContentLayout, _ size: Size) -> CGAffineTransform { setSize(size: size) - var rect = node.bounds + let node = nodeRenderer.node() + var rect = node?.bounds if let canvas = node as? SVGCanvas { - if let view = nodesMap.getView(canvas) { + if let view = nodeRenderer.view { rect = canvas.layout(size: view.bounds.size.toMacaw()).rect() } else { rect = BoundsUtils.getNodesBounds(canvas.contents) diff --git a/Source/views/NodesMap.swift b/Source/views/NodesMap.swift deleted file mode 100644 index 5207b6e3..00000000 --- a/Source/views/NodesMap.swift +++ /dev/null @@ -1,103 +0,0 @@ -import Foundation - -#if os(iOS) -import UIKit -#endif - -let nodesMap = NodesMap() -var parentsMap = [Node: Set]() - -class NodesMap { - let map = NSMapTable(keyOptions: NSMapTableWeakMemory, valueOptions: NSMapTableWeakMemory) - - // MARK: - Macaw View - func add(_ node: Node, view: MacawView) { - map.setObject(view, forKey: node) - - if let group = node as? Group { - group.contents.forEach { child in - self.add(child, view: view) - self.add(child, parent: node) - } - } - } - - func getView(_ node: Node) -> MacawView? { - return map.object(forKey: node) - } - - func remove(_ node: Node) { - map.removeObject(forKey: node) - parentsMap.removeValue(forKey: node) - - if let group = node as? Group { - group.contents.forEach { child in - self.remove(child) - } - } - } - - // MARK: - Parents - func add(_ node: Node, parent: Node) { - if var nodesSet = parentsMap[node] { - nodesSet.insert(parent) - } else { - parentsMap[node] = Set([parent]) - } - - if let group = node as? Group { - group.contents.forEach { child in - self.add(child, parent: node) - } - } - } - - func parents(_ node: Node) -> [Node] { - guard let nodesSet = parentsMap[node] else { - return [] - } - - return Array(nodesSet) - } - - func replace(node: Node, to: Node) { - let parents = parentsMap[node] - let hostingView = map.object(forKey: node) - - remove(node) - - parents?.forEach { parent in - guard let group = parent as? Group else { - return - } - - var contents = group.contents - var indexToInsert = 0 - if let index = contents.index(of: node) { - contents.remove(at: index) - indexToInsert = index - } - - contents.insert(to, at: indexToInsert) - group.contents = contents - - add(to, parent: parent) - } - - if let view = hostingView { - add(to, view: view) - } - - // Replacing node in hosting view if needed - guard let hostingNode = hostingView?.node else { - return - } - - if hostingNode == node { - hostingView?.node = to - } - - // Replacing id - to.id = node.id - } -} diff --git a/Source/views/ShapeLayer.swift b/Source/views/ShapeLayer.swift index 87065bc5..95a10e6e 100644 --- a/Source/views/ShapeLayer.swift +++ b/Source/views/ShapeLayer.swift @@ -8,7 +8,6 @@ import AppKit class ShapeLayer: CAShapeLayer { weak var node: Node? - var renderingInterval: RenderingInterval? var renderTransform: CGAffineTransform? weak var animationCache: AnimationCache? var shouldRenderContent = true @@ -35,7 +34,7 @@ class ShapeLayer: CAShapeLayer { ctx.concatenate(renderTransform) } - let renderer = RenderUtils.createNodeRenderer(node, view: renderContext.view, animationCache: animationCache, interval: renderingInterval) + let renderer = RenderUtils.createNodeRenderer(node, view: renderContext.view, animationCache: animationCache) renderer.directRender(in: ctx, force: isForceRenderingEnabled) renderer.dispose() } From 149332e6e6a9d2f3f30f6030f7a1a064a2c6b29d Mon Sep 17 00:00:00 2001 From: Alisa Mylnikova Date: Fri, 2 Nov 2018 13:58:40 +0700 Subject: [PATCH 2/5] Fix z positions test --- .../Animation/AnimationUtilsTests.swift | 45 ++++++++++++------- .../Cache/AnimationCache.swift | 4 +- Source/render/NodeRenderer.swift | 4 +- Source/views/MacawView.swift | 9 ++-- 4 files changed, 38 insertions(+), 24 deletions(-) diff --git a/MacawTests/Animation/AnimationUtilsTests.swift b/MacawTests/Animation/AnimationUtilsTests.swift index 307e4e41..b776b2c6 100644 --- a/MacawTests/Animation/AnimationUtilsTests.swift +++ b/MacawTests/Animation/AnimationUtilsTests.swift @@ -13,27 +13,38 @@ class AnimationUtilsTests: XCTestCase { func testIndex() { let rootGroup = Group() - let a = Node() + let a = Shape(form: Locus()) rootGroup.contents.append(a) - let aGroup = Group() - let b = Node() - let c = Node() - aGroup.contents.append(b) - aGroup.contents.append(c) - rootGroup.contents.append(aGroup) + let bGroup = Group() + let c = Shape(form: Locus()) + let d = Shape(form: Locus()) + bGroup.contents.append(c) + bGroup.contents.append(d) + rootGroup.contents.append(bGroup) - let d = Node() - let e = Node() - rootGroup.contents.append(d) + let e = Shape(form: Locus()) + let f = Shape(form: Locus()) rootGroup.contents.append(e) + rootGroup.contents.append(f) + + let view = MacawView() + view.node = rootGroup + view.draw(CGRect(x: 0, y: 0, width: 100, height: 100)) + let rootRenderer = view.renderer as? GroupRenderer + let aRenderer = rootRenderer?.renderers[0] + let bRenderer = rootRenderer?.renderers[1] as? GroupRenderer + let cRenderer = bRenderer?.renderers[0] + let dRenderer = bRenderer?.renderers[1] + let eRenderer = rootRenderer?.renderers[2] + let fRenderer = rootRenderer?.renderers[3] - XCTAssert(AnimationUtils.absoluteIndex(rootGroup) == 0) - XCTAssert(AnimationUtils.absoluteIndex(a) == 1) - XCTAssert(AnimationUtils.absoluteIndex(aGroup) == 2) - XCTAssert(AnimationUtils.absoluteIndex(b) == 3) - XCTAssert(AnimationUtils.absoluteIndex(c) == 4 ) - XCTAssert(AnimationUtils.absoluteIndex(d) == 5 ) - XCTAssert(AnimationUtils.absoluteIndex(e) == 6 ) + XCTAssert(rootRenderer?.zPosition == 0) + XCTAssert(aRenderer?.zPosition == 1) + XCTAssert(bRenderer?.zPosition == 2) + XCTAssert(cRenderer?.zPosition == 3) + XCTAssert(dRenderer?.zPosition == 4 ) + XCTAssert(eRenderer?.zPosition == 5 ) + XCTAssert(fRenderer?.zPosition == 6 ) } } diff --git a/Source/animation/types/animation_generators/Cache/AnimationCache.swift b/Source/animation/types/animation_generators/Cache/AnimationCache.swift index adc8ce89..b000f92b 100644 --- a/Source/animation/types/animation_generators/Cache/AnimationCache.swift +++ b/Source/animation/types/animation_generators/Cache/AnimationCache.swift @@ -32,7 +32,7 @@ class AnimationCache { return ShapeLayer() } - if let cachedLayer = layerCache[renderer] { + if let cachedLayer = layerCache[renderer] { cachedLayer.linksCounter += 1 return cachedLayer.layer } @@ -148,7 +148,7 @@ class AnimationCache { func isAnimating(_ node: Node) -> Bool { - let renderer = layerCache.keys.filter{ $0.node() === node }.first + let renderer = layerCache.keys.filter { $0.node() === node }.first if let renderer = renderer, let _ = layerCache[renderer] { return true } diff --git a/Source/render/NodeRenderer.swift b/Source/render/NodeRenderer.swift index c824bfc2..edaa3de3 100644 --- a/Source/render/NodeRenderer.swift +++ b/Source/render/NodeRenderer.swift @@ -10,7 +10,7 @@ enum ColoringMode { case rgb, greyscale, alphaOnly } -class NodeRenderer{ +class NodeRenderer { weak var view: MView? weak var parentRenderer: NodeRenderer? @@ -72,7 +72,7 @@ class NodeRenderer{ open func dispose() { removeObservers() - node()?.animationObservers = node()?.animationObservers.filter{ !($0 as? NodeRenderer === self) } ?? [] + node()?.animationObservers = node()?.animationObservers.filter { !($0 as? NodeRenderer === self) } ?? [] } open func node() -> Node? { diff --git a/Source/views/MacawView.swift b/Source/views/MacawView.swift index a5a47fe5..3eef807a 100644 --- a/Source/views/MacawView.swift +++ b/Source/views/MacawView.swift @@ -196,13 +196,16 @@ open class MacawView: MView, MGestureRecognizerDelegate { renderer.render(in: ctx, force: false, opacity: node.opacity) } - private func calculateZPosition(_ nodeRenderer: NodeRenderer?, currentIndex: Int = 0) { + private func calculateZPosition(_ nodeRenderer: NodeRenderer?, currentIndex: Int = 0) -> Int { nodeRenderer?.zPosition = currentIndex if let groupRenderer = nodeRenderer as? GroupRenderer { - for (i, child) in groupRenderer.renderers.enumerated() { - calculateZPosition(child, currentIndex: currentIndex + i + 1) + var i = currentIndex + 1 + for child in groupRenderer.renderers { + i = calculateZPosition(child, currentIndex: i) } + return i } + return currentIndex + 1 } public final func findNodeAt(location: CGPoint) -> Node? { From 3d875f731ed8974cecb2af4305b5d54fee4614ab Mon Sep 17 00:00:00 2001 From: Alisa Mylnikova Date: Fri, 2 Nov 2018 15:02:07 +0700 Subject: [PATCH 3/5] Restore tag search --- Source/model/scene/Group.swift | 29 +++++++++++++++++++++++++++++ Source/model/scene/Node.swift | 14 ++++++++++++++ Source/views/MacawView.swift | 2 +- 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/Source/model/scene/Group.swift b/Source/model/scene/Group.swift index 5b376565..bf7e5290 100644 --- a/Source/model/scene/Group.swift +++ b/Source/model/scene/Group.swift @@ -25,6 +25,35 @@ open class Group: Node { self.contentsVar.node = self } + // Searching + + override public func nodeBy(tag: String) -> Node? { + if let node = super.nodeBy(tag: tag) { + return node + } + + for child in contents { + if let node = child.nodeBy(tag: tag) { + return node + } + } + + return .none + } + + override public func nodesBy(tag: String) -> [Node] { + var result = [Node]() + contents.forEach { child in + result.append(contentsOf: child.nodesBy(tag: tag)) + } + + if let node = super.nodeBy(tag: tag) { + result.append(node) + } + + return result + } + override open var bounds: Rect? { return BoundsUtils.getNodesBounds(contents) } diff --git a/Source/model/scene/Node.swift b/Source/model/scene/Node.swift index 043def80..845bcc60 100644 --- a/Source/model/scene/Node.swift +++ b/Source/model/scene/Node.swift @@ -38,6 +38,20 @@ open class Node: Drawable { set(val) { effectVar.value = val } } + // MARK: - Searching + public func nodeBy(tag: String) -> Node? { + if self.tag.contains(tag) { + return self + } + + return .none + } + + public func nodesBy(tag: String) -> [Node] { + return [nodeBy(tag: tag)].compactMap { $0 } + } + + // MARK: - Events internal var animationObservers = [AnimationObserver]() var touchPressedHandlers = [ChangeHandler]() diff --git a/Source/views/MacawView.swift b/Source/views/MacawView.swift index 3eef807a..7ebdf26c 100644 --- a/Source/views/MacawView.swift +++ b/Source/views/MacawView.swift @@ -196,7 +196,7 @@ open class MacawView: MView, MGestureRecognizerDelegate { renderer.render(in: ctx, force: false, opacity: node.opacity) } - private func calculateZPosition(_ nodeRenderer: NodeRenderer?, currentIndex: Int = 0) -> Int { + @discardableResult private func calculateZPosition(_ nodeRenderer: NodeRenderer?, currentIndex: Int = 0) -> Int { nodeRenderer?.zPosition = currentIndex if let groupRenderer = nodeRenderer as? GroupRenderer { var i = currentIndex + 1 From 5ea809f7b303846924066e15360540a4a9d962e6 Mon Sep 17 00:00:00 2001 From: Alisa Mylnikova Date: Wed, 7 Nov 2018 17:10:54 +0700 Subject: [PATCH 4/5] Little optimization --- Source/views/MacawView.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Source/views/MacawView.swift b/Source/views/MacawView.swift index 7ebdf26c..53c8d5c4 100644 --- a/Source/views/MacawView.swift +++ b/Source/views/MacawView.swift @@ -408,10 +408,7 @@ open class MacawView: MView, MGestureRecognizerDelegate { func findTappedNodes(location: CGPoint) -> [Node]? { MGraphicsBeginImageContextWithOptions(self.bounds.size, false, 1.0) if let ctx = MGraphicsGetCurrentContext() { - guard let foundNodes = doFindAllNodes(location: location, ctx: ctx) else { - return nil - } - return foundNodes + return doFindAllNodes(location: location, ctx: ctx) } MGraphicsEndImageContext() return nil From 4f23e4672af7f07415cc3a2bd0a7d04ab5086a0d Mon Sep 17 00:00:00 2001 From: Alisa Mylnikova Date: Fri, 9 Nov 2018 12:19:58 +0700 Subject: [PATCH 5/5] Code review improvements --- Example/Example.xcodeproj/project.pbxproj | 4 ++-- MacawTests/Animation/AnimationUtilsTests.swift | 12 +++++++----- Source/animation/AnimationProducer.swift | 2 +- Source/animation/AnimationUtils.swift | 2 +- Source/render/GroupRenderer.swift | 2 +- Source/render/ImageRenderer.swift | 2 +- Source/render/NodeRenderer.swift | 6 +++--- Source/render/RenderContext.swift | 4 ++-- Source/render/RenderUtils.swift | 2 +- Source/render/ShapeRenderer.swift | 2 +- Source/render/TextRenderer.swift | 2 +- 11 files changed, 21 insertions(+), 19 deletions(-) diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index 19bcb20f..04fc2ac4 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -498,7 +498,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -516,7 +516,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.exyte.Example.Example; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/MacawTests/Animation/AnimationUtilsTests.swift b/MacawTests/Animation/AnimationUtilsTests.swift index b776b2c6..a0eb175a 100644 --- a/MacawTests/Animation/AnimationUtilsTests.swift +++ b/MacawTests/Animation/AnimationUtilsTests.swift @@ -12,19 +12,21 @@ import XCTest class AnimationUtilsTests: XCTestCase { func testIndex() { + let shape = Circle(cx: 0, cy: 0, r: 10) + let rootGroup = Group() - let a = Shape(form: Locus()) + let a = Shape(form: shape) rootGroup.contents.append(a) let bGroup = Group() - let c = Shape(form: Locus()) - let d = Shape(form: Locus()) + let c = Shape(form: shape) + let d = Shape(form: shape) bGroup.contents.append(c) bGroup.contents.append(d) rootGroup.contents.append(bGroup) - let e = Shape(form: Locus()) - let f = Shape(form: Locus()) + let e = Shape(form: shape) + let f = Shape(form: shape) rootGroup.contents.append(e) rootGroup.contents.append(f) diff --git a/Source/animation/AnimationProducer.swift b/Source/animation/AnimationProducer.swift index 111e8aaa..a62fa26b 100644 --- a/Source/animation/AnimationProducer.swift +++ b/Source/animation/AnimationProducer.swift @@ -86,7 +86,7 @@ class AnimationProducer { break } - guard let macawView = animation.nodeRenderer?.view as? MacawView else { + guard let macawView = animation.nodeRenderer?.view else { storedAnimations[node] = animation return } diff --git a/Source/animation/AnimationUtils.swift b/Source/animation/AnimationUtils.swift index 4faa20cb..bda0bf91 100644 --- a/Source/animation/AnimationUtils.swift +++ b/Source/animation/AnimationUtils.swift @@ -11,7 +11,7 @@ class AnimationUtils { while parentRenderer != nil { if let canvas = parentRenderer?.node() as? SVGCanvas, - let view = parentRenderer?.view as? MacawView { + let view = parentRenderer?.view { let rect = canvas.layout(size: view.bounds.size.toMacaw()).rect() let canvasTransform = view.contentLayout.layout(rect: rect, into: view.bounds.size.toMacaw()).move(dx: rect.x, dy: rect.y) transform = canvasTransform.concat(with: transform) diff --git a/Source/render/GroupRenderer.swift b/Source/render/GroupRenderer.swift index 80ed6598..3f142867 100644 --- a/Source/render/GroupRenderer.swift +++ b/Source/render/GroupRenderer.swift @@ -9,7 +9,7 @@ class GroupRenderer: NodeRenderer { weak var group: Group? var renderers: [NodeRenderer] = [] - init(group: Group, view: MView?, animationCache: AnimationCache?) { + init(group: Group, view: MacawView?, animationCache: AnimationCache?) { self.group = group super.init(node: group, view: view, animationCache: animationCache) updateRenderers() diff --git a/Source/render/ImageRenderer.swift b/Source/render/ImageRenderer.swift index a66a195a..e187d30d 100644 --- a/Source/render/ImageRenderer.swift +++ b/Source/render/ImageRenderer.swift @@ -13,7 +13,7 @@ class ImageRenderer: NodeRenderer { var renderedPaths: [CGPath] = [CGPath]() - init(image: Image, view: MView?, animationCache: AnimationCache?) { + init(image: Image, view: MacawView?, animationCache: AnimationCache?) { self.image = image super.init(node: image, view: view, animationCache: animationCache) } diff --git a/Source/render/NodeRenderer.swift b/Source/render/NodeRenderer.swift index edaa3de3..e061a269 100644 --- a/Source/render/NodeRenderer.swift +++ b/Source/render/NodeRenderer.swift @@ -12,7 +12,7 @@ enum ColoringMode { class NodeRenderer { - weak var view: MView? + weak var view: MacawView? weak var parentRenderer: NodeRenderer? internal var zPosition: Int = 0 @@ -21,7 +21,7 @@ class NodeRenderer { fileprivate var active = false weak var animationCache: AnimationCache? - init(node: Node, view: MView?, animationCache: AnimationCache?) { + init(node: Node, view: MacawView?, animationCache: AnimationCache?) { self.view = view self.animationCache = animationCache @@ -306,7 +306,7 @@ class NodeRenderer { group.contents = contents } - if let hostingView = view as? MacawView, hostingView.node == node { + if let hostingView = view, hostingView.node == node { hostingView.node = replacementNode } } diff --git a/Source/render/RenderContext.swift b/Source/render/RenderContext.swift index e1020a0d..92c85092 100644 --- a/Source/render/RenderContext.swift +++ b/Source/render/RenderContext.swift @@ -5,10 +5,10 @@ import UIKit #endif class RenderContext { - weak var view: MView? + weak var view: MacawView? var cgContext: CGContext? - init(view: MView?) { + init(view: MacawView?) { self.view = view self.cgContext = nil } diff --git a/Source/render/RenderUtils.swift b/Source/render/RenderUtils.swift index ac78baaa..3e0d046c 100644 --- a/Source/render/RenderUtils.swift +++ b/Source/render/RenderUtils.swift @@ -18,7 +18,7 @@ class RenderUtils { class func createNodeRenderer( _ node: Node, - view: MView?, + view: MacawView?, animationCache: AnimationCache? ) -> NodeRenderer { if let group = node as? Group { diff --git a/Source/render/ShapeRenderer.swift b/Source/render/ShapeRenderer.swift index d703b9ba..364a70ea 100644 --- a/Source/render/ShapeRenderer.swift +++ b/Source/render/ShapeRenderer.swift @@ -10,7 +10,7 @@ class ShapeRenderer: NodeRenderer { weak var shape: Shape? - init(shape: Shape, view: MView?, animationCache: AnimationCache?) { + init(shape: Shape, view: MacawView?, animationCache: AnimationCache?) { self.shape = shape super.init(node: shape, view: view, animationCache: animationCache) } diff --git a/Source/render/TextRenderer.swift b/Source/render/TextRenderer.swift index 6e34e88d..9c7b4f49 100644 --- a/Source/render/TextRenderer.swift +++ b/Source/render/TextRenderer.swift @@ -9,7 +9,7 @@ import AppKit class TextRenderer: NodeRenderer { weak var text: Text? - init(text: Text, view: MView?, animationCache: AnimationCache?) { + init(text: Text, view: MacawView?, animationCache: AnimationCache?) { self.text = text super.init(node: text, view: view, animationCache: animationCache) }