From bcb7136ab624eced8d5533388bfb2bfd4088276e Mon Sep 17 00:00:00 2001 From: Maxwell Talbot Date: Fri, 7 May 2021 15:24:47 +0100 Subject: [PATCH 1/2] adding loop mode and direction and example for them. --- .../RiveExample.xcodeproj/project.pbxproj | 4 + Example-iOS/Source/Main.storyboard | 237 ++++++++++++++++-- Example-iOS/Source/UIkit/LoopMode.swift | 161 ++++++++++++ Source/Renderer/Rive.h | 26 ++ Source/Renderer/Rive.mm | 22 ++ Source/Views/RiveView.swift | 143 +++++++++-- 6 files changed, 558 insertions(+), 35 deletions(-) create mode 100644 Example-iOS/Source/UIkit/LoopMode.swift diff --git a/Example-iOS/RiveExample.xcodeproj/project.pbxproj b/Example-iOS/RiveExample.xcodeproj/project.pbxproj index 38cc4ce4..5112dc8f 100644 --- a/Example-iOS/RiveExample.xcodeproj/project.pbxproj +++ b/Example-iOS/RiveExample.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ 042C88EB2644447500E7DBB2 /* clipping.riv in Resources */ = {isa = PBXBuildFile; fileRef = 042C88D92644447500E7DBB2 /* clipping.riv */; }; 042C88EC2644447500E7DBB2 /* vader.riv in Resources */ = {isa = PBXBuildFile; fileRef = 042C88DA2644447500E7DBB2 /* vader.riv */; }; 04A8F6C126452E31002C909A /* RiveRuntime.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04A8F6BD26452E10002C909A /* RiveRuntime.framework */; }; + 04A8F6C526454711002C909A /* LoopMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04A8F6C426454711002C909A /* LoopMode.swift */; }; C9A84F38264495600014D8E0 /* RiveSwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A84F37264495600014D8E0 /* RiveSwiftUIView.swift */; }; C9A84F5E2644A75A0014D8E0 /* ExamplesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A84F5D2644A75A0014D8E0 /* ExamplesViewController.swift */; }; C9A84F602644AB6B0014D8E0 /* RiveViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A84F5F2644AB6B0014D8E0 /* RiveViewWrapper.swift */; }; @@ -97,6 +98,7 @@ 042C88D92644447500E7DBB2 /* clipping.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = clipping.riv; sourceTree = ""; }; 042C88DA2644447500E7DBB2 /* vader.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = vader.riv; sourceTree = ""; }; 04A8F6AB26452A91002C909A /* RiveRuntime.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RiveRuntime.xcodeproj; path = ../RiveRuntime.xcodeproj; sourceTree = ""; }; + 04A8F6C426454711002C909A /* LoopMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopMode.swift; sourceTree = ""; }; C9A84F37264495600014D8E0 /* RiveSwiftUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveSwiftUIView.swift; sourceTree = ""; }; C9A84F5D2644A75A0014D8E0 /* ExamplesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExamplesViewController.swift; sourceTree = ""; }; C9A84F5F2644AB6B0014D8E0 /* RiveViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveViewWrapper.swift; sourceTree = ""; }; @@ -129,6 +131,7 @@ 042C888B2643EEE300E7DBB2 /* Layout.swift */, 042C888F2644250D00E7DBB2 /* MultipleAnimations.swift */, C9A84F5D2644A75A0014D8E0 /* ExamplesViewController.swift */, + 04A8F6C426454711002C909A /* LoopMode.swift */, ); path = UIkit; sourceTree = ""; @@ -355,6 +358,7 @@ 042C888E2644230700E7DBB2 /* utility.swift in Sources */, 042C88902644250D00E7DBB2 /* MultipleAnimations.swift in Sources */, C9A84F38264495600014D8E0 /* RiveSwiftUIView.swift in Sources */, + 04A8F6C526454711002C909A /* LoopMode.swift in Sources */, C9C73E9A24FC471E00EF9516 /* SceneDelegate.swift in Sources */, C9A84F5E2644A75A0014D8E0 /* ExamplesViewController.swift in Sources */, 042C888C2643EEE300E7DBB2 /* Layout.swift in Sources */, diff --git a/Example-iOS/Source/Main.storyboard b/Example-iOS/Source/Main.storyboard index efda88eb..532650a2 100644 --- a/Example-iOS/Source/Main.storyboard +++ b/Example-iOS/Source/Main.storyboard @@ -18,7 +18,7 @@ - + + @@ -259,31 +266,233 @@ - + - - + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/Example-iOS/Source/UIkit/LoopMode.swift b/Example-iOS/Source/UIkit/LoopMode.swift new file mode 100644 index 00000000..39b711ae --- /dev/null +++ b/Example-iOS/Source/UIkit/LoopMode.swift @@ -0,0 +1,161 @@ +// +// LoopMode.swift +// RiveExample +// +// Created by Maxwell Talbot on 07/05/2021. +// Copyright © 2021 Rive. All rights reserved. +// + +import UIKit +import RiveRuntime + +class LoopMode: UIView { + typealias ButtonAction = ()->Void + @IBOutlet var riveView: RiveView! + + @IBAction func resetButton(_ sender: Any) { + triggeredResetButton?() + } + @IBAction func forwardsButton(_ sender: Any){ + triggeredForwardsButton?() + } + @IBAction func autoButton(_ sender: Any){ + triggeredAutoButton?() + } + @IBAction func backwardsButton(_ sender: Any){ + triggeredBackwardsButton?() + } + + @IBAction func rotatePlayButton(_ sender: Any){ + triggeredRotatePlayButton?() + } + @IBAction func rotateOneShotButton(_ sender: Any){ + triggeredRotateOneShotButton?() + } + @IBAction func rotateLoopButton(_ sender: Any){ + triggeredRotateLoopButton?() + } + @IBAction func rotatePingPongButton(_ sender: Any){ + triggeredRotatePingPongButton?() + } + + @IBAction func loopDownPlayButton(_ sender: Any){ + triggeredLoopDownPlayButton?() + } + @IBAction func loopDownOneShotButton(_ sender: Any){ + triggeredLoopDownOneShotButton?() + } + @IBAction func loopDownLoopButton(_ sender: Any){ + triggeredLoopDownLoopButton?() + } + @IBAction func loopDownPingPongButton(_ sender: Any){ + triggeredLoopDownPingPongButton?() + } + + @IBAction func ltrPlayButton(_ sender: Any){ + triggeredLtrPlayButton?() + } + @IBAction func ltrOneShotButton(_ sender: Any){ + triggeredLtrOneShotButton?() + } + @IBAction func ltrLoopButton(_ sender: Any){ + triggeredLtrLoopButton?() + } + @IBAction func ltrPingPongButton(_ sender: Any){ + triggeredLtrPingPongButton?() + } + + var triggeredResetButton: ButtonAction? + var triggeredForwardsButton: ButtonAction? + var triggeredAutoButton: ButtonAction? + var triggeredBackwardsButton: ButtonAction? + + var triggeredRotatePlayButton: ButtonAction? + var triggeredRotateOneShotButton: ButtonAction? + var triggeredRotateLoopButton: ButtonAction? + var triggeredRotatePingPongButton: ButtonAction? + + var triggeredLoopDownPlayButton: ButtonAction? + var triggeredLoopDownOneShotButton: ButtonAction? + var triggeredLoopDownLoopButton: ButtonAction? + var triggeredLoopDownPingPongButton: ButtonAction? + + var triggeredLtrPlayButton: ButtonAction? + var triggeredLtrOneShotButton: ButtonAction? + var triggeredLtrLoopButton: ButtonAction? + var triggeredLtrPingPongButton: ButtonAction? + +} + +class LoopModeController: UIViewController { + let loopResourceName = "loopy" + var direction = Direction.DirectionAuto + + override public func loadView() { + super.loadView() + + guard let loopModeView = view as? LoopMode else { + fatalError("Could not find LayoutView") + } + + loopModeView.riveView.configure( + withRiveFile: getRiveFile(resourceName: loopResourceName), + andAutoPlay: false + ) + + loopModeView.triggeredResetButton = { + + } + loopModeView.triggeredForwardsButton = { + self.direction = Direction.DirectionForwards + } + loopModeView.triggeredAutoButton = { + self.direction = Direction.DirectionAuto + } + loopModeView.triggeredBackwardsButton = { + self.direction = Direction.DirectionBackwards + } + + loopModeView.triggeredRotatePlayButton = { + loopModeView.riveView.play(animationName:"oneshot", direction: self.direction) + } + loopModeView.triggeredRotateOneShotButton = { + loopModeView.riveView.play(animationName:"oneshot", loop: Loop.LoopOneShot, direction: self.direction) + } + loopModeView.triggeredRotateLoopButton = { + loopModeView.riveView.play(animationName:"oneshot", loop: Loop.LoopLoop, direction: self.direction) + } + loopModeView.triggeredRotatePingPongButton = { + loopModeView.riveView.play(animationName:"oneshot", loop: Loop.LoopPingPong, direction: self.direction) + } + + loopModeView.triggeredLoopDownPlayButton = { + loopModeView.riveView.play(animationName:"loop", direction: self.direction) + } + loopModeView.triggeredLoopDownOneShotButton = { + loopModeView.riveView.play(animationName:"loop", loop: Loop.LoopOneShot, direction: self.direction) + } + loopModeView.triggeredLoopDownLoopButton = { + loopModeView.riveView.play(animationName:"loop", loop: Loop.LoopLoop, direction: self.direction) + } + loopModeView.triggeredLoopDownPingPongButton = { + loopModeView.riveView.play(animationName:"loop", loop: Loop.LoopPingPong, direction: self.direction) + } + + loopModeView.triggeredLtrPlayButton = { + loopModeView.riveView.play(animationName:"pingpong", direction: self.direction) + } + loopModeView.triggeredLtrLoopButton = { + loopModeView.riveView.play(animationName:"pingpong", loop: Loop.LoopLoop, direction: self.direction) + } + loopModeView.triggeredLtrOneShotButton = { + loopModeView.riveView.play(animationName:"pingpong", loop: Loop.LoopOneShot, direction: self.direction) + } + loopModeView.triggeredLtrPingPongButton = { + loopModeView.riveView.play(animationName:"pingpong", loop: Loop.LoopPingPong, direction: self.direction) + } + + + } +} + diff --git a/Source/Renderer/Rive.h b/Source/Renderer/Rive.h index d4ff5ceb..d3955e67 100644 --- a/Source/Renderer/Rive.h +++ b/Source/Renderer/Rive.h @@ -11,6 +11,27 @@ NS_ASSUME_NONNULL_BEGIN +/* + * LoopMode + */ +typedef NS_ENUM(NSInteger, Loop) { + LoopOneShot, + LoopLoop, + LoopPingPong, + LoopAuto, + wat +}; + +/* + * Direction + */ +typedef NS_ENUM(NSInteger, Direction) { + DirectionBackwards, + DirectionForwards, + DirectionAuto, + huh +}; + /* * Fits */ @@ -72,6 +93,8 @@ typedef NS_ENUM(NSInteger, Alignment) { - (const RiveLinearAnimation *)animation; - (void)applyTo:(RiveArtboard*)artboard; - (bool)advanceBy:(double)elapsedSeconds; +- (int)direction; +- (void)direction:(int)direction; @end @@ -85,7 +108,10 @@ typedef NS_ENUM(NSInteger, Alignment) { - (NSInteger)workStart; - (NSInteger)workEnd; - (NSInteger)duration; +- (float)endTime; - (NSInteger)fps; +- (int)loop; +- (void)loop:(int)loopMode; - (void)apply:(float)time to:(RiveArtboard *)artboard; @end diff --git a/Source/Renderer/Rive.mm b/Source/Renderer/Rive.mm index 7c93c895..5c660ab4 100644 --- a/Source/Renderer/Rive.mm +++ b/Source/Renderer/Rive.mm @@ -324,6 +324,13 @@ -(NSInteger) duration { return animation->duration(); } +-(float) endTime { + if (animation->enableWorkArea()){ + return animation->workEnd()/animation->fps(); + } + return animation->duration()/animation->fps(); +} + -(NSInteger) fps { return animation->fps(); } @@ -332,6 +339,15 @@ -(void) apply:(float) time to:(RiveArtboard *) artboard { animation->apply(artboard.artboard, time); } +-(void) loop:(int)loopType { +// TODO: fix this. we shoudln't be casting the const out of this. + ((rive::LinearAnimation*) animation)->loopValue(loopType); +} + +-(int) loop { + return animation->loopValue(); +} + @end /* @@ -370,6 +386,12 @@ -(void) applyTo:(RiveArtboard*) artboard { -(bool) advanceBy:(double)elapsedSeconds { return instance->advance(elapsedSeconds); } +-(void) direction:(int)direction { + instance->direction(direction); +} +-(int) direction{ + return instance->direction(); +} @end diff --git a/Source/Views/RiveView.swift b/Source/Views/RiveView.swift index f2a9a92f..88fe6039 100644 --- a/Source/Views/RiveView.swift +++ b/Source/Views/RiveView.swift @@ -60,9 +60,11 @@ public class RiveView: UIView { open func configure( withRiveFile riveFile: RiveFile, andArtboard artboard: String?=nil, - andAnimation animation: String?=nil + andAnimation animation: String?=nil, + andAutoPlay autoPlay: Bool=true ) { self.riveFile = riveFile + self.autoPlay = autoPlay if let artboardName = artboard { self.artboard = riveFile.artboard(fromName:artboardName) @@ -78,32 +80,18 @@ public class RiveView: UIView { fatalError("No animations in the file.") } - var linearAnimation: RiveLinearAnimation? - if let animationName = animation { - linearAnimation = artboard.animation(fromName: animationName) - }else { - linearAnimation = artboard.firstAnimation() - } - - if let thisLinearAnimation=linearAnimation { - animations.append(thisLinearAnimation.instance()) - } - else { - fatalError("Animation not found in file.") - } - // Advance the artboard, this will ensure the first // frame is displayed when the artboard is drawn artboard.advance(by: 0) // Start the animation loop if autoPlay { - animations.forEach{ animation in - playingAnimations.insert(animation) + if let animationName = animation { + play(animationName: animationName) + }else { + play() } - } - runTimer() } /* @@ -122,13 +110,19 @@ public class RiveView: UIView { func runTimer() { if (displayLink == nil){ displayLink = CADisplayLink(target: self, selector: #selector(tick)); + // Note: common didnt pause on scroll. + displayLink?.add(to: .main, forMode: .common) + } + if (displayLink?.isPaused==true){ + lastTime=0 + displayLink!.isPaused=false } - displayLink?.add(to: .main, forMode: .default) } // Stops the animation timer func stopTimer() { - displayLink?.remove(from: .main, forMode: .default) + // should we pause or invalidate? + displayLink?.isPaused=true } @@ -165,6 +159,11 @@ public class RiveView: UIView { animation.apply(to: artboard) if !stillPlaying { playingAnimations.remove(animation) + if (animation.animation().loop() == Loop.LoopOneShot.rawValue) { + animations.removeAll(where: {animationInstance in + return animationInstance == animation + }) + } } } } @@ -182,4 +181,106 @@ public class RiveView: UIView { // Trigger a redraw self.setNeedsDisplay() } + + public func play( + loop: Loop = Loop.LoopAuto, + direction: Direction = Direction.DirectionAuto + ) { + guard let guardedArtboard=artboard else { + return; + } + + _playAnimation( + animationName:guardedArtboard.firstAnimation().name(), + loop:loop, + direction:direction + ) + runTimer() + } + + + public func play( + animationName: String, + loop: Loop = Loop.LoopAuto, + direction: Direction = Direction.DirectionAuto, + isStateMachine: Bool = false + ) { + _playAnimation( + animationName:animationName, + loop:loop, + direction:direction, + isStateMachine:isStateMachine + ) + runTimer() + } + + private func _playAnimation( + animationName: String, + loop: Loop = Loop.LoopAuto, + direction: Direction = Direction.DirectionAuto, + isStateMachine: Bool = false + ) + { + if (isStateMachine) { +// val stateMachineInstances = _getOrCreateStateMachines(animationName) +// stateMachineInstances.forEach { stateMachineInstance -> +// _play(stateMachineInstance) +// } + } else { + let animationInstances = _animations(animationName: animationName) + + animationInstances.forEach { animationInstance in + _play( + animation:animationInstance, + loop:loop, direction:direction + ) + } + if (animationInstances.isEmpty) { + guard let guardedArtboard=artboard else { + return + } + let animationInstance = guardedArtboard.animation(fromName:animationName).instance() + + _play(animation:animationInstance, loop:loop, direction:direction) + + } + } + } + + + private func _animations(animationName: String)->[RiveLinearAnimationInstance] { + return _animations(animationNames:[animationName]) + } + + private func _animations(animationNames: [String])-> [RiveLinearAnimationInstance] { + return animations.filter { animationInstance in + animationNames.contains(animationInstance.animation().name()) + } + } + + private func _play( + animation animationInstance: RiveLinearAnimationInstance, + loop: Loop, + direction: Direction + ) { + if (loop != Loop.LoopAuto) { + animationInstance.animation().loop(Int32(loop.rawValue)) + } + if (!animations.contains(animationInstance)) { + if (direction == Direction.DirectionBackwards) { + animationInstance.setTime(animationInstance.animation().endTime()) + } + animations.append( + animationInstance + ) + } + if (direction == Direction.DirectionForwards) { + animationInstance.direction(1) + }else if (direction == Direction.DirectionBackwards) { + animationInstance.direction(-1) + } + + playingAnimations.insert(animationInstance) + // notifyPlay(animationInstance) + } } From 3822912b7379111a70513801ae4f49e80612c8cd Mon Sep 17 00:00:00 2001 From: Maxwell Talbot Date: Fri, 7 May 2021 15:46:17 +0100 Subject: [PATCH 2/2] sort of got reset in --- Example-iOS/Source/UIkit/LoopMode.swift | 6 ++++++ Source/Views/RiveView.swift | 22 +++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Example-iOS/Source/UIkit/LoopMode.swift b/Example-iOS/Source/UIkit/LoopMode.swift index 39b711ae..bbd4ae22 100644 --- a/Example-iOS/Source/UIkit/LoopMode.swift +++ b/Example-iOS/Source/UIkit/LoopMode.swift @@ -104,7 +104,13 @@ class LoopModeController: UIViewController { ) loopModeView.triggeredResetButton = { + loopModeView.riveView.reset() + // TODO: just calling reset on an existing file is really not so hot. + loopModeView.riveView.configure( + withRiveFile: getRiveFile(resourceName: self.loopResourceName), + andAutoPlay: false + ) } loopModeView.triggeredForwardsButton = { self.direction = Direction.DirectionForwards diff --git a/Source/Views/RiveView.swift b/Source/Views/RiveView.swift index 88fe6039..e361ec70 100644 --- a/Source/Views/RiveView.swift +++ b/Source/Views/RiveView.swift @@ -82,7 +82,7 @@ public class RiveView: UIView { // Advance the artboard, this will ensure the first // frame is displayed when the artboard is drawn - artboard.advance(by: 0) +// artboard.advance(by: 0) // Start the animation loop if autoPlay { @@ -91,6 +91,8 @@ public class RiveView: UIView { }else { play() } + }else { + advance(delta: 0) } } @@ -125,6 +127,24 @@ public class RiveView: UIView { displayLink?.isPaused=true } + func clear() { + playingAnimations.removeAll() + playingStateMachines.removeAll() + animations.removeAll() + stateMachines.removeAll() + stopTimer() + lastTime=0 + } + + public func reset() { + clear() + stopTimer() + if let riveFile = self.riveFile { + // TODO: this is totally not enough to reset the file. i guess its because the file's artboard is already changed. + configure(withRiveFile: riveFile, andAutoPlay: autoPlay) + } + } + // Animates a frame @objc func tick() {