diff --git a/example/assets/little_machine.riv b/example/assets/little_machine.riv new file mode 100644 index 0000000..bbfd024 Binary files /dev/null and b/example/assets/little_machine.riv differ diff --git a/example/assets/skills.riv b/example/assets/skills.riv new file mode 100644 index 0000000..ee24fb8 Binary files /dev/null and b/example/assets/skills.riv differ diff --git a/example/lib/example_state_machine.dart b/example/lib/example_state_machine.dart index ec8db10..9eb220c 100644 --- a/example/lib/example_state_machine.dart +++ b/example/lib/example_state_machine.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:rive/rive.dart'; +/// An example showing how to drive two boolean state machine inputs. class ExampleStateMachine extends StatefulWidget { const ExampleStateMachine({Key? key}) : super(key: key); @@ -16,8 +17,8 @@ class _ExampleStateMachineState extends State { Artboard? _riveArtboard; StateMachineController? _controller; - StateMachineInput? _hoverInput; - StateMachineInput? _pressInput; + SMIInput? _hoverInput; + SMIInput? _pressInput; @override void initState() { @@ -50,7 +51,7 @@ class _ExampleStateMachineState extends State { return Scaffold( backgroundColor: Colors.grey, appBar: AppBar( - title: const Text('State Machine Example'), + title: const Text('Button State Machine'), ), body: Center( child: _riveArtboard == null diff --git a/example/lib/little_machine.dart b/example/lib/little_machine.dart new file mode 100644 index 0000000..d2237b4 --- /dev/null +++ b/example/lib/little_machine.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:rive/rive.dart'; + +/// An example showing how to drive a StateMachine via a trigger input. +class LittleMachine extends StatefulWidget { + const LittleMachine({Key? key}) : super(key: key); + + @override + _LittleMachineState createState() => _LittleMachineState(); +} + +class _LittleMachineState extends State { + /// Tracks if the animation is playing by whether controller is running. + bool get isPlaying => _controller?.isActive ?? false; + + Artboard? _riveArtboard; + StateMachineController? _controller; + SMIInput? _trigger; + + @override + void initState() { + super.initState(); + + // Load the animation file from the bundle, note that you could also + // download this. The RiveFile just expects a list of bytes. + rootBundle.load('assets/little_machine.riv').then( + (data) async { + // Load the RiveFile from the binary data. + final file = RiveFile.import(data); + + // The artboard is the root of the animation and gets drawn in the + // Rive widget. + final artboard = file.mainArtboard; + var controller = + StateMachineController.fromArtboard(artboard, 'State Machine 1'); + if (controller != null) { + artboard.addController(controller); + _trigger = controller.findInput('Trigger 1'); + } + setState(() => _riveArtboard = artboard); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.grey, + appBar: AppBar( + title: const Text('Little Machine'), + ), + body: Center( + child: _riveArtboard == null + ? const SizedBox() + : GestureDetector( + onTapDown: (_) => _trigger?.value = true, + child: Column( + children: [ + const SizedBox(height: 10), + const Text( + 'Press to activate!', + style: TextStyle( + fontSize: 18, + ), + ), + const SizedBox(height: 10), + Expanded( + child: Rive( + artboard: _riveArtboard!, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index a89e7a1..2434d5e 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:rive_example/example_animation.dart'; import 'package:rive_example/example_state_machine.dart'; +import 'package:rive_example/little_machine.dart'; +import 'package:rive_example/state_machine_skills.dart'; void main() => runApp(MaterialApp( title: 'Navigation Basics', @@ -33,7 +35,7 @@ class Home extends StatelessWidget { height: 10, ), ElevatedButton( - child: const Text('State Machine'), + child: const Text('Button State Machine'), onPressed: () { Navigator.push( context, @@ -43,6 +45,34 @@ class Home extends StatelessWidget { ); }, ), + const SizedBox( + height: 10, + ), + ElevatedButton( + child: const Text('Skills Machine'), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const StateMachineSkills(), + ), + ); + }, + ), + const SizedBox( + height: 10, + ), + ElevatedButton( + child: const Text('Little Machine'), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const LittleMachine(), + ), + ); + }, + ), ], ), ), diff --git a/example/lib/state_machine_skills.dart b/example/lib/state_machine_skills.dart new file mode 100644 index 0000000..4b16f3f --- /dev/null +++ b/example/lib/state_machine_skills.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:rive/rive.dart'; + +/// An example showing how to drive a StateMachine via one numeric input. +class StateMachineSkills extends StatefulWidget { + const StateMachineSkills({Key? key}) : super(key: key); + + @override + _StateMachineSkillsState createState() => _StateMachineSkillsState(); +} + +class _StateMachineSkillsState extends State { + /// Tracks if the animation is playing by whether controller is running. + bool get isPlaying => _controller?.isActive ?? false; + + Artboard? _riveArtboard; + StateMachineController? _controller; + SMIInput? _levelInput; + + @override + void initState() { + super.initState(); + + // Load the animation file from the bundle, note that you could also + // download this. The RiveFile just expects a list of bytes. + rootBundle.load('assets/skills.riv').then( + (data) async { + // Load the RiveFile from the binary data. + final file = RiveFile.import(data); + + // The artboard is the root of the animation and gets drawn in the + // Rive widget. + final artboard = file.mainArtboard; + var controller = + StateMachineController.fromArtboard(artboard, 'Designer\'s Test'); + if (controller != null) { + artboard.addController(controller); + _levelInput = controller.findInput('Level'); + } + setState(() => _riveArtboard = artboard); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.grey, + appBar: AppBar( + title: const Text('Skills Machine'), + ), + body: Center( + child: _riveArtboard == null + ? const SizedBox() + : Stack( + children: [ + Positioned.fill( + child: Rive( + artboard: _riveArtboard!, + ), + ), + Positioned.fill( + bottom: 100, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + ElevatedButton( + child: const Text('Beginner'), + onPressed: () => _levelInput?.value = 0, + ), + const SizedBox(width: 10), + ElevatedButton( + child: const Text('Intermediate'), + onPressed: () => _levelInput?.value = 1, + ), + const SizedBox(width: 10), + ElevatedButton( + child: const Text('Expert'), + onPressed: () => _levelInput?.value = 2, + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/generated/animation/state_machine_number_base.dart b/lib/src/generated/animation/state_machine_number_base.dart new file mode 100644 index 0000000..e3d4592 --- /dev/null +++ b/lib/src/generated/animation/state_machine_number_base.dart @@ -0,0 +1,41 @@ +/// Core automatically generated +/// lib/src/generated/animation/state_machine_number_base.dart. +/// Do not modify manually. + +import 'package:rive/src/generated/animation/state_machine_component_base.dart'; +import 'package:rive/src/generated/animation/state_machine_input_base.dart'; +import 'package:rive/src/rive_core/animation/state_machine_input.dart'; + +abstract class StateMachineNumberBase extends StateMachineInput { + static const int typeKey = 56; + @override + int get coreType => StateMachineNumberBase.typeKey; + @override + Set get coreTypes => { + StateMachineNumberBase.typeKey, + StateMachineInputBase.typeKey, + StateMachineComponentBase.typeKey + }; + + /// -------------------------------------------------------------------------- + /// Value field with key 140. + static const double valueInitialValue = 0; + double _value = valueInitialValue; + static const int valuePropertyKey = 140; + double get value => _value; + + /// Change the [_value] field value. + /// [valueChanged] will be invoked only if the field's value has changed. + set value(double value) { + if (_value == value) { + return; + } + double from = _value; + _value = value; + if (hasValidated) { + valueChanged(from, value); + } + } + + void valueChanged(double from, double to); +} diff --git a/lib/src/generated/animation/transition_number_condition_base.dart b/lib/src/generated/animation/transition_number_condition_base.dart new file mode 100644 index 0000000..9cc38d1 --- /dev/null +++ b/lib/src/generated/animation/transition_number_condition_base.dart @@ -0,0 +1,41 @@ +/// Core automatically generated +/// lib/src/generated/animation/transition_number_condition_base.dart. +/// Do not modify manually. + +import 'package:rive/src/generated/animation/transition_condition_base.dart'; +import 'package:rive/src/generated/animation/transition_value_condition_base.dart'; +import 'package:rive/src/rive_core/animation/transition_value_condition.dart'; + +abstract class TransitionNumberConditionBase extends TransitionValueCondition { + static const int typeKey = 70; + @override + int get coreType => TransitionNumberConditionBase.typeKey; + @override + Set get coreTypes => { + TransitionNumberConditionBase.typeKey, + TransitionValueConditionBase.typeKey, + TransitionConditionBase.typeKey + }; + + /// -------------------------------------------------------------------------- + /// Value field with key 157. + static const double valueInitialValue = 0; + double _value = valueInitialValue; + static const int valuePropertyKey = 157; + double get value => _value; + + /// Change the [_value] field value. + /// [valueChanged] will be invoked only if the field's value has changed. + set value(double value) { + if (_value == value) { + return; + } + double from = _value; + _value = value; + if (hasValidated) { + valueChanged(from, value); + } + } + + void valueChanged(double from, double to); +} diff --git a/lib/src/generated/rive_core_context.dart b/lib/src/generated/rive_core_context.dart index 13c43bb..6d12f31 100644 --- a/lib/src/generated/rive_core_context.dart +++ b/lib/src/generated/rive_core_context.dart @@ -21,13 +21,13 @@ import 'package:rive/src/generated/animation/linear_animation_base.dart'; import 'package:rive/src/generated/animation/state_machine_base.dart'; import 'package:rive/src/generated/animation/state_machine_bool_base.dart'; import 'package:rive/src/generated/animation/state_machine_component_base.dart'; -import 'package:rive/src/generated/animation/state_machine_double_base.dart'; import 'package:rive/src/generated/animation/state_machine_layer_base.dart'; +import 'package:rive/src/generated/animation/state_machine_number_base.dart'; import 'package:rive/src/generated/animation/state_machine_trigger_base.dart'; import 'package:rive/src/generated/animation/state_transition_base.dart'; import 'package:rive/src/generated/animation/transition_bool_condition_base.dart'; import 'package:rive/src/generated/animation/transition_condition_base.dart'; -import 'package:rive/src/generated/animation/transition_double_condition_base.dart'; +import 'package:rive/src/generated/animation/transition_number_condition_base.dart'; import 'package:rive/src/generated/animation/transition_trigger_condition_base.dart'; import 'package:rive/src/generated/animation/transition_value_condition_base.dart'; import 'package:rive/src/generated/artboard_base.dart'; @@ -81,12 +81,12 @@ import 'package:rive/src/rive_core/animation/keyframe_id.dart'; import 'package:rive/src/rive_core/animation/linear_animation.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_double.dart'; import 'package:rive/src/rive_core/animation/state_machine_layer.dart'; +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/animation/state_transition.dart'; import 'package:rive/src/rive_core/animation/transition_bool_condition.dart'; -import 'package:rive/src/rive_core/animation/transition_double_condition.dart'; +import 'package:rive/src/rive_core/animation/transition_number_condition.dart'; import 'package:rive/src/rive_core/animation/transition_trigger_condition.dart'; import 'package:rive/src/rive_core/artboard.dart'; import 'package:rive/src/rive_core/backboard.dart'; @@ -129,14 +129,16 @@ class RiveCoreContext { return AnimationState(); case KeyedObjectBase.typeKey: return KeyedObject(); + case StateMachineNumberBase.typeKey: + return StateMachineNumber(); case TransitionTriggerConditionBase.typeKey: return TransitionTriggerCondition(); case KeyedPropertyBase.typeKey: return KeyedProperty(); - case StateMachineDoubleBase.typeKey: - return StateMachineDouble(); case KeyFrameIdBase.typeKey: return KeyFrameId(); + case TransitionNumberConditionBase.typeKey: + return TransitionNumberCondition(); case AnyStateBase.typeKey: return AnyState(); case StateMachineLayerBase.typeKey: @@ -145,8 +147,6 @@ class RiveCoreContext { return Animation(); case CubicInterpolatorBase.typeKey: return CubicInterpolator(); - case TransitionDoubleConditionBase.typeKey: - return TransitionDoubleCondition(); case StateTransitionBase.typeKey: return StateTransition(); case KeyFrameDoubleBase.typeKey: @@ -262,26 +262,26 @@ class RiveCoreContext { object.objectId = value; } break; - case TransitionConditionBase.inputIdPropertyKey: - if (object is TransitionConditionBase && value is int) { - object.inputId = value; - } - break; case StateMachineComponentBase.namePropertyKey: if (object is StateMachineComponentBase && value is String) { object.name = value; } break; + case StateMachineNumberBase.valuePropertyKey: + if (object is StateMachineNumberBase && value is double) { + object.value = value; + } + break; + case TransitionConditionBase.inputIdPropertyKey: + if (object is TransitionConditionBase && value is int) { + object.inputId = value; + } + break; case KeyedPropertyBase.propertyKeyPropertyKey: if (object is KeyedPropertyBase && value is int) { object.propertyKey = value; } break; - case StateMachineDoubleBase.valuePropertyKey: - if (object is StateMachineDoubleBase && value is double) { - object.value = value; - } - break; case KeyFrameBase.framePropertyKey: if (object is KeyFrameBase && value is int) { object.frame = value; @@ -302,6 +302,16 @@ class RiveCoreContext { object.value = value; } break; + case TransitionValueConditionBase.opValuePropertyKey: + if (object is TransitionValueConditionBase && value is int) { + object.opValue = value; + } + break; + case TransitionNumberConditionBase.valuePropertyKey: + if (object is TransitionNumberConditionBase && value is double) { + object.value = value; + } + break; case AnimationBase.namePropertyKey: if (object is AnimationBase && value is String) { object.name = value; @@ -327,16 +337,6 @@ class RiveCoreContext { object.y2 = value; } break; - case TransitionValueConditionBase.opValuePropertyKey: - if (object is TransitionValueConditionBase && value is int) { - object.opValue = value; - } - break; - case TransitionDoubleConditionBase.valuePropertyKey: - if (object is TransitionDoubleConditionBase && value is double) { - object.value = value; - } - break; case StateTransitionBase.stateToIdPropertyKey: if (object is StateTransitionBase && value is int) { object.stateToId = value; @@ -861,12 +861,12 @@ class RiveCoreContext { case DrawRulesBase.drawTargetIdPropertyKey: case TendonBase.boneIdPropertyKey: return uintType; - case StateMachineDoubleBase.valuePropertyKey: + case StateMachineNumberBase.valuePropertyKey: + case TransitionNumberConditionBase.valuePropertyKey: case CubicInterpolatorBase.x1PropertyKey: case CubicInterpolatorBase.y1PropertyKey: case CubicInterpolatorBase.x2PropertyKey: case CubicInterpolatorBase.y2PropertyKey: - case TransitionDoubleConditionBase.valuePropertyKey: case KeyFrameDoubleBase.valuePropertyKey: case LinearAnimationBase.speedPropertyKey: case LinearGradientBase.startXPropertyKey: @@ -1040,8 +1040,10 @@ class RiveCoreContext { static double getDouble(Core object, int propertyKey) { switch (propertyKey) { - case StateMachineDoubleBase.valuePropertyKey: - return (object as StateMachineDoubleBase).value; + case StateMachineNumberBase.valuePropertyKey: + return (object as StateMachineNumberBase).value; + case TransitionNumberConditionBase.valuePropertyKey: + return (object as TransitionNumberConditionBase).value; case CubicInterpolatorBase.x1PropertyKey: return (object as CubicInterpolatorBase).x1; case CubicInterpolatorBase.y1PropertyKey: @@ -1050,8 +1052,6 @@ class RiveCoreContext { return (object as CubicInterpolatorBase).x2; case CubicInterpolatorBase.y2PropertyKey: return (object as CubicInterpolatorBase).y2; - case TransitionDoubleConditionBase.valuePropertyKey: - return (object as TransitionDoubleConditionBase).value; case KeyFrameDoubleBase.valuePropertyKey: return (object as KeyFrameDoubleBase).value; case LinearAnimationBase.speedPropertyKey: @@ -1340,8 +1340,11 @@ class RiveCoreContext { static void setDouble(Core object, int propertyKey, double value) { switch (propertyKey) { - case StateMachineDoubleBase.valuePropertyKey: - (object as StateMachineDoubleBase).value = value; + case StateMachineNumberBase.valuePropertyKey: + (object as StateMachineNumberBase).value = value; + break; + case TransitionNumberConditionBase.valuePropertyKey: + (object as TransitionNumberConditionBase).value = value; break; case CubicInterpolatorBase.x1PropertyKey: (object as CubicInterpolatorBase).x1 = value; @@ -1355,9 +1358,6 @@ class RiveCoreContext { case CubicInterpolatorBase.y2PropertyKey: (object as CubicInterpolatorBase).y2 = value; break; - case TransitionDoubleConditionBase.valuePropertyKey: - (object as TransitionDoubleConditionBase).value = value; - break; case KeyFrameDoubleBase.valuePropertyKey: (object as KeyFrameDoubleBase).value = value; break; diff --git a/lib/src/rive_core/animation/state_machine_double.dart b/lib/src/rive_core/animation/state_machine_double.dart deleted file mode 100644 index 1fe1463..0000000 --- a/lib/src/rive_core/animation/state_machine_double.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:rive/src/generated/animation/state_machine_double_base.dart'; -export 'package:rive/src/generated/animation/state_machine_double_base.dart'; - -class StateMachineDouble extends StateMachineDoubleBase { - @override - void valueChanged(double from, double to) {} - @override - bool isValidType() => T == double; - @override - dynamic get controllerValue => value; -} diff --git a/lib/src/rive_core/animation/state_machine_number.dart b/lib/src/rive_core/animation/state_machine_number.dart new file mode 100644 index 0000000..26dfe13 --- /dev/null +++ b/lib/src/rive_core/animation/state_machine_number.dart @@ -0,0 +1,11 @@ +import 'package:rive/src/generated/animation/state_machine_number_base.dart'; +export 'package:rive/src/generated/animation/state_machine_number_base.dart'; + +class StateMachineNumber extends StateMachineNumberBase { + @override + void valueChanged(double from, double to) {} + @override + bool isValidType() => T == double; + @override + dynamic get controllerValue => value; +} diff --git a/lib/src/rive_core/animation/transition_double_condition.dart b/lib/src/rive_core/animation/transition_number_condition.dart similarity index 69% rename from lib/src/rive_core/animation/transition_double_condition.dart rename to lib/src/rive_core/animation/transition_number_condition.dart index 8748f32..a7fb680 100644 --- a/lib/src/rive_core/animation/transition_double_condition.dart +++ b/lib/src/rive_core/animation/transition_number_condition.dart @@ -1,20 +1,20 @@ import 'dart:collection'; -import 'package:rive/src/rive_core/animation/state_machine_double.dart'; +import 'package:rive/src/rive_core/animation/state_machine_number.dart'; import 'package:rive/src/rive_core/animation/transition_condition.dart'; -import 'package:rive/src/generated/animation/transition_double_condition_base.dart'; -export 'package:rive/src/generated/animation/transition_double_condition_base.dart'; +import 'package:rive/src/generated/animation/transition_number_condition_base.dart'; +export 'package:rive/src/generated/animation/transition_number_condition_base.dart'; -class TransitionDoubleCondition extends TransitionDoubleConditionBase { +class TransitionNumberCondition extends TransitionNumberConditionBase { @override void valueChanged(double from, double to) {} @override - bool validate() => super.validate() && (input is StateMachineDouble); + bool validate() => super.validate() && (input is StateMachineNumber); @override bool evaluate(HashMap values) { - if (input is! StateMachineDouble) { + if (input is! StateMachineNumber) { return true; } - var doubleInput = input as StateMachineDouble; + var doubleInput = input as StateMachineNumber; dynamic providedValue = values[input.id]; double inputValue = providedValue is double ? providedValue : doubleInput.value; diff --git a/lib/src/rive_core/shapes/paint/shape_paint_mutator.dart b/lib/src/rive_core/shapes/paint/shape_paint_mutator.dart index dfea358..53544ca 100644 --- a/lib/src/rive_core/shapes/paint/shape_paint_mutator.dart +++ b/lib/src/rive_core/shapes/paint/shape_paint_mutator.dart @@ -4,7 +4,7 @@ import 'package:rive/src/rive_core/shapes/shape_paint_container.dart'; abstract class ShapePaintMutator { ShapePaintContainer? _shapePaintContainer; - late Paint _paint; + Paint _paint = Paint(); ShapePaintContainer? get shapePaintContainer => _shapePaintContainer; Paint get paint => _paint; double _renderOpacity = 1; diff --git a/lib/src/rive_core/state_machine_controller.dart b/lib/src/rive_core/state_machine_controller.dart index c5af8e3..7a38896 100644 --- a/lib/src/rive_core/state_machine_controller.dart +++ b/lib/src/rive_core/state_machine_controller.dart @@ -37,12 +37,6 @@ class LayerController { if (_animationInstance != null) { _animationInstance!.advance(elapsedSeconds); } - for (int i = 0; updateState(inputValues); i++) { - if (i == 100) { - print('StateMachineController.apply exceeded max iterations.'); - return false; - } - } if (_transition != null && _stateFrom != null && _transition!.duration != 0) { @@ -52,22 +46,26 @@ class LayerController { } else { _mix = 1; } - var keepGoing = _mix != 1; if (_animationInstanceFrom != null && _mix < 1) { if (!_holdAnimationFrom) { _animationInstanceFrom!.advance(elapsedSeconds); } + } + for (int i = 0; updateState(inputValues); i++) { + if (i == 100) { + print('StateMachineController.apply exceeded max iterations.'); + return false; + } + } + if (_animationInstanceFrom != null && _mix < 1) { _animationInstanceFrom!.animation.apply(_animationInstanceFrom!.time, mix: 1 - _mix, coreContext: core); } if (_animationInstance != null) { _animationInstance!.animation .apply(_animationInstance!.time, mix: _mix, coreContext: core); - if (_animationInstance!.keepGoing) { - keepGoing = true; - } } - return keepGoing; + return _mix != 1 || (_animationInstance?.keepGoing ?? false); } bool updateState(HashMap inputValues) { diff --git a/lib/src/state_machine_controller.dart b/lib/src/state_machine_controller.dart index f70065a..17a701c 100644 --- a/lib/src/state_machine_controller.dart +++ b/lib/src/state_machine_controller.dart @@ -1,17 +1,51 @@ +import 'package:flutter/foundation.dart'; +import 'package:rive/src/core/core.dart'; +import 'package:rive/src/generated/animation/state_machine_bool_base.dart'; +import 'package:rive/src/generated/animation/state_machine_number_base.dart'; +import 'package:rive/src/generated/animation/state_machine_trigger_base.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/state_machine_controller.dart' as core; -class StateMachineInput { - final int id; +/// [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 } + +/// StateMachine Instance Input. 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. +class SMIInput { + final core.StateMachineInput _input; final StateMachineController controller; - StateMachineInput._(this.id, this.controller); + final SMIType type; - T get value => controller.inputValues[id] as T; - set value(T newValue) => change(newValue); + SMIInput._(this._input, this.type, this.controller); - /// Change the value of the input, returns true if the value was changed the - /// and [StateMachineController] was activated. + @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 value of the input. Usually + /// it's easier to use the various value getter/setters on the derived + /// version of [SMIInput] like [SMIBool], [SMINumber], and [SMITrigger]. bool change(T value) { if (controller.inputValues[id] == value) { return false; @@ -20,15 +54,89 @@ class StateMachineInput { controller.isActive = true; return true; } + + T get value => controller.inputValues[id] as T; + set value(T newValue) => change(newValue); + + bool _is() { + 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 { + SMIBool._(StateMachineBool input, StateMachineController controller) + : super._( + input, + SMIType.boolean, + controller, + ) { + controller.inputValues[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 { + SMINumber._(StateMachineNumber input, StateMachineController controller) + : super._( + input, + SMIType.number, + controller, + ) { + controller.inputValues[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 { + SMITrigger._(StateMachineTrigger input, StateMachineController controller) + : super._( + input, + SMIType.trigger, + controller, + ) { + controller.inputValues[id] = false; + } + + void fire() => change(true); + @override + void advance() => change(false); } /// An AnimationController which controls a StateMachine and provides access to /// the inputs of the StateMachine. class StateMachineController extends core.StateMachineController { + final List _inputs = []; + + /// A list of inputs available in the StateMachine. + Iterable get inputs => _inputs; + StateMachineController(StateMachine stateMachine) : super(stateMachine) { 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) { for (final animation in artboard.animations) { @@ -39,13 +147,21 @@ class StateMachineController extends core.StateMachineController { return null; } - StateMachineInput? findInput(String name) { - for (final input in stateMachine.inputs) { - if (input.name == name && input.isValidType()) { - inputValues[input.id] = input.controllerValue; - return StateMachineInput._(input.id, this); + /// Find an input with a specific backing type and a given name. + SMIInput? findInput(String name) { + for (final input in _inputs) { + if (input._is() && input.name == name) { + return input as SMIInput; } } return null; } + + @override + void apply(CoreContext core, double elapsedSeconds) { + super.apply(core, elapsedSeconds); + for (final input in _inputs) { + input.advance(); + } + } }