diff --git a/.rive_head b/.rive_head index ea5fdcd5..6fb705ca 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -0dcbdade49885fcae4de304972ae37f6283f19d1 +b47ff1523153056a3359ad9b65d7dbef212bbd17 diff --git a/.rive_renderer b/.rive_renderer index e7f431dc..3bddbfa5 100644 --- a/.rive_renderer +++ b/.rive_renderer @@ -1 +1 @@ -d7323869190ee9b8819c1ad6824888456d47b9b3 +54501530ad9f358b743da5599145c708b04bfcc3 diff --git a/Example-iOS/Assets/rating_animation.riv b/Example-iOS/Assets/rating_animation.riv new file mode 100644 index 00000000..330f7e91 Binary files /dev/null and b/Example-iOS/Assets/rating_animation.riv differ diff --git a/Example-iOS/RiveExample.xcodeproj/project.pbxproj b/Example-iOS/RiveExample.xcodeproj/project.pbxproj index d8d204c9..af965a3f 100644 --- a/Example-iOS/RiveExample.xcodeproj/project.pbxproj +++ b/Example-iOS/RiveExample.xcodeproj/project.pbxproj @@ -133,6 +133,10 @@ E57798AC2A731C5800FF25C3 /* testtext.riv in Resources */ = {isa = PBXBuildFile; fileRef = E577989D2A72C77900FF25C3 /* testtext.riv */; }; E57798AE2A73264100FF25C3 /* text_test_2.riv in Resources */ = {isa = PBXBuildFile; fileRef = E57798AD2A73264100FF25C3 /* text_test_2.riv */; }; E57798AF2A73264100FF25C3 /* text_test_2.riv in Resources */ = {isa = PBXBuildFile; fileRef = E57798AD2A73264100FF25C3 /* text_test_2.riv */; }; + E5964AAB2A9CD49200140479 /* SwiftEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5964AAA2A9CD49200140479 /* SwiftEvents.swift */; }; + E5964AAC2A9CD49200140479 /* SwiftEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5964AAA2A9CD49200140479 /* SwiftEvents.swift */; }; + E5964AB12A9CDB2100140479 /* rating_animation.riv in Resources */ = {isa = PBXBuildFile; fileRef = E5964AB02A9CDB2100140479 /* rating_animation.riv */; }; + E5964AB22A9CDB2100140479 /* rating_animation.riv in Resources */ = {isa = PBXBuildFile; fileRef = E5964AB02A9CDB2100140479 /* rating_animation.riv */; }; E5A7874A27E115170056F24B /* energy_bar_example.riv in Resources */ = {isa = PBXBuildFile; fileRef = E5A7874727E115170056F24B /* energy_bar_example.riv */; }; E5A7874C27E1158E0056F24B /* prop_example.riv in Resources */ = {isa = PBXBuildFile; fileRef = E5A7874B27E1158E0056F24B /* prop_example.riv */; }; E5CD7D7127DC331900BFE5E2 /* SwiftMeshAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5CD7D7027DC331900BFE5E2 /* SwiftMeshAnimation.swift */; }; @@ -262,6 +266,8 @@ E577989D2A72C77900FF25C3 /* testtext.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = testtext.riv; sourceTree = ""; }; E57798A82A730A9B00FF25C3 /* SwiftTestText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTestText.swift; sourceTree = ""; }; E57798AD2A73264100FF25C3 /* text_test_2.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = text_test_2.riv; sourceTree = ""; }; + E5964AAA2A9CD49200140479 /* SwiftEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftEvents.swift; sourceTree = ""; }; + E5964AB02A9CDB2100140479 /* rating_animation.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = rating_animation.riv; sourceTree = ""; }; E5A7874727E115170056F24B /* energy_bar_example.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = energy_bar_example.riv; sourceTree = ""; }; E5A7874B27E1158E0056F24B /* prop_example.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = prop_example.riv; sourceTree = ""; }; E5CD7D7027DC331900BFE5E2 /* SwiftMeshAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftMeshAnimation.swift; sourceTree = ""; }; @@ -371,6 +377,7 @@ C3E383472837E6B00029D65E /* watch_v1.riv */, C3ECAC29281837B300A81123 /* leg_day_events_example.riv */, C3ECAC28281837B300A81123 /* play_button_event_example.riv */, + E5964AB02A9CDB2100140479 /* rating_animation.riv */, C3ECAC2A281837B300A81123 /* switch_event_example.riv */, C3ECAC222817BE1100A81123 /* magic_8-ball_v2.riv */, E577989D2A72C77900FF25C3 /* testtext.riv */, @@ -426,6 +433,7 @@ C3E2B58B2833ECFE00A8651B /* SwiftCannonGame.swift */, C3C074ED28414F4600E8EB33 /* SwiftTestParityAnimSM.swift */, E57798A82A730A9B00FF25C3 /* SwiftTestText.swift */, + E5964AAA2A9CD49200140479 /* SwiftEvents.swift */, ); path = SwiftUI; sourceTree = ""; @@ -611,6 +619,7 @@ 04E51C5A2A151C230075E473 /* flux_capacitor.riv in Resources */, 04E51C5B2A151C230075E473 /* juice_v7.riv in Resources */, 04E51C5C2A151C230075E473 /* life_bar.riv in Resources */, + E5964AB22A9CDB2100140479 /* rating_animation.riv in Resources */, 04E51C5D2A151C230075E473 /* prop_example.riv in Resources */, 04E51C5E2A151C230075E473 /* two_bone_ik.riv in Resources */, 04E51C5F2A151C230075E473 /* energy_bar_example.riv in Resources */, @@ -682,6 +691,7 @@ 042C88E22644447500E7DBB2 /* f22.riv in Resources */, 046AFA712673AF04004ED497 /* blendmodes.riv in Resources */, 042C88EC2644447500E7DBB2 /* vader.riv in Resources */, + E5964AB12A9CDB2100140479 /* rating_animation.riv in Resources */, C9C73E9E24FC471E00EF9516 /* Assets.xcassets in Resources */, 0450446126B3F71E007B25CA /* constrained.riv in Resources */, C9D3DE68264F49F4001BA265 /* life_bar.riv in Resources */, @@ -701,6 +711,7 @@ buildActionMask = 2147483647; files = ( 04E51C382A151A1E0075E473 /* ContentView.swift in Sources */, + E5964AAC2A9CD49200140479 /* SwiftEvents.swift in Sources */, 04E51C362A151A1E0075E473 /* Example__macOS_App.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -726,6 +737,7 @@ C3ECAC2F281840A300A81123 /* ClockViewModel.swift in Sources */, 04026DC827CE3EE6002B3DBF /* SwiftLayout.swift in Sources */, C9C73E9A24FC471E00EF9516 /* SceneDelegate.swift in Sources */, + E5964AAB2A9CD49200140479 /* SwiftEvents.swift in Sources */, 04C4C83E2646FE410047E614 /* StateMachine.swift in Sources */, C324DB5B2807216B0060589F /* RiveSlider.swift in Sources */, 042C888C2643EEE300E7DBB2 /* Layout.swift in Sources */, diff --git a/Example-iOS/Source/Examples/SwiftUI/SwiftEvents.swift b/Example-iOS/Source/Examples/SwiftUI/SwiftEvents.swift new file mode 100644 index 00000000..b3f7c2ac --- /dev/null +++ b/Example-iOS/Source/Examples/SwiftUI/SwiftEvents.swift @@ -0,0 +1,63 @@ +// +// SwiftEvents.swift +// RiveExample +// +// Created by Zach Plata on 8/28/23. +// Copyright © 2023 Rive. All rights reserved. +// + +import SwiftUI +import RiveRuntime + +struct SwiftEvents: DismissableView { + var dismiss: () -> Void = {} + @StateObject private var rvm = RiveEventsVMExample() + + var body: some View { + VStack { + rvm.view() + Text("Event Message") + .font(.headline) + .padding(.bottom, 10) + Text(rvm.eventText) + .padding() + .background(rvm.eventText.isEmpty ? Color.clear : Color.black) + .foregroundColor(.white) + .cornerRadius(10) + } + } +} + +class RiveEventsVMExample: RiveViewModel { + @Published var eventText = "" + + init() { + super.init(fileName: "rating_animation") + } + + func view() -> some View { + return super.view().frame(width: 400, height: 400, alignment: .center) + } + + @objc func onRiveEventReceived(onRiveEvent riveEvent: RiveEvent) { + debugPrint("Event Name: \(riveEvent.name())") + debugPrint("Event Type: \(riveEvent.type())") + if let openUrlEvent = riveEvent as? RiveOpenUrlEvent { + debugPrint("Open URL Event Properties: \(openUrlEvent.properties())") + if let url = URL(string: openUrlEvent.url()) { + #if os(iOS) + UIApplication.shared.open(url) + #else + NSWorkspace.shared.open(url) + #endif + } + } else if let generalEvent = riveEvent as? RiveGeneralEvent { + let genEventProperties = generalEvent.properties(); + debugPrint("General Event Properites: \(genEventProperties)") + if let msg = genEventProperties["message"] { + eventText = msg as! String + } + } + + } +} diff --git a/Example-iOS/Source/ExamplesMaster.swift b/Example-iOS/Source/ExamplesMaster.swift index ea943e4c..11969021 100644 --- a/Example-iOS/Source/ExamplesMaster.swift +++ b/Example-iOS/Source/ExamplesMaster.swift @@ -36,7 +36,8 @@ class ExamplesMasterTableViewController: UITableViewController { ("Cannon Game", typeErased(dismissableView: SwiftCannonGame())), ("State Machine", typeErased(dismissableView: SwiftStateMachine())), ("Mesh Animation", typeErased(dismissableView: SwiftMeshAnimation())), - ("Playing with Text", typeErased(dismissableView: TextInputView())) + ("Playing with Text", typeErased(dismissableView: TextInputView())), + ("Rive Events", typeErased(dismissableView: SwiftEvents())) ] diff --git a/RiveRuntime.xcodeproj/project.pbxproj b/RiveRuntime.xcodeproj/project.pbxproj index 759be832..1dfdcede 100644 --- a/RiveRuntime.xcodeproj/project.pbxproj +++ b/RiveRuntime.xcodeproj/project.pbxproj @@ -68,6 +68,10 @@ E57798A62A72C9C500FF25C3 /* RiveTextValueRun.mm in Sources */ = {isa = PBXBuildFile; fileRef = E57798A52A72C9C500FF25C3 /* RiveTextValueRun.mm */; }; E57798A72A72EEAD00FF25C3 /* RiveTextValueRun.h in Headers */ = {isa = PBXBuildFile; fileRef = E57798A12A72C81F00FF25C3 /* RiveTextValueRun.h */; settings = {ATTRIBUTES = (Public, ); }; }; E57798AB2A7310B700FF25C3 /* testtext.riv in Resources */ = {isa = PBXBuildFile; fileRef = E57798AA2A7310B700FF25C3 /* testtext.riv */; }; + E5964A962A965A9300140479 /* RiveEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = E5964A952A965A9300140479 /* RiveEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E5964A982A9697B600140479 /* RiveEvent.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5964A972A9697B600140479 /* RiveEvent.mm */; }; + E599DCF92AAFA06100D1E49A /* rating_animation.riv in Resources */ = {isa = PBXBuildFile; fileRef = E599DCF82AAFA06100D1E49A /* rating_animation.riv */; }; + E599DCFA2AAFA06100D1E49A /* rating_animation.riv in Resources */ = {isa = PBXBuildFile; fileRef = E599DCF82AAFA06100D1E49A /* rating_animation.riv */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -147,6 +151,9 @@ E57798A12A72C81F00FF25C3 /* RiveTextValueRun.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; path = RiveTextValueRun.h; sourceTree = ""; }; E57798A52A72C9C500FF25C3 /* RiveTextValueRun.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RiveTextValueRun.mm; sourceTree = ""; }; E57798AA2A7310B700FF25C3 /* testtext.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = testtext.riv; sourceTree = ""; }; + E5964A952A965A9300140479 /* RiveEvent.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; path = RiveEvent.h; sourceTree = ""; }; + E5964A972A9697B600140479 /* RiveEvent.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = RiveEvent.mm; sourceTree = ""; }; + E599DCF82AAFA06100D1E49A /* rating_animation.riv */ = {isa = PBXFileReference; lastKnownFileType = file; name = rating_animation.riv; path = "Example-iOS/Assets/rating_animation.riv"; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -185,6 +192,7 @@ E57798A12A72C81F00FF25C3 /* RiveTextValueRun.h */, 83DE4CA62AAAE72000B88B72 /* RenderContext.hh */, 83DE4C922AA8DD9F00B88B72 /* RenderContextManager.h */, + E5964A952A965A9300140479 /* RiveEvent.h */, ); path = include; sourceTree = ""; @@ -199,6 +207,7 @@ 04ED72F2299C115100E8DE53 /* empty_animation_state.riv */, 04BE53FF2649403600427B39 /* multiple_animations.riv */, E57798AA2A7310B700FF25C3 /* testtext.riv */, + E599DCF82AAFA06100D1E49A /* rating_animation.riv */, 04BE54022649403600427B39 /* multiple_state_machines.riv */, 04BE54002649403600427B39 /* multipleartboards.riv */, 04BE53FE2649403600427B39 /* noanimation.riv */, @@ -239,6 +248,8 @@ E57798A52A72C9C500FF25C3 /* RiveTextValueRun.mm */, 04BE5431264D243D00427B39 /* LayerState.mm */, 83DE4C902AA8DD7B00B88B72 /* RenderContextManager.mm */, + 274175FC286DB9CE000A60D1 /* cg_skia_factory.cpp */, + E5964A972A9697B600140479 /* RiveEvent.mm */, ); path = Renderer; sourceTree = ""; @@ -320,6 +331,7 @@ E57798A72A72EEAD00FF25C3 /* RiveTextValueRun.h in Headers */, 046FB7F1264EAA60000129B1 /* RiveLinearAnimationInstance.h in Headers */, 04BE5430264D1F4100427B39 /* LayerState.h in Headers */, + E5964A962A965A9300140479 /* RiveEvent.h in Headers */, C9C741F424FC510200EF9516 /* Rive.h in Headers */, 04BE5436264D2A7500427B39 /* RivePrivateHeaders.h in Headers */, C9C73EE224FC478900EF9516 /* RiveRuntime.h in Headers */, @@ -410,6 +422,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + E599DCF92AAFA06100D1E49A /* rating_animation.riv in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -426,6 +439,7 @@ 04BE540B2649403600427B39 /* multiple_animations.riv in Resources */, E57798AB2A7310B700FF25C3 /* testtext.riv in Resources */, 04ED72F3299C115100E8DE53 /* empty_animation_state.riv in Resources */, + E599DCFA2AAFA06100D1E49A /* rating_animation.riv in Resources */, 04BE54062649403600427B39 /* off_road_car_blog.riv in Resources */, 04BE540C2649403600427B39 /* multipleartboards.riv in Resources */, 04BE540D2649403600427B39 /* junk.riv in Resources */, @@ -447,6 +461,7 @@ 2A707937272628AD00C035A1 /* rive_renderer_view.mm in Sources */, C34609FC27FF9114002DBCB7 /* RiveFile+Extensions.swift in Sources */, C3468E5827EB9887008652FD /* RiveView.swift in Sources */, + E5964A982A9697B600140479 /* RiveEvent.mm in Sources */, 04BE5434264D267900427B39 /* LayerState.mm in Sources */, C9601F2B250C25930032AA07 /* RiveRenderer.mm in Sources */, 83DE4C912AA8DD7B00B88B72 /* RenderContextManager.mm in Sources */, diff --git a/Source/Renderer/RiveEvent.mm b/Source/Renderer/RiveEvent.mm new file mode 100644 index 00000000..9ae90648 --- /dev/null +++ b/Source/Renderer/RiveEvent.mm @@ -0,0 +1,137 @@ +// +// RiveEvent.m +// RiveRuntime +// +// Created by Zach Plata on 8/23/23. +// Copyright © 2023 Rive. All rights reserved. +// + +#import +#import + + +/* + * RiveEvent + */ +@implementation RiveEvent +{ + const rive::Event* instance; + float secondsDelay; +} + +- (const rive::Event*)getInstance +{ + return instance; +} + +- (instancetype)initWithRiveEvent:(const rive::Event*)riveEvent + delay:(float)delay +{ + if (self = [super init]) + { + secondsDelay = delay; + instance = riveEvent; + return self; + } + else + { + return nil; + } +} + +- (NSString*)name +{ + std::string str = ((const rive::Event*)instance)->name(); + return [NSString stringWithCString:str.c_str() encoding:[NSString defaultCStringEncoding]]; +} + +- (NSInteger)type +{ + return ((rive::Event*)[self getInstance])->coreType(); +} + +- (float)delay +{ + return secondsDelay; +} + +- (NSDictionary*)properties +{ + bool hasCustomProperties = false; + NSMutableDictionary* customProperties = [NSMutableDictionary dictionary]; + for (auto child : ((const rive::Event*)instance)->children()) + { + if (child->is()) + { + std::string eventName = child->name(); + if (!eventName.empty()) + { + NSString* convertedName = [NSString stringWithCString:eventName.c_str() encoding:[NSString defaultCStringEncoding]]; + switch (child->coreType()) + { + case rive::CustomPropertyBoolean::typeKey: { + bool customBoolValue = child->as()->propertyValue(); + customProperties[convertedName] = @(customBoolValue); + break; + } + case rive::CustomPropertyString::typeKey: { + std::string customStringValue = child->as()->propertyValue(); + NSString* convertedStringValue = [NSString stringWithCString:customStringValue.c_str() encoding:[NSString defaultCStringEncoding]]; + customProperties[convertedName] = convertedStringValue; + break; + } + case rive::CustomPropertyNumber::typeKey: { + float customNumValue = child->as()->propertyValue(); + customProperties[convertedName] = @(customNumValue); + break; + } + } + hasCustomProperties = true; + } + } + } + if (hasCustomProperties) { + return customProperties; + } + return nil; +} +@end + +/* + * RiveGeneralEvent + */ +@implementation RiveGeneralEvent +@end + +/* + * RiveOpenUrlEvent + */ +@implementation RiveOpenUrlEvent +- (NSString*)url +{ + std::string str = ((const rive::OpenUrlEvent*)[self getInstance])->url(); + return [NSString stringWithCString:str.c_str() encoding:[NSString defaultCStringEncoding]]; +} + +- (NSString*)target +{ + uint32_t targetValue = ((const rive::OpenUrlEvent*)[self getInstance])->targetValue(); + std::string targetString; + switch (targetValue) + { + case 0: + targetString = "_blank"; + break; + case 1: + targetString = "_parent"; + break; + case 2: + targetString = "_self"; + break; + case 3: + targetString = "_top"; + break; + } + return [NSString stringWithCString:targetString.c_str() encoding:[NSString defaultCStringEncoding]]; +} +@end diff --git a/Source/Renderer/RiveStateMachineInstance.mm b/Source/Renderer/RiveStateMachineInstance.mm index b83684a4..51be75e1 100644 --- a/Source/Renderer/RiveStateMachineInstance.mm +++ b/Source/Renderer/RiveStateMachineInstance.mm @@ -257,6 +257,25 @@ - (NSInteger)stateChangedCount return instance->stateChangedCount(); } +- (NSInteger)reportedEventCount +{ + return instance->reportedEventCount(); +} + + +- (RiveEvent*)_convertEvent:(const rive::Event*)event delay:(float)delay +{ + if (event->is()) + { + return [[RiveOpenUrlEvent alloc] initWithRiveEvent:event delay:delay]; + } + else if (event->is()) + { + return [[RiveGeneralEvent alloc] initWithRiveEvent:event delay:delay]; + } + return nil; +} + - (RiveLayerState*)_convertLayerState:(const rive::LayerState*)layerState { if (layerState->is()) @@ -281,6 +300,18 @@ - (RiveLayerState*)_convertLayerState:(const rive::LayerState*)layerState } } +- (RiveEvent*)reportedEventAt:(NSInteger)index +{ + const rive::EventReport report = instance->reportedEventAt(index); + const rive::Event* event = report.event(); + if (event == nullptr) + { + return nil; + } else { + return [self _convertEvent:event delay:report.secondsDelay()]; + } +} + - (RiveLayerState*)stateChangedFromIndex:(NSInteger)index error:(NSError**)error { const rive::LayerState* layerState = instance->stateChangedByIndex(index); diff --git a/Source/Renderer/include/Rive.h b/Source/Renderer/include/Rive.h index 59d54300..82b11b45 100644 --- a/Source/Renderer/include/Rive.h +++ b/Source/Renderer/include/Rive.h @@ -18,6 +18,7 @@ #import #import #import +#import #import #import diff --git a/Source/Renderer/include/RiveEvent.h b/Source/Renderer/include/RiveEvent.h new file mode 100644 index 00000000..693f5747 --- /dev/null +++ b/Source/Renderer/include/RiveEvent.h @@ -0,0 +1,52 @@ +// +// RiveEvent.h +// RiveRuntime +// +// Created by Zach Plata on 8/23/23. +// Copyright © 2023 Rive. All rights reserved. +// + +#ifndef rive_event_h +#define rive_event_h + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RiveEventReport: NSObject +- (float) secondsDelay; +@end + +/* + * RiveEvent + */ +@interface RiveEvent : NSObject +/// Name of the RiveEvent +- (NSString*)name; +/// Type of the RiveEvent +- (NSInteger)type; +/// Delay in seconds since the Event was actually fired (applicable in cases of Events fired off from timeline animations) +- (float) delay; +/// Dictionary of custom properties set on any event +- (NSDictionary*)properties; +@end + +/* + * RiveGeneralEvent + */ +@interface RiveGeneralEvent : RiveEvent +@end + +/* + * RiveOpenUrlEvent + */ +@interface RiveOpenUrlEvent: RiveEvent +/// URL of a link to open +- (NSString*)url; +/// Target value for a link to open with +- (NSString*)target; +@end + +NS_ASSUME_NONNULL_END + +#endif /* rive_event_h */ diff --git a/Source/Renderer/include/RivePrivateHeaders.h b/Source/Renderer/include/RivePrivateHeaders.h index a66a67dc..63d1b0bd 100644 --- a/Source/Renderer/include/RivePrivateHeaders.h +++ b/Source/Renderer/include/RivePrivateHeaders.h @@ -27,6 +27,11 @@ #import "rive/animation/exit_state.hpp" #import "rive/animation/animation_state.hpp" #import "rive/text/text_value_run.hpp" +#import "rive/event.hpp" +#include "rive/open_url_event.hpp" +#include "rive/custom_property_boolean.hpp" +#include "rive/custom_property_string.hpp" +#include "rive/custom_property_number.hpp" // MARK: - Feature Flags @@ -66,6 +71,14 @@ @interface RiveSMIBool () @end +/** + * RiveEvent interface + */ +@interface RiveEvent () +- (instancetype)initWithRiveEvent:(const rive::Event*)riveEvent + delay:(float)delay; +@end + /* * RiveTextValueRun interface */ diff --git a/Source/Renderer/include/RiveStateMachineInstance.h b/Source/Renderer/include/RiveStateMachineInstance.h index 927fc1b4..a499e542 100644 --- a/Source/Renderer/include/RiveStateMachineInstance.h +++ b/Source/Renderer/include/RiveStateMachineInstance.h @@ -19,6 +19,7 @@ NS_ASSUME_NONNULL_BEGIN @class RiveSMITrigger; @class RiveSMINumber; @class RiveLayerState; +@class RiveEvent; /* * RiveStateMachineInstance @@ -37,6 +38,10 @@ NS_ASSUME_NONNULL_BEGIN - (NSInteger)stateChangedCount; - (RiveLayerState* __nullable)stateChangedFromIndex:(NSInteger)index error:(NSError**)error; - (NSArray*)stateChanges; +/// Returns number of reported events on the state machine since the last frame +- (NSInteger)reportedEventCount; +/// Returns a RiveEvent from the list of reported events at the given index +- (const RiveEvent*)reportedEventAt:(NSInteger)index; // MARK: Touch diff --git a/Source/RiveView.swift b/Source/RiveView.swift index 5b1cf6f8..4666147a 100644 --- a/Source/RiveView.swift +++ b/Source/RiveView.swift @@ -187,6 +187,13 @@ open class RiveView: RiveRendererView { eventQueue.fireAll() if let stateMachine = riveModel?.stateMachine { + let firedEventCount = stateMachine.reportedEventCount() + if (firedEventCount > 0) { + for i in 0.. #import #import +#import #import diff --git a/Tests/RiveDelegatesTest.swift b/Tests/RiveDelegatesTest.swift index 0831acbd..fa0e778c 100644 --- a/Tests/RiveDelegatesTest.swift +++ b/Tests/RiveDelegatesTest.swift @@ -38,6 +38,7 @@ class DrDelegate: RivePlayerDelegate, RiveStateMachineDelegate { var loops = [String]() var stateMachineNames = [String]() var stateMachineStates = [String]() + var events = [RiveEvent]() func player(playedWithModel riveModel: RiveModel?) { if let stateMachineName = riveModel?.stateMachine?.name() { @@ -81,6 +82,10 @@ class DrDelegate: RivePlayerDelegate, RiveStateMachineDelegate { stateMachineNames.append(stateMachine.name()) stateMachineStates.append(stateName) } + + func onRiveEventReceived(onRiveEvent riveEvent: RiveEvent) { + events.append(riveEvent) + } } // Technical Note: @@ -240,6 +245,32 @@ class DelegatesTest: XCTestCase { XCTAssertEqual(delegate.linearAnimaitonStops.count, 0) } + func testGeneralEventsReported() throws { + let delegate = DrDelegate() + let file = try RiveFile(testfileName: "rating_animation") + let model = RiveModel(riveFile: file) + let viewModel = RiveViewModel(model, stateMachineName: "State Machine 1", autoPlay: true) + let view = viewModel.createRiveView() + + view.playerDelegate = delegate + view.stateMachineDelegate = delegate + viewModel.setInput("rating", value: 2.0) + view.advance(delta:0.0) + view.advance(delta:0.1) + + XCTAssertEqual(delegate.events.count, 1) + XCTAssertEqual(delegate.events.first?.name(), "rating2") + XCTAssertTrue(delegate.events.first is RiveGeneralEvent) + + viewModel.setInput("rating", value: 5.0) + view.advance(delta:0.0) + view.advance(delta:0.1) + XCTAssertEqual(delegate.events.count, 3) + XCTAssertEqual(delegate.events.last?.name(), "gotorive") + XCTAssertTrue(delegate.events.last is RiveOpenUrlEvent) + XCTAssertTrue((delegate.events.last as? RiveOpenUrlEvent)?.url != nil) + } + func testStateMachineLayerStates() throws { let delegate = DrDelegate() let file = try RiveFile(testfileName: "what_a_state")