mirror of
https://github.com/exyte/Macaw.git
synced 2024-09-11 05:05:23 +03:00
Add animation layer hierarchy
This commit is contained in:
parent
916240f118
commit
44dfb89d16
@ -16,6 +16,7 @@
|
|||||||
575129B61CBD14AF00BD3C2E /* AnimationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575129B51CBD14AF00BD3C2E /* AnimationsView.swift */; };
|
575129B61CBD14AF00BD3C2E /* AnimationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 575129B51CBD14AF00BD3C2E /* AnimationsView.swift */; };
|
||||||
57AF398C1E67E9DB00F0BFE2 /* EventsExampleController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57AF398B1E67E9DB00F0BFE2 /* EventsExampleController.swift */; };
|
57AF398C1E67E9DB00F0BFE2 /* EventsExampleController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57AF398B1E67E9DB00F0BFE2 /* EventsExampleController.swift */; };
|
||||||
58E4D50C1D841C6E00EC8815 /* TransformExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E4D50B1D841C6E00EC8815 /* TransformExampleView.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 */; };
|
5BAE3CB120C54E3D006BEF51 /* FiltersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAE3CB020C54E3D006BEF51 /* FiltersViewController.swift */; };
|
||||||
66AE19DB1CC8CB3C00B78B5E /* tiger.svg in Resources */ = {isa = PBXBuildFile; fileRef = 66AE19DA1CC8CB3C00B78B5E /* tiger.svg */; };
|
66AE19DB1CC8CB3C00B78B5E /* tiger.svg in Resources */ = {isa = PBXBuildFile; fileRef = 66AE19DA1CC8CB3C00B78B5E /* tiger.svg */; };
|
||||||
B02E75F11C16104900D1971D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B02E75F01C16104900D1971D /* AppDelegate.swift */; };
|
B02E75F11C16104900D1971D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B02E75F01C16104900D1971D /* AppDelegate.swift */; };
|
||||||
@ -50,6 +51,7 @@
|
|||||||
575129B51CBD14AF00BD3C2E /* AnimationsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationsView.swift; sourceTree = "<group>"; };
|
575129B51CBD14AF00BD3C2E /* AnimationsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationsView.swift; sourceTree = "<group>"; };
|
||||||
57AF398B1E67E9DB00F0BFE2 /* EventsExampleController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventsExampleController.swift; sourceTree = "<group>"; };
|
57AF398B1E67E9DB00F0BFE2 /* EventsExampleController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventsExampleController.swift; sourceTree = "<group>"; };
|
||||||
58E4D50B1D841C6E00EC8815 /* TransformExampleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformExampleView.swift; sourceTree = "<group>"; };
|
58E4D50B1D841C6E00EC8815 /* TransformExampleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformExampleView.swift; sourceTree = "<group>"; };
|
||||||
|
5B195EAB2276D5C40008AE8B /* AnimationsHierarchyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationsHierarchyViewController.swift; sourceTree = "<group>"; };
|
||||||
5BAE3CB020C54E3D006BEF51 /* FiltersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersViewController.swift; sourceTree = "<group>"; };
|
5BAE3CB020C54E3D006BEF51 /* FiltersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersViewController.swift; sourceTree = "<group>"; };
|
||||||
66AE19DA1CC8CB3C00B78B5E /* tiger.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = tiger.svg; path = Example/Assets/SVG/tiger.svg; sourceTree = "<group>"; };
|
66AE19DA1CC8CB3C00B78B5E /* tiger.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = tiger.svg; path = Example/Assets/SVG/tiger.svg; sourceTree = "<group>"; };
|
||||||
B02E75ED1C16104900D1971D /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
B02E75ED1C16104900D1971D /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@ -111,6 +113,7 @@
|
|||||||
5747F9BB1E38B660004E338F /* Morphing */,
|
5747F9BB1E38B660004E338F /* Morphing */,
|
||||||
57AF398A1E67E86400F0BFE2 /* Events */,
|
57AF398A1E67E86400F0BFE2 /* Events */,
|
||||||
5BAE3CAF20C54DA5006BEF51 /* Filters */,
|
5BAE3CAF20C54DA5006BEF51 /* Filters */,
|
||||||
|
5BE9F15D226EEAE500030967 /* AnimationsHierarchy */,
|
||||||
574EC4401CB7E2440063F317 /* MenuViewController.swift */,
|
574EC4401CB7E2440063F317 /* MenuViewController.swift */,
|
||||||
);
|
);
|
||||||
path = Examples;
|
path = Examples;
|
||||||
@ -158,6 +161,14 @@
|
|||||||
path = Filters;
|
path = Filters;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
5BE9F15D226EEAE500030967 /* AnimationsHierarchy */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
5B195EAB2276D5C40008AE8B /* AnimationsHierarchyViewController.swift */,
|
||||||
|
);
|
||||||
|
path = AnimationsHierarchy;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
65CBD13FE29C7F0265D0E051 /* Frameworks */ = {
|
65CBD13FE29C7F0265D0E051 /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -360,6 +371,7 @@
|
|||||||
574EC4441CBB60AF0063F317 /* AnimationsExampleController.swift in Sources */,
|
574EC4441CBB60AF0063F317 /* AnimationsExampleController.swift in Sources */,
|
||||||
5747F9BD1E38B683004E338F /* MorphingView.swift in Sources */,
|
5747F9BD1E38B683004E338F /* MorphingView.swift in Sources */,
|
||||||
575129B61CBD14AF00BD3C2E /* AnimationsView.swift in Sources */,
|
575129B61CBD14AF00BD3C2E /* AnimationsView.swift in Sources */,
|
||||||
|
5B195EAD2276D5C40008AE8B /* AnimationsHierarchyViewController.swift in Sources */,
|
||||||
58E4D50C1D841C6E00EC8815 /* TransformExampleView.swift in Sources */,
|
58E4D50C1D841C6E00EC8815 /* TransformExampleView.swift in Sources */,
|
||||||
B02E75F11C16104900D1971D /* AppDelegate.swift in Sources */,
|
B02E75F11C16104900D1971D /* AppDelegate.swift in Sources */,
|
||||||
5BAE3CB120C54E3D006BEF51 /* FiltersViewController.swift in Sources */,
|
5BAE3CB120C54E3D006BEF51 /* FiltersViewController.swift in Sources */,
|
||||||
|
@ -383,5 +383,38 @@
|
|||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="-113" y="1196"/>
|
<point key="canvasLocation" x="-113" y="1196"/>
|
||||||
</scene>
|
</scene>
|
||||||
|
<!--AnimationsHierarchy-->
|
||||||
|
<scene sceneID="8bU-7m-OgE">
|
||||||
|
<objects>
|
||||||
|
<viewController storyboardIdentifier="AnimationsHierarchyViewController" title="Animations Hierarchy" id="T46-H6-N3f" userLabel="AnimationsHierarchy" customClass="AnimationsHierarchyViewController" customModule="Example" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="F0r-c7-NMQ"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="MiP-PT-hkh"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="ONf-4d-IZ5">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="WpG-iW-4sM" customClass="MacawView" customModule="Macaw">
|
||||||
|
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
</view>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="WpG-iW-4sM" firstAttribute="top" secondItem="F0r-c7-NMQ" secondAttribute="bottom" id="IyG-gg-H9z"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="WpG-iW-4sM" secondAttribute="trailing" id="Zx6-cu-1ui"/>
|
||||||
|
<constraint firstItem="WpG-iW-4sM" firstAttribute="leading" secondItem="ONf-4d-IZ5" secondAttribute="leading" id="gPb-ic-ege"/>
|
||||||
|
<constraint firstItem="MiP-PT-hkh" firstAttribute="top" secondItem="WpG-iW-4sM" secondAttribute="bottom" id="iOg-aV-zFW"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
<connections>
|
||||||
|
<outlet property="animView" destination="WpG-iW-4sM" id="a3r-wa-eYi"/>
|
||||||
|
</connections>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="t7f-Bd-Zcu" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="598" y="1194"/>
|
||||||
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
</document>
|
</document>
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,7 +2,7 @@ import UIKit
|
|||||||
|
|
||||||
open class MenuViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
|
open class MenuViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
|
||||||
|
|
||||||
@IBOutlet var tableView: UITableView?
|
@IBOutlet var tableView: UITableView!
|
||||||
|
|
||||||
fileprivate var viewControllers = [
|
fileprivate var viewControllers = [
|
||||||
"FirstPageViewController",
|
"FirstPageViewController",
|
||||||
@ -13,7 +13,8 @@ open class MenuViewController: UIViewController, UITableViewDataSource, UITableV
|
|||||||
"MorphingExampleController",
|
"MorphingExampleController",
|
||||||
"EventsExampleController",
|
"EventsExampleController",
|
||||||
"FiltersViewController",
|
"FiltersViewController",
|
||||||
"TextsViewController"
|
"TextsViewController",
|
||||||
|
"AnimationsHierarchyViewController"
|
||||||
].map {
|
].map {
|
||||||
UIStoryboard(name: "Main", bundle: .none).instantiateViewController(withIdentifier: $0)
|
UIStoryboard(name: "Main", bundle: .none).instantiateViewController(withIdentifier: $0)
|
||||||
}
|
}
|
||||||
@ -24,10 +25,6 @@ open class MenuViewController: UIViewController, UITableViewDataSource, UITableV
|
|||||||
tableView?.reloadData()
|
tableView?.reloadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
open func numberOfSections(in tableView: UITableView) -> Int {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
return viewControllers.count
|
return viewControllers.count
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,7 @@ class AnimationProducer {
|
|||||||
|
|
||||||
struct ContentAnimationDesc {
|
struct ContentAnimationDesc {
|
||||||
let animation: ContentsAnimation
|
let animation: ContentsAnimation
|
||||||
let layer: CALayer
|
|
||||||
weak var cache: AnimationCache?
|
weak var cache: AnimationCache?
|
||||||
let topLayers: [ShapeLayer]
|
|
||||||
let startDate: Date
|
let startDate: Date
|
||||||
let finishDate: Date
|
let finishDate: Date
|
||||||
let completion: (() -> Void)?
|
let completion: (() -> Void)?
|
||||||
@ -92,11 +90,7 @@ class AnimationProducer {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let layer = macawView.mLayer else {
|
guard let layer = macawView.mLayer, let cache = macawView.animationCache else {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let cache = macawView.animationCache else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,39 +250,9 @@ class AnimationProducer {
|
|||||||
unionBounds = unionBounds?.union(rect: contentsAnimation.getVFunc()(t).group().bounds!)
|
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(
|
let animationDesc = ContentAnimationDesc(
|
||||||
animation: contentsAnimation,
|
animation: contentsAnimation,
|
||||||
layer: layer,
|
|
||||||
cache: cache,
|
cache: cache,
|
||||||
topLayers: topLayers,
|
|
||||||
startDate: Date(),
|
startDate: Date(),
|
||||||
finishDate: Date(timeInterval: contentsAnimation.duration, since: startDate),
|
finishDate: Date(timeInterval: contentsAnimation.duration, since: startDate),
|
||||||
completion: completion
|
completion: completion
|
||||||
@ -322,11 +286,6 @@ class AnimationProducer {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
defer {
|
|
||||||
animationDesc.layer.setNeedsDisplay()
|
|
||||||
animationDesc.layer.displayIfNeeded()
|
|
||||||
}
|
|
||||||
|
|
||||||
let progress = currentDate.timeIntervalSince(animationDesc.startDate) / animation.duration + animation.pausedProgress
|
let progress = currentDate.timeIntervalSince(animationDesc.startDate) / animation.duration + animation.pausedProgress
|
||||||
|
|
||||||
// Completion
|
// Completion
|
||||||
@ -345,9 +304,6 @@ class AnimationProducer {
|
|||||||
contentsAnimations.remove(at: count - 1 - index)
|
contentsAnimations.remove(at: count - 1 - index)
|
||||||
animationDesc.cache?.freeLayer(renderer)
|
animationDesc.cache?.freeLayer(renderer)
|
||||||
animationDesc.completion?()
|
animationDesc.completion?()
|
||||||
for layer in animationDesc.topLayers {
|
|
||||||
animationDesc.cache?.freeLayer(layer: layer)
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -369,11 +325,6 @@ class AnimationProducer {
|
|||||||
animation.pausedProgress = progress
|
animation.pausedProgress = progress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for layer in animationDesc.topLayers {
|
|
||||||
layer.setNeedsDisplay()
|
|
||||||
layer.displayIfNeeded()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,14 +3,14 @@ import Foundation
|
|||||||
class AnimationUtils {
|
class AnimationUtils {
|
||||||
|
|
||||||
class func absolutePosition(_ nodeRenderer: NodeRenderer?, _ context: AnimationContext) -> Transform {
|
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 {
|
class func absoluteTransform(_ nodeRenderer: NodeRenderer?, _ context: AnimationContext, pos: Transform) -> Transform {
|
||||||
var transform = pos
|
var transform = pos
|
||||||
var parentRenderer = nodeRenderer?.parentRenderer
|
var parentRenderer = nodeRenderer?.parentRenderer
|
||||||
while parentRenderer != nil {
|
while parentRenderer != nil {
|
||||||
if let node = parentRenderer?.node() {
|
if let node = parentRenderer?.node {
|
||||||
transform = node.place.concat(with: transform)
|
transform = node.place.concat(with: transform)
|
||||||
}
|
}
|
||||||
parentRenderer = parentRenderer?.parentRenderer
|
parentRenderer = parentRenderer?.parentRenderer
|
||||||
@ -20,14 +20,14 @@ class AnimationUtils {
|
|||||||
|
|
||||||
class func absoluteClip(_ nodeRenderer: NodeRenderer?) -> Locus? {
|
class func absoluteClip(_ nodeRenderer: NodeRenderer?) -> Locus? {
|
||||||
// shouldn't this be a superposition of all parents' clips?
|
// shouldn't this be a superposition of all parents' clips?
|
||||||
let node = nodeRenderer?.node()
|
let node = nodeRenderer?.node
|
||||||
if let nodeClip = node?.clip {
|
if let nodeClip = node?.clip {
|
||||||
return nodeClip
|
return nodeClip
|
||||||
}
|
}
|
||||||
|
|
||||||
var parentRenderer = nodeRenderer?.parentRenderer
|
var parentRenderer = nodeRenderer?.parentRenderer
|
||||||
while parentRenderer != nil {
|
while parentRenderer != nil {
|
||||||
if let parentClip = parentRenderer?.node()?.clip {
|
if let parentClip = parentRenderer?.node.clip {
|
||||||
return parentClip
|
return parentClip
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,22 +38,4 @@ class AnimationUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static var indexCache = [Node: Int]()
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,12 @@ open class Easing {
|
|||||||
public static let easeIn: Easing = EaseIn()
|
public static let easeIn: Easing = EaseIn()
|
||||||
public static let easeOut: Easing = EaseOut()
|
public static let easeOut: Easing = EaseOut()
|
||||||
public static let easeInOut: Easing = EaseInOut()
|
public static let easeInOut: Easing = EaseInOut()
|
||||||
|
public static let elasticOut: Easing = ElasticOut()
|
||||||
public static let elasticInOut: Easing = ElasticInOut()
|
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 {
|
public static func elasticInOut(elasticity: Double = 10.0) -> ElasticInOut {
|
||||||
return ElasticInOut(elasticity: elasticity)
|
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 {
|
public class ElasticInOut: Easing {
|
||||||
let elasticity: Double
|
let elasticity: Double
|
||||||
|
|
||||||
@ -62,19 +90,17 @@ public class ElasticInOut: Easing {
|
|||||||
if time == 0 {
|
if time == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
var t = time / 0.5
|
let t = time / 0.5 - 1
|
||||||
if t == 2 {
|
if t == 1 {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
let p = 0.3 * 1.5
|
let p = 0.3 * 1.5
|
||||||
let s = p / 4
|
let s = p / 4
|
||||||
|
|
||||||
if t < 1 {
|
if t < 0 {
|
||||||
t -= 1
|
|
||||||
let postFix = pow(2, elasticity * t)
|
let postFix = pow(2, elasticity * t)
|
||||||
return (-0.5 * (postFix * sin((t - s) * (2 * .pi) / p)))
|
return (-0.5 * (postFix * sin((t - s) * (2 * .pi) / p)))
|
||||||
}
|
}
|
||||||
t -= 1
|
|
||||||
let postFix = pow(2, -elasticity * t)
|
let postFix = pow(2, -elasticity * t)
|
||||||
return (postFix * sin((t - s) * (2 * .pi) / p ) * 0.5 + 1)
|
return (postFix * sin((t - s) * (2 * .pi) / p ) * 0.5 + 1)
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,10 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
class ShapeAnimation: AnimationImpl<Shape> {
|
class ShapeAnimation: AnimationImpl<Shape> {
|
||||||
let toParentGlobalTransfrom: Transform
|
let fromLayoutGlobalTransfrom: Transform
|
||||||
|
let toLayoutGlobalTransfrom: Transform
|
||||||
|
|
||||||
convenience init(animatedNode: Shape, finalValue: Shape, toParentGlobalTransfrom: Transform = .identity, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
convenience init(animatedNode: Shape, finalValue: Shape, fromLayoutGlobalTransfrom: Transform = .identity, toLayoutGlobalTransfrom: Transform = .identity, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||||
|
|
||||||
let interpolationFunc = { (t: Double) -> Shape in
|
let interpolationFunc = { (t: Double) -> Shape in
|
||||||
if t == 0 {
|
if t == 0 {
|
||||||
@ -19,11 +20,12 @@ class ShapeAnimation: AnimationImpl<Shape> {
|
|||||||
return finalValue
|
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) {
|
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.toParentGlobalTransfrom = toParentGlobalTransfrom
|
self.fromLayoutGlobalTransfrom = fromLayoutGlobalTransfrom
|
||||||
|
self.toLayoutGlobalTransfrom = toLayoutGlobalTransfrom
|
||||||
super.init(observableValue: AnimatableVariable<Shape>(animatedNode), valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps)
|
super.init(observableValue: AnimatableVariable<Shape>(animatedNode), valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||||
type = .shape
|
type = .shape
|
||||||
node = animatedNode
|
node = animatedNode
|
||||||
@ -33,8 +35,9 @@ class ShapeAnimation: AnimationImpl<Shape> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(animatedNode: Shape, factory: @escaping (() -> ((Double) -> Shape)), toParentGlobalTransfrom: Transform = .identity, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
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.toParentGlobalTransfrom = toParentGlobalTransfrom
|
self.fromLayoutGlobalTransfrom = fromLayoutGlobalTransfrom
|
||||||
|
self.toLayoutGlobalTransfrom = toLayoutGlobalTransfrom
|
||||||
super.init(observableValue: AnimatableVariable<Shape>(animatedNode), factory: factory, animationDuration: animationDuration, delay: delay, fps: fps)
|
super.init(observableValue: AnimatableVariable<Shape>(animatedNode), factory: factory, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||||
type = .shape
|
type = .shape
|
||||||
node = animatedNode
|
node = animatedNode
|
||||||
|
@ -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 {
|
func animation(angle: Double, x: Double? = .none, y: Double? = .none, during: Double = 1.0, delay: Double = 0.0) -> Animation {
|
||||||
let bounds = node!.bounds!
|
let bounds = node!.bounds!
|
||||||
|
let place = node!.place
|
||||||
|
|
||||||
let factory = { () -> (Double) -> Transform in { t in
|
let factory = { () -> (Double) -> Transform in { t in
|
||||||
let asin = sin(angle * t); let acos = cos(angle * t)
|
let asin = sin(angle * t); let acos = cos(angle * t)
|
||||||
|
|
||||||
let rotation = Transform(
|
let rotation = Transform(
|
||||||
m11: acos, m12: -asin,
|
m11: acos, m12: -asin,
|
||||||
m21: asin, m22: acos,
|
m21: asin, m22: acos
|
||||||
dx: 0.0, dy: 0.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
let move = Transform.move(
|
let move = Transform.move(
|
||||||
dx: x ?? bounds.w / 2.0,
|
dx: x ?? bounds.x + bounds.w / 2.0,
|
||||||
dy: y ?? bounds.h / 2.0
|
dy: y ?? bounds.y + bounds.h / 2.0
|
||||||
)
|
)
|
||||||
|
|
||||||
let t1 = move.concat(with: rotation)
|
return place.concat(with: move).concat(with: rotation).concat(with: move.invert()!)
|
||||||
let t2 = t1.concat(with: move.invert()!)
|
|
||||||
let result = t1.concat(with: t2)
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
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)
|
return TransformAnimation(animatedNode: self.node!, factory: factory, along: path, animationDuration: during, delay: delay)
|
||||||
}
|
}
|
||||||
|
@ -9,18 +9,17 @@ import AppKit
|
|||||||
class AnimationCache {
|
class AnimationCache {
|
||||||
|
|
||||||
class CachedLayer {
|
class CachedLayer {
|
||||||
let layer: ShapeLayer
|
let rootLayer: ShapeLayer
|
||||||
let animation: Animation
|
let animationLayer: ShapeLayer
|
||||||
var linksCounter = 1
|
|
||||||
|
|
||||||
required init(layer: ShapeLayer, animation: Animation) {
|
required init(rootLayer: ShapeLayer, animationLayer: ShapeLayer) {
|
||||||
self.layer = layer
|
self.rootLayer = rootLayer
|
||||||
self.animation = animation
|
self.animationLayer = animationLayer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
weak var sceneLayer: CALayer?
|
weak var sceneLayer: CALayer?
|
||||||
var layerCache = [NodeRenderer: CachedLayer]()
|
var cache = [NodeRenderer: CachedLayer]()
|
||||||
|
|
||||||
required init(sceneLayer: CALayer) {
|
required init(sceneLayer: CALayer) {
|
||||||
self.sceneLayer = sceneLayer
|
self.sceneLayer = sceneLayer
|
||||||
@ -28,44 +27,45 @@ class AnimationCache {
|
|||||||
|
|
||||||
func layerForNodeRenderer(_ renderer: NodeRenderer, _ context: AnimationContext, animation: Animation, customBounds: Rect? = .none, shouldRenderContent: Bool = true) -> ShapeLayer {
|
func layerForNodeRenderer(_ renderer: NodeRenderer, _ context: AnimationContext, animation: Animation, customBounds: Rect? = .none, shouldRenderContent: Bool = true) -> ShapeLayer {
|
||||||
|
|
||||||
guard let node = renderer.node() else {
|
let node = renderer.node
|
||||||
return ShapeLayer()
|
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] {
|
// 'sublayer' is for actual CAAnimations, and 'layer' is for manual transforming and hierarchy changes
|
||||||
cachedLayer.linksCounter += 1
|
let sublayer = ShapeLayer()
|
||||||
return cachedLayer.layer
|
sublayer.shouldRenderContent = shouldRenderContent
|
||||||
}
|
sublayer.animationCache = self
|
||||||
|
|
||||||
let layer = ShapeLayer()
|
let layer = ShapeLayer()
|
||||||
layer.shouldRenderContent = shouldRenderContent
|
layer.addSublayer(sublayer)
|
||||||
layer.animationCache = self
|
layer.masksToBounds = false
|
||||||
|
|
||||||
// Use to debug animation layers
|
// Use to debug animation layers
|
||||||
// layer.backgroundColor = MColor.green.cgColor
|
// sublayer.backgroundColor = MColor.green.cgColor
|
||||||
// layer.borderWidth = 1.0
|
// sublayer.borderWidth = 2.0
|
||||||
// layer.borderColor = MColor.blue.cgColor
|
// sublayer.borderColor = MColor.red.cgColor
|
||||||
|
// layer.backgroundColor = MColor.blue.cgColor
|
||||||
|
// layer.borderWidth = 2.0
|
||||||
|
// layer.borderColor = MColor.cyan.cgColor
|
||||||
|
|
||||||
let calculatedBounds = customBounds ?? node.bounds
|
let calculatedBounds = customBounds ?? node.bounds
|
||||||
if let shapeBounds = calculatedBounds {
|
if let shapeBounds = calculatedBounds {
|
||||||
let cgRect = shapeBounds.toCG()
|
let cgRect = shapeBounds.toCG()
|
||||||
|
|
||||||
let origFrame = CGRect(x: 0.0, y: 0.0,
|
let anchorPoint = CGPoint(
|
||||||
width: cgRect.width,
|
|
||||||
height: cgRect.height)
|
|
||||||
|
|
||||||
layer.bounds = origFrame
|
|
||||||
layer.anchorPoint = CGPoint(
|
|
||||||
x: -1.0 * cgRect.origin.x / cgRect.width,
|
x: -1.0 * cgRect.origin.x / cgRect.width,
|
||||||
y: -1.0 * cgRect.origin.y / cgRect.height
|
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.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
|
// Clip
|
||||||
if let clip = AnimationUtils.absoluteClip(renderer) {
|
if let clip = AnimationUtils.absoluteClip(renderer) {
|
||||||
let maskLayer = CAShapeLayer()
|
let maskLayer = CAShapeLayer()
|
||||||
@ -77,18 +77,48 @@ class AnimationCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
layer.opacity = Float(node.opacity)
|
sublayer.opacity = Float(node.opacity)
|
||||||
layer.renderer = renderer
|
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()
|
cache[renderer] = CachedLayer(rootLayer: layer, animationLayer: sublayer)
|
||||||
sceneLayer?.addSublayer(layer)
|
|
||||||
|
// 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()
|
sceneLayer?.setNeedsDisplay()
|
||||||
|
|
||||||
return layer
|
return sublayer
|
||||||
}
|
}
|
||||||
|
|
||||||
private func calculateAnimationScale(animation: Animation) -> CGFloat {
|
private func calculateAnimationScale(animation: Animation) -> CGFloat {
|
||||||
@ -130,106 +160,73 @@ class AnimationCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func freeLayerHard(_ renderer: NodeRenderer) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let layer = cachedLayer.layer
|
cache.removeValue(forKey: nodeRenderer)
|
||||||
layerCache.removeValue(forKey: renderer)
|
|
||||||
sceneLayer?.setNeedsDisplay()
|
|
||||||
layer.removeFromSuperlayer()
|
|
||||||
}
|
|
||||||
|
|
||||||
func freeLayer(layer: ShapeLayer) {
|
// find first parent with cached layer
|
||||||
var cached: CachedLayer?
|
var parent: NodeRenderer? = nodeRenderer.parentRenderer
|
||||||
var renderer: NodeRenderer?
|
var parentCachedLayer: CALayer? = sceneLayer
|
||||||
layerCache.forEach { key, value in
|
while parent != nil {
|
||||||
if value.layer === layer {
|
if let parent = parent, let cached = cache[parent] {
|
||||||
cached = value
|
parentCachedLayer = cached.animationLayer
|
||||||
renderer = key
|
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
|
layer.animationLayer.removeFromSuperlayer()
|
||||||
|
layer.rootLayer.removeFromSuperlayer()
|
||||||
if cachedLayer.linksCounter != 0 {
|
parentCachedLayer?.setNeedsDisplay()
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let layer = cachedLayer.layer
|
|
||||||
layerCache.removeValue(forKey: nodeRenderer)
|
|
||||||
sceneLayer?.setNeedsDisplay()
|
sceneLayer?.setNeedsDisplay()
|
||||||
layer.removeFromSuperlayer()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func freeLayer(_ renderer: NodeRenderer) {
|
func isAnimating(_ nodeRenderer: NodeRenderer) -> Bool {
|
||||||
guard let cachedLayer = layerCache[renderer] else {
|
return cache[nodeRenderer] != nil
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cachedLayer.linksCounter -= 1
|
|
||||||
|
|
||||||
if cachedLayer.linksCounter != 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let layer = cachedLayer.layer
|
|
||||||
layerCache.removeValue(forKey: renderer)
|
|
||||||
sceneLayer?.setNeedsDisplay()
|
|
||||||
layer.removeFromSuperlayer()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isAnimating(_ node: Node) -> Bool {
|
func isAnimating(_ node: Node) -> Bool {
|
||||||
|
if let renderer = cache.keys.first(where: { $0.node === node }) {
|
||||||
let renderer = layerCache.keys.first { $0.node() === node }
|
return isAnimating(renderer)
|
||||||
if let renderer = renderer, let _ = layerCache[renderer] {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func isChildrenAnimating(_ group: Group) -> Bool {
|
func uncachedParentsPlace(_ renderer: NodeRenderer) -> Transform {
|
||||||
|
var parent: NodeRenderer? = renderer.parentRenderer
|
||||||
for child in group.contents {
|
var uncachedParentsPlace = Transform.identity
|
||||||
if isAnimating(child) {
|
while parent != nil {
|
||||||
return true
|
if let parent = parent {
|
||||||
}
|
if cache[parent] != nil {
|
||||||
|
break
|
||||||
if let childGroup = child as? Group {
|
}
|
||||||
return isChildrenAnimating(childGroup)
|
uncachedParentsPlace = uncachedParentsPlace.concat(with: parent.node.place)
|
||||||
}
|
}
|
||||||
|
parent = parent?.parentRenderer
|
||||||
}
|
}
|
||||||
|
return uncachedParentsPlace
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -13,19 +13,21 @@ import AppKit
|
|||||||
|
|
||||||
extension AnimationProducer {
|
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 {
|
guard let combine = combineAnimation as? CombineAnimation else {
|
||||||
return animations
|
return animations
|
||||||
}
|
}
|
||||||
|
|
||||||
let globalToPosition = globalToPosition
|
|
||||||
let during = combine.duration
|
let during = combine.duration
|
||||||
let delay = combine.delay
|
let delay = combine.delay
|
||||||
let fromNode = combine.node as! Group
|
let fromNode = combine.node as! Group
|
||||||
let to = combine.toNodes
|
let to = combine.toNodes
|
||||||
|
|
||||||
|
let fromContentsCopy = fromNode.contents.compactMap { SceneUtils.copyNode($0) }
|
||||||
|
fromNode.contents = fromContentsCopy
|
||||||
|
|
||||||
// Shapes on same hierarhy level
|
// 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 toShapes = to.compactMap { $0 as? Shape }
|
||||||
let minPathsNumber = min(fromShapes.count, toShapes.count)
|
let minPathsNumber = min(fromShapes.count, toShapes.count)
|
||||||
|
|
||||||
@ -34,7 +36,7 @@ extension AnimationProducer {
|
|||||||
let fromShape = fromShapes[i]
|
let fromShape = fromShapes[i]
|
||||||
let toShape = toShapes[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)
|
animations.append(animation)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +68,7 @@ extension AnimationProducer {
|
|||||||
let toGroup = toGroups[i]
|
let toGroup = toGroups[i]
|
||||||
|
|
||||||
let groupAnimation = fromGroup.contentsVar.animation(to: toGroup.contents, during: during, delay: delay)
|
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)
|
animations.append(contentsOf: groupAnimations)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,9 +121,10 @@ extension AnimationProducer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var animations = combine.animations
|
var animations = combine.animations
|
||||||
if let toBounds = combine.toNodes.group().bounds {
|
if let fromBounds = combine.node?.bounds, let toBounds = combine.toNodes.group().bounds {
|
||||||
let globalTransform = view.contentLayout.layout(rect: toBounds, into: view.frame.size.toMacaw())
|
let fromLayoutTransform = view.contentLayout.layout(rect: fromBounds, into: view.frame.size.toMacaw())
|
||||||
let childAnimations = createChildAnimations(combine, globalToPosition: globalTransform) as! [BasicAnimation]
|
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)
|
animations.append(contentsOf: childAnimations)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,6 +183,12 @@ extension AnimationProducer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CATransaction.setDisableActions(true)
|
||||||
|
defer {
|
||||||
|
CATransaction.commit()
|
||||||
|
//CATransaction.flush()
|
||||||
|
}
|
||||||
|
|
||||||
// Launching
|
// Launching
|
||||||
animations.forEach { animation in
|
animations.forEach { animation in
|
||||||
self.play(animation, context)
|
self.play(animation, context)
|
||||||
|
@ -23,9 +23,8 @@ func addMorphingAnimation(_ animation: BasicAnimation, _ context: AnimationConte
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let mutatingShape = SceneUtils.shapeCopy(from: shape)
|
let transactionsDisabled = CATransaction.disableActions()
|
||||||
renderer.replaceNode(with: mutatingShape)
|
CATransaction.setDisableActions(true)
|
||||||
animation.node = mutatingShape
|
|
||||||
|
|
||||||
let fromLocus = morphingAnimation.getVFunc()(0.0)
|
let fromLocus = morphingAnimation.getVFunc()(0.0)
|
||||||
let toLocus = morphingAnimation.getVFunc()(animation.autoreverses ? 0.5 : 1.0)
|
let toLocus = morphingAnimation.getVFunc()(animation.autoreverses ? 0.5 : 1.0)
|
||||||
@ -35,24 +34,29 @@ func addMorphingAnimation(_ animation: BasicAnimation, _ context: AnimationConte
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Creating proper animation
|
// Creating proper animation
|
||||||
let generatedAnim = pathAnimation(
|
let generatedAnimation = pathAnimation(
|
||||||
from: fromLocus,
|
from: fromLocus,
|
||||||
to: toLocus,
|
to: toLocus,
|
||||||
duration: duration,
|
duration: duration)
|
||||||
renderTransform: layer.renderTransform!)
|
|
||||||
|
|
||||||
generatedAnim.repeatCount = Float(animation.repeatCount)
|
generatedAnimation.repeatCount = Float(animation.repeatCount)
|
||||||
generatedAnim.timingFunction = caTimingFunction(animation.easing)
|
generatedAnimation.timingFunction = caTimingFunction(animation.easing)
|
||||||
generatedAnim.autoreverses = animation.autoreverses
|
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 {
|
if animation.manualStop {
|
||||||
animation.progress = 0.0
|
animation.progress = 0.0
|
||||||
mutatingShape.form = morphingAnimation.getVFunc()(0.0)
|
shape.form = morphingAnimation.getVFunc()(0.0)
|
||||||
} else if finished {
|
} else if finished {
|
||||||
animation.progress = 1.0
|
animation.progress = 1.0
|
||||||
mutatingShape.form = morphingAnimation.getVFunc()(1.0)
|
shape.form = morphingAnimation.getVFunc()(1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
animationCache?.freeLayer(renderer)
|
animationCache?.freeLayer(renderer)
|
||||||
@ -65,19 +69,10 @@ func addMorphingAnimation(_ animation: BasicAnimation, _ context: AnimationConte
|
|||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
|
|
||||||
generatedAnim.progress = { progress in
|
|
||||||
|
|
||||||
let t = Double(progress)
|
|
||||||
mutatingShape.form = morphingAnimation.getVFunc()(t)
|
|
||||||
|
|
||||||
animation.progress = t
|
|
||||||
animation.onProgressUpdate?(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
layer.path = fromLocus.toCGPath()
|
layer.path = fromLocus.toCGPath()
|
||||||
|
|
||||||
// Stroke
|
// Stroke
|
||||||
if let stroke = mutatingShape.stroke {
|
if let stroke = shape.stroke {
|
||||||
if let color = stroke.fill as? Color {
|
if let color = stroke.fill as? Color {
|
||||||
layer.strokeColor = color.toCG()
|
layer.strokeColor = color.toCG()
|
||||||
} else {
|
} else {
|
||||||
@ -91,24 +86,27 @@ func addMorphingAnimation(_ animation: BasicAnimation, _ context: AnimationConte
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fill
|
// Fill
|
||||||
if let color = mutatingShape.fill as? Color {
|
if let color = shape.fill as? Color {
|
||||||
layer.fillColor = color.toCG()
|
layer.fillColor = color.toCG()
|
||||||
} else {
|
} else {
|
||||||
layer.fillColor = MColor.clear.cgColor
|
layer.fillColor = MColor.clear.cgColor
|
||||||
}
|
}
|
||||||
|
|
||||||
let animationId = animation.ID
|
let animationId = animation.ID
|
||||||
layer.add(generatedAnim, forKey: animationId)
|
layer.add(generatedAnimation, forKey: animationId)
|
||||||
animation.removeFunc = { [weak layer] in
|
animation.removeFunc = { [weak layer] in
|
||||||
layer?.removeAnimation(forKey: animationId)
|
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()
|
||||||
let fromPath = from.toCGPath().copy(using: &transform)
|
let toPath = to.toCGPath()
|
||||||
let toPath = to.toCGPath().copy(using: &transform)
|
|
||||||
|
|
||||||
let animation = CABasicAnimation(keyPath: "path")
|
let animation = CABasicAnimation(keyPath: "path")
|
||||||
animation.fromValue = fromPath
|
animation.fromValue = fromPath
|
||||||
|
@ -15,6 +15,9 @@ func addOpacityAnimation(_ animation: BasicAnimation, _ context: AnimationContex
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let transactionsDisabled = CATransaction.disableActions()
|
||||||
|
CATransaction.setDisableActions(true)
|
||||||
|
|
||||||
// Creating proper animation
|
// Creating proper animation
|
||||||
let generatedAnimation = opacityAnimationByFunc(opacityAnimation.getVFunc(),
|
let generatedAnimation = opacityAnimationByFunc(opacityAnimation.getVFunc(),
|
||||||
duration: animation.getDuration(),
|
duration: animation.getDuration(),
|
||||||
@ -23,6 +26,12 @@ func addOpacityAnimation(_ animation: BasicAnimation, _ context: AnimationContex
|
|||||||
generatedAnimation.repeatCount = Float(animation.repeatCount)
|
generatedAnimation.repeatCount = Float(animation.repeatCount)
|
||||||
generatedAnimation.timingFunction = caTimingFunction(animation.easing)
|
generatedAnimation.timingFunction = caTimingFunction(animation.easing)
|
||||||
|
|
||||||
|
generatedAnimation.progress = { progress in
|
||||||
|
let t = Double(progress)
|
||||||
|
animation.progress = t
|
||||||
|
animation.onProgressUpdate?(t)
|
||||||
|
}
|
||||||
|
|
||||||
generatedAnimation.completion = { finished in
|
generatedAnimation.completion = { finished in
|
||||||
|
|
||||||
animationCache?.freeLayer(renderer)
|
animationCache?.freeLayer(renderer)
|
||||||
@ -49,22 +58,17 @@ func addOpacityAnimation(_ animation: BasicAnimation, _ context: AnimationContex
|
|||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
|
|
||||||
generatedAnimation.progress = { progress in
|
if let layer = animationCache?.layerForNodeRenderer(renderer, context, animation: animation) {
|
||||||
|
|
||||||
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) {
|
|
||||||
let animationId = animation.ID
|
let animationId = animation.ID
|
||||||
layer.add(generatedAnimation, forKey: animationId)
|
layer.add(generatedAnimation, forKey: animationId)
|
||||||
animation.removeFunc = { [weak layer] in
|
animation.removeFunc = { [weak layer] in
|
||||||
layer?.removeAnimation(forKey: animationId)
|
layer?.removeAnimation(forKey: animationId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !transactionsDisabled {
|
||||||
|
CATransaction.commit()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func opacityAnimationByFunc(_ valueFunc: (Double) -> Double, duration: Double, offset: Double, fps: UInt) -> CAAnimation {
|
func opacityAnimationByFunc(_ valueFunc: (Double) -> Double, duration: Double, offset: Double, fps: UInt) -> CAAnimation {
|
||||||
|
@ -23,44 +23,49 @@ func addShapeAnimation(_ animation: BasicAnimation, _ context: AnimationContext,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let transactionsDisabled = CATransaction.disableActions()
|
||||||
|
CATransaction.setDisableActions(true)
|
||||||
|
|
||||||
let fromShape = shapeAnimation.getVFunc()(0.0)
|
let fromShape = shapeAnimation.getVFunc()(0.0)
|
||||||
let toShape = shapeAnimation.getVFunc()(animation.autoreverses ? 0.5 : 1.0)
|
let toShape = shapeAnimation.getVFunc()(animation.autoreverses ? 0.5 : 1.0)
|
||||||
let duration = animation.autoreverses ? animation.getDuration() / 2.0 : animation.getDuration()
|
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 {
|
guard let layer = animationCache?.layerForNodeRenderer(renderer, context, animation: animation, shouldRenderContent: false) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creating proper animation
|
// Creating proper animation
|
||||||
let generatedAnim = generateShapeAnimation(context,
|
let generatedAnimation = generateShapeAnimation(context,
|
||||||
from: mutatingShape,
|
from: fromShape,
|
||||||
to: toShape,
|
to: toShape,
|
||||||
animation: shapeAnimation,
|
animation: shapeAnimation,
|
||||||
duration: duration,
|
duration: duration)
|
||||||
renderTransform: layer.renderTransform!)
|
|
||||||
|
|
||||||
generatedAnim.repeatCount = Float(animation.repeatCount)
|
generatedAnimation.repeatCount = Float(animation.repeatCount)
|
||||||
generatedAnim.timingFunction = caTimingFunction(animation.easing)
|
generatedAnimation.timingFunction = caTimingFunction(animation.easing)
|
||||||
generatedAnim.autoreverses = animation.autoreverses
|
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
|
animation.progress = animation.manualStop ? 0.0 : 1.0
|
||||||
|
|
||||||
if !animation.autoreverses && finished {
|
if !animation.autoreverses && finished {
|
||||||
mutatingShape.form = toShape.form
|
shape.form = toShape.form
|
||||||
mutatingShape.stroke = toShape.stroke
|
shape.stroke = toShape.stroke
|
||||||
mutatingShape.fill = toShape.fill
|
shape.fill = toShape.fill
|
||||||
}
|
}
|
||||||
|
|
||||||
if !finished {
|
if !finished {
|
||||||
animation.progress = 0.0
|
animation.progress = 0.0
|
||||||
mutatingShape.form = fromShape.form
|
shape.form = fromShape.form
|
||||||
mutatingShape.stroke = fromShape.stroke
|
shape.stroke = fromShape.stroke
|
||||||
mutatingShape.fill = fromShape.fill
|
shape.fill = fromShape.fill
|
||||||
}
|
}
|
||||||
|
|
||||||
animationCache?.freeLayer(renderer)
|
animationCache?.freeLayer(renderer)
|
||||||
@ -72,27 +77,6 @@ func addShapeAnimation(_ animation: BasicAnimation, _ context: AnimationContext,
|
|||||||
completion()
|
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()
|
layer.path = fromShape.form.toCGPath()
|
||||||
|
|
||||||
// Stroke
|
// Stroke
|
||||||
@ -121,21 +105,23 @@ func addShapeAnimation(_ animation: BasicAnimation, _ context: AnimationContext,
|
|||||||
}
|
}
|
||||||
|
|
||||||
let animationId = animation.ID
|
let animationId = animation.ID
|
||||||
layer.add(generatedAnim, forKey: animationId)
|
layer.add(generatedAnimation, forKey: animationId)
|
||||||
animation.removeFunc = { [weak layer] in
|
animation.removeFunc = { [weak layer] in
|
||||||
layer?.removeAnimation(forKey: animationId)
|
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()
|
let group = CAAnimationGroup()
|
||||||
|
|
||||||
// Shape
|
|
||||||
// Path
|
// Path
|
||||||
var transform = renderTransform
|
let fromPath = from.form.toCGPath()
|
||||||
let fromPath = from.form.toCGPath().copy(using: &transform)
|
let toPath = to.form.toCGPath()
|
||||||
let toPath = to.form.toCGPath().copy(using: &transform)
|
|
||||||
|
|
||||||
let pathAnimation = CABasicAnimation(keyPath: "path")
|
let pathAnimation = CABasicAnimation(keyPath: "path")
|
||||||
pathAnimation.fromValue = fromPath
|
pathAnimation.fromValue = fromPath
|
||||||
@ -145,16 +131,12 @@ fileprivate func generateShapeAnimation(_ context: AnimationContext, from: Shape
|
|||||||
group.animations = [pathAnimation]
|
group.animations = [pathAnimation]
|
||||||
|
|
||||||
// Transform
|
// Transform
|
||||||
let scaleAnimation = CABasicAnimation(keyPath: "transform")
|
let transformAnimation = CABasicAnimation(keyPath: "transform")
|
||||||
scaleAnimation.duration = duration
|
transformAnimation.duration = duration
|
||||||
let parentPos = AnimationUtils.absolutePosition(animation.nodeRenderer?.parentRenderer, context)
|
transformAnimation.fromValue = CATransform3DMakeAffineTransform(animation.fromLayoutGlobalTransfrom.concat(with: from.place).toCG())
|
||||||
let fromPos = parentPos.concat(with: from.place)
|
transformAnimation.toValue = CATransform3DMakeAffineTransform(animation.toLayoutGlobalTransfrom.concat(with: to.place).toCG())
|
||||||
let toParentPos = animation.toParentGlobalTransfrom
|
|
||||||
let toPos = toParentPos.concat(with: to.place)
|
|
||||||
scaleAnimation.fromValue = CATransform3DMakeAffineTransform(fromPos.toCG())
|
|
||||||
scaleAnimation.toValue = CATransform3DMakeAffineTransform(toPos.toCG())
|
|
||||||
|
|
||||||
group.animations?.append(scaleAnimation)
|
group.animations?.append(transformAnimation)
|
||||||
|
|
||||||
// Fill
|
// Fill
|
||||||
let fromFillColor = from.fill as? Color ?? Color.clear
|
let fromFillColor = from.fill as? Color ?? Color.clear
|
||||||
|
@ -11,33 +11,37 @@ func addTransformAnimation(_ animation: BasicAnimation, _ context: AnimationCont
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let node = animation.node, let renderer = animation.nodeRenderer else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if transformAnimation.trajectory != nil && transformAnimation.easing === Easing.elasticInOut {
|
if transformAnimation.trajectory != nil && transformAnimation.easing === Easing.elasticInOut {
|
||||||
fatalError("Transform animation with trajectory can't have elastic easing, try using contentVar animation instead")
|
fatalError("Transform animation with trajectory can't have elastic easing, try using contentVar animation instead")
|
||||||
}
|
}
|
||||||
|
|
||||||
node.placeVar.value = transformAnimation.getVFunc()(0.0)
|
guard let node = animation.node, let renderer = animation.nodeRenderer else {
|
||||||
|
|
||||||
// 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 {
|
|
||||||
return
|
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 {
|
if animation.paused {
|
||||||
animation.pausedProgress += animation.progress
|
animation.pausedProgress += animation.progress
|
||||||
@ -63,25 +67,18 @@ func addTransformAnimation(_ animation: BasicAnimation, _ context: AnimationCont
|
|||||||
completion()
|
completion()
|
||||||
}
|
}
|
||||||
|
|
||||||
generatedAnim.progress = { progress in
|
let animationId = animation.ID
|
||||||
|
layer.add(generatedAnimation, forKey: animationId)
|
||||||
let t = Double(progress)
|
animation.removeFunc = { [weak layer] in
|
||||||
node.placeVar.value = transformAnimation.getVFunc()(t)
|
layer?.removeAnimation(forKey: animationId)
|
||||||
|
|
||||||
animation.progress = t
|
|
||||||
animation.onProgressUpdate?(t)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let renderer = animation.nodeRenderer, let layer = animationCache?.layerForNodeRenderer(renderer, context, animation: animation) {
|
if !transactionsDisabled {
|
||||||
let animationId = animation.ID
|
CATransaction.commit()
|
||||||
layer.add(generatedAnim, forKey: animationId)
|
|
||||||
animation.removeFunc = { [weak layer] in
|
|
||||||
layer?.removeAnimation(forKey: animationId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
let valueFunc = animation.getVFunc()
|
||||||
|
|
||||||
@ -90,8 +87,6 @@ func transformAnimationByFunc(_ animation: TransformAnimation, _ context: Animat
|
|||||||
pathAnimation.timingFunction = caTimingFunction(animation.easing)
|
pathAnimation.timingFunction = caTimingFunction(animation.easing)
|
||||||
pathAnimation.duration = duration / 2
|
pathAnimation.duration = duration / 2
|
||||||
pathAnimation.autoreverses = animation.autoreverses
|
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.fillMode = MCAMediaTimingFillMode.forwards
|
||||||
pathAnimation.isRemovedOnCompletion = false
|
pathAnimation.isRemovedOnCompletion = false
|
||||||
pathAnimation.path = trajectory.toCGPath()
|
pathAnimation.path = trajectory.toCGPath()
|
||||||
@ -105,7 +100,7 @@ func transformAnimationByFunc(_ animation: TransformAnimation, _ context: Animat
|
|||||||
tValue.append(1.0)
|
tValue.append(1.0)
|
||||||
for t in tValue {
|
for t in tValue {
|
||||||
let progress = animation.easing.progressFor(time: t)
|
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())
|
let cgValue = CATransform3DMakeAffineTransform(value.toCG())
|
||||||
transformValues.append(cgValue)
|
transformValues.append(cgValue)
|
||||||
}
|
}
|
||||||
|
@ -12,11 +12,6 @@ import UIKit
|
|||||||
import AppKit
|
import AppKit
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public enum Relativity {
|
|
||||||
case parent
|
|
||||||
case scene
|
|
||||||
}
|
|
||||||
|
|
||||||
class NodePath {
|
class NodePath {
|
||||||
let node: Node
|
let node: Node
|
||||||
let location: CGPoint
|
let location: CGPoint
|
||||||
@ -36,11 +31,13 @@ public struct TouchPoint {
|
|||||||
|
|
||||||
private let absoluteLocation: Point
|
private let absoluteLocation: Point
|
||||||
private let relativeLocation: Point // location inside the node
|
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.id = id
|
||||||
self.absoluteLocation = location
|
self.absoluteLocation = location
|
||||||
self.relativeLocation = relativeLocation
|
self.relativeLocation = relativeToNodeLocation
|
||||||
|
self.viewLocation = relativeToViewLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
public func location(in relativity: Relativity = .parent) -> Point {
|
public func location(in relativity: Relativity = .parent) -> Point {
|
||||||
@ -49,6 +46,8 @@ public struct TouchPoint {
|
|||||||
return relativeLocation
|
return relativeLocation
|
||||||
case .scene:
|
case .scene:
|
||||||
return absoluteLocation
|
return absoluteLocation
|
||||||
|
case .view:
|
||||||
|
return viewLocation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,6 +82,10 @@ public final class Transform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func invert() -> 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
|
let det = self.m11 * self.m22 - self.m12 * self.m21
|
||||||
if det == 0 {
|
if det == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
@ -39,14 +39,16 @@ class SceneUtils {
|
|||||||
let clip = referenceNode.clip
|
let clip = referenceNode.clip
|
||||||
let tag = referenceNode.tag
|
let tag = referenceNode.tag
|
||||||
|
|
||||||
|
var result: Node?
|
||||||
|
|
||||||
if let shape = referenceNode as? Shape {
|
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 {
|
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 {
|
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 {
|
if let group = referenceNode as? Group {
|
||||||
var contents = [Node]()
|
var contents = [Node]()
|
||||||
@ -55,8 +57,19 @@ class SceneUtils {
|
|||||||
contents.append(copy)
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,16 @@ import UIKit
|
|||||||
|
|
||||||
class GroupRenderer: NodeRenderer {
|
class GroupRenderer: NodeRenderer {
|
||||||
|
|
||||||
weak var group: Group?
|
var group: Group
|
||||||
var renderers: [NodeRenderer] = []
|
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
|
self.group = group
|
||||||
super.init(node: group, view: view, animationCache: animationCache)
|
super.init(node: group, view: view, animationCache: animationCache, parentRenderer: parentRenderer)
|
||||||
updateRenderers()
|
updateRenderers()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,23 +26,23 @@ class GroupRenderer: NodeRenderer {
|
|||||||
override func doAddObservers() {
|
override func doAddObservers() {
|
||||||
super.doAddObservers()
|
super.doAddObservers()
|
||||||
|
|
||||||
guard let group = group else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
group.contentsVar.onChange { [weak self] _ in
|
group.contentsVar.onChange { [weak self] _ in
|
||||||
self?.updateRenderers()
|
self?.updateRenderers()
|
||||||
}
|
}
|
||||||
observe(group.contentsVar)
|
observe(group.contentsVar)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func node() -> Node? {
|
override func freeCachedAbsPlace() {
|
||||||
return group
|
for renderer in renderers {
|
||||||
|
renderer.freeCachedAbsPlace()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func doRender(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
|
override func doRender(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
|
||||||
renderers.forEach { renderer in
|
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()
|
renderers.removeAll()
|
||||||
|
|
||||||
if let updatedRenderers = group?.contents.compactMap ({ child -> NodeRenderer? in
|
renderers = group.contents.compactMap { child -> NodeRenderer? in
|
||||||
let childRenderer = RenderUtils.createNodeRenderer(child, view: view, animationCache: animationCache)
|
return RenderUtils.createNodeRenderer(child, view: view, animationCache: animationCache, parentRenderer: self)
|
||||||
childRenderer.parentRenderer = self
|
|
||||||
return childRenderer
|
|
||||||
}) {
|
|
||||||
renderers = updatedRenderers
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var parent: NodeRenderer = self
|
var parent: NodeRenderer = self
|
||||||
@ -79,12 +79,4 @@ class GroupRenderer: NodeRenderer {
|
|||||||
}
|
}
|
||||||
parent.calculateZPositionRecursively()
|
parent.calculateZPositionRecursively()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func replaceNode(with replacementNode: Node) {
|
|
||||||
super.replaceNode(with: replacementNode)
|
|
||||||
|
|
||||||
if let node = replacementNode as? Group {
|
|
||||||
group = node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -9,30 +9,26 @@ import UIKit
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
class ImageRenderer: NodeRenderer {
|
class ImageRenderer: NodeRenderer {
|
||||||
weak var image: Image?
|
var image: Image
|
||||||
|
|
||||||
var renderedPaths: [CGPath] = [CGPath]()
|
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
|
self.image = image
|
||||||
super.init(node: image, view: view, animationCache: animationCache)
|
super.init(node: image, view: view, animationCache: animationCache, parentRenderer: parentRenderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
dispose()
|
dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func node() -> Node? {
|
|
||||||
return image
|
|
||||||
}
|
|
||||||
|
|
||||||
override func doAddObservers() {
|
override func doAddObservers() {
|
||||||
super.doAddObservers()
|
super.doAddObservers()
|
||||||
|
|
||||||
guard let image = image else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
observe(image.srcVar)
|
observe(image.srcVar)
|
||||||
observe(image.xAlignVar)
|
observe(image.xAlignVar)
|
||||||
observe(image.yAlignVar)
|
observe(image.yAlignVar)
|
||||||
@ -42,9 +38,6 @@ class ImageRenderer: NodeRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func doRender(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
|
override func doRender(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
|
||||||
guard let image = image else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var mImage: MImage?
|
var mImage: MImage?
|
||||||
if image.src.contains("memory") {
|
if image.src.contains("memory") {
|
||||||
@ -64,9 +57,6 @@ class ImageRenderer: NodeRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func doFindNodeAt(path: NodePath, ctx: CGContext) -> NodePath? {
|
override func doFindNodeAt(path: NodePath, ctx: CGContext) -> NodePath? {
|
||||||
guard let image = image else {
|
|
||||||
return .none
|
|
||||||
}
|
|
||||||
|
|
||||||
if let mImage = MImage(named: image.src),
|
if let mImage = MImage(named: image.src),
|
||||||
let rect = BoundsUtils.getRect(of: image, mImage: mImage) {
|
let rect = BoundsUtils.getRect(of: image, mImage: mImage) {
|
||||||
@ -77,12 +67,4 @@ class ImageRenderer: NodeRenderer {
|
|||||||
}
|
}
|
||||||
return .none
|
return .none
|
||||||
}
|
}
|
||||||
|
|
||||||
override func replaceNode(with replacementNode: Node) {
|
|
||||||
super.replaceNode(with: replacementNode)
|
|
||||||
|
|
||||||
if let node = replacementNode as? Image {
|
|
||||||
image = node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,12 @@ import UIKit
|
|||||||
import AppKit
|
import AppKit
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
public enum Relativity {
|
||||||
|
case parent
|
||||||
|
case scene
|
||||||
|
case view
|
||||||
|
}
|
||||||
|
|
||||||
enum ColoringMode {
|
enum ColoringMode {
|
||||||
case rgb, greyscale, alphaOnly
|
case rgb, greyscale, alphaOnly
|
||||||
}
|
}
|
||||||
@ -13,17 +19,54 @@ enum ColoringMode {
|
|||||||
class NodeRenderer {
|
class NodeRenderer {
|
||||||
|
|
||||||
weak var view: MacawView?
|
weak var view: MacawView?
|
||||||
weak var parentRenderer: NodeRenderer?
|
let parentRenderer: GroupRenderer?
|
||||||
internal var zPosition: Int = 0
|
var zPosition: Int = 0
|
||||||
|
|
||||||
fileprivate let onNodeChange: () -> Void
|
fileprivate let onNodeChange: () -> Void
|
||||||
fileprivate let disposables = GroupDisposable()
|
fileprivate let disposables = GroupDisposable()
|
||||||
fileprivate var active = false
|
fileprivate var active = false
|
||||||
weak var animationCache: AnimationCache?
|
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.view = view
|
||||||
self.animationCache = animationCache
|
self.animationCache = animationCache
|
||||||
|
self.parentRenderer = parentRenderer
|
||||||
|
|
||||||
onNodeChange = { [unowned node, weak view] in
|
onNodeChange = { [unowned node, weak view] in
|
||||||
guard let isAnimating = animationCache?.isAnimating(node) else {
|
guard let isAnimating = animationCache?.isAnimating(node) else {
|
||||||
@ -45,10 +88,6 @@ class NodeRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func doAddObservers() {
|
func doAddObservers() {
|
||||||
guard let node = node() else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
observe(node.placeVar)
|
observe(node.placeVar)
|
||||||
observe(node.opaqueVar)
|
observe(node.opaqueVar)
|
||||||
observe(node.opacityVar)
|
observe(node.opacityVar)
|
||||||
@ -56,6 +95,10 @@ class NodeRenderer {
|
|||||||
observe(node.effectVar)
|
observe(node.effectVar)
|
||||||
|
|
||||||
node.animationObservers.append(self)
|
node.animationObservers.append(self)
|
||||||
|
|
||||||
|
node.placeVar.onChange { [weak self] _ in
|
||||||
|
self?.freeCachedAbsPlace()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func observe<E>(_ v: Variable<E>) {
|
func observe<E>(_ v: Variable<E>) {
|
||||||
@ -72,11 +115,7 @@ class NodeRenderer {
|
|||||||
|
|
||||||
open func dispose() {
|
open func dispose() {
|
||||||
removeObservers()
|
removeObservers()
|
||||||
node()?.animationObservers = node()?.animationObservers.filter { !($0 as? NodeRenderer === self) } ?? []
|
node.animationObservers = node.animationObservers.filter { !($0 as? NodeRenderer === self) }
|
||||||
}
|
|
||||||
|
|
||||||
open func node() -> Node? {
|
|
||||||
fatalError("Unsupported")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final public func render(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
|
final public func render(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
|
||||||
@ -84,9 +123,6 @@ class NodeRenderer {
|
|||||||
defer {
|
defer {
|
||||||
context.restoreGState()
|
context.restoreGState()
|
||||||
}
|
}
|
||||||
guard let node = node() else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let newOpacity = node.opacity * opacity
|
let newOpacity = node.opacity * opacity
|
||||||
|
|
||||||
context.concatenate(node.place.toCG())
|
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) {
|
final func directRender(in context: CGContext, force: Bool = true, opacity: Double = 1.0, coloringMode: ColoringMode = .rgb) {
|
||||||
guard let node = node() else {
|
if let isAnimating = animationCache?.isAnimating(self), isAnimating {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if let isAnimating = animationCache?.isAnimating(node), isAnimating {
|
|
||||||
self.removeObservers()
|
self.removeObservers()
|
||||||
if !force {
|
if !force {
|
||||||
return
|
return
|
||||||
@ -166,7 +198,7 @@ class NodeRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func applyEffects(_ effects: [Effect], context: CGContext, opacity: Double, coloringMode: ColoringMode = .rgb) {
|
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
|
return
|
||||||
}
|
}
|
||||||
var inset: Double = 0
|
var inset: Double = 0
|
||||||
@ -232,65 +264,51 @@ class NodeRenderer {
|
|||||||
fatalError("Unsupported")
|
fatalError("Unsupported")
|
||||||
}
|
}
|
||||||
|
|
||||||
final func findNodeAt(parentNodePath: NodePath, ctx: CGContext) -> NodePath? {
|
final func findNodeAt(location: CGPoint, ctx: CGContext) -> NodePath? {
|
||||||
guard let node = node() else {
|
guard node.opaque, let inverted = node.place.invert() else {
|
||||||
return .none
|
return .none
|
||||||
}
|
}
|
||||||
|
|
||||||
if node.opaque {
|
ctx.saveGState()
|
||||||
let place = node.place
|
defer {
|
||||||
if let inverted = place.invert() {
|
ctx.restoreGState()
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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? {
|
public func doFindNodeAt(path: NodePath, ctx: CGContext) -> NodePath? {
|
||||||
return nil
|
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() {
|
func calculateZPositionRecursively() {
|
||||||
calculateZPosition(self)
|
calculateZPosition(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func applyClip(in context: CGContext) {
|
private func applyClip(in context: CGContext) {
|
||||||
guard let node = node() else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let clip = node.clip else {
|
guard let clip = node.clip else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -309,7 +327,7 @@ class NodeRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func getMaskedImage(bounds: Rect) -> CGImage {
|
private func getMaskedImage(bounds: Rect) -> CGImage {
|
||||||
let mask = node()!.mask!
|
let mask = node.mask!
|
||||||
let image = renderToImage(bounds: bounds)
|
let image = renderToImage(bounds: bounds)
|
||||||
let nodeRenderer = RenderUtils.createNodeRenderer(mask, view: .none, animationCache: animationCache)
|
let nodeRenderer = RenderUtils.createNodeRenderer(mask, view: .none, animationCache: animationCache)
|
||||||
let maskImage = nodeRenderer.renderToImage(bounds: bounds, coloringMode: .greyscale)
|
let maskImage = nodeRenderer.renderToImage(bounds: bounds, coloringMode: .greyscale)
|
||||||
@ -352,7 +370,11 @@ class NodeRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getAllChildrenRecursive() -> [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] {
|
private func getAllChildren(_ nodeRenderer: NodeRenderer) -> [NodeRenderer] {
|
||||||
|
@ -16,19 +16,15 @@ class RenderUtils {
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
class func createNodeRenderer(
|
class func createNodeRenderer(_ node: Node, view: MacawView?, animationCache: AnimationCache?, parentRenderer: GroupRenderer? = nil) -> NodeRenderer {
|
||||||
_ node: Node,
|
|
||||||
view: MacawView?,
|
|
||||||
animationCache: AnimationCache?
|
|
||||||
) -> NodeRenderer {
|
|
||||||
if let group = node as? Group {
|
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 {
|
} 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 {
|
} 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 {
|
} 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)")
|
fatalError("Unsupported node: \(node)")
|
||||||
}
|
}
|
||||||
|
@ -8,37 +8,30 @@ import AppKit
|
|||||||
|
|
||||||
class ShapeRenderer: NodeRenderer {
|
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
|
self.shape = shape
|
||||||
super.init(node: shape, view: view, animationCache: animationCache)
|
super.init(node: shape, view: view, animationCache: animationCache, parentRenderer: parentRenderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
dispose()
|
dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func node() -> Node? {
|
override var node: Node {
|
||||||
return shape
|
return shape
|
||||||
}
|
}
|
||||||
|
|
||||||
override func doAddObservers() {
|
override func doAddObservers() {
|
||||||
super.doAddObservers()
|
super.doAddObservers()
|
||||||
|
|
||||||
guard let shape = shape else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
observe(shape.formVar)
|
observe(shape.formVar)
|
||||||
observe(shape.fillVar)
|
observe(shape.fillVar)
|
||||||
observe(shape.strokeVar)
|
observe(shape.strokeVar)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func doRender(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
|
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 {
|
if shape.fill == nil && shape.stroke == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -61,10 +54,6 @@ class ShapeRenderer: NodeRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func doFindNodeAt(path: NodePath, ctx: CGContext) -> NodePath? {
|
override func doFindNodeAt(path: NodePath, ctx: CGContext) -> NodePath? {
|
||||||
guard let shape = shape else {
|
|
||||||
return .none
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderUtils.setGeometry(shape.form, ctx: ctx)
|
RenderUtils.setGeometry(shape.form, ctx: ctx)
|
||||||
var drawingMode: CGPathDrawingMode?
|
var drawingMode: CGPathDrawingMode?
|
||||||
if let stroke = shape.stroke {
|
if let stroke = shape.stroke {
|
||||||
@ -172,9 +161,6 @@ class ShapeRenderer: NodeRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func drawPattern(_ pattern: Pattern, ctx: CGContext?, opacity: Double) {
|
fileprivate func drawPattern(_ pattern: Pattern, ctx: CGContext?, opacity: Double) {
|
||||||
guard let shape = shape else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var patternNode = pattern.content
|
var patternNode = pattern.content
|
||||||
if !pattern.userSpace, let node = BoundsUtils.createNodeFromRespectiveCoords(respectiveNode: pattern.content, absoluteLocus: shape.form) {
|
if !pattern.userSpace, let node = BoundsUtils.createNodeFromRespectiveCoords(respectiveNode: pattern.content, absoluteLocus: shape.form) {
|
||||||
patternNode = node
|
patternNode = node
|
||||||
@ -239,14 +225,6 @@ class ShapeRenderer: NodeRenderer {
|
|||||||
ctx!.restoreGState()
|
ctx!.restoreGState()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func replaceNode(with replacementNode: Node) {
|
|
||||||
super.replaceNode(with: replacementNode)
|
|
||||||
|
|
||||||
if let node = replacementNode as? Shape {
|
|
||||||
shape = node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Stroke {
|
extension Stroke {
|
||||||
|
@ -7,28 +7,24 @@ import AppKit
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
class TextRenderer: NodeRenderer {
|
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
|
self.text = text
|
||||||
super.init(node: text, view: view, animationCache: animationCache)
|
super.init(node: text, view: view, animationCache: animationCache, parentRenderer: parentRenderer)
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
dispose()
|
dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func node() -> Node? {
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
|
|
||||||
override func doAddObservers() {
|
override func doAddObservers() {
|
||||||
super.doAddObservers()
|
super.doAddObservers()
|
||||||
|
|
||||||
guard let text = text else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
observe(text.textVar)
|
observe(text.textVar)
|
||||||
observe(text.fontVar)
|
observe(text.fontVar)
|
||||||
observe(text.fillVar)
|
observe(text.fillVar)
|
||||||
@ -39,9 +35,6 @@ class TextRenderer: NodeRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func doRender(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
|
override func doRender(in context: CGContext, force: Bool, opacity: Double, coloringMode: ColoringMode = .rgb) {
|
||||||
guard let text = text else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let message = text.text
|
let message = text.text
|
||||||
let font = getMFont()
|
let font = getMFont()
|
||||||
@ -78,7 +71,7 @@ class TextRenderer: NodeRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func doFindNodeAt(path: NodePath, ctx: CGContext) -> NodePath? {
|
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
|
return .none
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +88,7 @@ class TextRenderer: NodeRenderer {
|
|||||||
// However it is needed for the Swift Package Manager to work accordingly.
|
// However it is needed for the Swift Package Manager to work accordingly.
|
||||||
return MFont()
|
return MFont()
|
||||||
}
|
}
|
||||||
guard let text = text, let textFont = text.font else {
|
guard let textFont = text.font else {
|
||||||
return MFont.systemFont(ofSize: MFont.mSystemFontSize)
|
return MFont.systemFont(ofSize: MFont.mSystemFontSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,9 +123,6 @@ class TextRenderer: NodeRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func getBounds(_ font: MFont) -> CGRect {
|
fileprivate func getBounds(_ font: MFont) -> CGRect {
|
||||||
guard let text = text else {
|
|
||||||
return .zero
|
|
||||||
}
|
|
||||||
|
|
||||||
var textAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: font]
|
var textAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: font]
|
||||||
if text.kerning != 0.0 {
|
if text.kerning != 0.0 {
|
||||||
@ -182,12 +172,4 @@ class TextRenderer: NodeRenderer {
|
|||||||
}
|
}
|
||||||
return MColor.black
|
return MColor.black
|
||||||
}
|
}
|
||||||
|
|
||||||
override func replaceNode(with replacementNode: Node) {
|
|
||||||
super.replaceNode(with: replacementNode)
|
|
||||||
|
|
||||||
if let node = replacementNode as? Text {
|
|
||||||
text = node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -230,14 +230,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
guard let renderer = renderer else {
|
guard let renderer = renderer else {
|
||||||
return .none
|
return .none
|
||||||
}
|
}
|
||||||
ctx.saveGState()
|
return renderer.findNodeAt(location: location, ctx: ctx)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func doFindNode(location: CGPoint) -> NodePath? {
|
private func doFindNode(location: CGPoint) -> NodePath? {
|
||||||
@ -275,12 +268,15 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
let inverted = node.place.invert()!
|
let inverted = node.place.invert()!
|
||||||
let loc = location.applying(inverted.toCG())
|
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())
|
let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque())
|
||||||
|
|
||||||
while let current = nodePath {
|
while let current = nodePath {
|
||||||
let node = current.node
|
let node = current.node
|
||||||
let relativeLocation = current.location
|
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])
|
let touchEvent = TouchEvent(node: node, points: [point])
|
||||||
|
|
||||||
if touchesOfNode[node] == nil {
|
if touchesOfNode[node] == nil {
|
||||||
@ -312,6 +308,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let invertedViewPlace = self.place.invert()!
|
||||||
var points = [TouchPoint]()
|
var points = [TouchPoint]()
|
||||||
for initialTouch in initialTouches {
|
for initialTouch in initialTouches {
|
||||||
guard let currentIndex = touchPoints.firstIndex(of: initialTouch) else {
|
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 location = CGPoint(x: currentTouch.x, y: currentTouch.y)
|
||||||
let inverted = currentNode.place.invert()!
|
let inverted = currentNode.place.invert()!
|
||||||
let loc = location.applying(inverted.toCG())
|
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)
|
points.append(point)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,6 +353,7 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let invertedViewPlace = self.place.invert()!
|
||||||
let touchPoints = convert(touches: touches)
|
let touchPoints = convert(touches: touches)
|
||||||
for touch in touchPoints {
|
for touch in touchPoints {
|
||||||
|
|
||||||
@ -364,8 +363,9 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
|||||||
let inverted = node.place.invert()!
|
let inverted = node.place.invert()!
|
||||||
let location = CGPoint(x: touch.x, y: touch.y)
|
let location = CGPoint(x: touch.x, y: touch.y)
|
||||||
let loc = location.applying(inverted.toCG())
|
let loc = location.applying(inverted.toCG())
|
||||||
|
let relativeToView = location.applying(invertedViewPlace.toCG())
|
||||||
let id = Int(bitPattern: Unmanaged.passUnretained(touch).toOpaque())
|
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])
|
let touchEvent = TouchEvent(node: node, points: [point])
|
||||||
|
|
||||||
node.handleTouchReleased(touchEvent)
|
node.handleTouchReleased(touchEvent)
|
||||||
@ -588,8 +588,8 @@ class LayoutHelper {
|
|||||||
|
|
||||||
public func getTransform(_ nodeRenderer: NodeRenderer, _ layout: ContentLayout, _ size: Size) -> Transform {
|
public func getTransform(_ nodeRenderer: NodeRenderer, _ layout: ContentLayout, _ size: Size) -> Transform {
|
||||||
setSize(size: size)
|
setSize(size: size)
|
||||||
let node = nodeRenderer.node()
|
let node = nodeRenderer.node
|
||||||
var rect = node?.bounds
|
var rect = node.bounds
|
||||||
if let canvas = node as? SVGCanvas {
|
if let canvas = node as? SVGCanvas {
|
||||||
if let view = nodeRenderer.view {
|
if let view = nodeRenderer.view {
|
||||||
rect = canvas.layout(size: view.bounds.size.toMacaw()).rect()
|
rect = canvas.layout(size: view.bounds.size.toMacaw()).rect()
|
||||||
|
@ -8,7 +8,6 @@ import AppKit
|
|||||||
|
|
||||||
class ShapeLayer: CAShapeLayer {
|
class ShapeLayer: CAShapeLayer {
|
||||||
weak var renderer: NodeRenderer?
|
weak var renderer: NodeRenderer?
|
||||||
var renderTransform: CGAffineTransform?
|
|
||||||
weak var animationCache: AnimationCache?
|
weak var animationCache: AnimationCache?
|
||||||
var shouldRenderContent = true
|
var shouldRenderContent = true
|
||||||
var isForceRenderingEnabled = true
|
var isForceRenderingEnabled = true
|
||||||
@ -22,10 +21,6 @@ class ShapeLayer: CAShapeLayer {
|
|||||||
let renderContext = RenderContext(view: .none)
|
let renderContext = RenderContext(view: .none)
|
||||||
renderContext.cgContext = ctx
|
renderContext.cgContext = ctx
|
||||||
|
|
||||||
if let renderTransform = renderTransform {
|
|
||||||
ctx.concatenate(renderTransform)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer?.directRender(in: ctx, force: isForceRenderingEnabled)
|
renderer?.directRender(in: ctx, force: isForceRenderingEnabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user