From cb450515007bf6bf4c6d4379fc9e8cd6842dffe1 Mon Sep 17 00:00:00 2001 From: dskuza Date: Tue, 16 Jul 2024 13:28:48 +0000 Subject: [PATCH] Add new state machine delegate callback for touch event hit results This pull request adds a new delegate function that can be used to perform (UI) updates based on the result of a touch or a mouse event. This is an additive change that will not break existing behavior. An example of where this could be used: - In a collection view, where the animation should not prevent scrolling from occurring if a non-interactive portion of the animation is touched. - By utilizing the new callback, a developer can check for a hit when a touch began, and update the appropriate collection view properties. - Add support for: - [x] iOS - [x] macOS Diffs= fe60de3c3 Add new state machine delegate callback for touch event hit results (#7579) Co-authored-by: David Skuza --- .rive_head | 2 +- Source/Renderer/RiveStateMachineInstance.mm | 41 ++++++++++--- .../include/RiveStateMachineInstance.h | 15 +++-- Source/RiveView.swift | 59 +++++++++++++++---- 4 files changed, 94 insertions(+), 23 deletions(-) diff --git a/.rive_head b/.rive_head index 2db75dca..90eae0e2 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -63b6a13d3a2adc1c555747fcd76234360bad7a1d +fe60de3c316eda974dab3e00522e9ee7a135834d diff --git a/Source/Renderer/RiveStateMachineInstance.mm b/Source/Renderer/RiveStateMachineInstance.mm index 2327a0af..e3fd1aee 100644 --- a/Source/Renderer/RiveStateMachineInstance.mm +++ b/Source/Renderer/RiveStateMachineInstance.mm @@ -13,6 +13,27 @@ 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 () @@ -360,24 +381,28 @@ - (NSInteger)layerCount // MARK: Touch -- (void)touchBeganAtLocation:(CGPoint)touchLocation +- (RiveHitResult)touchBeganAtLocation:(CGPoint)touchLocation { - instance->pointerDown(rive::Vec2D(touchLocation.x, touchLocation.y)); + return RiveHitResultFromRuntime( + instance->pointerDown(rive::Vec2D(touchLocation.x, touchLocation.y))); } -- (void)touchMovedAtLocation:(CGPoint)touchLocation +- (RiveHitResult)touchMovedAtLocation:(CGPoint)touchLocation { - instance->pointerMove(rive::Vec2D(touchLocation.x, touchLocation.y)); + return RiveHitResultFromRuntime( + instance->pointerMove(rive::Vec2D(touchLocation.x, touchLocation.y))); } -- (void)touchEndedAtLocation:(CGPoint)touchLocation +- (RiveHitResult)touchEndedAtLocation:(CGPoint)touchLocation { - instance->pointerUp(rive::Vec2D(touchLocation.x, touchLocation.y)); + return RiveHitResultFromRuntime( + instance->pointerUp(rive::Vec2D(touchLocation.x, touchLocation.y))); } -- (void)touchCancelledAtLocation:(CGPoint)touchLocation +- (RiveHitResult)touchCancelledAtLocation:(CGPoint)touchLocation { - instance->pointerUp(rive::Vec2D(touchLocation.x, touchLocation.y)); + return RiveHitResultFromRuntime( + instance->pointerUp(rive::Vec2D(touchLocation.x, touchLocation.y))); } @end diff --git a/Source/Renderer/include/RiveStateMachineInstance.h b/Source/Renderer/include/RiveStateMachineInstance.h index a499e542..9806800a 100644 --- a/Source/Renderer/include/RiveStateMachineInstance.h +++ b/Source/Renderer/include/RiveStateMachineInstance.h @@ -21,6 +21,9 @@ NS_ASSUME_NONNULL_BEGIN @class RiveLayerState; @class RiveEvent; +/// A type mirroring rive::HitResult, but available in both ObjC and Swift. +typedef NS_ENUM(NSInteger, RiveHitResult) { none, hit, hitOpaque }; + /* * RiveStateMachineInstance */ @@ -47,19 +50,23 @@ NS_ASSUME_NONNULL_BEGIN /// Tells this StateMachineInstance that a user began touching the artboard /// @param touchLocation A CGPoint in the coordinate space of the animating artboard -- (void)touchBeganAtLocation:(CGPoint)touchLocation; +/// @return The RiveHitResult of a touch beginning at the provided location. +- (RiveHitResult)touchBeganAtLocation:(CGPoint)touchLocation; /// Tells this StateMachineInstance that a touch moved on the artboard /// @param touchLocation A CGPoint in the coordinate space of the animating artboard -- (void)touchMovedAtLocation:(CGPoint)touchLocation; +/// @return The RiveHitResult of a touch moving at the provided location. +- (RiveHitResult)touchMovedAtLocation:(CGPoint)touchLocation; /// Tells this StateMachineInstance that a user finished touching the artboard /// @param touchLocation A CGPoint in the coordinate space of the animating artboard -- (void)touchEndedAtLocation:(CGPoint)touchLocation; +/// @return The RiveHitResult of a touch ending at the provided location. +- (RiveHitResult)touchEndedAtLocation:(CGPoint)touchLocation; /// Tells this StateMachineInstance that a user cancelled touching the artboard /// @param touchLocation A CGPoint in the coordinate space of the animating artboard -- (void)touchCancelledAtLocation:(CGPoint)touchLocation; +/// @return The RiveHitResult of a touch being cancelled at the provided location. +- (RiveHitResult)touchCancelledAtLocation:(CGPoint)touchLocation; // MARK: Debug diff --git a/Source/RiveView.swift b/Source/RiveView.swift index a11c39a0..b2f2f1d3 100644 --- a/Source/RiveView.swift +++ b/Source/RiveView.swift @@ -8,7 +8,6 @@ import Foundation - open class RiveView: RiveRendererView { // MARK: Configuration internal weak var riveModel: RiveModel? @@ -264,25 +263,37 @@ open class RiveView: RiveRendererView { #if os(iOS) open override func touchesBegan(_ touches: Set, with event: UIEvent?) { handleTouch(touches.first!, delegate: stateMachineDelegate?.touchBegan) { - $0.touchBegan(atLocation: $1) + let result = $0.touchBegan(atLocation: $1) + if let stateMachine = riveModel?.stateMachine { + stateMachineDelegate?.stateMachine?(stateMachine, didReceiveHitResult: result, from: .began) + } } } open override func touchesMoved(_ touches: Set, with event: UIEvent?) { handleTouch(touches.first!, delegate: stateMachineDelegate?.touchMoved) { - $0.touchMoved(atLocation: $1) + let result = $0.touchMoved(atLocation: $1) + if let stateMachine = riveModel?.stateMachine { + stateMachineDelegate?.stateMachine?(stateMachine, didReceiveHitResult: result, from: .moved) + } } } open override func touchesEnded(_ touches: Set, with event: UIEvent?) { handleTouch(touches.first!, delegate: stateMachineDelegate?.touchEnded) { - $0.touchEnded(atLocation: $1) + let result = $0.touchEnded(atLocation: $1) + if let stateMachine = riveModel?.stateMachine { + stateMachineDelegate?.stateMachine?(stateMachine, didReceiveHitResult: result, from: .ended) + } } } open override func touchesCancelled(_ touches: Set, with event: UIEvent?) { handleTouch(touches.first!, delegate: stateMachineDelegate?.touchCancelled) { - $0.touchCancelled(atLocation: $1) + let result = $0.touchCancelled(atLocation: $1) + if let stateMachine = riveModel?.stateMachine { + stateMachineDelegate?.stateMachine?(stateMachine, didReceiveHitResult: result, from: .cancelled) + } } } @@ -319,31 +330,46 @@ open class RiveView: RiveRendererView { #else open override func mouseDown(with event: NSEvent) { handleTouch(event, delegate: stateMachineDelegate?.touchBegan) { - $0.touchBegan(atLocation: $1) + let result = $0.touchBegan(atLocation: $1) + if let stateMachine = riveModel?.stateMachine { + stateMachineDelegate?.stateMachine?(stateMachine, didReceiveHitResult: result, from: .began) + } } } open override func mouseMoved(with event: NSEvent) { handleTouch(event, delegate: stateMachineDelegate?.touchMoved) { - $0.touchMoved(atLocation: $1) + let result = $0.touchMoved(atLocation: $1) + if let stateMachine = riveModel?.stateMachine { + stateMachineDelegate?.stateMachine?(stateMachine, didReceiveHitResult: result, from: .moved) + } } } open override func mouseDragged(with event: NSEvent) { handleTouch(event, delegate: stateMachineDelegate?.touchMoved) { - $0.touchMoved(atLocation: $1) + let result = $0.touchMoved(atLocation: $1) + if let stateMachine = riveModel?.stateMachine { + stateMachineDelegate?.stateMachine?(stateMachine, didReceiveHitResult: result, from: .moved) + } } } open override func mouseUp(with event: NSEvent) { handleTouch(event, delegate: stateMachineDelegate?.touchEnded) { - $0.touchEnded(atLocation: $1) + let result = $0.touchEnded(atLocation: $1) + if let stateMachine = riveModel?.stateMachine { + stateMachineDelegate?.stateMachine?(stateMachine, didReceiveHitResult: result, from: .ended) + } } } open override func mouseExited(with event: NSEvent) { handleTouch(event, delegate: stateMachineDelegate?.touchCancelled) { - $0.touchCancelled(atLocation: $1) + let result = $0.touchCancelled(atLocation: $1) + if let stateMachine = riveModel?.stateMachine { + stateMachineDelegate?.stateMachine?(stateMachine, didReceiveHitResult: result, from: .cancelled) + } } } @@ -414,6 +440,18 @@ open class RiveView: RiveRendererView { } } +/// An enum of possible touch or mouse events when interacting with an animation. +@objc public enum RiveTouchEvent: Int { + /// The touch event that occurs when a touch or mouse button click occurs. + case began + /// The touch event that occurs when a touch or mouse is dragged. + case moved + /// The touch event that occurs when a touch or mouse button is lifted. + case ended + /// The touch event that occurs when a touch or mouse click is cancelled. + case cancelled +} + @objc public protocol RiveStateMachineDelegate: AnyObject { @objc optional func touchBegan(onArtboard artboard: RiveArtboard?, atLocation location: CGPoint) @objc optional func touchMoved(onArtboard artboard: RiveArtboard?, atLocation location: CGPoint) @@ -422,6 +460,7 @@ open class RiveView: RiveRendererView { @objc optional func stateMachine(_ stateMachine: RiveStateMachineInstance, receivedInput input: StateMachineInput) @objc optional func stateMachine(_ stateMachine: RiveStateMachineInstance, didChangeState stateName: String) + @objc optional func stateMachine(_ stateMachine: RiveStateMachineInstance, didReceiveHitResult hitResult: RiveHitResult, from event: RiveTouchEvent) @objc optional func onRiveEventReceived(onRiveEvent riveEvent: RiveEvent) }