Skip to content

Commit

Permalink
Fix relative timestamps in child animations (#17)
Browse files Browse the repository at this point in the history
Fixes calculation of relative timestamp for execution blocks and property assignments in child animations.
  • Loading branch information
NickEntin authored Feb 21, 2020
1 parent e70a080 commit b2dc16e
Show file tree
Hide file tree
Showing 2 changed files with 215 additions and 2 deletions.
213 changes: 213 additions & 0 deletions Example/Unit Tests/ChildAnimationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,217 @@ final class ChildAnimationTests: XCTestCase {
_ = animationInstance
}

// MARK: - Tests - Execution Blocks

func testExecutionBlocks_fullDurationChild() {
var executedBlocks: [String] = []

var child = Animation<Element>()
child.addExecution(
onForward: { _ in executedBlocks.append("A") },
at: 0
)
child.addExecution(
onForward: { _ in executedBlocks.append("B") },
at: 0.5
)
child.addExecution(
onForward: { _ in executedBlocks.append("C") },
at: 1
)

var parent = Animation<Element>()
parent.addChild(child, for: \.self, startingAt: 0, relativeDuration: 1)

let element = Element()

let driver = TestDriver()

let animationInstance = AnimationInstance(
animation: parent,
element: element,
driver: driver
)

driver.runForward(to: 0)
XCTAssertEqual(executedBlocks, ["A"])

driver.runForward(to: 0.5)
XCTAssertEqual(executedBlocks, ["A", "B"])

driver.runForward(to: 1)
XCTAssertEqual(executedBlocks, ["A", "B", "C"])

_ = animationInstance
}

func testExecutionBlocks_partialDurationChildren() {
var executedBlocks: [String] = []

func makeChild(prefix: String) -> Animation<Element> {
var child = Animation<Element>()
child.addExecution(
onForward: { _ in executedBlocks.append("\(prefix)A") },
at: 0
)
child.addExecution(
onForward: { _ in executedBlocks.append("\(prefix)B") },
at: 0.5
)
child.addExecution(
onForward: { _ in executedBlocks.append("\(prefix)C") },
at: 1
)
return child
}

var parent = Animation<Element>()
parent.addChild(makeChild(prefix: "1"), for: \.self, startingAt: 0, relativeDuration: 0.2)
parent.addChild(makeChild(prefix: "2"), for: \.self, startingAt: 0.3, relativeDuration: 0.5)
parent.addChild(makeChild(prefix: "3"), for: \.self, startingAt: 0.5, relativeDuration: 0.5)

let element = Element()

let driver = TestDriver()

let animationInstance = AnimationInstance(
animation: parent,
element: element,
driver: driver
)

driver.runForward(to: 0)
XCTAssertEqual(executedBlocks, ["1A"])

driver.runForward(to: 0.1)
XCTAssertEqual(executedBlocks, ["1A", "1B"])

driver.runForward(to: 0.2)
XCTAssertEqual(executedBlocks, ["1A", "1B", "1C"])

driver.runForward(to: 0.3)
XCTAssertEqual(executedBlocks, ["1A", "1B", "1C", "2A"])

driver.runForward(to: 0.5)
XCTAssertEqual(executedBlocks, ["1A", "1B", "1C", "2A", "3A"])

driver.runForward(to: 0.55)
XCTAssertEqual(executedBlocks, ["1A", "1B", "1C", "2A", "3A", "2B"])

driver.runForward(to: 0.75)
XCTAssertEqual(executedBlocks, ["1A", "1B", "1C", "2A", "3A", "2B", "3B"])

driver.runForward(to: 0.8)
XCTAssertEqual(executedBlocks, ["1A", "1B", "1C", "2A", "3A", "2B", "3B", "2C"])

driver.runForward(to: 1)
XCTAssertEqual(executedBlocks, ["1A", "1B", "1C", "2A", "3A", "2B", "3B", "2C", "3C"])

_ = animationInstance
}

// MARK: - Tests - Property Assignments

func testPropertyAssignments_fullDurationChild() {
var child = Animation<Subelement>()
child.addAssignment(for: \.propertyOne, at: 0, value: 0)
child.addAssignment(for: \.propertyOne, at: 0.5, value: 0.5)
child.addAssignment(for: \.propertyOne, at: 1, value: 1)

var parent = Animation<Element>()
parent.addChild(child, for: \.subelementOne, startingAt: 0, relativeDuration: 1)

let element = Element()

let driver = TestDriver()

let animationInstance = AnimationInstance(
animation: parent,
element: element,
driver: driver
)

driver.runForward(to: 0)
XCTAssertEqual(element.subelementOne.propertyOne, 0)

driver.runForward(to: 0.5)
XCTAssertEqual(element.subelementOne.propertyOne, 0.5)

driver.runForward(to: 1)
XCTAssertEqual(element.subelementOne.propertyOne, 1)

_ = animationInstance
}

func testPropertyAssignments_partialDurationChildren() {
var child = Animation<Subelement>()
child.addAssignment(for: \.propertyOne, at: 0, value: 0)
child.addAssignment(for: \.propertyOne, at: 0.5, value: 0.5)
child.addAssignment(for: \.propertyOne, at: 1, value: 1)

var parent = Animation<Element>()
parent.addChild(child, for: \.subelementOne, startingAt: 0, relativeDuration: 0.2)
parent.addChild(child, for: \.subelementTwo, startingAt: 0.3, relativeDuration: 0.5)
parent.addChild(child, for: \.subelementThree, startingAt: 0.5, relativeDuration: 0.5)

let element = Element()

let driver = TestDriver()

let animationInstance = AnimationInstance(
animation: parent,
element: element,
driver: driver
)

driver.runForward(to: 0)
XCTAssertEqual(element.subelementOne.propertyOne, 0)
XCTAssertEqual(element.subelementTwo.propertyOne, -1)
XCTAssertEqual(element.subelementThree.propertyOne, -1)

driver.runForward(to: 0.1)
XCTAssertEqual(element.subelementOne.propertyOne, 0.5)
XCTAssertEqual(element.subelementTwo.propertyOne, -1)
XCTAssertEqual(element.subelementThree.propertyOne, -1)

driver.runForward(to: 0.2)
XCTAssertEqual(element.subelementOne.propertyOne, 1)
XCTAssertEqual(element.subelementTwo.propertyOne, -1)
XCTAssertEqual(element.subelementThree.propertyOne, -1)

driver.runForward(to: 0.3)
XCTAssertEqual(element.subelementOne.propertyOne, 1)
XCTAssertEqual(element.subelementTwo.propertyOne, 0)
XCTAssertEqual(element.subelementThree.propertyOne, -1)

driver.runForward(to: 0.5)
XCTAssertEqual(element.subelementOne.propertyOne, 1)
XCTAssertEqual(element.subelementTwo.propertyOne, 0)
XCTAssertEqual(element.subelementThree.propertyOne, 0)

driver.runForward(to: 0.55)
XCTAssertEqual(element.subelementOne.propertyOne, 1)
XCTAssertEqual(element.subelementTwo.propertyOne, 0.5)
XCTAssertEqual(element.subelementThree.propertyOne, 0)

driver.runForward(to: 0.75)
XCTAssertEqual(element.subelementOne.propertyOne, 1)
XCTAssertEqual(element.subelementTwo.propertyOne, 0.5)
XCTAssertEqual(element.subelementThree.propertyOne, 0.5)

driver.runForward(to: 0.8)
XCTAssertEqual(element.subelementOne.propertyOne, 1)
XCTAssertEqual(element.subelementTwo.propertyOne, 1)
XCTAssertEqual(element.subelementThree.propertyOne, 0.5)

driver.runForward(to: 1)
XCTAssertEqual(element.subelementOne.propertyOne, 1)
XCTAssertEqual(element.subelementTwo.propertyOne, 1)
XCTAssertEqual(element.subelementThree.propertyOne, 1)

_ = animationInstance
}

}

// MARK: -
Expand All @@ -194,6 +405,8 @@ private extension ChildAnimationTests {

var subelementTwo: Subelement = .init()

var subelementThree: Subelement = .init()

}

final class Subelement {
Expand Down
4 changes: 2 additions & 2 deletions Stagehand/Classes/Core/Animation/Animation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ public struct Animation<ElementType: AnyObject> {
assignments.append(
contentsOf: childAnimation.assignments.map { childAssignment in
// Adjust the relative timestamp for the child's animation curve.
let relativeTimestamp = relativeStartTimestamp + (childAssignment.relativeTimestamp / relativeDuration)
let relativeTimestamp = relativeStartTimestamp + (childAssignment.relativeTimestamp * relativeDuration)
let adjustedRelativeTimestamp = childAnimation.curve.adjustedProgress(for: relativeTimestamp)

return Assignment(
Expand All @@ -395,7 +395,7 @@ public struct Animation<ElementType: AnyObject> {
executionBlocks.append(
contentsOf: childAnimation.executionBlocks.map { childExecutionBlock in
// Adjust the relative timestamp for the child's animation curve.
let relativeTimestamp = relativeStartTimestamp + (childExecutionBlock.relativeTimestamp / relativeDuration)
let relativeTimestamp = relativeStartTimestamp + (childExecutionBlock.relativeTimestamp * relativeDuration)
let adjustedRelativeTimestamp = childAnimation.curve.adjustedProgress(for: relativeTimestamp)

return ExecutionBlock(
Expand Down

0 comments on commit b2dc16e

Please sign in to comment.