mirror of
https://github.com/exyte/Macaw.git
synced 2024-09-11 05:05:23 +03:00
Merge pull request #512 from f3dm76/task/fix-boy-animation
Fix morphing issues
This commit is contained in:
commit
42be7d1144
@ -483,6 +483,8 @@
|
||||
5BAE204C208E1EF4006BF277 /* SVGCanvas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAE201E208E1211006BF277 /* SVGCanvas.swift */; };
|
||||
5BAE2058208F24DE006BF277 /* SceneSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAE2057208F24DE006BF277 /* SceneSerialization.swift */; };
|
||||
5BAEA9C9206CEAA20049AAAE /* viewBox.svg in Resources */ = {isa = PBXBuildFile; fileRef = 5BAEA9C8206CEAA20049AAAE /* viewBox.svg */; };
|
||||
5BC2CA0D21C7B8F500AC46D9 /* CombinationAnimationGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2CA0C21C7B8F400AC46D9 /* CombinationAnimationGenerator.swift */; };
|
||||
5BC2CA0E21C7B8F900AC46D9 /* CombinationAnimationGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2CA0C21C7B8F400AC46D9 /* CombinationAnimationGenerator.swift */; };
|
||||
5BFEF5CE20B80A83008DAC11 /* BlendEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFEF5CC20B80A82008DAC11 /* BlendEffect.swift */; };
|
||||
5BFEF5CF20B80A83008DAC11 /* BlendEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFEF5CC20B80A82008DAC11 /* BlendEffect.swift */; };
|
||||
5BFEF5D020B80A83008DAC11 /* ColorMatrixEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BFEF5CD20B80A83008DAC11 /* ColorMatrixEffect.swift */; };
|
||||
@ -894,6 +896,7 @@
|
||||
5BAE2035208E163C006BF277 /* clip.reference */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = clip.reference; sourceTree = "<group>"; };
|
||||
5BAE2057208F24DE006BF277 /* SceneSerialization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneSerialization.swift; sourceTree = "<group>"; };
|
||||
5BAEA9C8206CEAA20049AAAE /* viewBox.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = viewBox.svg; sourceTree = "<group>"; };
|
||||
5BC2CA0C21C7B8F400AC46D9 /* CombinationAnimationGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CombinationAnimationGenerator.swift; sourceTree = "<group>"; };
|
||||
5BFEF5CC20B80A82008DAC11 /* BlendEffect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlendEffect.swift; sourceTree = "<group>"; };
|
||||
5BFEF5CD20B80A83008DAC11 /* ColorMatrixEffect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorMatrixEffect.swift; sourceTree = "<group>"; };
|
||||
5BFEF5D420BC1C1E008DAC11 /* paths-data-18-f-manual.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "paths-data-18-f-manual.svg"; sourceTree = "<group>"; };
|
||||
@ -1121,6 +1124,7 @@
|
||||
57E5E0FC1E3B393900D1CB28 /* OpacityGenerator.swift */,
|
||||
5B7D7ED221300D4A00B5ED00 /* TimingFunction.swift */,
|
||||
57E5E0FE1E3B393900D1CB28 /* TransformGenerator.swift */,
|
||||
5BC2CA0C21C7B8F400AC46D9 /* CombinationAnimationGenerator.swift */,
|
||||
);
|
||||
path = animation_generators;
|
||||
sourceTree = "<group>";
|
||||
@ -2094,6 +2098,7 @@
|
||||
5B6E194220AC58F900454E7E /* Color.swift in Sources */,
|
||||
57614B551F83D15600875933 /* Common_iOS.swift in Sources */,
|
||||
57614B561F83D15600875933 /* Common_macOS.swift in Sources */,
|
||||
5BC2CA0E21C7B8F900AC46D9 /* CombinationAnimationGenerator.swift in Sources */,
|
||||
57614B571F83D15600875933 /* MDisplayLink.swift in Sources */,
|
||||
57614B591F83D15600875933 /* Event.swift in Sources */,
|
||||
57614B5B1F83D15600875933 /* Interpolable.swift in Sources */,
|
||||
@ -2233,6 +2238,7 @@
|
||||
5B6E194120AC58F900454E7E /* Color.swift in Sources */,
|
||||
A718CD4D1F45C28F00966E06 /* Common_macOS.swift in Sources */,
|
||||
57F1087A1F53C92000DC365B /* MDisplayLink.swift in Sources */,
|
||||
5BC2CA0D21C7B8F500AC46D9 /* CombinationAnimationGenerator.swift in Sources */,
|
||||
57E5E1741E3B393900D1CB28 /* Event.swift in Sources */,
|
||||
57E5E15D1E3B393900D1CB28 /* Interpolable.swift in Sources */,
|
||||
57E5E1731E3B393900D1CB28 /* Variable.swift in Sources */,
|
||||
|
@ -45,8 +45,8 @@ class AnimationUtilsTests: XCTestCase {
|
||||
XCTAssert(aRenderer?.zPosition == 1)
|
||||
XCTAssert(bRenderer?.zPosition == 2)
|
||||
XCTAssert(cRenderer?.zPosition == 3)
|
||||
XCTAssert(dRenderer?.zPosition == 4 )
|
||||
XCTAssert(eRenderer?.zPosition == 5 )
|
||||
XCTAssert(fRenderer?.zPosition == 6 )
|
||||
XCTAssert(dRenderer?.zPosition == 4)
|
||||
XCTAssert(eRenderer?.zPosition == 5)
|
||||
XCTAssert(fRenderer?.zPosition == 6)
|
||||
}
|
||||
}
|
||||
|
@ -194,8 +194,9 @@ class MacawSVGTests: XCTestCase {
|
||||
|
||||
func createJSON(_ testResourcePath: String) {
|
||||
do {
|
||||
let node = try SVGParser.parse(fullPath: testResourcePath)
|
||||
let path = testResourcePath.replacingOccurrences(of: ".svg", with: ".reference")
|
||||
let bundle = Bundle(for: type(of: TestUtils()))
|
||||
let node = try SVGParser.parse(resource: testResourcePath, fromBundle: bundle)
|
||||
let path = testResourcePath + ".reference"
|
||||
try getJSONData(node: node).write(to: URL(fileURLWithPath: path))
|
||||
} catch {
|
||||
XCTFail(error.localizedDescription)
|
||||
|
@ -24,6 +24,12 @@
|
||||
],
|
||||
"node" : "Group"
|
||||
},
|
||||
{
|
||||
"contents" : [
|
||||
|
||||
],
|
||||
"node" : "Group"
|
||||
},
|
||||
{
|
||||
"contents" : [
|
||||
{
|
||||
@ -47,6 +53,105 @@
|
||||
}
|
||||
],
|
||||
"node" : "Group"
|
||||
},
|
||||
{
|
||||
"contents" : [
|
||||
{
|
||||
"contents" : [
|
||||
{
|
||||
"contents" : [
|
||||
|
||||
],
|
||||
"node" : "Group"
|
||||
}
|
||||
],
|
||||
"node" : "Group"
|
||||
}
|
||||
],
|
||||
"node" : "Group"
|
||||
}
|
||||
],
|
||||
"node" : "Group"
|
||||
},
|
||||
{
|
||||
"contents" : [
|
||||
{
|
||||
"contents" : [
|
||||
|
||||
],
|
||||
"node" : "Group"
|
||||
},
|
||||
{
|
||||
"contents" : [
|
||||
|
||||
],
|
||||
"node" : "Group"
|
||||
}
|
||||
],
|
||||
"node" : "Group"
|
||||
},
|
||||
{
|
||||
"contents" : [
|
||||
{
|
||||
"contents" : [
|
||||
{
|
||||
"contents" : [
|
||||
|
||||
],
|
||||
"node" : "Group"
|
||||
}
|
||||
],
|
||||
"node" : "Group"
|
||||
}
|
||||
],
|
||||
"node" : "Group"
|
||||
},
|
||||
{
|
||||
"contents" : [
|
||||
{
|
||||
"contents" : [
|
||||
|
||||
],
|
||||
"node" : "Group"
|
||||
},
|
||||
{
|
||||
"contents" : [
|
||||
|
||||
],
|
||||
"node" : "Group"
|
||||
}
|
||||
],
|
||||
"node" : "Group"
|
||||
},
|
||||
{
|
||||
"contents" : [
|
||||
{
|
||||
"contents" : [
|
||||
|
||||
],
|
||||
"node" : "Group"
|
||||
},
|
||||
{
|
||||
"contents" : [
|
||||
|
||||
],
|
||||
"node" : "Group"
|
||||
}
|
||||
],
|
||||
"node" : "Group"
|
||||
},
|
||||
{
|
||||
"contents" : [
|
||||
{
|
||||
"contents" : [
|
||||
{
|
||||
"contents" : [
|
||||
|
||||
],
|
||||
"node" : "Group"
|
||||
}
|
||||
],
|
||||
"node" : "Group"
|
||||
}
|
||||
],
|
||||
"node" : "Group"
|
||||
|
@ -149,7 +149,7 @@ class AnimationProducer {
|
||||
}
|
||||
|
||||
// MARK: - Sequence animation
|
||||
fileprivate func addAnimationSequence(_ animationSequnce: Animation) {
|
||||
func addAnimationSequence(_ animationSequnce: Animation) {
|
||||
guard let sequence = animationSequnce as? AnimationSequence else {
|
||||
return
|
||||
}
|
||||
@ -197,73 +197,6 @@ class AnimationProducer {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Combine animation
|
||||
fileprivate func addCombineAnimation(_ combineAnimation: Animation) {
|
||||
guard let combine = combineAnimation as? CombineAnimation else {
|
||||
return
|
||||
}
|
||||
|
||||
// Reversing
|
||||
if combine.autoreverses {
|
||||
combine.animations.forEach { animation in
|
||||
animation.autoreverses = true
|
||||
}
|
||||
}
|
||||
|
||||
// repeat count
|
||||
if combine.repeatCount > 0.00001 {
|
||||
var sequence = [Animation]()
|
||||
|
||||
for _ in 0..<Int(combine.repeatCount) {
|
||||
sequence.append(combine)
|
||||
}
|
||||
|
||||
combine.repeatCount = 0.0
|
||||
addAnimationSequence(sequence.sequence())
|
||||
return
|
||||
}
|
||||
|
||||
// Looking for longest animation
|
||||
var longestAnimation: BasicAnimation?
|
||||
combine.animations.forEach { animation in
|
||||
guard let longest = longestAnimation else {
|
||||
longestAnimation = animation
|
||||
return
|
||||
}
|
||||
|
||||
if longest.getDuration() < animation.getDuration() {
|
||||
longestAnimation = animation
|
||||
}
|
||||
}
|
||||
|
||||
// Attaching completion empty animation and potential next animation
|
||||
if let completion = combine.completion {
|
||||
let completionAnimation = EmptyAnimation(completion: completion)
|
||||
if let next = combine.next {
|
||||
completionAnimation.next = next
|
||||
}
|
||||
|
||||
longestAnimation?.next = completionAnimation
|
||||
|
||||
} else {
|
||||
if let next = combine.next {
|
||||
longestAnimation?.next = next
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
combine.removeFunc = { [weak combine] in
|
||||
combine?.animations.forEach { animation in
|
||||
animation.removeFunc?()
|
||||
}
|
||||
}
|
||||
|
||||
// Launching
|
||||
combine.animations.forEach { animation in
|
||||
self.addAnimation(animation)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Empty Animation
|
||||
fileprivate func executeCompletion(_ emptyAnimation: BasicAnimation) {
|
||||
emptyAnimation.completion?()
|
||||
|
@ -22,6 +22,15 @@ class AnimationUtils {
|
||||
parentRenderer = parentRenderer?.parentRenderer
|
||||
}
|
||||
|
||||
var rootRenderer = nodeRenderer
|
||||
while rootRenderer?.parentRenderer != nil {
|
||||
rootRenderer = rootRenderer?.parentRenderer
|
||||
}
|
||||
if let view = rootRenderer?.view, let bounds = rootRenderer?.node()?.bounds {
|
||||
let a = view.contentLayout.layout(rect: bounds, into: view.bounds.size.toMacaw())
|
||||
transform = transform.concat(with: a)
|
||||
}
|
||||
|
||||
return transform
|
||||
}
|
||||
|
||||
|
@ -3,9 +3,13 @@ import Foundation
|
||||
internal class CombineAnimation: BasicAnimation {
|
||||
|
||||
let animations: [BasicAnimation]
|
||||
let toNodes: [Node]
|
||||
let duration: Double
|
||||
|
||||
required init(animations: [BasicAnimation], delay: Double = 0.0, node: Node? = .none) {
|
||||
required init(animations: [BasicAnimation], during: Double = 1.0, delay: Double = 0.0, node: Node? = .none, toNodes: [Node] = []) {
|
||||
self.animations = animations
|
||||
self.duration = during
|
||||
self.toNodes = toNodes
|
||||
|
||||
super.init()
|
||||
|
||||
@ -78,12 +82,12 @@ internal class CombineAnimation: BasicAnimation {
|
||||
}
|
||||
|
||||
public extension Sequence where Iterator.Element: Animation {
|
||||
public func combine(delay: Double = 0.0, node: Node? = .none) -> Animation {
|
||||
public func combine(delay: Double = 0.0, node: Node? = .none, toNodes: [Node] = []) -> Animation {
|
||||
|
||||
var toCombine = [BasicAnimation]()
|
||||
self.forEach { animation in
|
||||
toCombine.append(animation as! BasicAnimation)
|
||||
}
|
||||
return CombineAnimation(animations: toCombine, delay: delay, node: node ?? toCombine.first?.node)
|
||||
return CombineAnimation(animations: toCombine, delay: delay, node: node ?? toCombine.first?.node, toNodes: toNodes)
|
||||
}
|
||||
}
|
||||
|
@ -82,88 +82,7 @@ public extension AnimatableVariable where T: ContentsInterpolation {
|
||||
fromNode = passedFromNode
|
||||
}
|
||||
|
||||
// Shapes on same hierarhy level
|
||||
let fromShapes = fromNode.contents.compactMap { $0 as? Shape }
|
||||
let toShapes = to.compactMap { $0 as? Shape }
|
||||
let minPathsNumber = min(fromShapes.count, toShapes.count)
|
||||
|
||||
var animations = [Animation]()
|
||||
for i in 0..<minPathsNumber {
|
||||
let fromShape = fromShapes[i]
|
||||
let toShape = toShapes[i]
|
||||
|
||||
let animation = ShapeAnimation(animatedNode: fromShape, finalValue: toShape, animationDuration: during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
if fromShapes.count > minPathsNumber {
|
||||
for i in minPathsNumber..<fromShapes.count {
|
||||
let shapeToHide = fromShapes[i]
|
||||
let animation = shapeToHide.opacityVar.animation(to: 0.0, during: during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
}
|
||||
|
||||
if toShapes.count > minPathsNumber {
|
||||
for i in minPathsNumber..<toShapes.count {
|
||||
let shapeToShow = toShapes[i]
|
||||
shapeToShow.opacity = 0.0
|
||||
fromNode.contents.append(shapeToShow)
|
||||
|
||||
let animation = shapeToShow.opacityVar.animation(to: 1.0, during: during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
}
|
||||
|
||||
// Groups on same hierahy level
|
||||
let fromGroups = fromNode.contents.compactMap { $0 as? Group }
|
||||
let toGroups = to.compactMap { $0 as? Group }
|
||||
let minGroupsNumber = min(fromGroups.count, toGroups.count)
|
||||
for i in 0..<minGroupsNumber {
|
||||
let fromGroup = fromGroups[i]
|
||||
let toGroup = toGroups[i]
|
||||
let groupAnimation = fromGroup.contentsVar.animation(to: toGroup.contents, during: during, delay: delay)
|
||||
animations.append(groupAnimation)
|
||||
}
|
||||
|
||||
for i in minGroupsNumber..<fromGroups.count {
|
||||
let groupToHide = fromGroups[i]
|
||||
let animation = groupToHide.opacityVar.animation(to: 0.0, during: during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
for i in minGroupsNumber..<toGroups.count {
|
||||
let groupToShow = toGroups[i]
|
||||
groupToShow.opacity = 0.0
|
||||
fromNode.contents.append(groupToShow)
|
||||
|
||||
let animation = groupToShow.opacityVar.animation(to: 1.0, during: during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
// Rest nodes
|
||||
let fromNodes = fromNode.contents.filter {
|
||||
!($0 is Group || $0 is Shape)
|
||||
}
|
||||
|
||||
let toNodes = to.filter {
|
||||
!($0 is Group || $0 is Shape)
|
||||
}
|
||||
|
||||
fromNodes.forEach { node in
|
||||
let animation = node.opacityVar.animation(to: 0.0, during: during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
toNodes.forEach { node in
|
||||
node.opacity = 0.0
|
||||
fromNode.contents.append(node)
|
||||
|
||||
let animation = node.opacityVar.animation(to: 1.0, during: during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
return animations.combine(node: fromNode)
|
||||
return CombineAnimation(animations: [], during: during, node: fromNode, toNodes: to)
|
||||
}
|
||||
|
||||
public func animate(from: Group? = nil, to: [Node], during: Double = 1.0, delay: Double = 0.0) {
|
||||
|
@ -7,7 +7,9 @@
|
||||
//
|
||||
|
||||
class ShapeAnimation: AnimationImpl<Shape> {
|
||||
convenience init(animatedNode: Shape, finalValue: Shape, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
let toParentGlobalTransfrom: Transform
|
||||
|
||||
convenience init(animatedNode: Shape, finalValue: Shape, toParentGlobalTransfrom: Transform = .identity, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
|
||||
let interpolationFunc = { (t: Double) -> Shape in
|
||||
if t == 0 {
|
||||
@ -17,10 +19,11 @@ class ShapeAnimation: AnimationImpl<Shape> {
|
||||
return finalValue
|
||||
}
|
||||
|
||||
self.init(animatedNode: animatedNode, valueFunc: interpolationFunc, animationDuration: animationDuration, delay: delay, autostart: autostart, fps: fps)
|
||||
self.init(animatedNode: animatedNode, valueFunc: interpolationFunc, toParentGlobalTransfrom: toParentGlobalTransfrom, animationDuration: animationDuration, delay: delay, autostart: autostart, fps: fps)
|
||||
}
|
||||
|
||||
init(animatedNode: Shape, valueFunc: @escaping (Double) -> Shape, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
init(animatedNode: Shape, valueFunc: @escaping (Double) -> Shape, toParentGlobalTransfrom: Transform = .identity, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
self.toParentGlobalTransfrom = toParentGlobalTransfrom
|
||||
super.init(observableValue: AnimatableVariable<Shape>(animatedNode), valueFunc: valueFunc, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .shape
|
||||
node = animatedNode
|
||||
@ -30,7 +33,8 @@ class ShapeAnimation: AnimationImpl<Shape> {
|
||||
}
|
||||
}
|
||||
|
||||
init(animatedNode: Shape, factory: @escaping (() -> ((Double) -> Shape)), animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
init(animatedNode: Shape, factory: @escaping (() -> ((Double) -> Shape)), toParentGlobalTransfrom: Transform = .identity, animationDuration: Double, delay: Double = 0.0, autostart: Bool = false, fps: UInt = 30) {
|
||||
self.toParentGlobalTransfrom = toParentGlobalTransfrom
|
||||
super.init(observableValue: AnimatableVariable<Shape>(animatedNode), factory: factory, animationDuration: animationDuration, delay: delay, fps: fps)
|
||||
type = .shape
|
||||
node = animatedNode
|
||||
|
@ -51,8 +51,8 @@ class AnimationCache {
|
||||
let cgRect = shapeBounds.toCG()
|
||||
|
||||
let origFrame = CGRect(x: 0.0, y: 0.0,
|
||||
width: round(cgRect.width),
|
||||
height: round(cgRect.height))
|
||||
width: cgRect.width,
|
||||
height: cgRect.height)
|
||||
|
||||
layer.bounds = origFrame
|
||||
layer.anchorPoint = CGPoint(
|
||||
@ -78,7 +78,7 @@ class AnimationCache {
|
||||
}
|
||||
|
||||
layer.opacity = Float(node.opacity)
|
||||
layer.node = node
|
||||
layer.renderer = renderer
|
||||
|
||||
layer.contentsScale = calculateAnimationScale(animation: animation)
|
||||
|
||||
|
@ -0,0 +1,185 @@
|
||||
//
|
||||
// CombinationAnimationGenerator.swift
|
||||
// Macaw
|
||||
//
|
||||
// Created by Alisa Mylnikova on 17/12/2018.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension AnimationProducer {
|
||||
|
||||
func createChildAnimations(_ combineAnimation: Animation, globalToPosition: Transform = .identity, animations: [Animation] = []) -> [Animation] {
|
||||
guard let combine = combineAnimation as? CombineAnimation else {
|
||||
return animations
|
||||
}
|
||||
|
||||
let globalToPosition = globalToPosition
|
||||
let during = combine.duration
|
||||
let delay = combine.delay
|
||||
let fromNode = combine.node as! Group
|
||||
let to = combine.toNodes
|
||||
|
||||
// Shapes on same hierarhy level
|
||||
let fromShapes = fromNode.contents.compactMap { $0 as? Shape }
|
||||
let toShapes = to.compactMap { $0 as? Shape }
|
||||
let minPathsNumber = min(fromShapes.count, toShapes.count)
|
||||
|
||||
var animations = [Animation]()
|
||||
for i in 0..<minPathsNumber {
|
||||
let fromShape = fromShapes[i]
|
||||
let toShape = toShapes[i]
|
||||
|
||||
let animation = ShapeAnimation(animatedNode: fromShape, finalValue: toShape, toParentGlobalTransfrom: globalToPosition, animationDuration: during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
if fromShapes.count > minPathsNumber {
|
||||
for i in minPathsNumber..<fromShapes.count {
|
||||
let shapeToHide = fromShapes[i]
|
||||
let animation = shapeToHide.opacityVar.animation(to: 0.0, during: during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
}
|
||||
|
||||
if toShapes.count > minPathsNumber {
|
||||
for i in minPathsNumber..<toShapes.count {
|
||||
let shapeToShow = toShapes[i]
|
||||
shapeToShow.opacity = 0.0
|
||||
fromNode.contents.append(shapeToShow)
|
||||
|
||||
let animation = shapeToShow.opacityVar.animation(to: 1.0, during: during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
}
|
||||
|
||||
// Groups on same hierahy level
|
||||
let fromGroups = fromNode.contents.compactMap { $0 as? Group }
|
||||
let toGroups = to.compactMap { $0 as? Group }
|
||||
let minGroupsNumber = min(fromGroups.count, toGroups.count)
|
||||
for i in 0..<minGroupsNumber {
|
||||
let fromGroup = fromGroups[i]
|
||||
let toGroup = toGroups[i]
|
||||
|
||||
let groupAnimation = fromGroup.contentsVar.animation(to: toGroup.contents, during: during, delay: delay)
|
||||
let groupAnimations = createChildAnimations(groupAnimation, globalToPosition: globalToPosition.concat(with: toGroup.place), animations: animations)
|
||||
animations.append(contentsOf: groupAnimations)
|
||||
}
|
||||
|
||||
for i in minGroupsNumber..<fromGroups.count {
|
||||
let groupToHide = fromGroups[i]
|
||||
let animation = groupToHide.opacityVar.animation(to: 0.0, during: during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
for i in minGroupsNumber..<toGroups.count {
|
||||
let groupToShow = toGroups[i]
|
||||
groupToShow.opacity = 0.0
|
||||
fromNode.contents.append(groupToShow)
|
||||
|
||||
let animation = groupToShow.opacityVar.animation(to: 1.0, during: during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
// Rest nodes
|
||||
let fromNodes = fromNode.contents.filter {
|
||||
!($0 is Group || $0 is Shape)
|
||||
}
|
||||
|
||||
let toNodes = to.filter {
|
||||
!($0 is Group || $0 is Shape)
|
||||
}
|
||||
|
||||
fromNodes.forEach { node in
|
||||
let animation = node.opacityVar.animation(to: 0.0, during: during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
toNodes.forEach { node in
|
||||
node.opacity = 0.0
|
||||
fromNode.contents.append(node)
|
||||
|
||||
let animation = node.opacityVar.animation(to: 1.0, during: during, delay: delay)
|
||||
animations.append(animation)
|
||||
}
|
||||
|
||||
return animations
|
||||
}
|
||||
|
||||
// MARK: - Combine animation
|
||||
func addCombineAnimation(_ combineAnimation: Animation) {
|
||||
guard let combine = combineAnimation as? CombineAnimation,
|
||||
let renderer = combine.nodeRenderer,
|
||||
let view = renderer.view else {
|
||||
return
|
||||
}
|
||||
|
||||
var animations = combine.animations
|
||||
if let toBounds = combine.toNodes.group().bounds {
|
||||
let globalTransform = view.contentLayout.layout(rect: toBounds, into: view.frame.size.toMacaw())
|
||||
let childAnimations = createChildAnimations(combine, globalToPosition: globalTransform) as! [BasicAnimation]
|
||||
animations.append(contentsOf: childAnimations)
|
||||
}
|
||||
|
||||
// Reversing
|
||||
if combine.autoreverses {
|
||||
animations.forEach { animation in
|
||||
animation.autoreverses = true
|
||||
}
|
||||
}
|
||||
|
||||
// repeat count
|
||||
if combine.repeatCount > 0.00001 {
|
||||
var sequence = [Animation]()
|
||||
|
||||
for _ in 0..<Int(combine.repeatCount) {
|
||||
sequence.append(combine)
|
||||
}
|
||||
|
||||
combine.repeatCount = 0.0
|
||||
addAnimationSequence(sequence.sequence())
|
||||
return
|
||||
}
|
||||
|
||||
// Looking for longest animation
|
||||
var longestAnimation: BasicAnimation?
|
||||
animations.forEach { animation in
|
||||
guard let longest = longestAnimation else {
|
||||
longestAnimation = animation
|
||||
return
|
||||
}
|
||||
|
||||
if longest.getDuration() < animation.getDuration() {
|
||||
longestAnimation = animation
|
||||
}
|
||||
}
|
||||
|
||||
// Attaching completion empty animation and potential next animation
|
||||
if let completion = combine.completion {
|
||||
let completionAnimation = EmptyAnimation(completion: completion)
|
||||
if let next = combine.next {
|
||||
completionAnimation.next = next
|
||||
}
|
||||
|
||||
longestAnimation?.next = completionAnimation
|
||||
|
||||
} else {
|
||||
if let next = combine.next {
|
||||
longestAnimation?.next = next
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
combine.removeFunc = {
|
||||
animations.forEach { animation in
|
||||
animation.removeFunc?()
|
||||
}
|
||||
}
|
||||
|
||||
// Launching
|
||||
animations.forEach { animation in
|
||||
self.addAnimation(animation)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -148,8 +148,9 @@ fileprivate func generateShapeAnimation(from: Shape, to: Shape, animation: Shape
|
||||
let scaleAnimation = CABasicAnimation(keyPath: "transform")
|
||||
scaleAnimation.duration = duration
|
||||
let parentPos = AnimationUtils.absolutePosition(animation.nodeRenderer?.parentRenderer)
|
||||
let fromPos = parentPos.concat(with: to.place)
|
||||
let toPos = parentPos.concat(with: to.place)
|
||||
let fromPos = parentPos.concat(with: from.place)
|
||||
let toParentPos = animation.toParentGlobalTransfrom
|
||||
let toPos = toParentPos.concat(with: to.place)
|
||||
scaleAnimation.fromValue = CATransform3DMakeAffineTransform(fromPos.toCG())
|
||||
scaleAnimation.toValue = CATransform3DMakeAffineTransform(toPos.toCG())
|
||||
|
||||
|
@ -68,6 +68,12 @@ class GroupRenderer: NodeRenderer {
|
||||
}) {
|
||||
renderers = updatedRenderers
|
||||
}
|
||||
|
||||
var parent: NodeRenderer = self
|
||||
while let parentRenderer = parent.parentRenderer {
|
||||
parent = parentRenderer
|
||||
}
|
||||
parent.calculateZPositionRecursively()
|
||||
}
|
||||
|
||||
override func replaceNode(with replacementNode: Node) {
|
||||
|
@ -282,6 +282,10 @@ class NodeRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
func calculateZPositionRecursively() {
|
||||
calculateZPosition(self)
|
||||
}
|
||||
|
||||
private func applyClip(in context: CGContext) {
|
||||
guard let node = node() else {
|
||||
return
|
||||
@ -358,3 +362,15 @@ extension NodeRenderer: AnimationObserver {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@discardableResult fileprivate func calculateZPosition(_ nodeRenderer: NodeRenderer, currentIndex: Int = 0) -> Int {
|
||||
nodeRenderer.zPosition = currentIndex
|
||||
if let groupRenderer = nodeRenderer as? GroupRenderer {
|
||||
var i = currentIndex + 1
|
||||
for child in groupRenderer.renderers {
|
||||
i = calculateZPosition(child, currentIndex: i)
|
||||
}
|
||||
return i
|
||||
}
|
||||
return currentIndex + 1
|
||||
}
|
||||
|
@ -178,8 +178,6 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
||||
}
|
||||
|
||||
override open func draw(_ rect: CGRect) {
|
||||
calculateZPosition(renderer)
|
||||
|
||||
context.cgContext = MGraphicsGetCurrentContext()
|
||||
guard let ctx = context.cgContext else {
|
||||
return
|
||||
@ -192,22 +190,11 @@ open class MacawView: MView, MGestureRecognizerDelegate {
|
||||
guard let renderer = renderer else {
|
||||
return
|
||||
}
|
||||
renderer.calculateZPositionRecursively()
|
||||
ctx.concatenate(layoutHelper.getTransform(renderer, contentLayout, bounds.size.toMacaw()))
|
||||
renderer.render(in: ctx, force: false, opacity: node.opacity)
|
||||
}
|
||||
|
||||
@discardableResult private func calculateZPosition(_ nodeRenderer: NodeRenderer?, currentIndex: Int = 0) -> Int {
|
||||
nodeRenderer?.zPosition = currentIndex
|
||||
if let groupRenderer = nodeRenderer as? GroupRenderer {
|
||||
var i = currentIndex + 1
|
||||
for child in groupRenderer.renderers {
|
||||
i = calculateZPosition(child, currentIndex: i)
|
||||
}
|
||||
return i
|
||||
}
|
||||
return currentIndex + 1
|
||||
}
|
||||
|
||||
public final func findNodeAt(location: CGPoint) -> Node? {
|
||||
guard let ctx = context.cgContext else {
|
||||
return .none
|
||||
|
@ -7,7 +7,7 @@ import AppKit
|
||||
#endif
|
||||
|
||||
class ShapeLayer: CAShapeLayer {
|
||||
weak var node: Node?
|
||||
weak var renderer: NodeRenderer?
|
||||
var renderTransform: CGAffineTransform?
|
||||
weak var animationCache: AnimationCache?
|
||||
var shouldRenderContent = true
|
||||
@ -19,14 +19,6 @@ class ShapeLayer: CAShapeLayer {
|
||||
return
|
||||
}
|
||||
|
||||
guard let node = node else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let animationCache = animationCache else {
|
||||
return
|
||||
}
|
||||
|
||||
let renderContext = RenderContext(view: .none)
|
||||
renderContext.cgContext = ctx
|
||||
|
||||
@ -34,8 +26,7 @@ class ShapeLayer: CAShapeLayer {
|
||||
ctx.concatenate(renderTransform)
|
||||
}
|
||||
|
||||
let renderer = RenderUtils.createNodeRenderer(node, view: renderContext.view, animationCache: animationCache)
|
||||
renderer.directRender(in: ctx, force: isForceRenderingEnabled)
|
||||
renderer.dispose()
|
||||
renderer!.directRender(in: ctx, force: isForceRenderingEnabled)
|
||||
renderer!.dispose()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user