mirror of
https://github.com/rive-app/rive-flutter.git
synced 2025-08-06 16:40:27 +08:00

Flutter runtime needed an additional callback hooked up to listen for nested artboard events because its main artboard is a RuntimeArtboard. Diffs= c5f85ed7f Fix event listening for RuntimeArtboard (#6202) 5e33d8c96 fix: Made default and copy constructors explicit. (#6203) Co-authored-by: Gordon Hayes <pggordonhayes@gmail.com> Co-authored-by: Philip Chung <philterdesign@gmail.com>
206 lines
6.8 KiB
Dart
206 lines
6.8 KiB
Dart
import 'package:rive/src/core/core.dart';
|
|
import 'package:rive/src/rive_core/animation/state_machine.dart';
|
|
import 'package:rive/src/rive_core/animation/state_machine_bool.dart';
|
|
import 'package:rive/src/rive_core/animation/state_machine_input.dart' as core;
|
|
import 'package:rive/src/rive_core/animation/state_machine_number.dart';
|
|
import 'package:rive/src/rive_core/animation/state_machine_trigger.dart';
|
|
import 'package:rive/src/rive_core/artboard.dart';
|
|
import 'package:rive/src/rive_core/event.dart';
|
|
import 'package:rive/src/rive_core/state_machine_controller.dart' as core;
|
|
|
|
/// [StateMachine]s supports three input types. The StateMachine mostly
|
|
/// abstracts types by allowing the programmer to query for an input of a
|
|
/// specific Dart backing type, mapping it to the correct StateMachine type.
|
|
/// This is the most flexible API to use to check if a type with a given name
|
|
/// exists. However, if you need to iterate inputs and query their types, this
|
|
/// enum is exposed for convenience.
|
|
enum SMIType { number, boolean, trigger }
|
|
|
|
/// SMI = StateMachineInstance
|
|
///
|
|
/// This is the abstraction of an instanced input
|
|
/// from the [StateMachine]. Whenever a [StateMachineController] is created, the
|
|
/// list of inputs in the corresponding [StateMachine] is wrapped into a set of
|
|
/// [SMIInput] objects that ensure inputs are initialized to design-time values.
|
|
/// The implementation can now change these values freely as they are decoupled
|
|
/// from the backing [StateMachine] and can safely be re-instanced by another
|
|
/// controller later.
|
|
abstract class SMIInput<T> {
|
|
final core.StateMachineInput _input;
|
|
final StateMachineController controller;
|
|
final SMIType type;
|
|
|
|
SMIInput._(this._input, this.type, this.controller);
|
|
|
|
@protected
|
|
void advance() {}
|
|
|
|
/// The id of the input within the context of the [StateMachine] it belongs
|
|
/// to.
|
|
int get id => _input.id;
|
|
|
|
/// The name given to this input at design time in Rive.
|
|
String get name => _input.name;
|
|
|
|
/// Convenience method for changing the backing [SMIInput.value] of the input.
|
|
/// For [SMITrigger] it's usually preferable to use the [SMITrigger.fire]
|
|
/// method to change the input value, but calling change(true) is totally
|
|
/// valid.
|
|
bool change(T value) {
|
|
if (controller.getInputValue(id) == value) {
|
|
return false;
|
|
}
|
|
controller.setInputValue(id, value);
|
|
controller.isActive = true;
|
|
return true;
|
|
}
|
|
|
|
T get value => controller.getInputValue(id) as T;
|
|
set value(T newValue) => change(newValue);
|
|
|
|
bool _is<K>() {
|
|
return K == T;
|
|
}
|
|
}
|
|
|
|
/// A boolean StateMachine input instance. Use the [value] property to change
|
|
/// the input which will automatically re-activate the [StateMachineController]
|
|
/// if necessary.
|
|
class SMIBool extends SMIInput<bool> {
|
|
SMIBool._(StateMachineBool input, StateMachineController controller)
|
|
: super._(
|
|
input,
|
|
SMIType.boolean,
|
|
controller,
|
|
) {
|
|
controller.setInputValue(id, input.value);
|
|
}
|
|
}
|
|
|
|
/// A numeric StateMachine input instance. Use the [value] property to change
|
|
/// the input which will automatically re-activate the [StateMachineController]
|
|
/// if necessary.
|
|
class SMINumber extends SMIInput<double> {
|
|
SMINumber._(StateMachineNumber input, StateMachineController controller)
|
|
: super._(
|
|
input,
|
|
SMIType.number,
|
|
controller,
|
|
) {
|
|
controller.setInputValue(id, input.value);
|
|
}
|
|
}
|
|
|
|
/// A trigger StateMachine input instance. Use the [fire] method to change the
|
|
/// input which will automatically re-activate the [StateMachineController] if
|
|
/// necessary.
|
|
class SMITrigger extends SMIInput<bool> {
|
|
SMITrigger._(StateMachineTrigger input, StateMachineController controller)
|
|
: super._(
|
|
input,
|
|
SMIType.trigger,
|
|
controller,
|
|
) {
|
|
controller.setInputValue(id, false);
|
|
}
|
|
|
|
void fire() => change(true);
|
|
@override
|
|
void advance() => change(false);
|
|
}
|
|
|
|
/// Callback signature for events firing.
|
|
typedef OnRuntimeEvent = void Function(Event);
|
|
|
|
/// An AnimationController which controls a StateMachine and provides access to
|
|
/// the inputs of the StateMachine.
|
|
class StateMachineController extends core.StateMachineController {
|
|
final List<SMIInput> _inputs = <SMIInput>[];
|
|
|
|
/// A list of inputs available in the StateMachine.
|
|
Iterable<SMIInput> get inputs => _inputs;
|
|
|
|
final _runtimeEventListeners = <OnRuntimeEvent>{};
|
|
|
|
StateMachineController(
|
|
StateMachine stateMachine, {
|
|
core.OnStateChange? onStateChange,
|
|
// ignore: deprecated_member_use_from_same_package
|
|
}) : super(stateMachine, onStateChange: onStateChange) {
|
|
isActive = true;
|
|
for (final input in stateMachine.inputs) {
|
|
switch (input.coreType) {
|
|
case StateMachineNumberBase.typeKey:
|
|
_inputs.add(SMINumber._(input as StateMachineNumber, this));
|
|
break;
|
|
case StateMachineBoolBase.typeKey:
|
|
_inputs.add(SMIBool._(input as StateMachineBool, this));
|
|
break;
|
|
case StateMachineTriggerBase.typeKey:
|
|
_inputs.add(SMITrigger._(input as StateMachineTrigger, this));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Instance a [StateMachineController] from an [artboard] with the given
|
|
/// [stateMachineName]. Returns the [StateMachineController] or null if no
|
|
/// [StateMachine] with [stateMachineName] is found.
|
|
static StateMachineController? fromArtboard(
|
|
Artboard artboard,
|
|
String stateMachineName, {
|
|
core.OnStateChange? onStateChange,
|
|
}) {
|
|
for (final animation in artboard.animations) {
|
|
if (animation is StateMachine && animation.name == stateMachineName) {
|
|
final controller =
|
|
StateMachineController(animation, onStateChange: onStateChange);
|
|
if (artboard is RuntimeArtboard) {
|
|
artboard.addNestedEventListener(controller);
|
|
}
|
|
return controller;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Find an input with a specific backing type and a given name.
|
|
SMIInput<T>? findInput<T>(String name) {
|
|
for (final input in _inputs) {
|
|
if (input._is<T>() && input.name == name) {
|
|
return input as SMIInput<T>;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Find an input of specific concrete input type, with a given name.
|
|
T? findSMI<T>(String name) {
|
|
for (final input in _inputs) {
|
|
if (input is T && input.name == name) {
|
|
return input as T;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
void advanceInputs() {
|
|
for (final input in _inputs) {
|
|
input.advance();
|
|
}
|
|
}
|
|
|
|
void addRuntimeEventListener(OnRuntimeEvent callback) =>
|
|
_runtimeEventListeners.add(callback);
|
|
void removeRuntimeEventListener(OnRuntimeEvent callback) =>
|
|
_runtimeEventListeners.remove(callback);
|
|
|
|
@override
|
|
void applyEvents() {
|
|
var events = reportedEvents.toList(growable: false);
|
|
super.applyEvents();
|
|
_runtimeEventListeners.toList().forEach(events.forEach);
|
|
}
|
|
}
|