mirror of
https://github.com/rive-app/rive-flutter.git
synced 2025-05-17 21:36:06 +08:00
Adding ability to control state machine.
This commit is contained in:
BIN
example/assets/rocket.riv
Normal file
BIN
example/assets/rocket.riv
Normal file
Binary file not shown.
67
example/lib/example_animation.dart
Normal file
67
example/lib/example_animation.dart
Normal file
@ -0,0 +1,67 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:rive/rive.dart';
|
||||
|
||||
class ExampleAnimation extends StatefulWidget {
|
||||
const ExampleAnimation({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ExampleAnimationState createState() => _ExampleAnimationState();
|
||||
}
|
||||
|
||||
class _ExampleAnimationState extends State<ExampleAnimation> {
|
||||
void _togglePlay() {
|
||||
setState(() => _controller.isActive = !_controller.isActive);
|
||||
}
|
||||
|
||||
/// Tracks if the animation is playing by whether controller is running.
|
||||
bool get isPlaying => _controller?.isActive ?? false;
|
||||
|
||||
Artboard _riveArtboard;
|
||||
RiveAnimationController _controller;
|
||||
@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/dino.riv').then(
|
||||
(data) async {
|
||||
final file = RiveFile();
|
||||
|
||||
// Load the RiveFile from the binary data.
|
||||
if (file.import(data)) {
|
||||
// The artboard is the root of the animation and gets drawn in the
|
||||
// Rive widget.
|
||||
final artboard = file.mainArtboard;
|
||||
// Add a controller to play back a known animation on the main/default
|
||||
// artboard. We store a reference to it so we can toggle playback.
|
||||
artboard.addController(_controller = SimpleAnimation('Run'));
|
||||
setState(() => _riveArtboard = artboard);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Animation Example'),
|
||||
),
|
||||
body: Center(
|
||||
child: _riveArtboard == null
|
||||
? const SizedBox()
|
||||
: Rive(artboard: _riveArtboard),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _togglePlay,
|
||||
tooltip: isPlaying ? 'Pause' : 'Play',
|
||||
child: Icon(
|
||||
isPlaying ? Icons.pause : Icons.play_arrow,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
83
example/lib/example_state_machine.dart
Normal file
83
example/lib/example_state_machine.dart
Normal file
@ -0,0 +1,83 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:rive/rive.dart';
|
||||
|
||||
class ExampleStateMachine extends StatefulWidget {
|
||||
const ExampleStateMachine({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_ExampleStateMachineState createState() => _ExampleStateMachineState();
|
||||
}
|
||||
|
||||
class _ExampleStateMachineState extends State<ExampleStateMachine> {
|
||||
void _togglePlay() {
|
||||
setState(() => _controller.isActive = !_controller.isActive);
|
||||
}
|
||||
|
||||
/// Tracks if the animation is playing by whether controller is running.
|
||||
bool get isPlaying => _controller?.isActive ?? false;
|
||||
|
||||
Artboard _riveArtboard;
|
||||
StateMachineController _controller;
|
||||
StateMachineInput<bool> _hoverInput;
|
||||
StateMachineInput<bool> _pressInput;
|
||||
|
||||
@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/rocket.riv').then(
|
||||
(data) async {
|
||||
final file = RiveFile();
|
||||
|
||||
// Load the RiveFile from the binary data.
|
||||
if (file.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, 'Button');
|
||||
if (controller != null) {
|
||||
artboard.addController(controller);
|
||||
_hoverInput = controller.findInput('Hover');
|
||||
_pressInput = controller.findInput('Press');
|
||||
}
|
||||
setState(() => _riveArtboard = artboard);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.grey,
|
||||
appBar: AppBar(
|
||||
title: const Text('State Machine Example'),
|
||||
),
|
||||
body: Center(
|
||||
child: _riveArtboard == null
|
||||
? const SizedBox()
|
||||
: MouseRegion(
|
||||
onEnter: (_) => _hoverInput.value = true,
|
||||
onExit: (_) => _hoverInput.value = false,
|
||||
child: GestureDetector(
|
||||
onTapDown: (_) => _pressInput.value = true,
|
||||
onTapCancel: () => _pressInput.value = false,
|
||||
onTapUp: (_) => _pressInput.value = false,
|
||||
child: SizedBox(
|
||||
width: 250,
|
||||
height: 250,
|
||||
child: Rive(
|
||||
artboard: _riveArtboard,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,77 +1,49 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:rive/rive.dart';
|
||||
import 'package:rive_example/example_animation.dart';
|
||||
import 'package:rive_example/example_state_machine.dart';
|
||||
|
||||
void main() => runApp(MyApp());
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
@override
|
||||
_MyAppState createState() => _MyAppState();
|
||||
}
|
||||
|
||||
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const MaterialApp(
|
||||
home: MyHomePage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_MyHomePageState createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
void _togglePlay() {
|
||||
setState(() => _controller.isActive = !_controller.isActive);
|
||||
}
|
||||
|
||||
/// Tracks if the animation is playing by whether controller is running.
|
||||
bool get isPlaying => _controller?.isActive ?? false;
|
||||
|
||||
Artboard _riveArtboard;
|
||||
RiveAnimationController _controller;
|
||||
@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/off_road_car.riv').then(
|
||||
(data) async {
|
||||
final file = RiveFile();
|
||||
|
||||
// Load the RiveFile from the binary data.
|
||||
if (file.import(data)) {
|
||||
// The artboard is the root of the animation and gets drawn in the
|
||||
// Rive widget.
|
||||
final artboard = file.mainArtboard;
|
||||
// Add a controller to play back a known animation on the main/default
|
||||
// artboard.We store a reference to it so we can toggle playback.
|
||||
artboard.addController(_controller = SimpleAnimation('idle'));
|
||||
setState(() => _riveArtboard = artboard);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
void main() => runApp(MaterialApp(
|
||||
title: 'Navigation Basics',
|
||||
home: Home(),
|
||||
));
|
||||
|
||||
class Home extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Center(
|
||||
child: _riveArtboard == null
|
||||
? const SizedBox()
|
||||
: Rive(artboard: _riveArtboard),
|
||||
appBar: AppBar(
|
||||
title: const Text('Rive Examples'),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _togglePlay,
|
||||
tooltip: isPlaying ? 'Pause' : 'Play',
|
||||
child: Icon(
|
||||
isPlaying ? Icons.pause : Icons.play_arrow,
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
child: const Text('Animation'),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => const ExampleAnimation(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
ElevatedButton(
|
||||
child: const Text('State Machine'),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute<void>(
|
||||
builder: (context) => const ExampleStateMachine(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -18,3 +18,5 @@ export 'package:rive/src/rive_core/shapes/paint/radial_gradient.dart';
|
||||
export 'package:rive/src/rive_core/runtime/runtime_header.dart'
|
||||
show riveVersion;
|
||||
export 'package:rive/src/extensions.dart';
|
||||
export 'package:rive/src/rive_core/animation/state_machine.dart';
|
||||
export 'package:rive/src/state_machine_controller.dart';
|
@ -7,10 +7,14 @@ import 'package:rive/src/rive_core/component.dart';
|
||||
class ArtboardImporter extends ImportStackObject {
|
||||
final RuntimeArtboard artboard;
|
||||
ArtboardImporter(this.artboard);
|
||||
final List<LinearAnimation> animations = [];
|
||||
|
||||
void addComponent(Core<CoreContext> object) => artboard.addObject(object);
|
||||
|
||||
void addAnimation(Animation animation) {
|
||||
if (animation is LinearAnimation) {
|
||||
animations.add(animation);
|
||||
}
|
||||
artboard.addObject(animation);
|
||||
animation.artboard = artboard;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ class LayerStateImporter extends ImportStackObject {
|
||||
|
||||
void addTransition(StateTransition transition) {
|
||||
state.context.addObject(transition);
|
||||
transition.stateFrom = state;
|
||||
state.internalAddTransition(transition);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1,10 +1,12 @@
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/rive_core/animation/animation_state.dart';
|
||||
import 'package:rive/src/rive_core/animation/layer_state.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_layer.dart';
|
||||
|
||||
class StateMachineLayerImporter extends ImportStackObject {
|
||||
final StateMachineLayer layer;
|
||||
StateMachineLayerImporter(this.layer);
|
||||
final ArtboardImporter artboardImporter;
|
||||
StateMachineLayerImporter(this.layer, this.artboardImporter);
|
||||
|
||||
final List<LayerState> importedStates = [];
|
||||
|
||||
@ -15,9 +17,19 @@ class StateMachineLayerImporter extends ImportStackObject {
|
||||
layer.internalAddState(state);
|
||||
}
|
||||
|
||||
bool _resolved = false;
|
||||
@override
|
||||
bool resolve() {
|
||||
assert(!_resolved);
|
||||
_resolved = true;
|
||||
for (final state in importedStates) {
|
||||
if (state is AnimationState) {
|
||||
int artboardAnimationIndex = state.animationId;
|
||||
assert(artboardAnimationIndex >= 0 &&
|
||||
artboardAnimationIndex < artboardImporter.animations.length);
|
||||
state.animationId =
|
||||
artboardImporter.animations[artboardAnimationIndex].id;
|
||||
}
|
||||
for (final transition in state.transitions) {
|
||||
// At import time the stateToId is an index relative to the entire layer
|
||||
// (which state in this layer). We can use that to find the matching
|
||||
|
@ -3,13 +3,23 @@ import 'package:rive/src/rive_core/animation/state_transition.dart';
|
||||
import 'package:rive/src/rive_core/animation/transition_condition.dart';
|
||||
|
||||
class StateTransitionImporter extends ImportStackObject {
|
||||
final StateMachineImporter stateMachineImporter;
|
||||
final StateTransition transition;
|
||||
StateTransitionImporter(this.transition);
|
||||
StateTransitionImporter(this.transition, this.stateMachineImporter);
|
||||
|
||||
void addCondition(TransitionCondition condition) {
|
||||
transition.context.addObject(condition);
|
||||
transition.internalAddCondition(condition);
|
||||
}
|
||||
|
||||
@override
|
||||
bool resolve() => true;
|
||||
bool resolve() {
|
||||
var inputs = stateMachineImporter.machine.inputs;
|
||||
for (final condition in transition.conditions) {
|
||||
var inputIndex = condition.inputId;
|
||||
assert(inputIndex >= 0 && inputIndex < inputs.length);
|
||||
condition.inputId = inputs[inputIndex].id;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,10 @@ import 'package:rive/src/generated/animation/keyframe_double_base.dart';
|
||||
import 'package:rive/src/generated/rive_core_context.dart';
|
||||
export 'package:rive/src/generated/animation/keyframe_double_base.dart';
|
||||
|
||||
double toDegrees(double rad) {
|
||||
return rad / 3.14 * 180;
|
||||
}
|
||||
|
||||
void _apply(
|
||||
Core<CoreContext> object, int propertyKey, double mix, double value) {
|
||||
if (mix == 1) {
|
||||
|
@ -5,6 +5,10 @@ class LinearAnimationInstance {
|
||||
final LinearAnimation animation;
|
||||
double _time = 0;
|
||||
int _direction = 1;
|
||||
bool _didLoop = false;
|
||||
bool get didLoop => _didLoop;
|
||||
double _spilledTime = 0;
|
||||
double get spilledTime => _spilledTime;
|
||||
LinearAnimationInstance(this.animation)
|
||||
: _time =
|
||||
(animation.enableWorkArea ? animation.workStart : 0).toDouble() /
|
||||
@ -27,7 +31,9 @@ class LinearAnimationInstance {
|
||||
double get startTime =>
|
||||
(animation.enableWorkArea ? animation.workStart : 0).toDouble() /
|
||||
animation.fps;
|
||||
double get progress => (_time - startTime) / (endTime - startTime);
|
||||
void reset() => _time = startTime;
|
||||
bool get keepGoing => animation.loop != Loop.oneShot || !_didLoop;
|
||||
bool advance(double elapsedSeconds) {
|
||||
_time += elapsedSeconds * animation.speed * _direction;
|
||||
double frames = _time * animation.fps;
|
||||
@ -35,31 +41,41 @@ class LinearAnimationInstance {
|
||||
var end = animation.enableWorkArea ? animation.workEnd : animation.duration;
|
||||
var range = end - start;
|
||||
bool keepGoing = true;
|
||||
_didLoop = false;
|
||||
_spilledTime = 0;
|
||||
switch (animation.loop) {
|
||||
case Loop.oneShot:
|
||||
if (frames > end) {
|
||||
keepGoing = false;
|
||||
_spilledTime = (frames - end) / animation.fps;
|
||||
frames = end.toDouble();
|
||||
_time = frames / animation.fps;
|
||||
_didLoop = true;
|
||||
}
|
||||
break;
|
||||
case Loop.loop:
|
||||
if (frames >= end) {
|
||||
_spilledTime = (frames - end) / animation.fps;
|
||||
frames = _time * animation.fps;
|
||||
frames = start + (frames - start) % range;
|
||||
_time = frames / animation.fps;
|
||||
_didLoop = true;
|
||||
}
|
||||
break;
|
||||
case Loop.pingPong:
|
||||
while (true) {
|
||||
if (_direction == 1 && frames >= end) {
|
||||
_spilledTime = (frames - end) / animation.fps;
|
||||
_direction = -1;
|
||||
frames = end + (end - frames);
|
||||
_time = frames / animation.fps;
|
||||
_didLoop = true;
|
||||
} else if (_direction == -1 && frames < start) {
|
||||
_spilledTime = (start - frames) / animation.fps;
|
||||
_direction = 1;
|
||||
frames = start + (start - frames);
|
||||
_time = frames / animation.fps;
|
||||
_didLoop = true;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
@ -4,4 +4,8 @@ export 'package:rive/src/generated/animation/state_machine_bool_base.dart';
|
||||
class StateMachineBool extends StateMachineBoolBase {
|
||||
@override
|
||||
void valueChanged(bool from, bool to) {}
|
||||
@override
|
||||
bool isValidType<T>() => T == bool;
|
||||
@override
|
||||
dynamic get controllerValue => value;
|
||||
}
|
||||
|
@ -4,4 +4,8 @@ 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>() => T == double;
|
||||
@override
|
||||
dynamic get controllerValue => value;
|
||||
}
|
||||
|
@ -8,4 +8,6 @@ abstract class StateMachineInput extends StateMachineInputBase {
|
||||
@override
|
||||
ListBase<StateMachineComponent> machineComponentList(StateMachine machine) =>
|
||||
machine?.inputs;
|
||||
bool isValidType<T>() => false;
|
||||
dynamic get controllerValue => null;
|
||||
}
|
||||
|
@ -11,4 +11,9 @@ class StateMachineTrigger extends StateMachineTriggerBase {
|
||||
void reset() {
|
||||
_triggered = false;
|
||||
}
|
||||
|
||||
@override
|
||||
bool isValidType<T>() => T == bool;
|
||||
@override
|
||||
dynamic get controllerValue => _triggered;
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ export 'package:rive/src/generated/animation/state_transition_base.dart';
|
||||
class StateTransition extends StateTransitionBase {
|
||||
final StateTransitionConditions conditions = StateTransitionConditions();
|
||||
LayerState stateTo;
|
||||
LayerState stateFrom;
|
||||
@override
|
||||
void onAdded() {}
|
||||
@override
|
||||
|
@ -1,10 +1,16 @@
|
||||
import 'dart:collection';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_bool.dart';
|
||||
import 'package:rive/src/rive_core/animation/transition_condition.dart';
|
||||
import 'package:rive/src/generated/animation/transition_bool_condition_base.dart';
|
||||
export 'package:rive/src/generated/animation/transition_bool_condition_base.dart';
|
||||
|
||||
class TransitionBoolCondition extends TransitionBoolConditionBase {
|
||||
@override
|
||||
void valueChanged(bool from, bool to) {}
|
||||
@override
|
||||
bool validate() => input == null || input is StateMachineBool;
|
||||
bool evaluate(HashMap<int, dynamic> values) {
|
||||
var boolInput = input as StateMachineBool;
|
||||
dynamic providedValue = values[input.id];
|
||||
bool value = providedValue is bool ? providedValue : boolInput.value;
|
||||
return (value && op == TransitionConditionOp.equal) ||
|
||||
(!value && op == TransitionConditionOp.notEqual);
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'dart:collection';
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_input.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_transition.dart';
|
||||
@ -36,6 +37,7 @@ abstract class TransitionCondition extends TransitionConditionBase {
|
||||
input = inputId == null ? null : context.resolve(inputId);
|
||||
}
|
||||
|
||||
bool evaluate(HashMap<int, dynamic> values);
|
||||
@override
|
||||
bool import(ImportStack importStack) {
|
||||
var importer = importStack
|
||||
|
@ -1,4 +1,6 @@
|
||||
import 'dart:collection';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_double.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';
|
||||
|
||||
@ -7,4 +9,26 @@ class TransitionDoubleCondition extends TransitionDoubleConditionBase {
|
||||
void valueChanged(double from, double to) {}
|
||||
@override
|
||||
bool validate() => input == null || input is StateMachineDouble;
|
||||
@override
|
||||
bool evaluate(HashMap<int, dynamic> values) {
|
||||
var doubleInput = input as StateMachineDouble;
|
||||
dynamic providedValue = values[input.id];
|
||||
double inputValue =
|
||||
providedValue is double ? providedValue : doubleInput.value;
|
||||
switch (op) {
|
||||
case TransitionConditionOp.equal:
|
||||
return inputValue == value;
|
||||
case TransitionConditionOp.notEqual:
|
||||
return inputValue != value;
|
||||
case TransitionConditionOp.lessThanOrEqual:
|
||||
return inputValue <= value;
|
||||
case TransitionConditionOp.lessThan:
|
||||
return inputValue < value;
|
||||
case TransitionConditionOp.greaterThanOrEqual:
|
||||
return inputValue >= value;
|
||||
case TransitionConditionOp.greaterThan:
|
||||
return inputValue > value;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,21 @@
|
||||
import 'dart:collection';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_trigger.dart';
|
||||
import 'package:rive/src/generated/animation/transition_trigger_condition_base.dart';
|
||||
export 'package:rive/src/generated/animation/transition_trigger_condition_base.dart';
|
||||
|
||||
class TransitionTriggerCondition extends TransitionTriggerConditionBase {
|
||||
@override
|
||||
bool validate() => input == null || input is StateMachineTrigger;
|
||||
bool evaluate(HashMap<int, dynamic> values) {
|
||||
dynamic providedValue = values[input.id];
|
||||
if (providedValue is bool && providedValue) {
|
||||
values[input.id] = false;
|
||||
return true;
|
||||
}
|
||||
var triggerInput = input as StateMachineTrigger;
|
||||
if (triggerInput.triggered) {
|
||||
triggerInput.reset();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import 'package:rive/src/rive_core/animation/transition_condition.dart';
|
||||
import 'package:rive/src/generated/animation/transition_value_condition_base.dart';
|
||||
export 'package:rive/src/generated/animation/transition_value_condition_base.dart';
|
||||
|
||||
class TransitionValueCondition extends TransitionValueConditionBase {
|
||||
abstract class TransitionValueCondition extends TransitionValueConditionBase {
|
||||
TransitionConditionOp get op => TransitionConditionOp.values[opValue];
|
||||
@override
|
||||
void opValueChanged(int from, int to) {}
|
||||
}
|
||||
|
141
lib/src/rive_core/state_machine_controller.dart
Normal file
141
lib/src/rive_core/state_machine_controller.dart
Normal file
@ -0,0 +1,141 @@
|
||||
import 'dart:collection';
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/rive_core/animation/animation_state.dart';
|
||||
import 'package:rive/src/rive_core/animation/layer_state.dart';
|
||||
import 'package:rive/src/rive_core/animation/linear_animation_instance.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_layer.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_transition.dart';
|
||||
import 'package:rive/src/rive_core/rive_animation_controller.dart';
|
||||
|
||||
class LayerController {
|
||||
final StateMachineLayer layer;
|
||||
LayerState _currentState;
|
||||
LinearAnimationInstance _animationInstanceFrom;
|
||||
StateTransition _transition;
|
||||
double _mix = 1.0;
|
||||
LinearAnimationInstance _animationInstance;
|
||||
LayerController(this.layer) {
|
||||
_changeState(layer.entryState);
|
||||
}
|
||||
bool _changeState(LayerState state) {
|
||||
if (state == _currentState) {
|
||||
return false;
|
||||
}
|
||||
_currentState = state;
|
||||
return true;
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_changeState(null);
|
||||
}
|
||||
|
||||
bool apply(CoreContext core, double elapsedSeconds,
|
||||
HashMap<int, dynamic> inputValues) {
|
||||
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 && _transition.duration != 0) {
|
||||
_mix = (_mix + elapsedSeconds / (_transition.duration / 1000))
|
||||
.clamp(0, 1)
|
||||
.toDouble();
|
||||
} else {
|
||||
_mix = 1;
|
||||
}
|
||||
var keepGoing = _mix != 1;
|
||||
if (_animationInstanceFrom != null && _mix < 1) {
|
||||
_animationInstanceFrom.advance(elapsedSeconds);
|
||||
_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;
|
||||
}
|
||||
|
||||
bool updateState(HashMap<int, dynamic> inputValues) {
|
||||
if (layer.anyState != null && tryChangeState(layer.anyState, inputValues)) {
|
||||
return true;
|
||||
}
|
||||
return tryChangeState(_currentState, inputValues);
|
||||
}
|
||||
|
||||
bool tryChangeState(LayerState stateFrom, HashMap<int, dynamic> inputValues) {
|
||||
for (final transition in stateFrom.transitions) {
|
||||
bool valid = true;
|
||||
for (final condition in transition.conditions) {
|
||||
if (!condition.evaluate(inputValues)) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (valid && _changeState(transition.stateTo)) {
|
||||
_transition = transition;
|
||||
if (_mix != 0) {
|
||||
_animationInstanceFrom = _animationInstance;
|
||||
}
|
||||
if (_currentState is AnimationState) {
|
||||
var spilledTime = _animationInstanceFrom?.spilledTime ?? 0;
|
||||
_animationInstance = LinearAnimationInstance(
|
||||
(_currentState as AnimationState).animation);
|
||||
_animationInstance.advance(spilledTime);
|
||||
_mix = 0;
|
||||
} else {
|
||||
_animationInstance = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class StateMachineController extends RiveAnimationController<CoreContext> {
|
||||
final StateMachine stateMachine;
|
||||
final inputValues = HashMap<int, dynamic>();
|
||||
StateMachineController(this.stateMachine);
|
||||
final layerControllers = <LayerController>[];
|
||||
void _clearLayerControllers() {
|
||||
for (final layer in layerControllers) {
|
||||
layer.dispose();
|
||||
}
|
||||
layerControllers.clear();
|
||||
}
|
||||
|
||||
@override
|
||||
bool init(CoreContext core) {
|
||||
_clearLayerControllers();
|
||||
for (final layer in stateMachine.layers) {
|
||||
layerControllers.add(LayerController(layer));
|
||||
}
|
||||
return super.init(core);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_clearLayerControllers();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void apply(CoreContext core, double elapsedSeconds) {
|
||||
bool keepGoing = false;
|
||||
for (final layerController in layerControllers) {
|
||||
if (layerController.apply(core, elapsedSeconds, inputValues)) {
|
||||
keepGoing = true;
|
||||
}
|
||||
}
|
||||
isActive = keepGoing;
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import 'package:rive/src/generated/animation/state_machine_base.dart';
|
||||
import 'package:rive/src/rive_core/animation/keyed_property.dart';
|
||||
import 'package:rive/src/rive_core/animation/layer_state.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_layer.dart';
|
||||
import 'package:rive/src/rive_core/component.dart';
|
||||
import 'package:rive/src/rive_core/runtime/runtime_header.dart';
|
||||
import 'package:rive/src/rive_core/backboard.dart';
|
||||
@ -21,6 +22,8 @@ import 'package:rive/src/rive_core/animation/keyed_object.dart';
|
||||
import 'package:rive/src/rive_core/animation/linear_animation.dart';
|
||||
import 'package:rive/src/rive_core/artboard.dart';
|
||||
|
||||
import 'rive_core/animation/state_transition.dart';
|
||||
|
||||
class RiveFile {
|
||||
RuntimeHeader _header;
|
||||
RuntimeHeader get header => _header;
|
||||
@ -71,10 +74,6 @@ class RiveFile {
|
||||
if (object == null) {
|
||||
continue;
|
||||
}
|
||||
var previousOfType = importStack.latest(object.coreType);
|
||||
if (previousOfType != null) {
|
||||
previousOfType.resolve();
|
||||
}
|
||||
|
||||
ImportStackObject stackObject;
|
||||
var stackType = object.coreType;
|
||||
@ -104,6 +103,16 @@ class RiveFile {
|
||||
case StateMachineBase.typeKey:
|
||||
stackObject = StateMachineImporter(object as StateMachine);
|
||||
break;
|
||||
case StateMachineLayerBase.typeKey:
|
||||
{
|
||||
// Needs artboard importer to resolve linear animations.
|
||||
var artboardImporter =
|
||||
importStack.latest<ArtboardImporter>(ArtboardBase.typeKey);
|
||||
assert(artboardImporter != null);
|
||||
stackObject = StateMachineLayerImporter(
|
||||
object as StateMachineLayer, artboardImporter);
|
||||
break;
|
||||
}
|
||||
case EntryStateBase.typeKey:
|
||||
case AnyStateBase.typeKey:
|
||||
case ExitStateBase.typeKey:
|
||||
@ -111,6 +120,15 @@ class RiveFile {
|
||||
stackObject = LayerStateImporter(object as LayerState);
|
||||
stackType = LayerStateBase.typeKey;
|
||||
break;
|
||||
case StateTransitionBase.typeKey:
|
||||
{
|
||||
var stateMachineImporter = importStack
|
||||
.latest<StateMachineImporter>(StateMachineBase.typeKey);
|
||||
assert(stateMachineImporter != null);
|
||||
stackObject = StateTransitionImporter(
|
||||
object as StateTransition, stateMachineImporter);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (object is Component) {
|
||||
// helper = _ArtboardObjectImportHelper();
|
||||
|
52
lib/src/state_machine_controller.dart
Normal file
52
lib/src/state_machine_controller.dart
Normal file
@ -0,0 +1,52 @@
|
||||
|
||||
import 'package:rive/src/rive_core/animation/state_machine.dart';
|
||||
import 'package:rive/src/rive_core/artboard.dart';
|
||||
import 'package:rive/src/rive_core/state_machine_controller.dart' as core;
|
||||
|
||||
class StateMachineInput<T> {
|
||||
final int id;
|
||||
final StateMachineController controller;
|
||||
StateMachineInput._(this.id, this.controller);
|
||||
|
||||
T get value => controller.inputValues[id] as T;
|
||||
set value(T newValue) => change(newValue);
|
||||
|
||||
/// Change the value of the input, returns true if the value was changed the
|
||||
/// and [StateMachineController] was activated.
|
||||
bool change(T value) {
|
||||
if (controller.inputValues[id] == value) {
|
||||
return false;
|
||||
}
|
||||
controller.inputValues[id] = value;
|
||||
controller.isActive = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// An AnimationController which controls a StateMachine and provides access to
|
||||
/// the inputs of the StateMachine.
|
||||
class StateMachineController extends core.StateMachineController {
|
||||
StateMachineController(StateMachine stateMachine) : super(stateMachine) {
|
||||
isActive = true;
|
||||
}
|
||||
|
||||
factory StateMachineController.fromArtboard(
|
||||
Artboard artboard, String stateMachineName) {
|
||||
for (final animation in artboard.animations) {
|
||||
if (animation is StateMachine && animation.name == stateMachineName) {
|
||||
return StateMachineController(animation);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
StateMachineInput<T> findInput<T>(String name) {
|
||||
for (final input in stateMachine.inputs) {
|
||||
if (input.name == name && input.isValidType<T>()) {
|
||||
inputValues[input.id] = input.controllerValue;
|
||||
return StateMachineInput<T>._(input.id, this);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user