Blend states!

This commit is contained in:
Luigi Rosso
2021-05-07 19:41:50 -07:00
parent 7daa856992
commit 37adf790ee
120 changed files with 2661 additions and 296 deletions

Binary file not shown.

View File

@ -0,0 +1,94 @@
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 and number
/// input.
class LiquidDownload extends StatefulWidget {
const LiquidDownload({Key? key}) : super(key: key);
@override
_LiquidDownloadState createState() => _LiquidDownloadState();
}
class _LiquidDownloadState extends State<LiquidDownload> {
/// Tracks if the animation is playing by whether controller is running.
bool get isPlaying => _controller?.isActive ?? false;
Artboard? _riveArtboard;
StateMachineController? _controller;
SMIInput<bool>? _start;
SMIInput<double>? _progress;
@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/liquid_download.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, 'Download');
if (controller != null) {
artboard.addController(controller);
_start = controller.findInput('Download');
_progress = controller.findInput('Progress');
}
setState(() => _riveArtboard = artboard);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey,
appBar: AppBar(
title: const Text('Liquid Download'),
),
body: Center(
child: _riveArtboard == null
? const SizedBox()
: GestureDetector(
onTapDown: (_) => _start?.value = true,
child: Column(
children: [
const SizedBox(height: 10),
const Text(
'Press to activate, slide for progress...',
style: TextStyle(
fontSize: 18,
),
),
Slider(
value: _progress!.value,
min: 0,
max: 100,
label: _progress!.value.round().toString(),
onChanged: (double value) {
setState(() {
_progress!.value = value;
});
},
),
const SizedBox(height: 10),
Expanded(
child: Rive(
artboard: _riveArtboard!,
),
),
],
),
),
),
);
}
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rive_example/example_animation.dart'; import 'package:rive_example/example_animation.dart';
import 'package:rive_example/example_state_machine.dart'; import 'package:rive_example/example_state_machine.dart';
import 'package:rive_example/liquid_download.dart';
import 'package:rive_example/little_machine.dart'; import 'package:rive_example/little_machine.dart';
import 'package:rive_example/state_machine_skills.dart'; import 'package:rive_example/state_machine_skills.dart';
@ -73,6 +74,20 @@ class Home extends StatelessWidget {
); );
}, },
), ),
const SizedBox(
height: 10,
),
ElevatedButton(
child: const Text('Liquid Download'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute<void>(
builder: (context) => const LiquidDownload(),
),
);
},
),
], ],
), ),
), ),

View File

@ -0,0 +1,20 @@
import 'dart:collection';
import 'package:rive/src/rive_core/animation/blend_animation.dart';
class BlendAnimations<T extends BlendAnimation> extends ListBase<T> {
final List<T?> _values = [];
List<T> get values => _values.cast<T>();
@override
int get length => _values.length;
@override
set length(int value) => _values.length = value;
@override
T operator [](int index) => _values[index]!;
@override
void operator []=(int index, T value) => _values[index] = value;
}

View File

@ -3,6 +3,7 @@ import 'dart:collection';
import 'package:rive/src/rive_core/runtime/exceptions/rive_format_error_exception.dart'; import 'package:rive/src/rive_core/runtime/exceptions/rive_format_error_exception.dart';
export 'package:rive/src/animation_list.dart'; export 'package:rive/src/animation_list.dart';
export 'package:rive/src/state_machine_components.dart'; export 'package:rive/src/state_machine_components.dart';
export 'package:rive/src/blend_animations.dart';
export 'package:rive/src/state_transition_conditions.dart'; export 'package:rive/src/state_transition_conditions.dart';
export 'package:rive/src/state_transitions.dart'; export 'package:rive/src/state_transitions.dart';
export 'package:rive/src/container_children.dart'; export 'package:rive/src/container_children.dart';

View File

@ -1,4 +1,8 @@
import 'package:rive/src/core/importers/artboard_import_stack_object.dart'; import 'package:rive/src/core/importers/artboard_import_stack_object.dart';
import 'package:rive/src/rive_core/animation/blend_animation.dart';
import 'package:rive/src/rive_core/animation/blend_state.dart';
import 'package:rive/src/rive_core/animation/blend_state_direct.dart';
import 'package:rive/src/rive_core/animation/blend_state_transition.dart';
import 'package:rive/src/rive_core/animation/layer_state.dart'; import 'package:rive/src/rive_core/animation/layer_state.dart';
import 'package:rive/src/rive_core/animation/state_transition.dart'; import 'package:rive/src/rive_core/animation/state_transition.dart';
@ -10,4 +14,24 @@ class LayerStateImporter extends ArtboardImportStackObject {
state.context.addObject(transition); state.context.addObject(transition);
state.internalAddTransition(transition); state.internalAddTransition(transition);
} }
bool addBlendAnimation(BlendAnimation blendAnimation) {
if (state is BlendStateDirect) {
var blendState = state as BlendStateDirect;
for (final transition
in state.transitions.whereType<BlendStateTransition>()) {
if (transition.exitBlendAnimationId >= 0 &&
transition.exitBlendAnimationId < blendState.animations.length) {
transition.exitBlendAnimation =
blendState.animations[transition.exitBlendAnimationId];
}
}
}
if (state is BlendState) {
(state as BlendState).internalAddAnimation(blendAnimation);
return true;
}
return false;
}
} }

View File

@ -1,13 +1,11 @@
import 'package:rive/rive.dart'; import 'package:rive/rive.dart';
import 'package:rive/src/core/core.dart'; 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/layer_state.dart';
import 'package:rive/src/rive_core/animation/state_machine_layer.dart'; import 'package:rive/src/rive_core/animation/state_machine_layer.dart';
class StateMachineLayerImporter extends ImportStackObject { class StateMachineLayerImporter extends ImportStackObject {
final StateMachineLayer layer; final StateMachineLayer layer;
final ArtboardImporter artboardImporter; StateMachineLayerImporter(this.layer);
StateMachineLayerImporter(this.layer, this.artboardImporter);
final List<LayerState> importedStates = []; final List<LayerState> importedStates = [];
@ -27,12 +25,6 @@ class StateMachineLayerImporter extends ImportStackObject {
assert(!_resolved); assert(!_resolved);
_resolved = true; _resolved = true;
for (final state in importedStates) { for (final state in importedStates) {
if (state is AnimationState) {
int artboardAnimationIndex = state.animationId;
assert(artboardAnimationIndex >= 0 &&
artboardAnimationIndex < artboardImporter.animations.length);
state.animation = artboardImporter.animations[artboardAnimationIndex];
}
for (final transition in state.transitions) { for (final transition in state.transitions) {
// At import time the stateToId is an index relative to the entire layer // 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 // (which state in this layer). We can use that to find the matching

View File

@ -0,0 +1,37 @@
/// Core automatically generated
/// lib/src/generated/animation/blend_animation_1d_base.dart.
/// Do not modify manually.
import 'package:rive/src/generated/animation/blend_animation_base.dart';
import 'package:rive/src/rive_core/animation/blend_animation.dart';
abstract class BlendAnimation1DBase extends BlendAnimation {
static const int typeKey = 75;
@override
int get coreType => BlendAnimation1DBase.typeKey;
@override
Set<int> get coreTypes =>
{BlendAnimation1DBase.typeKey, BlendAnimationBase.typeKey};
/// --------------------------------------------------------------------------
/// Value field with key 166.
static const double valueInitialValue = 0;
double _value = valueInitialValue;
static const int valuePropertyKey = 166;
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);
}

View File

@ -0,0 +1,38 @@
/// Core automatically generated
/// lib/src/generated/animation/blend_animation_base.dart.
/// Do not modify manually.
import 'package:rive/src/core/core.dart';
abstract class BlendAnimationBase<T extends CoreContext> extends Core<T> {
static const int typeKey = 74;
@override
int get coreType => BlendAnimationBase.typeKey;
@override
Set<int> get coreTypes => {BlendAnimationBase.typeKey};
/// --------------------------------------------------------------------------
/// AnimationId field with key 165.
static const int animationIdInitialValue = -1;
int _animationId = animationIdInitialValue;
static const int animationIdPropertyKey = 165;
/// Id of the animation this BlendAnimation references.
int get animationId => _animationId;
/// Change the [_animationId] field value.
/// [animationIdChanged] will be invoked only if the field's value has
/// changed.
set animationId(int value) {
if (_animationId == value) {
return;
}
int from = _animationId;
_animationId = value;
if (hasValidated) {
animationIdChanged(from, value);
}
}
void animationIdChanged(int from, int to);
}

View File

@ -0,0 +1,39 @@
/// Core automatically generated
/// lib/src/generated/animation/blend_animation_direct_base.dart.
/// Do not modify manually.
import 'package:rive/src/generated/animation/blend_animation_base.dart';
import 'package:rive/src/rive_core/animation/blend_animation.dart';
abstract class BlendAnimationDirectBase extends BlendAnimation {
static const int typeKey = 77;
@override
int get coreType => BlendAnimationDirectBase.typeKey;
@override
Set<int> get coreTypes =>
{BlendAnimationDirectBase.typeKey, BlendAnimationBase.typeKey};
/// --------------------------------------------------------------------------
/// InputId field with key 168.
static const int inputIdInitialValue = -1;
int _inputId = inputIdInitialValue;
static const int inputIdPropertyKey = 168;
/// Id of the input that drives the direct mix value for this animation.
int get inputId => _inputId;
/// Change the [_inputId] field value.
/// [inputIdChanged] will be invoked only if the field's value has changed.
set inputId(int value) {
if (_inputId == value) {
return;
}
int from = _inputId;
_inputId = value;
if (hasValidated) {
inputIdChanged(from, value);
}
}
void inputIdChanged(int from, int to);
}

View File

@ -0,0 +1,46 @@
/// Core automatically generated
/// lib/src/generated/animation/blend_state_1d_base.dart.
/// Do not modify manually.
import 'package:rive/src/generated/animation/blend_state_base.dart';
import 'package:rive/src/generated/animation/layer_state_base.dart';
import 'package:rive/src/generated/animation/state_machine_layer_component_base.dart';
import 'package:rive/src/rive_core/animation/blend_animation_1d.dart';
import 'package:rive/src/rive_core/animation/blend_state.dart';
abstract class BlendState1DBase extends BlendState<BlendAnimation1D> {
static const int typeKey = 76;
@override
int get coreType => BlendState1DBase.typeKey;
@override
Set<int> get coreTypes => {
BlendState1DBase.typeKey,
BlendStateBase.typeKey,
LayerStateBase.typeKey,
StateMachineLayerComponentBase.typeKey
};
/// --------------------------------------------------------------------------
/// InputId field with key 167.
static const int inputIdInitialValue = -1;
int _inputId = inputIdInitialValue;
static const int inputIdPropertyKey = 167;
/// Id of the input that drives the mix value for this blend state.
int get inputId => _inputId;
/// Change the [_inputId] field value.
/// [inputIdChanged] will be invoked only if the field's value has changed.
set inputId(int value) {
if (_inputId == value) {
return;
}
int from = _inputId;
_inputId = value;
if (hasValidated) {
inputIdChanged(from, value);
}
}
void inputIdChanged(int from, int to);
}

View File

@ -0,0 +1,19 @@
/// Core automatically generated
/// lib/src/generated/animation/blend_state_base.dart.
/// Do not modify manually.
import 'package:rive/src/generated/animation/layer_state_base.dart';
import 'package:rive/src/generated/animation/state_machine_layer_component_base.dart';
import 'package:rive/src/rive_core/animation/layer_state.dart';
abstract class BlendStateBase extends LayerState {
static const int typeKey = 72;
@override
int get coreType => BlendStateBase.typeKey;
@override
Set<int> get coreTypes => {
BlendStateBase.typeKey,
LayerStateBase.typeKey,
StateMachineLayerComponentBase.typeKey
};
}

View File

@ -0,0 +1,22 @@
/// Core automatically generated
/// lib/src/generated/animation/blend_state_direct_base.dart.
/// Do not modify manually.
import 'package:rive/src/generated/animation/blend_state_base.dart';
import 'package:rive/src/generated/animation/layer_state_base.dart';
import 'package:rive/src/generated/animation/state_machine_layer_component_base.dart';
import 'package:rive/src/rive_core/animation/blend_animation_direct.dart';
import 'package:rive/src/rive_core/animation/blend_state.dart';
abstract class BlendStateDirectBase extends BlendState<BlendAnimationDirect> {
static const int typeKey = 73;
@override
int get coreType => BlendStateDirectBase.typeKey;
@override
Set<int> get coreTypes => {
BlendStateDirectBase.typeKey,
BlendStateBase.typeKey,
LayerStateBase.typeKey,
StateMachineLayerComponentBase.typeKey
};
}

View File

@ -0,0 +1,44 @@
/// Core automatically generated
/// lib/src/generated/animation/blend_state_transition_base.dart.
/// Do not modify manually.
import 'package:rive/src/generated/animation/state_machine_layer_component_base.dart';
import 'package:rive/src/generated/animation/state_transition_base.dart';
import 'package:rive/src/rive_core/animation/state_transition.dart';
abstract class BlendStateTransitionBase extends StateTransition {
static const int typeKey = 78;
@override
int get coreType => BlendStateTransitionBase.typeKey;
@override
Set<int> get coreTypes => {
BlendStateTransitionBase.typeKey,
StateTransitionBase.typeKey,
StateMachineLayerComponentBase.typeKey
};
/// --------------------------------------------------------------------------
/// ExitBlendAnimationId field with key 171.
static const int exitBlendAnimationIdInitialValue = -1;
int _exitBlendAnimationId = exitBlendAnimationIdInitialValue;
static const int exitBlendAnimationIdPropertyKey = 171;
/// Id of the state the blend state animation used for exit time calculation.
int get exitBlendAnimationId => _exitBlendAnimationId;
/// Change the [_exitBlendAnimationId] field value.
/// [exitBlendAnimationIdChanged] will be invoked only if the field's value
/// has changed.
set exitBlendAnimationId(int value) {
if (_exitBlendAnimationId == value) {
return;
}
int from = _exitBlendAnimationId;
_exitBlendAnimationId = value;
if (hasValidated) {
exitBlendAnimationIdChanged(from, value);
}
}
void exitBlendAnimationIdChanged(int from, int to);
}

View File

@ -8,6 +8,12 @@ import 'package:rive/src/core/field_types/core_uint_type.dart';
import 'package:rive/src/generated/animation/animation_base.dart'; import 'package:rive/src/generated/animation/animation_base.dart';
import 'package:rive/src/generated/animation/animation_state_base.dart'; import 'package:rive/src/generated/animation/animation_state_base.dart';
import 'package:rive/src/generated/animation/any_state_base.dart'; import 'package:rive/src/generated/animation/any_state_base.dart';
import 'package:rive/src/generated/animation/blend_animation_1d_base.dart';
import 'package:rive/src/generated/animation/blend_animation_base.dart';
import 'package:rive/src/generated/animation/blend_animation_direct_base.dart';
import 'package:rive/src/generated/animation/blend_state_1d_base.dart';
import 'package:rive/src/generated/animation/blend_state_direct_base.dart';
import 'package:rive/src/generated/animation/blend_state_transition_base.dart';
import 'package:rive/src/generated/animation/cubic_interpolator_base.dart'; import 'package:rive/src/generated/animation/cubic_interpolator_base.dart';
import 'package:rive/src/generated/animation/entry_state_base.dart'; import 'package:rive/src/generated/animation/entry_state_base.dart';
import 'package:rive/src/generated/animation/exit_state_base.dart'; import 'package:rive/src/generated/animation/exit_state_base.dart';
@ -70,6 +76,11 @@ import 'package:rive/src/generated/transform_component_base.dart';
import 'package:rive/src/rive_core/animation/animation.dart'; import 'package:rive/src/rive_core/animation/animation.dart';
import 'package:rive/src/rive_core/animation/animation_state.dart'; import 'package:rive/src/rive_core/animation/animation_state.dart';
import 'package:rive/src/rive_core/animation/any_state.dart'; import 'package:rive/src/rive_core/animation/any_state.dart';
import 'package:rive/src/rive_core/animation/blend_animation_1d.dart';
import 'package:rive/src/rive_core/animation/blend_animation_direct.dart';
import 'package:rive/src/rive_core/animation/blend_state_1d.dart';
import 'package:rive/src/rive_core/animation/blend_state_direct.dart';
import 'package:rive/src/rive_core/animation/blend_state_transition.dart';
import 'package:rive/src/rive_core/animation/cubic_interpolator.dart'; import 'package:rive/src/rive_core/animation/cubic_interpolator.dart';
import 'package:rive/src/rive_core/animation/entry_state.dart'; import 'package:rive/src/rive_core/animation/entry_state.dart';
import 'package:rive/src/rive_core/animation/exit_state.dart'; import 'package:rive/src/rive_core/animation/exit_state.dart';
@ -129,6 +140,8 @@ class RiveCoreContext {
return AnimationState(); return AnimationState();
case KeyedObjectBase.typeKey: case KeyedObjectBase.typeKey:
return KeyedObject(); return KeyedObject();
case BlendAnimationDirectBase.typeKey:
return BlendAnimationDirect();
case StateMachineNumberBase.typeKey: case StateMachineNumberBase.typeKey:
return StateMachineNumber(); return StateMachineNumber();
case TransitionTriggerConditionBase.typeKey: case TransitionTriggerConditionBase.typeKey:
@ -161,10 +174,18 @@ class RiveCoreContext {
return LinearAnimation(); return LinearAnimation();
case StateMachineTriggerBase.typeKey: case StateMachineTriggerBase.typeKey:
return StateMachineTrigger(); return StateMachineTrigger();
case BlendStateDirectBase.typeKey:
return BlendStateDirect();
case ExitStateBase.typeKey: case ExitStateBase.typeKey:
return ExitState(); return ExitState();
case BlendAnimation1DBase.typeKey:
return BlendAnimation1D();
case BlendState1DBase.typeKey:
return BlendState1D();
case TransitionBoolConditionBase.typeKey: case TransitionBoolConditionBase.typeKey:
return TransitionBoolCondition(); return TransitionBoolCondition();
case BlendStateTransitionBase.typeKey:
return BlendStateTransition();
case StateMachineBoolBase.typeKey: case StateMachineBoolBase.typeKey:
return StateMachineBool(); return StateMachineBool();
case LinearGradientBase.typeKey: case LinearGradientBase.typeKey:
@ -262,6 +283,16 @@ class RiveCoreContext {
object.objectId = value; object.objectId = value;
} }
break; break;
case BlendAnimationBase.animationIdPropertyKey:
if (object is BlendAnimationBase && value is int) {
object.animationId = value;
}
break;
case BlendAnimationDirectBase.inputIdPropertyKey:
if (object is BlendAnimationDirectBase && value is int) {
object.inputId = value;
}
break;
case StateMachineComponentBase.namePropertyKey: case StateMachineComponentBase.namePropertyKey:
if (object is StateMachineComponentBase && value is String) { if (object is StateMachineComponentBase && value is String) {
object.name = value; object.name = value;
@ -402,6 +433,21 @@ class RiveCoreContext {
object.enableWorkArea = value; object.enableWorkArea = value;
} }
break; break;
case BlendAnimation1DBase.valuePropertyKey:
if (object is BlendAnimation1DBase && value is double) {
object.value = value;
}
break;
case BlendState1DBase.inputIdPropertyKey:
if (object is BlendState1DBase && value is int) {
object.inputId = value;
}
break;
case BlendStateTransitionBase.exitBlendAnimationIdPropertyKey:
if (object is BlendStateTransitionBase && value is int) {
object.exitBlendAnimationId = value;
}
break;
case StateMachineBoolBase.valuePropertyKey: case StateMachineBoolBase.valuePropertyKey:
if (object is StateMachineBoolBase && value is bool) { if (object is StateMachineBoolBase && value is bool) {
object.value = value; object.value = value;
@ -846,6 +892,8 @@ class RiveCoreContext {
case DrawTargetBase.placementValuePropertyKey: case DrawTargetBase.placementValuePropertyKey:
case AnimationStateBase.animationIdPropertyKey: case AnimationStateBase.animationIdPropertyKey:
case KeyedObjectBase.objectIdPropertyKey: case KeyedObjectBase.objectIdPropertyKey:
case BlendAnimationBase.animationIdPropertyKey:
case BlendAnimationDirectBase.inputIdPropertyKey:
case TransitionConditionBase.inputIdPropertyKey: case TransitionConditionBase.inputIdPropertyKey:
case KeyedPropertyBase.propertyKeyPropertyKey: case KeyedPropertyBase.propertyKeyPropertyKey:
case KeyFrameBase.framePropertyKey: case KeyFrameBase.framePropertyKey:
@ -862,6 +910,8 @@ class RiveCoreContext {
case LinearAnimationBase.loopValuePropertyKey: case LinearAnimationBase.loopValuePropertyKey:
case LinearAnimationBase.workStartPropertyKey: case LinearAnimationBase.workStartPropertyKey:
case LinearAnimationBase.workEndPropertyKey: case LinearAnimationBase.workEndPropertyKey:
case BlendState1DBase.inputIdPropertyKey:
case BlendStateTransitionBase.exitBlendAnimationIdPropertyKey:
case StrokeBase.capPropertyKey: case StrokeBase.capPropertyKey:
case StrokeBase.joinPropertyKey: case StrokeBase.joinPropertyKey:
case TrimPathBase.modeValuePropertyKey: case TrimPathBase.modeValuePropertyKey:
@ -889,6 +939,7 @@ class RiveCoreContext {
case CubicInterpolatorBase.y2PropertyKey: case CubicInterpolatorBase.y2PropertyKey:
case KeyFrameDoubleBase.valuePropertyKey: case KeyFrameDoubleBase.valuePropertyKey:
case LinearAnimationBase.speedPropertyKey: case LinearAnimationBase.speedPropertyKey:
case BlendAnimation1DBase.valuePropertyKey:
case LinearGradientBase.startXPropertyKey: case LinearGradientBase.startXPropertyKey:
case LinearGradientBase.startYPropertyKey: case LinearGradientBase.startYPropertyKey:
case LinearGradientBase.endXPropertyKey: case LinearGradientBase.endXPropertyKey:
@ -990,6 +1041,10 @@ class RiveCoreContext {
return (object as AnimationStateBase).animationId; return (object as AnimationStateBase).animationId;
case KeyedObjectBase.objectIdPropertyKey: case KeyedObjectBase.objectIdPropertyKey:
return (object as KeyedObjectBase).objectId; return (object as KeyedObjectBase).objectId;
case BlendAnimationBase.animationIdPropertyKey:
return (object as BlendAnimationBase).animationId;
case BlendAnimationDirectBase.inputIdPropertyKey:
return (object as BlendAnimationDirectBase).inputId;
case TransitionConditionBase.inputIdPropertyKey: case TransitionConditionBase.inputIdPropertyKey:
return (object as TransitionConditionBase).inputId; return (object as TransitionConditionBase).inputId;
case KeyedPropertyBase.propertyKeyPropertyKey: case KeyedPropertyBase.propertyKeyPropertyKey:
@ -1022,6 +1077,10 @@ class RiveCoreContext {
return (object as LinearAnimationBase).workStart; return (object as LinearAnimationBase).workStart;
case LinearAnimationBase.workEndPropertyKey: case LinearAnimationBase.workEndPropertyKey:
return (object as LinearAnimationBase).workEnd; return (object as LinearAnimationBase).workEnd;
case BlendState1DBase.inputIdPropertyKey:
return (object as BlendState1DBase).inputId;
case BlendStateTransitionBase.exitBlendAnimationIdPropertyKey:
return (object as BlendStateTransitionBase).exitBlendAnimationId;
case StrokeBase.capPropertyKey: case StrokeBase.capPropertyKey:
return (object as StrokeBase).cap; return (object as StrokeBase).cap;
case StrokeBase.joinPropertyKey: case StrokeBase.joinPropertyKey:
@ -1080,6 +1139,8 @@ class RiveCoreContext {
return (object as KeyFrameDoubleBase).value; return (object as KeyFrameDoubleBase).value;
case LinearAnimationBase.speedPropertyKey: case LinearAnimationBase.speedPropertyKey:
return (object as LinearAnimationBase).speed; return (object as LinearAnimationBase).speed;
case BlendAnimation1DBase.valuePropertyKey:
return (object as BlendAnimation1DBase).value;
case LinearGradientBase.startXPropertyKey: case LinearGradientBase.startXPropertyKey:
return (object as LinearGradientBase).startX; return (object as LinearGradientBase).startX;
case LinearGradientBase.startYPropertyKey: case LinearGradientBase.startYPropertyKey:
@ -1237,13 +1298,19 @@ class RiveCoreContext {
static void setString(Core object, int propertyKey, String value) { static void setString(Core object, int propertyKey, String value) {
switch (propertyKey) { switch (propertyKey) {
case ComponentBase.namePropertyKey: case ComponentBase.namePropertyKey:
if (object is ComponentBase) object.name = value; if (object is ComponentBase) {
object.name = value;
}
break; break;
case StateMachineComponentBase.namePropertyKey: case StateMachineComponentBase.namePropertyKey:
if (object is StateMachineComponentBase) object.name = value; if (object is StateMachineComponentBase) {
object.name = value;
}
break; break;
case AnimationBase.namePropertyKey: case AnimationBase.namePropertyKey:
if (object is AnimationBase) object.name = value; if (object is AnimationBase) {
object.name = value;
}
break; break;
} }
} }
@ -1251,121 +1318,219 @@ class RiveCoreContext {
static void setUint(Core object, int propertyKey, int value) { static void setUint(Core object, int propertyKey, int value) {
switch (propertyKey) { switch (propertyKey) {
case ComponentBase.parentIdPropertyKey: case ComponentBase.parentIdPropertyKey:
if (object is ComponentBase) object.parentId = value; if (object is ComponentBase) {
object.parentId = value;
}
break; break;
case DrawTargetBase.drawableIdPropertyKey: case DrawTargetBase.drawableIdPropertyKey:
if (object is DrawTargetBase) object.drawableId = value; if (object is DrawTargetBase) {
object.drawableId = value;
}
break; break;
case DrawTargetBase.placementValuePropertyKey: case DrawTargetBase.placementValuePropertyKey:
if (object is DrawTargetBase) object.placementValue = value; if (object is DrawTargetBase) {
object.placementValue = value;
}
break; break;
case AnimationStateBase.animationIdPropertyKey: case AnimationStateBase.animationIdPropertyKey:
if (object is AnimationStateBase) object.animationId = value; if (object is AnimationStateBase) {
object.animationId = value;
}
break; break;
case KeyedObjectBase.objectIdPropertyKey: case KeyedObjectBase.objectIdPropertyKey:
if (object is KeyedObjectBase) object.objectId = value; if (object is KeyedObjectBase) {
object.objectId = value;
}
break;
case BlendAnimationBase.animationIdPropertyKey:
if (object is BlendAnimationBase) {
object.animationId = value;
}
break;
case BlendAnimationDirectBase.inputIdPropertyKey:
if (object is BlendAnimationDirectBase) {
object.inputId = value;
}
break; break;
case TransitionConditionBase.inputIdPropertyKey: case TransitionConditionBase.inputIdPropertyKey:
if (object is TransitionConditionBase) object.inputId = value; if (object is TransitionConditionBase) {
object.inputId = value;
}
break; break;
case KeyedPropertyBase.propertyKeyPropertyKey: case KeyedPropertyBase.propertyKeyPropertyKey:
if (object is KeyedPropertyBase) object.propertyKey = value; if (object is KeyedPropertyBase) {
object.propertyKey = value;
}
break; break;
case KeyFrameBase.framePropertyKey: case KeyFrameBase.framePropertyKey:
if (object is KeyFrameBase) object.frame = value; if (object is KeyFrameBase) {
object.frame = value;
}
break; break;
case KeyFrameBase.interpolationTypePropertyKey: case KeyFrameBase.interpolationTypePropertyKey:
if (object is KeyFrameBase) object.interpolationType = value; if (object is KeyFrameBase) {
object.interpolationType = value;
}
break; break;
case KeyFrameBase.interpolatorIdPropertyKey: case KeyFrameBase.interpolatorIdPropertyKey:
if (object is KeyFrameBase) object.interpolatorId = value; if (object is KeyFrameBase) {
object.interpolatorId = value;
}
break; break;
case KeyFrameIdBase.valuePropertyKey: case KeyFrameIdBase.valuePropertyKey:
if (object is KeyFrameIdBase) object.value = value; if (object is KeyFrameIdBase) {
object.value = value;
}
break; break;
case TransitionValueConditionBase.opValuePropertyKey: case TransitionValueConditionBase.opValuePropertyKey:
if (object is TransitionValueConditionBase) object.opValue = value; if (object is TransitionValueConditionBase) {
object.opValue = value;
}
break; break;
case StateTransitionBase.stateToIdPropertyKey: case StateTransitionBase.stateToIdPropertyKey:
if (object is StateTransitionBase) object.stateToId = value; if (object is StateTransitionBase) {
object.stateToId = value;
}
break; break;
case StateTransitionBase.flagsPropertyKey: case StateTransitionBase.flagsPropertyKey:
if (object is StateTransitionBase) object.flags = value; if (object is StateTransitionBase) {
object.flags = value;
}
break; break;
case StateTransitionBase.durationPropertyKey: case StateTransitionBase.durationPropertyKey:
if (object is StateTransitionBase) object.duration = value; if (object is StateTransitionBase) {
object.duration = value;
}
break; break;
case StateTransitionBase.exitTimePropertyKey: case StateTransitionBase.exitTimePropertyKey:
if (object is StateTransitionBase) object.exitTime = value; if (object is StateTransitionBase) {
object.exitTime = value;
}
break; break;
case LinearAnimationBase.fpsPropertyKey: case LinearAnimationBase.fpsPropertyKey:
if (object is LinearAnimationBase) object.fps = value; if (object is LinearAnimationBase) {
object.fps = value;
}
break; break;
case LinearAnimationBase.durationPropertyKey: case LinearAnimationBase.durationPropertyKey:
if (object is LinearAnimationBase) object.duration = value; if (object is LinearAnimationBase) {
object.duration = value;
}
break; break;
case LinearAnimationBase.loopValuePropertyKey: case LinearAnimationBase.loopValuePropertyKey:
if (object is LinearAnimationBase) object.loopValue = value; if (object is LinearAnimationBase) {
object.loopValue = value;
}
break; break;
case LinearAnimationBase.workStartPropertyKey: case LinearAnimationBase.workStartPropertyKey:
if (object is LinearAnimationBase) object.workStart = value; if (object is LinearAnimationBase) {
object.workStart = value;
}
break; break;
case LinearAnimationBase.workEndPropertyKey: case LinearAnimationBase.workEndPropertyKey:
if (object is LinearAnimationBase) object.workEnd = value; if (object is LinearAnimationBase) {
object.workEnd = value;
}
break;
case BlendState1DBase.inputIdPropertyKey:
if (object is BlendState1DBase) {
object.inputId = value;
}
break;
case BlendStateTransitionBase.exitBlendAnimationIdPropertyKey:
if (object is BlendStateTransitionBase) {
object.exitBlendAnimationId = value;
}
break; break;
case StrokeBase.capPropertyKey: case StrokeBase.capPropertyKey:
if (object is StrokeBase) object.cap = value; if (object is StrokeBase) {
object.cap = value;
}
break; break;
case StrokeBase.joinPropertyKey: case StrokeBase.joinPropertyKey:
if (object is StrokeBase) object.join = value; if (object is StrokeBase) {
object.join = value;
}
break; break;
case TrimPathBase.modeValuePropertyKey: case TrimPathBase.modeValuePropertyKey:
if (object is TrimPathBase) object.modeValue = value; if (object is TrimPathBase) {
object.modeValue = value;
}
break; break;
case FillBase.fillRulePropertyKey: case FillBase.fillRulePropertyKey:
if (object is FillBase) object.fillRule = value; if (object is FillBase) {
object.fillRule = value;
}
break; break;
case PathBase.pathFlagsPropertyKey: case PathBase.pathFlagsPropertyKey:
if (object is PathBase) object.pathFlags = value; if (object is PathBase) {
object.pathFlags = value;
}
break; break;
case DrawableBase.blendModeValuePropertyKey: case DrawableBase.blendModeValuePropertyKey:
if (object is DrawableBase) object.blendModeValue = value; if (object is DrawableBase) {
object.blendModeValue = value;
}
break; break;
case DrawableBase.drawableFlagsPropertyKey: case DrawableBase.drawableFlagsPropertyKey:
if (object is DrawableBase) object.drawableFlags = value; if (object is DrawableBase) {
object.drawableFlags = value;
}
break; break;
case WeightBase.valuesPropertyKey: case WeightBase.valuesPropertyKey:
if (object is WeightBase) object.values = value; if (object is WeightBase) {
object.values = value;
}
break; break;
case WeightBase.indicesPropertyKey: case WeightBase.indicesPropertyKey:
if (object is WeightBase) object.indices = value; if (object is WeightBase) {
object.indices = value;
}
break; break;
case CubicWeightBase.inValuesPropertyKey: case CubicWeightBase.inValuesPropertyKey:
if (object is CubicWeightBase) object.inValues = value; if (object is CubicWeightBase) {
object.inValues = value;
}
break; break;
case CubicWeightBase.inIndicesPropertyKey: case CubicWeightBase.inIndicesPropertyKey:
if (object is CubicWeightBase) object.inIndices = value; if (object is CubicWeightBase) {
object.inIndices = value;
}
break; break;
case CubicWeightBase.outValuesPropertyKey: case CubicWeightBase.outValuesPropertyKey:
if (object is CubicWeightBase) object.outValues = value; if (object is CubicWeightBase) {
object.outValues = value;
}
break; break;
case CubicWeightBase.outIndicesPropertyKey: case CubicWeightBase.outIndicesPropertyKey:
if (object is CubicWeightBase) object.outIndices = value; if (object is CubicWeightBase) {
object.outIndices = value;
}
break; break;
case ClippingShapeBase.sourceIdPropertyKey: case ClippingShapeBase.sourceIdPropertyKey:
if (object is ClippingShapeBase) object.sourceId = value; if (object is ClippingShapeBase) {
object.sourceId = value;
}
break; break;
case ClippingShapeBase.fillRulePropertyKey: case ClippingShapeBase.fillRulePropertyKey:
if (object is ClippingShapeBase) object.fillRule = value; if (object is ClippingShapeBase) {
object.fillRule = value;
}
break; break;
case PolygonBase.pointsPropertyKey: case PolygonBase.pointsPropertyKey:
if (object is PolygonBase) object.points = value; if (object is PolygonBase) {
object.points = value;
}
break; break;
case DrawRulesBase.drawTargetIdPropertyKey: case DrawRulesBase.drawTargetIdPropertyKey:
if (object is DrawRulesBase) object.drawTargetId = value; if (object is DrawRulesBase) {
object.drawTargetId = value;
}
break; break;
case TendonBase.boneIdPropertyKey: case TendonBase.boneIdPropertyKey:
if (object is TendonBase) object.boneId = value; if (object is TendonBase) {
object.boneId = value;
}
break; break;
} }
} }
@ -1373,205 +1538,344 @@ class RiveCoreContext {
static void setDouble(Core object, int propertyKey, double value) { static void setDouble(Core object, int propertyKey, double value) {
switch (propertyKey) { switch (propertyKey) {
case StateMachineNumberBase.valuePropertyKey: case StateMachineNumberBase.valuePropertyKey:
if (object is StateMachineNumberBase) object.value = value; if (object is StateMachineNumberBase) {
object.value = value;
}
break; break;
case TransitionNumberConditionBase.valuePropertyKey: case TransitionNumberConditionBase.valuePropertyKey:
if (object is TransitionNumberConditionBase) object.value = value; if (object is TransitionNumberConditionBase) {
object.value = value;
}
break; break;
case CubicInterpolatorBase.x1PropertyKey: case CubicInterpolatorBase.x1PropertyKey:
if (object is CubicInterpolatorBase) object.x1 = value; if (object is CubicInterpolatorBase) {
object.x1 = value;
}
break; break;
case CubicInterpolatorBase.y1PropertyKey: case CubicInterpolatorBase.y1PropertyKey:
if (object is CubicInterpolatorBase) object.y1 = value; if (object is CubicInterpolatorBase) {
object.y1 = value;
}
break; break;
case CubicInterpolatorBase.x2PropertyKey: case CubicInterpolatorBase.x2PropertyKey:
if (object is CubicInterpolatorBase) object.x2 = value; if (object is CubicInterpolatorBase) {
object.x2 = value;
}
break; break;
case CubicInterpolatorBase.y2PropertyKey: case CubicInterpolatorBase.y2PropertyKey:
if (object is CubicInterpolatorBase) object.y2 = value; if (object is CubicInterpolatorBase) {
object.y2 = value;
}
break; break;
case KeyFrameDoubleBase.valuePropertyKey: case KeyFrameDoubleBase.valuePropertyKey:
if (object is KeyFrameDoubleBase) object.value = value; if (object is KeyFrameDoubleBase) {
object.value = value;
}
break; break;
case LinearAnimationBase.speedPropertyKey: case LinearAnimationBase.speedPropertyKey:
if (object is LinearAnimationBase) object.speed = value; if (object is LinearAnimationBase) {
object.speed = value;
}
break;
case BlendAnimation1DBase.valuePropertyKey:
if (object is BlendAnimation1DBase) {
object.value = value;
}
break; break;
case LinearGradientBase.startXPropertyKey: case LinearGradientBase.startXPropertyKey:
if (object is LinearGradientBase) object.startX = value; if (object is LinearGradientBase) {
object.startX = value;
}
break; break;
case LinearGradientBase.startYPropertyKey: case LinearGradientBase.startYPropertyKey:
if (object is LinearGradientBase) object.startY = value; if (object is LinearGradientBase) {
object.startY = value;
}
break; break;
case LinearGradientBase.endXPropertyKey: case LinearGradientBase.endXPropertyKey:
if (object is LinearGradientBase) object.endX = value; if (object is LinearGradientBase) {
object.endX = value;
}
break; break;
case LinearGradientBase.endYPropertyKey: case LinearGradientBase.endYPropertyKey:
if (object is LinearGradientBase) object.endY = value; if (object is LinearGradientBase) {
object.endY = value;
}
break; break;
case LinearGradientBase.opacityPropertyKey: case LinearGradientBase.opacityPropertyKey:
if (object is LinearGradientBase) object.opacity = value; if (object is LinearGradientBase) {
object.opacity = value;
}
break; break;
case StrokeBase.thicknessPropertyKey: case StrokeBase.thicknessPropertyKey:
if (object is StrokeBase) object.thickness = value; if (object is StrokeBase) {
object.thickness = value;
}
break; break;
case GradientStopBase.positionPropertyKey: case GradientStopBase.positionPropertyKey:
if (object is GradientStopBase) object.position = value; if (object is GradientStopBase) {
object.position = value;
}
break; break;
case TrimPathBase.startPropertyKey: case TrimPathBase.startPropertyKey:
if (object is TrimPathBase) object.start = value; if (object is TrimPathBase) {
object.start = value;
}
break; break;
case TrimPathBase.endPropertyKey: case TrimPathBase.endPropertyKey:
if (object is TrimPathBase) object.end = value; if (object is TrimPathBase) {
object.end = value;
}
break; break;
case TrimPathBase.offsetPropertyKey: case TrimPathBase.offsetPropertyKey:
if (object is TrimPathBase) object.offset = value; if (object is TrimPathBase) {
object.offset = value;
}
break; break;
case TransformComponentBase.rotationPropertyKey: case TransformComponentBase.rotationPropertyKey:
if (object is TransformComponentBase) object.rotation = value; if (object is TransformComponentBase) {
object.rotation = value;
}
break; break;
case TransformComponentBase.scaleXPropertyKey: case TransformComponentBase.scaleXPropertyKey:
if (object is TransformComponentBase) object.scaleX = value; if (object is TransformComponentBase) {
object.scaleX = value;
}
break; break;
case TransformComponentBase.scaleYPropertyKey: case TransformComponentBase.scaleYPropertyKey:
if (object is TransformComponentBase) object.scaleY = value; if (object is TransformComponentBase) {
object.scaleY = value;
}
break; break;
case TransformComponentBase.opacityPropertyKey: case TransformComponentBase.opacityPropertyKey:
if (object is TransformComponentBase) object.opacity = value; if (object is TransformComponentBase) {
object.opacity = value;
}
break; break;
case NodeBase.xPropertyKey: case NodeBase.xPropertyKey:
if (object is NodeBase) object.x = value; if (object is NodeBase) {
object.x = value;
}
break; break;
case NodeBase.yPropertyKey: case NodeBase.yPropertyKey:
if (object is NodeBase) object.y = value; if (object is NodeBase) {
object.y = value;
}
break; break;
case PathVertexBase.xPropertyKey: case PathVertexBase.xPropertyKey:
if (object is PathVertexBase) object.x = value; if (object is PathVertexBase) {
object.x = value;
}
break; break;
case PathVertexBase.yPropertyKey: case PathVertexBase.yPropertyKey:
if (object is PathVertexBase) object.y = value; if (object is PathVertexBase) {
object.y = value;
}
break; break;
case StraightVertexBase.radiusPropertyKey: case StraightVertexBase.radiusPropertyKey:
if (object is StraightVertexBase) object.radius = value; if (object is StraightVertexBase) {
object.radius = value;
}
break; break;
case CubicAsymmetricVertexBase.rotationPropertyKey: case CubicAsymmetricVertexBase.rotationPropertyKey:
if (object is CubicAsymmetricVertexBase) object.rotation = value; if (object is CubicAsymmetricVertexBase) {
object.rotation = value;
}
break; break;
case CubicAsymmetricVertexBase.inDistancePropertyKey: case CubicAsymmetricVertexBase.inDistancePropertyKey:
if (object is CubicAsymmetricVertexBase) object.inDistance = value; if (object is CubicAsymmetricVertexBase) {
object.inDistance = value;
}
break; break;
case CubicAsymmetricVertexBase.outDistancePropertyKey: case CubicAsymmetricVertexBase.outDistancePropertyKey:
if (object is CubicAsymmetricVertexBase) object.outDistance = value; if (object is CubicAsymmetricVertexBase) {
object.outDistance = value;
}
break; break;
case ParametricPathBase.widthPropertyKey: case ParametricPathBase.widthPropertyKey:
if (object is ParametricPathBase) object.width = value; if (object is ParametricPathBase) {
object.width = value;
}
break; break;
case ParametricPathBase.heightPropertyKey: case ParametricPathBase.heightPropertyKey:
if (object is ParametricPathBase) object.height = value; if (object is ParametricPathBase) {
object.height = value;
}
break; break;
case ParametricPathBase.originXPropertyKey: case ParametricPathBase.originXPropertyKey:
if (object is ParametricPathBase) object.originX = value; if (object is ParametricPathBase) {
object.originX = value;
}
break; break;
case ParametricPathBase.originYPropertyKey: case ParametricPathBase.originYPropertyKey:
if (object is ParametricPathBase) object.originY = value; if (object is ParametricPathBase) {
object.originY = value;
}
break; break;
case RectangleBase.cornerRadiusTLPropertyKey: case RectangleBase.cornerRadiusTLPropertyKey:
if (object is RectangleBase) object.cornerRadiusTL = value; if (object is RectangleBase) {
object.cornerRadiusTL = value;
}
break; break;
case RectangleBase.cornerRadiusTRPropertyKey: case RectangleBase.cornerRadiusTRPropertyKey:
if (object is RectangleBase) object.cornerRadiusTR = value; if (object is RectangleBase) {
object.cornerRadiusTR = value;
}
break; break;
case RectangleBase.cornerRadiusBLPropertyKey: case RectangleBase.cornerRadiusBLPropertyKey:
if (object is RectangleBase) object.cornerRadiusBL = value; if (object is RectangleBase) {
object.cornerRadiusBL = value;
}
break; break;
case RectangleBase.cornerRadiusBRPropertyKey: case RectangleBase.cornerRadiusBRPropertyKey:
if (object is RectangleBase) object.cornerRadiusBR = value; if (object is RectangleBase) {
object.cornerRadiusBR = value;
}
break; break;
case CubicMirroredVertexBase.rotationPropertyKey: case CubicMirroredVertexBase.rotationPropertyKey:
if (object is CubicMirroredVertexBase) object.rotation = value; if (object is CubicMirroredVertexBase) {
object.rotation = value;
}
break; break;
case CubicMirroredVertexBase.distancePropertyKey: case CubicMirroredVertexBase.distancePropertyKey:
if (object is CubicMirroredVertexBase) object.distance = value; if (object is CubicMirroredVertexBase) {
object.distance = value;
}
break; break;
case PolygonBase.cornerRadiusPropertyKey: case PolygonBase.cornerRadiusPropertyKey:
if (object is PolygonBase) object.cornerRadius = value; if (object is PolygonBase) {
object.cornerRadius = value;
}
break; break;
case StarBase.innerRadiusPropertyKey: case StarBase.innerRadiusPropertyKey:
if (object is StarBase) object.innerRadius = value; if (object is StarBase) {
object.innerRadius = value;
}
break; break;
case CubicDetachedVertexBase.inRotationPropertyKey: case CubicDetachedVertexBase.inRotationPropertyKey:
if (object is CubicDetachedVertexBase) object.inRotation = value; if (object is CubicDetachedVertexBase) {
object.inRotation = value;
}
break; break;
case CubicDetachedVertexBase.inDistancePropertyKey: case CubicDetachedVertexBase.inDistancePropertyKey:
if (object is CubicDetachedVertexBase) object.inDistance = value; if (object is CubicDetachedVertexBase) {
object.inDistance = value;
}
break; break;
case CubicDetachedVertexBase.outRotationPropertyKey: case CubicDetachedVertexBase.outRotationPropertyKey:
if (object is CubicDetachedVertexBase) object.outRotation = value; if (object is CubicDetachedVertexBase) {
object.outRotation = value;
}
break; break;
case CubicDetachedVertexBase.outDistancePropertyKey: case CubicDetachedVertexBase.outDistancePropertyKey:
if (object is CubicDetachedVertexBase) object.outDistance = value; if (object is CubicDetachedVertexBase) {
object.outDistance = value;
}
break; break;
case ArtboardBase.widthPropertyKey: case ArtboardBase.widthPropertyKey:
if (object is ArtboardBase) object.width = value; if (object is ArtboardBase) {
object.width = value;
}
break; break;
case ArtboardBase.heightPropertyKey: case ArtboardBase.heightPropertyKey:
if (object is ArtboardBase) object.height = value; if (object is ArtboardBase) {
object.height = value;
}
break; break;
case ArtboardBase.xPropertyKey: case ArtboardBase.xPropertyKey:
if (object is ArtboardBase) object.x = value; if (object is ArtboardBase) {
object.x = value;
}
break; break;
case ArtboardBase.yPropertyKey: case ArtboardBase.yPropertyKey:
if (object is ArtboardBase) object.y = value; if (object is ArtboardBase) {
object.y = value;
}
break; break;
case ArtboardBase.originXPropertyKey: case ArtboardBase.originXPropertyKey:
if (object is ArtboardBase) object.originX = value; if (object is ArtboardBase) {
object.originX = value;
}
break; break;
case ArtboardBase.originYPropertyKey: case ArtboardBase.originYPropertyKey:
if (object is ArtboardBase) object.originY = value; if (object is ArtboardBase) {
object.originY = value;
}
break; break;
case BoneBase.lengthPropertyKey: case BoneBase.lengthPropertyKey:
if (object is BoneBase) object.length = value; if (object is BoneBase) {
object.length = value;
}
break; break;
case RootBoneBase.xPropertyKey: case RootBoneBase.xPropertyKey:
if (object is RootBoneBase) object.x = value; if (object is RootBoneBase) {
object.x = value;
}
break; break;
case RootBoneBase.yPropertyKey: case RootBoneBase.yPropertyKey:
if (object is RootBoneBase) object.y = value; if (object is RootBoneBase) {
object.y = value;
}
break; break;
case SkinBase.xxPropertyKey: case SkinBase.xxPropertyKey:
if (object is SkinBase) object.xx = value; if (object is SkinBase) {
object.xx = value;
}
break; break;
case SkinBase.yxPropertyKey: case SkinBase.yxPropertyKey:
if (object is SkinBase) object.yx = value; if (object is SkinBase) {
object.yx = value;
}
break; break;
case SkinBase.xyPropertyKey: case SkinBase.xyPropertyKey:
if (object is SkinBase) object.xy = value; if (object is SkinBase) {
object.xy = value;
}
break; break;
case SkinBase.yyPropertyKey: case SkinBase.yyPropertyKey:
if (object is SkinBase) object.yy = value; if (object is SkinBase) {
object.yy = value;
}
break; break;
case SkinBase.txPropertyKey: case SkinBase.txPropertyKey:
if (object is SkinBase) object.tx = value; if (object is SkinBase) {
object.tx = value;
}
break; break;
case SkinBase.tyPropertyKey: case SkinBase.tyPropertyKey:
if (object is SkinBase) object.ty = value; if (object is SkinBase) {
object.ty = value;
}
break; break;
case TendonBase.xxPropertyKey: case TendonBase.xxPropertyKey:
if (object is TendonBase) object.xx = value; if (object is TendonBase) {
object.xx = value;
}
break; break;
case TendonBase.yxPropertyKey: case TendonBase.yxPropertyKey:
if (object is TendonBase) object.yx = value; if (object is TendonBase) {
object.yx = value;
}
break; break;
case TendonBase.xyPropertyKey: case TendonBase.xyPropertyKey:
if (object is TendonBase) object.xy = value; if (object is TendonBase) {
object.xy = value;
}
break; break;
case TendonBase.yyPropertyKey: case TendonBase.yyPropertyKey:
if (object is TendonBase) object.yy = value; if (object is TendonBase) {
object.yy = value;
}
break; break;
case TendonBase.txPropertyKey: case TendonBase.txPropertyKey:
if (object is TendonBase) object.tx = value; if (object is TendonBase) {
object.tx = value;
}
break; break;
case TendonBase.tyPropertyKey: case TendonBase.tyPropertyKey:
if (object is TendonBase) object.ty = value; if (object is TendonBase) {
object.ty = value;
}
break; break;
} }
} }
@ -1579,13 +1883,19 @@ class RiveCoreContext {
static void setColor(Core object, int propertyKey, int value) { static void setColor(Core object, int propertyKey, int value) {
switch (propertyKey) { switch (propertyKey) {
case KeyFrameColorBase.valuePropertyKey: case KeyFrameColorBase.valuePropertyKey:
if (object is KeyFrameColorBase) object.value = value; if (object is KeyFrameColorBase) {
object.value = value;
}
break; break;
case SolidColorBase.colorValuePropertyKey: case SolidColorBase.colorValuePropertyKey:
if (object is SolidColorBase) object.colorValue = value; if (object is SolidColorBase) {
object.colorValue = value;
}
break; break;
case GradientStopBase.colorValuePropertyKey: case GradientStopBase.colorValuePropertyKey:
if (object is GradientStopBase) object.colorValue = value; if (object is GradientStopBase) {
object.colorValue = value;
}
break; break;
} }
} }
@ -1593,25 +1903,39 @@ class RiveCoreContext {
static void setBool(Core object, int propertyKey, bool value) { static void setBool(Core object, int propertyKey, bool value) {
switch (propertyKey) { switch (propertyKey) {
case LinearAnimationBase.enableWorkAreaPropertyKey: case LinearAnimationBase.enableWorkAreaPropertyKey:
if (object is LinearAnimationBase) object.enableWorkArea = value; if (object is LinearAnimationBase) {
object.enableWorkArea = value;
}
break; break;
case StateMachineBoolBase.valuePropertyKey: case StateMachineBoolBase.valuePropertyKey:
if (object is StateMachineBoolBase) object.value = value; if (object is StateMachineBoolBase) {
object.value = value;
}
break; break;
case ShapePaintBase.isVisiblePropertyKey: case ShapePaintBase.isVisiblePropertyKey:
if (object is ShapePaintBase) object.isVisible = value; if (object is ShapePaintBase) {
object.isVisible = value;
}
break; break;
case StrokeBase.transformAffectsStrokePropertyKey: case StrokeBase.transformAffectsStrokePropertyKey:
if (object is StrokeBase) object.transformAffectsStroke = value; if (object is StrokeBase) {
object.transformAffectsStroke = value;
}
break; break;
case PointsPathBase.isClosedPropertyKey: case PointsPathBase.isClosedPropertyKey:
if (object is PointsPathBase) object.isClosed = value; if (object is PointsPathBase) {
object.isClosed = value;
}
break; break;
case RectangleBase.linkCornerRadiusPropertyKey: case RectangleBase.linkCornerRadiusPropertyKey:
if (object is RectangleBase) object.linkCornerRadius = value; if (object is RectangleBase) {
object.linkCornerRadius = value;
}
break; break;
case ClippingShapeBase.isVisiblePropertyKey: case ClippingShapeBase.isVisiblePropertyKey:
if (object is ClippingShapeBase) object.isVisible = value; if (object is ClippingShapeBase) {
object.isVisible = value;
}
break; break;
} }
} }

View File

@ -1,5 +1,6 @@
import 'package:rive/src/core/core.dart'; import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/artboard.dart'; import 'package:rive/src/rive_core/artboard.dart';
import 'package:rive/src/generated/animation/animation_base.dart'; import 'package:rive/src/generated/animation/animation_base.dart';
export 'package:rive/src/generated/animation/animation_base.dart'; export 'package:rive/src/generated/animation/animation_base.dart';
@ -17,10 +18,13 @@ class Animation extends AnimationBase<RuntimeArtboard> {
@override @override
void onAddedDirty() {} void onAddedDirty() {}
@override @override
void onAdded() {} void onAdded() {}
@override @override
bool validate() => super.validate() && _artboard != null; bool validate() => super.validate() && _artboard != null;
@override @override
void nameChanged(String from, String to) {} void nameChanged(String from, String to) {}
} }

View File

@ -1,5 +1,8 @@
import 'package:rive/src/core/core.dart'; import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/animation_state_instance.dart';
import 'package:rive/src/rive_core/animation/linear_animation.dart'; import 'package:rive/src/rive_core/animation/linear_animation.dart';
import 'package:rive/src/rive_core/animation/state_instance.dart';
import 'package:rive/src/rive_core/artboard.dart';
import 'package:rive/src/generated/animation/animation_state_base.dart'; import 'package:rive/src/generated/animation/animation_state_base.dart';
export 'package:rive/src/generated/animation/animation_state_base.dart'; export 'package:rive/src/generated/animation/animation_state_base.dart';
@ -15,6 +18,7 @@ class AnimationState extends AnimationStateBase {
if (_animation == value) { if (_animation == value) {
return; return;
} }
_animation = value; _animation = value;
animationId = value?.id ?? Core.missingId; animationId = value?.id ?? Core.missingId;
} }
@ -23,4 +27,33 @@ class AnimationState extends AnimationStateBase {
void animationIdChanged(int from, int to) { void animationIdChanged(int from, int to) {
animation = id == Core.missingId ? null : context.resolve(to); animation = id == Core.missingId ? null : context.resolve(to);
} }
@override
StateInstance makeInstance() {
if (animation == null) {
// Failed to load at runtime/some new type we don't understand.
return SystemStateInstance(this);
}
return AnimationStateInstance(this);
}
// We keep the importer code here so that we can inject this for runtime.
// #2690
@override
bool import(ImportStack stack) {
var importer = stack.latest<ArtboardImporter>(ArtboardBase.typeKey);
if (importer == null) {
return false;
}
if (animationId >= 0 && animationId < importer.artboard.animations.length) {
var found = importer.artboard.animations[animationId];
if (found is LinearAnimation) {
animation = found;
}
}
return super.import(stack);
}
} }

View File

@ -0,0 +1,25 @@
import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/animation_state.dart';
import 'package:rive/src/rive_core/animation/linear_animation_instance.dart';
import 'package:rive/src/rive_core/animation/state_instance.dart';
/// Simple wrapper around [LinearAnimationInstance] making it compatible with
/// the [StateMachine]'s [StateInstance] interface.
class AnimationStateInstance extends StateInstance {
final LinearAnimationInstance animationInstance;
AnimationStateInstance(AnimationState state)
: assert(state.animation != null),
animationInstance = LinearAnimationInstance(state.animation!),
super(state);
@override
void advance(double seconds, _) => animationInstance.advance(seconds);
@override
void apply(CoreContext core, double mix) => animationInstance.animation
.apply(animationInstance.time, coreContext: core, mix: mix);
@override
bool get keepGoing => animationInstance.keepGoing;
}

View File

@ -1,4 +1,8 @@
import 'package:rive/src/rive_core/animation/state_instance.dart';
import 'package:rive/src/generated/animation/any_state_base.dart'; import 'package:rive/src/generated/animation/any_state_base.dart';
export 'package:rive/src/generated/animation/any_state_base.dart'; export 'package:rive/src/generated/animation/any_state_base.dart';
class AnyState extends AnyStateBase {} class AnyState extends AnyStateBase {
@override
StateInstance makeInstance() => SystemStateInstance(this);
}

View File

@ -0,0 +1,52 @@
import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/layer_state.dart';
import 'package:rive/src/rive_core/animation/linear_animation.dart';
import 'package:rive/src/rive_core/artboard.dart';
import 'package:rive/src/generated/animation/blend_animation_base.dart';
import 'package:rive/src/generated/artboard_base.dart';
export 'package:rive/src/generated/animation/blend_animation_base.dart';
abstract class BlendAnimation extends BlendAnimationBase {
LinearAnimation? _animation;
LinearAnimation? get animation => _animation;
@override
void animationIdChanged(int from, int to) {
_animation = context.resolve(to);
}
@override
void onAdded() {}
@override
void onRemoved() {
super.onRemoved();
}
@override
void onAddedDirty() {}
@override
bool import(ImportStack importStack) {
var importer =
importStack.latest<LayerStateImporter>(LayerStateBase.typeKey);
if (importer == null || !importer.addBlendAnimation(this)) {
return false;
}
var artboardImporter =
importStack.latest<ArtboardImporter>(ArtboardBase.typeKey);
if (artboardImporter == null) {
return false;
}
if (animationId >= 0 &&
animationId < artboardImporter.artboard.animations.length) {
var found = artboardImporter.artboard.animations[animationId];
if (found is LinearAnimation) {
_animation = found;
}
}
return super.import(importStack);
}
}

View File

@ -0,0 +1,9 @@
import 'package:rive/src/generated/animation/blend_animation_1d_base.dart';
export 'package:rive/src/generated/animation/blend_animation_1d_base.dart';
class BlendAnimation1D extends BlendAnimation1DBase {
@override
void valueChanged(double from, double to) {
// TODO: implement valueChanged
}
}

View File

@ -0,0 +1,30 @@
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_number.dart';
import 'package:rive/src/generated/animation/blend_animation_direct_base.dart';
export 'package:rive/src/generated/animation/blend_animation_direct_base.dart';
class BlendAnimationDirect extends BlendAnimationDirectBase {
StateMachineNumber? _input;
StateMachineNumber? get input => _input;
@override
void inputIdChanged(int from, int to) {}
@override
bool import(ImportStack stack) {
var importer = stack.latest<StateMachineImporter>(StateMachineBase.typeKey);
if (importer == null) {
return false;
}
if (inputId >= 0 && inputId < importer.machine.inputs.length) {
var found = importer.machine.inputs[inputId];
if (found is StateMachineNumber) {
_input = found;
inputId = found.id;
}
}
return super.import(stack);
}
}

View File

@ -0,0 +1,20 @@
import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/blend_animation.dart';
import 'package:rive/src/generated/animation/blend_state_base.dart';
export 'package:rive/src/generated/animation/blend_state_base.dart';
//
abstract class BlendState<T extends BlendAnimation> extends BlendStateBase {
final BlendAnimations<T> _animations = BlendAnimations<T>();
BlendAnimations<T> get animations => _animations;
void internalAddAnimation(T animation) {
assert(!_animations.contains(animation),
'shouln\'t already contain the animation');
_animations.add(animation);
}
void internalRemoveAnimation(T animation) {
_animations.remove(animation);
}
}

View File

@ -0,0 +1,24 @@
import 'package:rive/src/rive_core/animation/blend_state_1d_instance.dart';
import 'package:rive/src/rive_core/animation/state_instance.dart';
import 'package:rive/src/rive_core/animation/state_machine_number.dart';
import 'package:rive/src/generated/animation/blend_state_1d_base.dart';
export 'package:rive/src/generated/animation/blend_state_1d_base.dart';
class BlendState1D extends BlendState1DBase {
StateMachineNumber? _input;
StateMachineNumber? get input => _input;
@override
void inputIdChanged(int from, int to) {
_input = context.resolve<StateMachineNumber>(to);
}
@override
void onAddedDirty() {
super.onAddedDirty();
_input = context.resolve<StateMachineNumber>(inputId);
}
@override
StateInstance makeInstance() => BlendState1DInstance(this);
}

View File

@ -0,0 +1,82 @@
import 'dart:collection';
import 'package:rive/src/rive_core/animation/blend_animation_1d.dart';
import 'package:rive/src/rive_core/animation/blend_state_1d.dart';
import 'package:rive/src/rive_core/animation/blend_state_instance.dart';
/// [BlendState1D] mixing logic that runs inside the [StateMachine].
class BlendState1DInstance
extends BlendStateInstance<BlendState1D, BlendAnimation1D> {
BlendState1DInstance(BlendState1D state) : super(state) {
animationInstances.sort(
(a, b) => a.blendAnimation.value.compareTo(b.blendAnimation.value));
}
/// Binary find the closest animation index.
int animationIndex(double value) {
int idx = 0;
int mid = 0;
double closestValue = 0;
int start = 0;
int end = animationInstances.length - 1;
while (start <= end) {
mid = (start + end) >> 1;
closestValue = animationInstances[mid].blendAnimation.value;
if (closestValue < value) {
start = mid + 1;
} else if (closestValue > value) {
end = mid - 1;
} else {
idx = start = mid;
break;
}
idx = start;
}
return idx;
}
BlendStateAnimationInstance<BlendAnimation1D>? _from;
BlendStateAnimationInstance<BlendAnimation1D>? _to;
@override
void advance(double seconds, HashMap<int, dynamic> inputValues) {
super.advance(seconds, inputValues);
dynamic inputValue = inputValues[(state as BlendState1D).inputId];
var value = (inputValue is double
? inputValue
: (state as BlendState1D).input?.value) ??
0;
int index = animationIndex(value);
_to = index >= 0 && index < animationInstances.length
? animationInstances[index]
: null;
_from = index - 1 >= 0 && index - 1 < animationInstances.length
? animationInstances[index - 1]
: null;
double mix, mixFrom;
if (_to == null ||
_from == null ||
_to!.blendAnimation.value == _from!.blendAnimation.value) {
mix = mixFrom = 1;
} else {
mix = (value - _from!.blendAnimation.value) /
(_to!.blendAnimation.value - _from!.blendAnimation.value);
mixFrom = 1.0 - mix;
}
var toValue = _to?.blendAnimation.value;
var fromValue = _from?.blendAnimation.value;
for (final animation in animationInstances) {
if (animation.blendAnimation.value == toValue) {
animation.mix = mix;
} else if (animation.blendAnimation.value == fromValue) {
animation.mix = mixFrom;
} else {
animation.mix = 0;
}
}
}
}

View File

@ -0,0 +1,9 @@
import 'package:rive/src/rive_core/animation/blend_state_direct_instance.dart';
import 'package:rive/src/rive_core/animation/state_instance.dart';
import 'package:rive/src/generated/animation/blend_state_direct_base.dart';
export 'package:rive/src/generated/animation/blend_state_direct_base.dart';
class BlendStateDirect extends BlendStateDirectBase {
@override
StateInstance makeInstance() => BlendStateDirectInstance(this);
}

View File

@ -0,0 +1,24 @@
import 'dart:collection';
import 'package:rive/src/rive_core/animation/blend_animation_direct.dart';
import 'package:rive/src/rive_core/animation/blend_state_direct.dart';
import 'package:rive/src/rive_core/animation/blend_state_instance.dart';
/// [BlendStateDirect] mixing logic that runs inside the [StateMachine].
class BlendStateDirectInstance
extends BlendStateInstance<BlendStateDirect, BlendAnimationDirect> {
BlendStateDirectInstance(BlendStateDirect state) : super(state);
@override
void advance(double seconds, HashMap<int, dynamic> inputValues) {
super.advance(seconds, inputValues);
for (final animation in animationInstances) {
dynamic inputValue = inputValues[animation.blendAnimation.inputId];
var value = (inputValue is double
? inputValue
: animation.blendAnimation.input?.value) ??
0;
animation.mix = value / 100;
}
}
}

View File

@ -0,0 +1,59 @@
import 'dart:collection';
import 'package:rive/src/core/core.dart';
import 'package:flutter/foundation.dart';
import 'package:rive/src/rive_core/animation/blend_animation.dart';
import 'package:rive/src/rive_core/animation/blend_state.dart';
import 'package:rive/src/rive_core/animation/linear_animation_instance.dart';
import 'package:rive/src/rive_core/animation/state_instance.dart';
/// Individual animation in a blend state instance.
class BlendStateAnimationInstance<T extends BlendAnimation> {
final T blendAnimation;
final LinearAnimationInstance animationInstance;
double mix = 0;
BlendStateAnimationInstance(this.blendAnimation)
: animationInstance = LinearAnimationInstance(blendAnimation.animation!);
}
/// Generic blend state instance which works for [BlendState<BlendAnimation>]s
/// where T represents the BlendState and K the BlendAnimation.
abstract class BlendStateInstance<T extends BlendState<K>,
K extends BlendAnimation> extends StateInstance {
final List<BlendStateAnimationInstance<K>> animationInstances;
BlendStateInstance(T state)
: animationInstances = state.animations
.where((animation) => animation.animation != null)
.map((animation) => BlendStateAnimationInstance(animation))
.toList(growable: false),
super(state);
bool _keepGoing = true;
@override
bool get keepGoing => _keepGoing;
@mustCallSuper
@override
void advance(double seconds, HashMap<int, dynamic> inputValues) {
_keepGoing = false;
// Advance all the animations in the blend state
for (final animation in animationInstances) {
if (animation.animationInstance.advance(seconds) && !keepGoing) {
_keepGoing = true;
}
}
}
@override
void apply(CoreContext core, double mix) {
for (final animation in animationInstances) {
double m = mix * animation.mix;
if (m == 0) {
continue;
}
animation.animationInstance.animation
.apply(animation.animationInstance.time, coreContext: core, mix: m);
}
}
}

View File

@ -0,0 +1,31 @@
import 'package:rive/src/rive_core/animation/blend_animation.dart';
import 'package:rive/src/rive_core/animation/blend_state_instance.dart';
import 'package:rive/src/rive_core/animation/layer_state.dart';
import 'package:rive/src/rive_core/animation/linear_animation.dart';
import 'package:rive/src/rive_core/animation/linear_animation_instance.dart';
import 'package:rive/src/rive_core/animation/state_instance.dart';
import 'package:rive/src/generated/animation/blend_state_transition_base.dart';
export 'package:rive/src/generated/animation/blend_state_transition_base.dart';
class BlendStateTransition extends BlendStateTransitionBase {
BlendAnimation? exitBlendAnimation;
@override
LinearAnimationInstance? exitTimeAnimationInstance(StateInstance stateFrom) {
if (stateFrom is BlendStateInstance) {
for (final blendAnimation in stateFrom.animationInstances) {
if (blendAnimation.blendAnimation == exitBlendAnimation) {
return blendAnimation.animationInstance;
}
}
}
return null;
}
@override
LinearAnimation? exitTimeAnimation(LayerState stateFrom) =>
exitBlendAnimation?.animation;
@override
void exitBlendAnimationIdChanged(int from, int to) {}
}

View File

@ -1,14 +1,18 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:rive/src/core/core.dart'; import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/interpolator.dart'; import 'package:rive/src/rive_core/animation/interpolator.dart';
import 'package:rive/src/rive_core/artboard.dart'; import 'package:rive/src/rive_core/artboard.dart';
import 'package:rive/src/generated/animation/cubic_interpolator_base.dart'; import 'package:rive/src/generated/animation/cubic_interpolator_base.dart';
const int newtonIterations = 4; const int newtonIterations = 4;
// Implements https://github.com/gre/bezier-easing/blob/master/src/index.js
const double newtonMinSlope = 0.001; const double newtonMinSlope = 0.001;
const double sampleStepSize = 1.0 / (splineTableSize - 1.0); const double sampleStepSize = 1.0 / (splineTableSize - 1.0);
const int splineTableSize = 11; const int splineTableSize = 11;
const int subdivisionMaxIterations = 10; const int subdivisionMaxIterations = 10;
const double subdivisionPrecision = 0.0000001; const double subdivisionPrecision = 0.0000001;
double _calcBezier(double aT, double aA1, double aA2) { double _calcBezier(double aT, double aA1, double aA2) {
return (((1.0 - 3.0 * aA2 + 3.0 * aA1) * aT + (3.0 * aA2 - 6.0 * aA1)) * aT + return (((1.0 - 3.0 * aA2 + 3.0 * aA1) * aT + (3.0 * aA2 - 6.0 * aA1)) * aT +
@ -16,14 +20,17 @@ double _calcBezier(double aT, double aA1, double aA2) {
aT; aT;
} }
// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
double _getSlope(double aT, double aA1, double aA2) { double _getSlope(double aT, double aA1, double aA2) {
return 3.0 * (1.0 - 3.0 * aA2 + 3.0 * aA1) * aT * aT + return 3.0 * (1.0 - 3.0 * aA2 + 3.0 * aA1) * aT * aT +
2.0 * (3.0 * aA2 - 6.0 * aA1) * aT + 2.0 * (3.0 * aA2 - 6.0 * aA1) * aT +
(3.0 * aA1); (3.0 * aA1);
} }
// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
class CubicInterpolator extends CubicInterpolatorBase implements Interpolator { class CubicInterpolator extends CubicInterpolatorBase implements Interpolator {
_CubicEase _ease = _CubicEase.make(0.42, 0, 0.58, 1); _CubicEase _ease = _CubicEase.make(0.42, 0, 0.58, 1);
@override @override
bool equalParameters(Interpolator other) { bool equalParameters(Interpolator other) {
if (other is CubicInterpolator) { if (other is CubicInterpolator) {
@ -37,16 +44,22 @@ class CubicInterpolator extends CubicInterpolatorBase implements Interpolator {
@override @override
void onAdded() => _updateStoredCubic(); void onAdded() => _updateStoredCubic();
@override @override
void onAddedDirty() {} void onAddedDirty() {}
@override @override
double transform(double value) => _ease.transform(value); double transform(double value) => _ease.transform(value);
@override @override
void x1Changed(double from, double to) => _updateStoredCubic(); void x1Changed(double from, double to) => _updateStoredCubic();
@override @override
void x2Changed(double from, double to) => _updateStoredCubic(); void x2Changed(double from, double to) => _updateStoredCubic();
@override @override
void y1Changed(double from, double to) => _updateStoredCubic(); void y1Changed(double from, double to) => _updateStoredCubic();
@override @override
void y2Changed(double from, double to) => _updateStoredCubic(); void y2Changed(double from, double to) => _updateStoredCubic();
void _updateStoredCubic() { void _updateStoredCubic() {
@ -60,6 +73,7 @@ class CubicInterpolator extends CubicInterpolatorBase implements Interpolator {
return false; return false;
} }
artboardHelper.addComponent(this); artboardHelper.addComponent(this);
return super.import(stack); return super.import(stack);
} }
} }
@ -68,23 +82,29 @@ class _Cubic extends _CubicEase {
final Float64List _values = Float64List(splineTableSize); final Float64List _values = Float64List(splineTableSize);
final double x1, y1, x2, y2; final double x1, y1, x2, y2;
_Cubic(this.x1, this.y1, this.x2, this.y2) { _Cubic(this.x1, this.y1, this.x2, this.y2) {
// Precompute values table
for (int i = 0; i < splineTableSize; ++i) { for (int i = 0; i < splineTableSize; ++i) {
_values[i] = _calcBezier(i * sampleStepSize, x1, x2); _values[i] = _calcBezier(i * sampleStepSize, x1, x2);
} }
} }
double getT(double x) { double getT(double x) {
double intervalStart = 0.0; double intervalStart = 0.0;
int currentSample = 1; int currentSample = 1;
int lastSample = splineTableSize - 1; int lastSample = splineTableSize - 1;
for (; for (;
currentSample != lastSample && _values[currentSample] <= x; currentSample != lastSample && _values[currentSample] <= x;
++currentSample) { ++currentSample) {
intervalStart += sampleStepSize; intervalStart += sampleStepSize;
} }
--currentSample; --currentSample;
// Interpolate to provide an initial guess for t
var dist = (x - _values[currentSample]) / var dist = (x - _values[currentSample]) /
(_values[currentSample + 1] - _values[currentSample]); (_values[currentSample + 1] - _values[currentSample]);
var guessForT = intervalStart + dist * sampleStepSize; var guessForT = intervalStart + dist * sampleStepSize;
var initialSlope = _getSlope(guessForT, x1, x2); var initialSlope = _getSlope(guessForT, x1, x2);
if (initialSlope >= newtonMinSlope) { if (initialSlope >= newtonMinSlope) {
for (int i = 0; i < newtonIterations; ++i) { for (int i = 0; i < newtonIterations; ++i) {
@ -124,6 +144,7 @@ class _Cubic extends _CubicEase {
abstract class _CubicEase { abstract class _CubicEase {
double transform(double t); double transform(double t);
static _CubicEase make(double x1, double y1, double x2, double y2) { static _CubicEase make(double x1, double y1, double x2, double y2) {
if (x1 == y1 && x2 == y2) { if (x1 == y1 && x2 == y2) {
return _LinearCubicEase(); return _LinearCubicEase();

View File

@ -1,4 +1,8 @@
import 'package:rive/src/rive_core/animation/state_instance.dart';
import 'package:rive/src/generated/animation/entry_state_base.dart'; import 'package:rive/src/generated/animation/entry_state_base.dart';
export 'package:rive/src/generated/animation/entry_state_base.dart'; export 'package:rive/src/generated/animation/entry_state_base.dart';
class EntryState extends EntryStateBase {} class EntryState extends EntryStateBase {
@override
StateInstance makeInstance() => SystemStateInstance(this);
}

View File

@ -1,4 +1,8 @@
import 'package:rive/src/rive_core/animation/state_instance.dart';
import 'package:rive/src/generated/animation/exit_state_base.dart'; import 'package:rive/src/generated/animation/exit_state_base.dart';
export 'package:rive/src/generated/animation/exit_state_base.dart'; export 'package:rive/src/generated/animation/exit_state_base.dart';
class ExitState extends ExitStateBase {} class ExitState extends ExitStateBase {
@override
StateInstance makeInstance() => SystemStateInstance(this);
}

View File

@ -1,5 +1,8 @@
abstract class Interpolator { abstract class Interpolator {
int get id; int get id;
/// Convert a linear interpolation factor to an eased one.
double transform(double value); double transform(double value);
bool equalParameters(Interpolator other); bool equalParameters(Interpolator other);
} }

View File

@ -1,7 +1,9 @@
import 'dart:collection'; import 'dart:collection';
import 'package:rive/src/core/core.dart'; import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/keyed_property.dart'; import 'package:rive/src/rive_core/animation/keyed_property.dart';
import 'package:rive/src/rive_core/component.dart'; import 'package:rive/src/rive_core/component.dart';
import 'package:rive/src/generated/animation/keyed_object_base.dart'; import 'package:rive/src/generated/animation/keyed_object_base.dart';
import 'linear_animation.dart'; import 'linear_animation.dart';
export 'package:rive/src/generated/animation/keyed_object_base.dart'; export 'package:rive/src/generated/animation/keyed_object_base.dart';
@ -9,20 +11,26 @@ export 'package:rive/src/generated/animation/keyed_object_base.dart';
class KeyedObject extends KeyedObjectBase<RuntimeArtboard> { class KeyedObject extends KeyedObjectBase<RuntimeArtboard> {
final HashMap<int, KeyedProperty> _keyedProperties = final HashMap<int, KeyedProperty> _keyedProperties =
HashMap<int, KeyedProperty>(); HashMap<int, KeyedProperty>();
Iterable<KeyedProperty> get keyedProperties => _keyedProperties.values; Iterable<KeyedProperty> get keyedProperties => _keyedProperties.values;
@override @override
void onAddedDirty() {} void onAddedDirty() {}
@override @override
void onAdded() {} void onAdded() {}
@override @override
bool validate() { bool validate() {
if (!super.validate()) { if (!super.validate()) {
return false; return false;
} }
var component = context.resolve<Component>(objectId); var component = context.resolve<Component>(objectId);
if (component == null) { if (component == null) {
return false; return false;
} }
return true; return true;
} }
@ -33,14 +41,22 @@ class KeyedObject extends KeyedObjectBase<RuntimeArtboard> {
bool isValidKeyedProperty(KeyedProperty property) { bool isValidKeyedProperty(KeyedProperty property) {
var value = _keyedProperties[property.propertyKey]; var value = _keyedProperties[property.propertyKey];
// If the property is already keyed, that's ok just make sure the
// KeyedObject matches.
if (value != null && value != property) { if (value != null && value != property) {
return false; return false;
} }
return true; return true;
} }
/// Called by rive_core to add a KeyedProperty to the animation. This should
/// be @internal when it's supported.
bool internalAddKeyedProperty(KeyedProperty property) { bool internalAddKeyedProperty(KeyedProperty property) {
var value = _keyedProperties[property.propertyKey]; var value = _keyedProperties[property.propertyKey];
// If the property is already keyed, that's ok just make sure the
// KeyedObject matches.
if (value != null && value != property) { if (value != null && value != property) {
return false; return false;
} }
@ -48,11 +64,17 @@ class KeyedObject extends KeyedObjectBase<RuntimeArtboard> {
return true; return true;
} }
/// Called by rive_core to remove a KeyedObject to the animation. This should
/// be @internal when it's supported.
bool internalRemoveKeyedProperty(KeyedProperty property) { bool internalRemoveKeyedProperty(KeyedProperty property) {
var removed = _keyedProperties.remove(property.propertyKey); var removed = _keyedProperties.remove(property.propertyKey);
if (_keyedProperties.isEmpty) { if (_keyedProperties.isEmpty) {
// Remove this keyed property.
context.removeObject(this); context.removeObject(this);
} }
// assert(removed == null || removed == property,
// '$removed was not $property or null');
return removed != null; return removed != null;
} }
@ -68,6 +90,7 @@ class KeyedObject extends KeyedObjectBase<RuntimeArtboard> {
@override @override
void objectIdChanged(int from, int to) {} void objectIdChanged(int from, int to) {}
@override @override
bool import(ImportStack stack) { bool import(ImportStack stack) {
var animationHelper = var animationHelper =
@ -76,6 +99,7 @@ class KeyedObject extends KeyedObjectBase<RuntimeArtboard> {
return false; return false;
} }
animationHelper.addKeyedObject(this); animationHelper.addKeyedObject(this);
return super.import(stack); return super.import(stack);
} }
} }

View File

@ -1,6 +1,7 @@
import 'package:rive/src/core/core.dart'; import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/keyed_object.dart'; import 'package:rive/src/rive_core/animation/keyed_object.dart';
import 'package:rive/src/rive_core/animation/keyframe.dart'; import 'package:rive/src/rive_core/animation/keyframe.dart';
import 'package:rive/src/generated/animation/keyed_property_base.dart'; import 'package:rive/src/generated/animation/keyed_property_base.dart';
export 'package:rive/src/generated/animation/keyed_property_base.dart'; export 'package:rive/src/generated/animation/keyed_property_base.dart';
@ -12,6 +13,8 @@ class KeyFrameList<T extends KeyFrameInterface> {
List<T> _keyframes = []; List<T> _keyframes = [];
Iterable<T> get keyframes => _keyframes; Iterable<T> get keyframes => _keyframes;
set keyframes(Iterable<T> frames) => _keyframes = frames.toList(); set keyframes(Iterable<T> frames) => _keyframes = frames.toList();
/// Get the keyframe immediately following the provided one.
T? after(T keyframe) { T? after(T keyframe) {
var index = _keyframes.indexOf(keyframe); var index = _keyframes.indexOf(keyframe);
if (index != -1 && index + 1 < _keyframes.length) { if (index != -1 && index + 1 < _keyframes.length) {
@ -20,12 +23,15 @@ class KeyFrameList<T extends KeyFrameInterface> {
return null; return null;
} }
/// Find the index in the keyframe list of a specific time frame.
int indexOfFrame(int frame) { int indexOfFrame(int frame) {
int idx = 0; int idx = 0;
// Binary find the keyframe index.
int mid = 0; int mid = 0;
int closestFrame = 0; int closestFrame = 0;
int start = 0; int start = 0;
int end = _keyframes.length - 1; int end = _keyframes.length - 1;
while (start <= end) { while (start <= end) {
mid = (start + end) >> 1; mid = (start + end) >> 1;
closestFrame = _keyframes[mid].frame; closestFrame = _keyframes[mid].frame;
@ -37,6 +43,7 @@ class KeyFrameList<T extends KeyFrameInterface> {
idx = start = mid; idx = start = mid;
break; break;
} }
idx = start; idx = start;
} }
return idx; return idx;
@ -49,13 +56,17 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
with KeyFrameList<KeyFrame> { with KeyFrameList<KeyFrame> {
@override @override
void onAdded() {} void onAdded() {}
@override @override
void onAddedDirty() {} void onAddedDirty() {}
@override @override
void onRemoved() { void onRemoved() {
super.onRemoved(); super.onRemoved();
} }
/// Called by rive_core to add a KeyFrame to this KeyedProperty. This should
/// be @internal when it's supported.
bool internalAddKeyFrame(KeyFrame frame) { bool internalAddKeyFrame(KeyFrame frame) {
if (_keyframes.contains(frame)) { if (_keyframes.contains(frame)) {
return false; return false;
@ -65,44 +76,64 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
return true; return true;
} }
/// Called by rive_core to remove a KeyFrame from this KeyedProperty. This
/// should be @internal when it's supported.
bool internalRemoveKeyFrame(KeyFrame frame) { bool internalRemoveKeyFrame(KeyFrame frame) {
var removed = _keyframes.remove(frame); var removed = _keyframes.remove(frame);
if (_keyframes.isEmpty) { if (_keyframes.isEmpty) {
// If they keyframes are now empty, we might want to remove this keyed
// property. Wait for any other pending changes to complete before
// checking.
context.dirty(_checkShouldRemove); context.dirty(_checkShouldRemove);
} }
return removed; return removed;
} }
void _checkShouldRemove() { void _checkShouldRemove() {
if (_keyframes.isEmpty) { if (_keyframes.isEmpty) {
// Remove this keyed property.
context.removeObject(this); context.removeObject(this);
} }
} }
/// Called by keyframes when their time value changes. This is a pretty rare
/// operation, usually occurs when a user moves a keyframe. Meaning: this
/// shouldn't make it into the runtimes unless we want to allow users moving
/// keyframes around at runtime via code for some reason.
void markKeyFrameOrderDirty() { void markKeyFrameOrderDirty() {
context.dirty(_sortAndValidateKeyFrames); context.dirty(_sortAndValidateKeyFrames);
} }
void _sortAndValidateKeyFrames() { void _sortAndValidateKeyFrames() {
sort(); sort();
for (int i = 0; i < _keyframes.length - 1; i++) { for (int i = 0; i < _keyframes.length - 1; i++) {
var a = _keyframes[i]; var a = _keyframes[i];
var b = _keyframes[i + 1]; var b = _keyframes[i + 1];
if (a.frame == b.frame) { if (a.frame == b.frame) {
// N.B. this removes it from the list too.
context.removeObject(a); context.removeObject(a);
// Repeat current.
i--; i--;
} }
} }
} }
/// Number of keyframes for this keyed property.
int get numFrames => _keyframes.length; int get numFrames => _keyframes.length;
KeyFrame getFrameAt(int index) => _keyframes[index]; KeyFrame getFrameAt(int index) => _keyframes[index];
int closestFrameIndex(double seconds) { int closestFrameIndex(double seconds) {
int idx = 0; int idx = 0;
// Binary find the keyframe index (use timeInSeconds here as opposed to the
// finder above which operates in frames).
int mid = 0; int mid = 0;
double closestSeconds = 0; double closestSeconds = 0;
int start = 0; int start = 0;
int end = _keyframes.length - 1; int end = _keyframes.length - 1;
while (start <= end) { while (start <= end) {
mid = (start + end) >> 1; mid = (start + end) >> 1;
closestSeconds = _keyframes[mid].seconds; closestSeconds = _keyframes[mid].seconds;
@ -123,10 +154,12 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
if (_keyframes.isEmpty) { if (_keyframes.isEmpty) {
return; return;
} }
int idx = closestFrameIndex(seconds); int idx = closestFrameIndex(seconds);
int pk = propertyKey; int pk = propertyKey;
if (idx == 0) { if (idx == 0) {
var first = _keyframes[0]; var first = _keyframes[0];
first.apply(object, pk, mix); first.apply(object, pk, mix);
} else { } else {
if (idx < _keyframes.length) { if (idx < _keyframes.length) {
@ -135,6 +168,8 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
if (seconds == toFrame.seconds) { if (seconds == toFrame.seconds) {
toFrame.apply(object, pk, mix); toFrame.apply(object, pk, mix);
} else { } else {
/// Equivalent to fromFrame.interpolation ==
/// KeyFrameInterpolation.hold.
if (fromFrame.interpolationType == 0) { if (fromFrame.interpolationType == 0) {
fromFrame.apply(object, pk, mix); fromFrame.apply(object, pk, mix);
} else { } else {
@ -143,6 +178,7 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
} }
} else { } else {
var last = _keyframes[idx - 1]; var last = _keyframes[idx - 1];
last.apply(object, pk, mix); last.apply(object, pk, mix);
} }
} }
@ -150,6 +186,7 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
@override @override
void propertyKeyChanged(int from, int to) {} void propertyKeyChanged(int from, int to) {}
@override @override
bool import(ImportStack stack) { bool import(ImportStack stack) {
var importer = stack.latest<KeyedObjectImporter>(KeyedObjectBase.typeKey); var importer = stack.latest<KeyedObjectImporter>(KeyedObjectBase.typeKey);
@ -157,6 +194,7 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
return false; return false;
} }
importer.addKeyedProperty(this); importer.addKeyedProperty(this);
return super.import(stack); return super.import(stack);
} }
} }

View File

@ -3,14 +3,18 @@ import 'package:rive/src/rive_core/animation/interpolator.dart';
import 'package:rive/src/rive_core/animation/keyed_property.dart'; import 'package:rive/src/rive_core/animation/keyed_property.dart';
import 'package:rive/src/rive_core/animation/keyframe_interpolation.dart'; import 'package:rive/src/rive_core/animation/keyframe_interpolation.dart';
import 'package:rive/src/rive_core/animation/linear_animation.dart'; import 'package:rive/src/rive_core/animation/linear_animation.dart';
import 'package:rive/src/generated/animation/keyframe_base.dart'; import 'package:rive/src/generated/animation/keyframe_base.dart';
export 'package:rive/src/generated/animation/keyframe_base.dart'; export 'package:rive/src/generated/animation/keyframe_base.dart';
abstract class KeyFrame extends KeyFrameBase<RuntimeArtboard> abstract class KeyFrame extends KeyFrameBase<RuntimeArtboard>
implements KeyFrameInterface { implements KeyFrameInterface {
double _timeInSeconds = 0; double _timeInSeconds = 0;
double get seconds => _timeInSeconds; double get seconds => _timeInSeconds;
bool get canInterpolate => true; bool get canInterpolate => true;
KeyFrameInterpolation get interpolation => KeyFrameInterpolation get interpolation =>
KeyFrameInterpolation.values[interpolationType]; KeyFrameInterpolation.values[interpolationType];
set interpolation(KeyFrameInterpolation value) { set interpolation(KeyFrameInterpolation value) {
@ -19,13 +23,17 @@ abstract class KeyFrame extends KeyFrameBase<RuntimeArtboard>
@override @override
void interpolationTypeChanged(int from, int to) {} void interpolationTypeChanged(int from, int to) {}
@override @override
void interpolatorIdChanged(int from, int to) { void interpolatorIdChanged(int from, int to) {
// This might resolve to null during a load or if context isn't available
// yet so we also do this in onAddedDirty.
interpolator = context.resolve(to); interpolator = context.resolve(to);
} }
@override @override
void onAdded() {} void onAdded() {}
void computeSeconds(LinearAnimation animation) { void computeSeconds(LinearAnimation animation) {
_timeInSeconds = frame / animation.fps; _timeInSeconds = frame / animation.fps;
} }
@ -44,15 +52,22 @@ abstract class KeyFrame extends KeyFrameBase<RuntimeArtboard>
@override @override
void frameChanged(int from, int to) {} void frameChanged(int from, int to) {}
/// Apply the value of this keyframe to the object's property.
void apply(Core object, int propertyKey, double mix); void apply(Core object, int propertyKey, double mix);
/// Interpolate the value between this keyframe and the next and apply it to
/// the object's property.
void applyInterpolation(Core object, int propertyKey, double seconds, void applyInterpolation(Core object, int propertyKey, double seconds,
covariant KeyFrame nextFrame, double mix); covariant KeyFrame nextFrame, double mix);
Interpolator? _interpolator; Interpolator? _interpolator;
Interpolator? get interpolator => _interpolator; Interpolator? get interpolator => _interpolator;
set interpolator(Interpolator? value) { set interpolator(Interpolator? value) {
if (_interpolator == value) { if (_interpolator == value) {
return; return;
} }
_interpolator = value; _interpolator = value;
interpolatorId = value?.id ?? Core.missingId; interpolatorId = value?.id ?? Core.missingId;
} }
@ -65,6 +80,7 @@ abstract class KeyFrame extends KeyFrameBase<RuntimeArtboard>
return false; return false;
} }
keyedPropertyHelper.addKeyFrame(this); keyedPropertyHelper.addKeyFrame(this);
return super.import(importStack); return super.import(importStack);
} }
} }

View File

@ -1,4 +1,5 @@
import 'dart:ui'; import 'dart:ui';
import 'package:rive/src/core/core.dart'; import 'package:rive/src/core/core.dart';
import 'package:rive/src/generated/animation/keyframe_color_base.dart'; import 'package:rive/src/generated/animation/keyframe_color_base.dart';
import 'package:rive/src/generated/rive_core_context.dart'; import 'package:rive/src/generated/rive_core_context.dart';
@ -22,13 +23,16 @@ class KeyFrameColor extends KeyFrameColorBase {
@override @override
void apply(Core<CoreContext> object, int propertyKey, double mix) => void apply(Core<CoreContext> object, int propertyKey, double mix) =>
_apply(object, propertyKey, mix, value); _apply(object, propertyKey, mix, value);
@override @override
void applyInterpolation(Core<CoreContext> object, int propertyKey, void applyInterpolation(Core<CoreContext> object, int propertyKey,
double currentTime, KeyFrameColor nextFrame, double mix) { double currentTime, KeyFrameColor nextFrame, double mix) {
var f = (currentTime - seconds) / (nextFrame.seconds - seconds); var f = (currentTime - seconds) / (nextFrame.seconds - seconds);
if (interpolator != null) { if (interpolator != null) {
f = interpolator!.transform(f); f = interpolator!.transform(f);
} }
var color = Color.lerp(Color(value), Color(nextFrame.value), f); var color = Color.lerp(Color(value), Color(nextFrame.value), f);
if (color != null) { if (color != null) {
_apply(object, propertyKey, mix, color.value); _apply(object, propertyKey, mix, color.value);

View File

@ -22,13 +22,16 @@ class KeyFrameDouble extends KeyFrameDoubleBase {
@override @override
void apply(Core<CoreContext> object, int propertyKey, double mix) => void apply(Core<CoreContext> object, int propertyKey, double mix) =>
_apply(object, propertyKey, mix, value); _apply(object, propertyKey, mix, value);
@override @override
void applyInterpolation(Core<CoreContext> object, int propertyKey, void applyInterpolation(Core<CoreContext> object, int propertyKey,
double currentTime, KeyFrameDouble nextFrame, double mix) { double currentTime, KeyFrameDouble nextFrame, double mix) {
var f = (currentTime - seconds) / (nextFrame.seconds - seconds); var f = (currentTime - seconds) / (nextFrame.seconds - seconds);
if (interpolator != null) { if (interpolator != null) {
f = interpolator!.transform(f); f = interpolator!.transform(f);
} }
_apply(object, propertyKey, mix, value + (nextFrame.value - value) * f); _apply(object, propertyKey, mix, value + (nextFrame.value - value) * f);
} }

View File

@ -6,6 +6,7 @@ export 'package:rive/src/generated/animation/keyframe_id_base.dart';
class KeyFrameId extends KeyFrameIdBase { class KeyFrameId extends KeyFrameIdBase {
@override @override
bool get canInterpolate => false; bool get canInterpolate => false;
@override @override
void apply(Core<CoreContext> object, int propertyKey, double mix) { void apply(Core<CoreContext> object, int propertyKey, double mix) {
RiveCoreContext.setUint(object, propertyKey, value); RiveCoreContext.setUint(object, propertyKey, value);

View File

@ -1 +1,12 @@
enum KeyFrameInterpolation { hold, linear, cubic } /// The type of interpolation used for a keyframe.
enum KeyFrameInterpolation {
/// Hold the incoming value until the next keyframe is reached.
hold,
/// Linearly interpolate from the incoming to the outgoing value.
linear,
/// Cubicly interpolate from incoming to outgoing value based on the
/// [CubicInterpolator]'s parameters.
cubic,
}

View File

@ -1,4 +1,5 @@
import 'package:rive/src/core/core.dart'; import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/state_instance.dart';
import 'package:rive/src/rive_core/animation/state_machine_layer.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/animation/state_transition.dart';
import 'package:rive/src/generated/animation/layer_state_base.dart'; import 'package:rive/src/generated/animation/layer_state_base.dart';
@ -7,11 +8,16 @@ export 'package:rive/src/generated/animation/layer_state_base.dart';
abstract class LayerState extends LayerStateBase { abstract class LayerState extends LayerStateBase {
final StateTransitions _transitions = StateTransitions(); final StateTransitions _transitions = StateTransitions();
StateTransitions get transitions => _transitions; StateTransitions get transitions => _transitions;
@override @override
void onAdded() {} void onAdded() {}
@override @override
void onAddedDirty() {} void onAddedDirty() {}
void internalAddTransition(StateTransition transition) { void internalAddTransition(StateTransition transition) {
assert(!_transitions.contains(transition),
'shouldn\'t already contain the transition');
_transitions.add(transition); _transitions.add(transition);
} }
@ -24,6 +30,8 @@ abstract class LayerState extends LayerStateBase {
super.onRemoved(); super.onRemoved();
} }
StateInstance makeInstance();
@override @override
bool import(ImportStack stack) { bool import(ImportStack stack) {
var importer = var importer =
@ -32,6 +40,7 @@ abstract class LayerState extends LayerStateBase {
return false; return false;
} }
importer.addState(this); importer.addState(this);
return super.import(stack); return super.import(stack);
} }
} }

View File

@ -1,4 +1,5 @@
import 'dart:collection'; import 'dart:collection';
import 'package:rive/src/core/core.dart'; import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/keyed_object.dart'; import 'package:rive/src/rive_core/animation/keyed_object.dart';
import 'package:rive/src/rive_core/animation/loop.dart'; import 'package:rive/src/rive_core/animation/loop.dart';
@ -7,8 +8,16 @@ import 'package:rive/src/generated/animation/linear_animation_base.dart';
export 'package:rive/src/generated/animation/linear_animation_base.dart'; export 'package:rive/src/generated/animation/linear_animation_base.dart';
class LinearAnimation extends LinearAnimationBase { class LinearAnimation extends LinearAnimationBase {
/// Map objectId to KeyedObject. N.B. this is the id of the object that we
/// want to key in core, not of the KeyedObject. It's a clear way to see if an
/// object is keyed in this animation.
final _keyedObjects = HashMap<int, KeyedObject>(); final _keyedObjects = HashMap<int, KeyedObject>();
/// The metadata for the objects that are keyed in this animation.
Iterable<KeyedObject> get keyedObjects => _keyedObjects.values; Iterable<KeyedObject> get keyedObjects => _keyedObjects.values;
/// Called by rive_core to add a KeyedObject to the animation. This should be
/// @internal when it's supported.
bool internalAddKeyedObject(KeyedObject object) { bool internalAddKeyedObject(KeyedObject object) {
if (internalCheckAddKeyedObject(object)) { if (internalCheckAddKeyedObject(object)) {
_keyedObjects[object.objectId] = object; _keyedObjects[object.objectId] = object;
@ -19,6 +28,9 @@ class LinearAnimation extends LinearAnimationBase {
bool internalCheckAddKeyedObject(KeyedObject object) { bool internalCheckAddKeyedObject(KeyedObject object) {
var value = _keyedObjects[object.objectId]; var value = _keyedObjects[object.objectId];
// If the object is already keyed, that's ok just make sure the KeyedObject
// matches.
if (value != null && value != object) { if (value != null && value != object) {
return false; return false;
} }
@ -29,6 +41,13 @@ class LinearAnimation extends LinearAnimationBase {
double get endSeconds => double get endSeconds =>
(enableWorkArea ? workEnd : duration).toDouble() / fps; (enableWorkArea ? workEnd : duration).toDouble() / fps;
double get durationSeconds => endSeconds - startSeconds; double get durationSeconds => endSeconds - startSeconds;
/// Pass in a different [core] context if you want to apply the animation to a
/// different instance. This isn't meant to be used yet but left as mostly a
/// note to remember that at runtime we have to support applying animations to
/// instances. We do a nice job of not duping all that data at runtime (so
/// animations exist once but entire Rive file can be instanced multiple times
/// playing different positions).
void apply(double time, {required CoreContext coreContext, double mix = 1}) { void apply(double time, {required CoreContext coreContext, double mix = 1}) {
for (final keyedObject in _keyedObjects.values) { for (final keyedObject in _keyedObjects.values) {
keyedObject.apply(time, mix, coreContext); keyedObject.apply(time, mix, coreContext);
@ -37,20 +56,28 @@ class LinearAnimation extends LinearAnimationBase {
Loop get loop => Loop.values[loopValue]; Loop get loop => Loop.values[loopValue];
set loop(Loop value) => loopValue = value.index; set loop(Loop value) => loopValue = value.index;
@override @override
void durationChanged(int from, int to) {} void durationChanged(int from, int to) {}
@override @override
void enableWorkAreaChanged(bool from, bool to) {} void enableWorkAreaChanged(bool from, bool to) {}
@override @override
void fpsChanged(int from, int to) {} void fpsChanged(int from, int to) {}
@override @override
void loopValueChanged(int from, int to) {} void loopValueChanged(int from, int to) {}
@override @override
void speedChanged(double from, double to) {} void speedChanged(double from, double to) {}
@override @override
void workEndChanged(int from, int to) {} void workEndChanged(int from, int to) {}
@override @override
void workStartChanged(int from, int to) {} void workStartChanged(int from, int to) {}
@override @override
bool import(ImportStack stack) { bool import(ImportStack stack) {
var artboardImporter = stack.latest<ArtboardImporter>(ArtboardBase.typeKey); var artboardImporter = stack.latest<ArtboardImporter>(ArtboardBase.typeKey);
@ -58,6 +85,7 @@ class LinearAnimation extends LinearAnimationBase {
return false; return false;
} }
artboardImporter.addAnimation(this); artboardImporter.addAnimation(this);
return super.import(stack); return super.import(stack);
} }
} }

View File

@ -11,47 +11,73 @@ class LinearAnimationInstance {
bool get didLoop => _didLoop; bool get didLoop => _didLoop;
double _spilledTime = 0; double _spilledTime = 0;
double get spilledTime => _spilledTime; double get spilledTime => _spilledTime;
double get totalTime => _totalTime; double get totalTime => _totalTime;
double get lastTotalTime => _lastTotalTime; double get lastTotalTime => _lastTotalTime;
LinearAnimationInstance(this.animation) LinearAnimationInstance(this.animation)
: _time = : _time =
(animation.enableWorkArea ? animation.workStart : 0).toDouble() / (animation.enableWorkArea ? animation.workStart : 0).toDouble() /
animation.fps; animation.fps;
/// Note that when time is set, the direction will be changed to 1
set time(double value) { set time(double value) {
if (_time == value) { if (_time == value) {
return; return;
} }
// Make sure to keep last and total in relative lockstep so state machines
// can track change even when setting time.
var diff = _totalTime - _lastTotalTime; var diff = _totalTime - _lastTotalTime;
_time = _totalTime = value; _time = _totalTime = value;
_lastTotalTime = _totalTime - diff; _lastTotalTime = _totalTime - diff;
_direction = 1; _direction = 1;
} }
/// Returns the current time position of the animation in seconds
double get time => _time; double get time => _time;
/// Direction should only be +1 or -1
set direction(int value) => _direction = value == -1 ? -1 : 1; set direction(int value) => _direction = value == -1 ? -1 : 1;
/// Returns the animation's play direction: 1 for forwards, -1 for backwards
int get direction => _direction; int get direction => _direction;
/// Returns the end time of the animation in seconds
double get endTime => double get endTime =>
(animation.enableWorkArea ? animation.workEnd : animation.duration) (animation.enableWorkArea ? animation.workEnd : animation.duration)
.toDouble() / .toDouble() /
animation.fps; animation.fps;
/// Returns the start time of the animation in seconds
double get startTime => double get startTime =>
(animation.enableWorkArea ? animation.workStart : 0).toDouble() / (animation.enableWorkArea ? animation.workStart : 0).toDouble() /
animation.fps; animation.fps;
double get progress => (_time - startTime) / (endTime - startTime); double get progress => (_time - startTime) / (endTime - startTime);
/// Resets the animation to the starting frame
void reset() => _time = startTime; void reset() => _time = startTime;
/// Whether the controller driving this animation should keep requesting
/// frames be drawn.
bool get keepGoing => animation.loop != Loop.oneShot || !_didLoop; bool get keepGoing => animation.loop != Loop.oneShot || !_didLoop;
bool advance(double elapsedSeconds) { bool advance(double elapsedSeconds) {
var deltaSeconds = elapsedSeconds * animation.speed * _direction; var deltaSeconds = elapsedSeconds * animation.speed * _direction;
_lastTotalTime = _totalTime; _lastTotalTime = _totalTime;
_totalTime += deltaSeconds; _totalTime += deltaSeconds;
_time += deltaSeconds; _time += deltaSeconds;
double frames = _time * animation.fps; double frames = _time * animation.fps;
var start = animation.enableWorkArea ? animation.workStart : 0; var start = animation.enableWorkArea ? animation.workStart : 0;
var end = animation.enableWorkArea ? animation.workEnd : animation.duration; var end = animation.enableWorkArea ? animation.workEnd : animation.duration;
var range = end - start; var range = end - start;
bool keepGoing = true; bool keepGoing = true;
_didLoop = false; _didLoop = false;
_spilledTime = 0; _spilledTime = 0;
switch (animation.loop) { switch (animation.loop) {
case Loop.oneShot: case Loop.oneShot:
if (frames > end) { if (frames > end) {
@ -72,6 +98,7 @@ class LinearAnimationInstance {
} }
break; break;
case Loop.pingPong: case Loop.pingPong:
// ignore: literal_only_boolean_expressions
while (true) { while (true) {
if (_direction == 1 && frames >= end) { if (_direction == 1 && frames >= end) {
_spilledTime = (frames - end) / animation.fps; _spilledTime = (frames - end) / animation.fps;
@ -86,6 +113,11 @@ class LinearAnimationInstance {
_time = frames / animation.fps; _time = frames / animation.fps;
_didLoop = true; _didLoop = true;
} else { } else {
// we're within the range, we can stop fixing. We do this in a
// loop to fix conditions when time has advanced so far that we've
// ping-ponged back and forth a few times in a single frame. We
// want to accomodate for this in cases where animations are not
// advanced on regular intervals.
break; break;
} }
} }

View File

@ -1 +1,12 @@
enum Loop { oneShot, loop, pingPong } /// Loop options for linear animations.
enum Loop {
/// Play until the duration or end of work area of the animation.
oneShot,
/// Play until the duration or end of work area of the animation and then go
/// back to the start (0 seconds).
loop,
/// Play to the end of the duration/work area and then play back.
pingPong,
}

View File

@ -0,0 +1,33 @@
import 'dart:collection';
import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/layer_state.dart';
/// Represents the instance of a [LayerState] which is being used in a
/// [LayerController] of a [StateMachineController]. Abstract representation of
/// an Animation (for [AnimationState]) or set of Animations in the case of a
/// [BlendState].
abstract class StateInstance {
final LayerState state;
StateInstance(this.state);
void advance(double seconds, HashMap<int, dynamic> inputValues);
void apply(CoreContext core, double mix);
bool get keepGoing;
}
/// A single one of these is created per Layer which just represents/wraps the
/// AnyState but conforms to the instance interface.
class SystemStateInstance extends StateInstance {
SystemStateInstance(LayerState state) : super(state);
@override
void advance(double seconds, HashMap<int, dynamic> inputValues) {}
@override
void apply(CoreContext core, double mix) {}
@override
bool get keepGoing => false;
}

View File

@ -10,6 +10,7 @@ class StateMachine extends StateMachineBase {
StateMachineComponents<StateMachineInput>(); StateMachineComponents<StateMachineInput>();
final StateMachineComponents<StateMachineLayer> layers = final StateMachineComponents<StateMachineLayer> layers =
StateMachineComponents<StateMachineLayer>(); StateMachineComponents<StateMachineLayer>();
@override @override
bool import(ImportStack stack) { bool import(ImportStack stack) {
var artboardImporter = stack.latest<ArtboardImporter>(ArtboardBase.typeKey); var artboardImporter = stack.latest<ArtboardImporter>(ArtboardBase.typeKey);
@ -17,6 +18,7 @@ class StateMachine extends StateMachineBase {
return false; return false;
} }
artboardImporter.addStateMachine(this); artboardImporter.addStateMachine(this);
return super.import(stack); return super.import(stack);
} }
} }

View File

@ -4,8 +4,10 @@ export 'package:rive/src/generated/animation/state_machine_bool_base.dart';
class StateMachineBool extends StateMachineBoolBase { class StateMachineBool extends StateMachineBoolBase {
@override @override
void valueChanged(bool from, bool to) {} void valueChanged(bool from, bool to) {}
@override @override
bool isValidType<T>() => T == bool; bool isValidType<T>() => T == bool;
@override @override
dynamic get controllerValue => value; dynamic get controllerValue => value;
} }

View File

@ -1,9 +1,11 @@
import 'dart:collection'; import 'dart:collection';
import 'package:rive/src/core/core.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.dart';
import 'package:rive/src/generated/animation/state_machine_component_base.dart'; import 'package:rive/src/generated/animation/state_machine_component_base.dart';
export 'package:rive/src/generated/animation/state_machine_component_base.dart'; export 'package:rive/src/generated/animation/state_machine_component_base.dart';
/// Implemented by state machine inputs and layers.
abstract class StateMachineComponent extends StateMachineComponentBase { abstract class StateMachineComponent extends StateMachineComponentBase {
StateMachine? _stateMachine; StateMachine? _stateMachine;
StateMachine? get stateMachine => _stateMachine; StateMachine? get stateMachine => _stateMachine;
@ -15,16 +17,22 @@ abstract class StateMachineComponent extends StateMachineComponentBase {
machineComponentList(_stateMachine!).remove(this); machineComponentList(_stateMachine!).remove(this);
} }
_stateMachine = machine; _stateMachine = machine;
if (_stateMachine != null) { if (_stateMachine != null) {
machineComponentList(_stateMachine!).add(this); machineComponentList(_stateMachine!).add(this);
} }
} }
// Intentionally using ListBase instead of FractionallyIndexedList here as
// it's more compatible with runtime.
ListBase<StateMachineComponent> machineComponentList(StateMachine machine); ListBase<StateMachineComponent> machineComponentList(StateMachine machine);
@override @override
void nameChanged(String from, String to) {} void nameChanged(String from, String to) {}
@override @override
void onAddedDirty() {} void onAddedDirty() {}
@override @override
void onRemoved() { void onRemoved() {
super.onRemoved(); super.onRemoved();
@ -39,6 +47,7 @@ abstract class StateMachineComponent extends StateMachineComponentBase {
return false; return false;
} }
importer.addMachineComponent(this); importer.addMachineComponent(this);
return super.import(importStack); return super.import(importStack);
} }
} }

View File

@ -1,4 +1,5 @@
import 'dart:collection'; import 'dart:collection';
import 'package:rive/src/rive_core/animation/state_machine.dart'; import 'package:rive/src/rive_core/animation/state_machine.dart';
import 'package:rive/src/rive_core/animation/state_machine_component.dart'; import 'package:rive/src/rive_core/animation/state_machine_component.dart';
import 'package:rive/src/generated/animation/state_machine_input_base.dart'; import 'package:rive/src/generated/animation/state_machine_input_base.dart';
@ -6,9 +7,11 @@ export 'package:rive/src/generated/animation/state_machine_input_base.dart';
abstract class StateMachineInput extends StateMachineInputBase { abstract class StateMachineInput extends StateMachineInputBase {
static final StateMachineInput unknown = _StateMachineUnknownInput(); static final StateMachineInput unknown = _StateMachineUnknownInput();
@override @override
ListBase<StateMachineComponent> machineComponentList(StateMachine machine) => ListBase<StateMachineComponent> machineComponentList(StateMachine machine) =>
machine.inputs; machine.inputs;
bool isValidType<T>() => false; bool isValidType<T>() => false;
dynamic get controllerValue => null; dynamic get controllerValue => null;
} }

View File

@ -1,4 +1,5 @@
import 'dart:collection'; import 'dart:collection';
import 'package:rive/src/rive_core/animation/any_state.dart'; import 'package:rive/src/rive_core/animation/any_state.dart';
import 'package:rive/src/rive_core/animation/entry_state.dart'; import 'package:rive/src/rive_core/animation/entry_state.dart';
import 'package:rive/src/rive_core/animation/exit_state.dart'; import 'package:rive/src/rive_core/animation/exit_state.dart';
@ -13,12 +14,17 @@ class StateMachineLayer extends StateMachineLayerBase {
LayerState? _entryState; LayerState? _entryState;
LayerState? _anyState; LayerState? _anyState;
LayerState? _exitState; LayerState? _exitState;
LayerState? get entryState => _entryState; LayerState? get entryState => _entryState;
LayerState? get anyState => _anyState; LayerState? get anyState => _anyState;
LayerState? get exitState => _exitState; LayerState? get exitState => _exitState;
@override @override
ListBase<StateMachineComponent> machineComponentList(StateMachine machine) => ListBase<StateMachineComponent> machineComponentList(StateMachine machine) =>
machine.layers; machine.layers;
/// Called by rive_core to add a LayerState to the StateMachineLayer. This
/// should be @internal when it's supported.
bool internalAddState(LayerState state) { bool internalAddState(LayerState state) {
switch (state.coreType) { switch (state.coreType) {
case AnyStateBase.typeKey: case AnyStateBase.typeKey:
@ -31,6 +37,7 @@ class StateMachineLayer extends StateMachineLayerBase {
_entryState = state; _entryState = state;
break; break;
} }
return true; return true;
} }
} }

View File

@ -1,4 +1,9 @@
// We really want this file to import core for the flutter runtime, so make the
// linter happy...
// ignore: unused_import
import 'package:rive/src/core/core.dart'; import 'package:rive/src/core/core.dart';
import 'package:rive/src/generated/animation/state_machine_layer_component_base.dart'; import 'package:rive/src/generated/animation/state_machine_layer_component_base.dart';
export 'package:rive/src/generated/animation/state_machine_layer_component_base.dart'; export 'package:rive/src/generated/animation/state_machine_layer_component_base.dart';

View File

@ -4,8 +4,10 @@ export 'package:rive/src/generated/animation/state_machine_number_base.dart';
class StateMachineNumber extends StateMachineNumberBase { class StateMachineNumber extends StateMachineNumberBase {
@override @override
void valueChanged(double from, double to) {} void valueChanged(double from, double to) {}
@override @override
bool isValidType<T>() => T == double; bool isValidType<T>() => T == double;
@override @override
dynamic get controllerValue => value; dynamic get controllerValue => value;
} }

View File

@ -4,6 +4,7 @@ export 'package:rive/src/generated/animation/state_machine_trigger_base.dart';
class StateMachineTrigger extends StateMachineTriggerBase { class StateMachineTrigger extends StateMachineTriggerBase {
bool _triggered = false; bool _triggered = false;
bool get triggered => _triggered; bool get triggered => _triggered;
void fire() { void fire() {
_triggered = true; _triggered = true;
} }
@ -14,6 +15,7 @@ class StateMachineTrigger extends StateMachineTriggerBase {
@override @override
bool isValidType<T>() => T == bool; bool isValidType<T>() => T == bool;
@override @override
dynamic get controllerValue => _triggered; dynamic get controllerValue => _triggered;
} }

View File

@ -1,24 +1,40 @@
import 'dart:collection';
import 'package:rive/src/core/core.dart'; import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/animation_state.dart'; import 'package:rive/src/rive_core/animation/animation_state.dart';
import 'package:rive/src/rive_core/animation/animation_state_instance.dart';
import 'package:rive/src/rive_core/animation/layer_state.dart'; import 'package:rive/src/rive_core/animation/layer_state.dart';
import 'package:rive/src/rive_core/animation/linear_animation.dart';
import 'package:rive/src/rive_core/animation/linear_animation_instance.dart';
import 'package:rive/src/rive_core/animation/state_instance.dart';
import 'package:rive/src/rive_core/animation/transition_condition.dart'; import 'package:rive/src/rive_core/animation/transition_condition.dart';
import 'package:rive/src/rive_core/animation/transition_trigger_condition.dart';
import 'package:rive/src/generated/animation/state_transition_base.dart'; import 'package:rive/src/generated/animation/state_transition_base.dart';
import 'package:rive/src/rive_core/state_transition_flags.dart'; import 'package:rive/src/rive_core/state_transition_flags.dart';
export 'package:rive/src/generated/animation/state_transition_base.dart'; export 'package:rive/src/generated/animation/state_transition_base.dart';
enum AllowTransition { no, waitingForExit, yes }
class StateTransition extends StateTransitionBase { class StateTransition extends StateTransitionBase {
final StateTransitionConditions conditions = StateTransitionConditions(); final StateTransitionConditions conditions = StateTransitionConditions();
LayerState? stateTo; LayerState? stateTo;
static final StateTransition unknown = StateTransition(); static final StateTransition unknown = StateTransition();
@override @override
bool validate() { bool validate() {
return super.validate() && stateTo != null; return super.validate() &&
// need this last so runtimes get it which makes the whole
// allowTransitionFrom thing above a little weird.
stateTo != null;
} }
@override @override
void onAdded() {} void onAdded() {}
@override @override
void onAddedDirty() {} void onAddedDirty() {}
@override @override
void onRemoved() { void onRemoved() {
super.onRemoved(); super.onRemoved();
@ -27,6 +43,11 @@ class StateTransition extends StateTransitionBase {
bool get isDisabled => (flags & StateTransitionFlags.disabled) != 0; bool get isDisabled => (flags & StateTransitionFlags.disabled) != 0;
bool get pauseOnExit => (flags & StateTransitionFlags.pauseOnExit) != 0; bool get pauseOnExit => (flags & StateTransitionFlags.pauseOnExit) != 0;
bool get enableExitTime => (flags & StateTransitionFlags.enableExitTime) != 0; bool get enableExitTime => (flags & StateTransitionFlags.enableExitTime) != 0;
/// The amount of time to mix the outgoing animation onto the incoming one
/// when changing state. Only applies when going out from an AnimationState.
/// [stateFrom] must be provided as at runtime we don't store the reference to
/// the state this transition comes from.
double mixTime(LayerState stateFrom) { double mixTime(LayerState stateFrom) {
if (duration == 0) { if (duration == 0) {
return 0; return 0;
@ -42,14 +63,30 @@ class StateTransition extends StateTransitionBase {
} }
} }
/// Provide the animation instance to use for computing percentage durations
/// for exit time.
LinearAnimationInstance? exitTimeAnimationInstance(StateInstance stateFrom) =>
stateFrom is AnimationStateInstance ? stateFrom.animationInstance : null;
/// Provide the animation to use for computing percentage durations for exit
/// time.
LinearAnimation? exitTimeAnimation(LayerState stateFrom) =>
stateFrom is AnimationState ? stateFrom.animation : null;
/// Computes the exit time in seconds of the [stateFrom]. Set [absolute] to
/// true if you want the returned time to be relative to the entire animation.
/// Set [absolute] to false if you want it relative to the work area.
double exitTimeSeconds(LayerState stateFrom, {bool absolute = false}) { double exitTimeSeconds(LayerState stateFrom, {bool absolute = false}) {
if ((flags & StateTransitionFlags.exitTimeIsPercentage) != 0) { if ((flags & StateTransitionFlags.exitTimeIsPercentage) != 0) {
var animationDuration = 0.0; var animationDuration = 0.0;
var start = 0.0; var start = 0.0;
if (stateFrom is AnimationState) {
start = absolute ? stateFrom.animation?.startSeconds ?? 0 : 0; var exitAnimation = exitTimeAnimation(stateFrom);
animationDuration = stateFrom.animation?.durationSeconds ?? 0; if (exitAnimation != null) {
start = absolute ? exitAnimation.startSeconds : 0;
animationDuration = exitAnimation.durationSeconds;
} }
return start + exitTime / 100 * animationDuration; return start + exitTime / 100 * animationDuration;
} else { } else {
return exitTime / 1000; return exitTime / 1000;
@ -64,28 +101,89 @@ class StateTransition extends StateTransitionBase {
return false; return false;
} }
importer.addTransition(this); importer.addTransition(this);
return super.import(importStack); return super.import(importStack);
} }
/// Called by rive_core to add a [TransitionCondition] to this
/// [StateTransition]. This should be @internal when it's supported.
bool internalAddCondition(TransitionCondition condition) { bool internalAddCondition(TransitionCondition condition) {
if (conditions.contains(condition)) { if (conditions.contains(condition)) {
return false; return false;
} }
conditions.add(condition); conditions.add(condition);
return true; return true;
} }
/// Called by rive_core to remove a [TransitionCondition] from this
/// [StateTransition]. This should be @internal when it's supported.
bool internalRemoveCondition(TransitionCondition condition) { bool internalRemoveCondition(TransitionCondition condition) {
var removed = conditions.remove(condition); var removed = conditions.remove(condition);
return removed; return removed;
} }
@override @override
void flagsChanged(int from, int to) {} void flagsChanged(int from, int to) {}
@override @override
void durationChanged(int from, int to) {} void durationChanged(int from, int to) {}
@override @override
void exitTimeChanged(int from, int to) {} void exitTimeChanged(int from, int to) {}
@override @override
void stateToIdChanged(int from, int to) {} void stateToIdChanged(int from, int to) {}
/// Returns true when this transition can be taken from [stateFrom] with the
/// given [inputValues].
AllowTransition allowed(StateInstance stateFrom,
HashMap<int, dynamic> inputValues, bool ignoreTriggers) {
if (isDisabled) {
return AllowTransition.no;
}
for (final condition in conditions) {
if ((ignoreTriggers && condition is TransitionTriggerCondition) ||
!condition.evaluate(inputValues)) {
return AllowTransition.no;
}
}
// For now we only enable exit time from AnimationStates, do we want to
// enable this for BlendStates? How would that work?
if (enableExitTime) {
var exitAnimation = exitTimeAnimationInstance(stateFrom);
if (exitAnimation != null) {
// Exit time is specified in a value less than a single loop, so we
// want to allow exiting regardless of which loop we're on. To do that
// we bring the exit time up to the loop our lastTime is at.
var lastTime = exitAnimation.lastTotalTime;
var time = exitAnimation.totalTime;
var exitTime = exitTimeSeconds(stateFrom.state);
var animationFrom = exitAnimation.animation;
if (exitTime < animationFrom.durationSeconds) {
// Get exit time relative to the loop lastTime was in.
exitTime += (lastTime / animationFrom.durationSeconds).floor() *
animationFrom.durationSeconds;
}
if (time < exitTime) {
return AllowTransition.waitingForExit;
}
}
}
return AllowTransition.yes;
}
bool applyExitCondition(StateInstance stateFrom) {
// Hold exit time when the user has set to pauseOnExit on this condition
// (only valid when exiting from an Animation).
bool useExitTime = enableExitTime && stateFrom is AnimationStateInstance;
if (pauseOnExit && useExitTime) {
stateFrom.animationInstance.time =
exitTimeSeconds(stateFrom.state, absolute: true);
return true;
}
return useExitTime;
}
} }

View File

@ -7,6 +7,7 @@ export 'package:rive/src/generated/animation/transition_bool_condition_base.dart
class TransitionBoolCondition extends TransitionBoolConditionBase { class TransitionBoolCondition extends TransitionBoolConditionBase {
@override @override
bool validate() => super.validate() && (input is StateMachineBool); bool validate() => super.validate() && (input is StateMachineBool);
@override @override
bool evaluate(HashMap<int, dynamic> values) { bool evaluate(HashMap<int, dynamic> values) {
if (input is! StateMachineBool) { if (input is! StateMachineBool) {

View File

@ -1,4 +1,5 @@
import 'dart:collection'; import 'dart:collection';
import 'package:rive/src/core/core.dart'; 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_machine_input.dart';
import 'package:rive/src/rive_core/animation/state_transition.dart'; import 'package:rive/src/rive_core/animation/state_transition.dart';
@ -11,7 +12,7 @@ enum TransitionConditionOp {
lessThanOrEqual, lessThanOrEqual,
greaterThanOrEqual, greaterThanOrEqual,
lessThan, lessThan,
greaterThan greaterThan,
} }
abstract class TransitionCondition extends TransitionConditionBase { abstract class TransitionCondition extends TransitionConditionBase {
@ -21,7 +22,9 @@ abstract class TransitionCondition extends TransitionConditionBase {
if (_input == value) { if (_input == value) {
return; return;
} }
_input = value; _input = value;
inputId = _input.id; inputId = _input.id;
} }
@ -32,12 +35,14 @@ abstract class TransitionCondition extends TransitionConditionBase {
@override @override
void onAdded() {} void onAdded() {}
@override @override
void onAddedDirty() { void onAddedDirty() {
input = context.resolveWithDefault(inputId, StateMachineInput.unknown); input = context.resolveWithDefault(inputId, StateMachineInput.unknown);
} }
bool evaluate(HashMap<int, dynamic> values); bool evaluate(HashMap<int, dynamic> values);
@override @override
bool import(ImportStack importStack) { bool import(ImportStack importStack) {
var importer = importStack var importer = importStack
@ -46,6 +51,7 @@ abstract class TransitionCondition extends TransitionConditionBase {
return false; return false;
} }
importer.addCondition(this); importer.addCondition(this);
return super.import(importStack); return super.import(importStack);
} }
} }

View File

@ -1,4 +1,5 @@
import 'dart:collection'; import 'dart:collection';
import 'package:rive/src/rive_core/animation/state_machine_number.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/rive_core/animation/transition_condition.dart';
import 'package:rive/src/generated/animation/transition_number_condition_base.dart'; import 'package:rive/src/generated/animation/transition_number_condition_base.dart';
@ -7,8 +8,10 @@ export 'package:rive/src/generated/animation/transition_number_condition_base.da
class TransitionNumberCondition extends TransitionNumberConditionBase { class TransitionNumberCondition extends TransitionNumberConditionBase {
@override @override
void valueChanged(double from, double to) {} void valueChanged(double from, double to) {}
@override @override
bool validate() => super.validate() && (input is StateMachineNumber); bool validate() => super.validate() && (input is StateMachineNumber);
@override @override
bool evaluate(HashMap<int, dynamic> values) { bool evaluate(HashMap<int, dynamic> values) {
if (input is! StateMachineNumber) { if (input is! StateMachineNumber) {

View File

@ -1,4 +1,5 @@
import 'dart:collection'; import 'dart:collection';
import 'package:rive/src/rive_core/animation/state_machine_trigger.dart'; import 'package:rive/src/rive_core/animation/state_machine_trigger.dart';
import 'package:rive/src/generated/animation/transition_trigger_condition_base.dart'; import 'package:rive/src/generated/animation/transition_trigger_condition_base.dart';
export 'package:rive/src/generated/animation/transition_trigger_condition_base.dart'; export 'package:rive/src/generated/animation/transition_trigger_condition_base.dart';
@ -6,6 +7,7 @@ export 'package:rive/src/generated/animation/transition_trigger_condition_base.d
class TransitionTriggerCondition extends TransitionTriggerConditionBase { class TransitionTriggerCondition extends TransitionTriggerConditionBase {
@override @override
bool validate() => super.validate() && (input is StateMachineTrigger); bool validate() => super.validate() && (input is StateMachineTrigger);
@override @override
bool evaluate(HashMap<int, dynamic> values) { bool evaluate(HashMap<int, dynamic> values) {
if (input is! StateMachineTrigger) { if (input is! StateMachineTrigger) {
@ -16,6 +18,7 @@ class TransitionTriggerCondition extends TransitionTriggerConditionBase {
values[input.id] = false; values[input.id] = false;
return true; return true;
} }
var triggerInput = input as StateMachineTrigger; var triggerInput = input as StateMachineTrigger;
if (triggerInput.triggered) { if (triggerInput.triggered) {
return true; return true;

View File

@ -4,6 +4,9 @@ export 'package:rive/src/generated/animation/transition_value_condition_base.dar
abstract class TransitionValueCondition extends TransitionValueConditionBase { abstract class TransitionValueCondition extends TransitionValueConditionBase {
TransitionConditionOp get op => TransitionConditionOp.values[opValue]; TransitionConditionOp get op => TransitionConditionOp.values[opValue];
@override @override
void opValueChanged(int from, int to) {} void opValueChanged(int from, int to) {
// TODO: implement opValueChanged
}
} }

View File

@ -1,4 +1,5 @@
import 'dart:ui'; import 'dart:ui';
import 'package:rive/src/core/core.dart'; import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/animation.dart'; import 'package:rive/src/rive_core/animation/animation.dart';
import 'package:rive/src/rive_core/component.dart'; import 'package:rive/src/rive_core/component.dart';
@ -12,34 +13,52 @@ import 'package:rive/src/rive_core/rive_animation_controller.dart';
import 'package:rive/src/rive_core/shapes/paint/shape_paint_mutator.dart'; import 'package:rive/src/rive_core/shapes/paint/shape_paint_mutator.dart';
import 'package:rive/src/rive_core/shapes/shape_paint_container.dart'; import 'package:rive/src/rive_core/shapes/shape_paint_container.dart';
import 'package:rive/src/utilities/dependency_sorter.dart'; import 'package:rive/src/utilities/dependency_sorter.dart';
import 'package:rive/src/generated/artboard_base.dart'; import 'package:rive/src/generated/artboard_base.dart';
export 'package:rive/src/generated/artboard_base.dart'; export 'package:rive/src/generated/artboard_base.dart';
class Artboard extends ArtboardBase with ShapePaintContainer { class Artboard extends ArtboardBase with ShapePaintContainer {
/// Artboard are one of the few (only?) components that can be orphaned.
@override @override
bool get canBeOrphaned => true; bool get canBeOrphaned => true;
final Path path = Path(); final Path path = Path();
List<Component> _dependencyOrder = []; List<Component> _dependencyOrder = [];
final List<Drawable> _drawables = []; final List<Drawable> _drawables = [];
final List<DrawRules> _rules = []; final List<DrawRules> _rules = [];
List<DrawTarget> _sortedDrawRules = []; List<DrawTarget> _sortedDrawRules = [];
final Set<Component> _components = {}; final Set<Component> _components = {};
List<Drawable> get drawables => _drawables; List<Drawable> get drawables => _drawables;
final AnimationList _animations = AnimationList(); final AnimationList _animations = AnimationList();
/// List of animations in this artboard.
AnimationList get animations => _animations; AnimationList get animations => _animations;
/// Does this artboard have animations?
bool get hasAnimations => _animations.isNotEmpty; bool get hasAnimations => _animations.isNotEmpty;
int _dirtDepth = 0; int _dirtDepth = 0;
int _dirt = 255; int _dirt = 255;
void forEachComponent(void Function(Component) callback) => void forEachComponent(void Function(Component) callback) =>
_components.forEach(callback); _components.forEach(callback);
@override @override
Artboard get artboard => this; Artboard get artboard => this;
Vec2D get originWorld { Vec2D get originWorld {
return Vec2D.fromValues(x + width * originX, y + height * originY); return Vec2D.fromValues(x + width * originX, y + height * originY);
} }
/// Walk the dependency tree and update components in order. Returns true if
/// any component updated.
bool updateComponents() { bool updateComponents() {
bool didUpdate = false; bool didUpdate = false;
if ((_dirt & ComponentDirt.drawOrder) != 0) { if ((_dirt & ComponentDirt.drawOrder) != 0) {
sortDrawOrder(); sortDrawOrder();
_dirt &= ~ComponentDirt.drawOrder; _dirt &= ~ComponentDirt.drawOrder;
@ -51,6 +70,8 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
int count = _dependencyOrder.length; int count = _dependencyOrder.length;
while ((_dirt & ComponentDirt.components) != 0 && step < maxSteps) { while ((_dirt & ComponentDirt.components) != 0 && step < maxSteps) {
_dirt &= ~ComponentDirt.components; _dirt &= ~ComponentDirt.components;
// Track dirt depth here so that if something else marks
// dirty, we restart.
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
Component component = _dependencyOrder[i]; Component component = _dependencyOrder[i];
_dirtDepth = i; _dirtDepth = i;
@ -71,6 +92,7 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
return didUpdate; return didUpdate;
} }
/// Update any dirty components in this artboard.
bool advance(double elapsedSeconds) { bool advance(double elapsedSeconds) {
bool didUpdate = false; bool didUpdate = false;
for (final controller in _animationControllers) { for (final controller in _animationControllers) {
@ -93,6 +115,10 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
context.markNeedsAdvance(); context.markNeedsAdvance();
_dirt |= ComponentDirt.components; _dirt |= ComponentDirt.components;
} }
/// If the order of the component is less than the current dirt depth,
/// update the dirt depth so that the update loop can break out early and
/// re-run (something up the tree is dirty).
if (component.graphOrder < _dirtDepth) { if (component.graphOrder < _dirtDepth) {
_dirtDepth = component.graphOrder; _dirtDepth = component.graphOrder;
} }
@ -100,17 +126,24 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
@override @override
bool resolveArtboard() => true; bool resolveArtboard() => true;
/// Sort the DAG for resolution in order of dependencies such that dependent
/// compnents process after their dependencies.
void sortDependencies() { void sortDependencies() {
var optimistic = DependencySorter<Component>(); var optimistic = DependencySorter<Component>();
var order = optimistic.sort(this); var order = optimistic.sort(this);
if (order.isEmpty) { if (order.isEmpty) {
// cycle detected, use a more robust solver
var robust = TarjansDependencySorter<Component>(); var robust = TarjansDependencySorter<Component>();
order = robust.sort(this); order = robust.sort(this);
} }
_dependencyOrder = order; _dependencyOrder = order;
for (final component in _dependencyOrder) { for (final component in _dependencyOrder) {
component.graphOrder = graphOrder++; component.graphOrder = graphOrder++;
// component.dirt = 255;
} }
_dirt |= ComponentDirt.components; _dirt |= ComponentDirt.components;
} }
@ -145,16 +178,23 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
return Vec2D.add(Vec2D(), worldTranslation, wt); return Vec2D.add(Vec2D(), worldTranslation, wt);
} }
/// Adds a component to the artboard. Good place for the artboard to check for
/// components it'll later need to do stuff with (like draw them or sort them
/// when the draw order changes).
void addComponent(Component component) { void addComponent(Component component) {
if (!_components.add(component)) { if (!_components.add(component)) {
return; return;
} }
} }
/// Remove a component from the artboard and its various tracked lists of
/// components.
void removeComponent(Component component) { void removeComponent(Component component) {
_components.remove(component); _components.remove(component);
} }
/// Let the artboard know that the drawables need to be resorted before
/// drawing next.
void markDrawOrderDirty() { void markDrawOrderDirty() {
if ((dirt & ComponentDirt.drawOrder) == 0) { if ((dirt & ComponentDirt.drawOrder) == 0) {
context.markNeedsAdvance(); context.markNeedsAdvance();
@ -162,13 +202,25 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
} }
} }
/// Draw the drawable components in this artboard.
void draw(Canvas canvas) { void draw(Canvas canvas) {
canvas.save(); canvas.save();
canvas.clipRect(Rect.fromLTWH(0, 0, width, height)); canvas.clipRect(Rect.fromLTWH(0, 0, width, height));
// Get into artboard's world space. This is because the artboard draws
// components in the artboard's space (in component lingo we call this world
// space). The artboards themselves are drawn in the editor's world space,
// which is the world space that is used by stageItems. This is a little
// confusing and perhaps we should find a better wording for the transform
// spaces. We used "world space" in components as that's the game engine
// ratified way of naming the top-most transformation. Perhaps we should
// rename those to artboardTransform and worldTransform is only reserved for
// stageItems? The other option is to stick with 'worldTransform' in
// components and use 'editor or stageTransform' for stageItems.
canvas.translate(width * originX, height * originY); canvas.translate(width * originX, height * originY);
for (final fill in fills) { for (final fill in fills) {
fill.draw(canvas, path); fill.draw(canvas, path);
} }
for (var drawable = _firstDrawable; for (var drawable = _firstDrawable;
drawable != null; drawable != null;
drawable = drawable.prev) { drawable = drawable.prev) {
@ -180,8 +232,10 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
canvas.restore(); canvas.restore();
} }
/// Our world transform is always the identity. Artboard defines world space.
@override @override
Mat2D get worldTransform => Mat2D(); Mat2D get worldTransform => Mat2D();
@override @override
void originXChanged(double from, double to) { void originXChanged(double from, double to) {
addDirt(ComponentDirt.worldTransform); addDirt(ComponentDirt.worldTransform);
@ -192,20 +246,31 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
addDirt(ComponentDirt.worldTransform); addDirt(ComponentDirt.worldTransform);
} }
/// Called by rive_core to add an Animation to an Artboard. This should be
/// @internal when it's supported.
bool internalAddAnimation(Animation animation) { bool internalAddAnimation(Animation animation) {
if (_animations.contains(animation)) { if (_animations.contains(animation)) {
return false; return false;
} }
_animations.add(animation); _animations.add(animation);
return true; return true;
} }
/// Called by rive_core to remove an Animation from an Artboard. This should
/// be @internal when it's supported.
bool internalRemoveAnimation(Animation animation) { bool internalRemoveAnimation(Animation animation) {
bool removed = _animations.remove(animation); bool removed = _animations.remove(animation);
return removed; return removed;
} }
/// The animation controllers that are called back whenever the artboard
/// advances.
final Set<RiveAnimationController> _animationControllers = {}; final Set<RiveAnimationController> _animationControllers = {};
/// Add an animation controller to this artboard. Playing will be scheduled if
/// it's already playing.
bool addController(RiveAnimationController controller) { bool addController(RiveAnimationController controller) {
if (_animationControllers.contains(controller) || if (_animationControllers.contains(controller) ||
!controller.init(context)) { !controller.init(context)) {
@ -219,6 +284,7 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
return true; return true;
} }
/// Remove an animation controller form this artboard.
bool removeController(RiveAnimationController controller) { bool removeController(RiveAnimationController controller) {
if (_animationControllers.remove(controller)) { if (_animationControllers.remove(controller)) {
controller.isActiveChanged.removeListener(_onControllerPlayingChanged); controller.isActiveChanged.removeListener(_onControllerPlayingChanged);
@ -229,25 +295,37 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
} }
void _onControllerPlayingChanged() => context.markNeedsAdvance(); void _onControllerPlayingChanged() => context.markNeedsAdvance();
@override @override
void onFillsChanged() {} void onFillsChanged() {}
@override @override
void onPaintMutatorChanged(ShapePaintMutator mutator) {} void onPaintMutatorChanged(ShapePaintMutator mutator) {}
@override @override
void onStrokesChanged() {} void onStrokesChanged() {}
@override @override
Vec2D get worldTranslation => Vec2D(); Vec2D get worldTranslation => Vec2D();
Drawable? _firstDrawable; Drawable? _firstDrawable;
void computeDrawOrder() { void computeDrawOrder() {
_drawables.clear(); _drawables.clear();
_rules.clear(); _rules.clear();
buildDrawOrder(_drawables, null, _rules); buildDrawOrder(_drawables, null, _rules);
// Build rule dependencies. In practice this'll need to happen anytime a
// target drawable is changed or rule is added/removed.
var root = DrawTarget(); var root = DrawTarget();
// Make sure all dependents are empty.
for (final nodeRules in _rules) { for (final nodeRules in _rules) {
for (final target in nodeRules.targets) { for (final target in nodeRules.targets) {
target.dependents.clear(); target.dependents.clear();
} }
} }
// Now build up the dependencies.
for (final nodeRules in _rules) { for (final nodeRules in _rules) {
for (final target in nodeRules.targets) { for (final target in nodeRules.targets) {
root.dependents.add(target); root.dependents.add(target);
@ -259,19 +337,25 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
} }
} }
} }
var sorter = DependencySorter<Component>(); var sorter = DependencySorter<Component>();
_sortedDrawRules = sorter.sort(root).cast<DrawTarget>().skip(1).toList(); _sortedDrawRules = sorter.sort(root).cast<DrawTarget>().skip(1).toList();
sortDrawOrder(); sortDrawOrder();
} }
void sortDrawOrder() { void sortDrawOrder() {
// Clear out rule first/last items.
for (final rule in _sortedDrawRules) { for (final rule in _sortedDrawRules) {
rule.first = rule.last = null; rule.first = rule.last = null;
} }
_firstDrawable = null; _firstDrawable = null;
Drawable? lastDrawable; Drawable? lastDrawable;
for (final drawable in _drawables) { for (final drawable in _drawables) {
var rules = drawable.flattenedDrawRules; var rules = drawable.flattenedDrawRules;
var target = rules?.activeTarget; var target = rules?.activeTarget;
if (target != null) { if (target != null) {
if (target.first == null) { if (target.first == null) {
@ -294,6 +378,7 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
} }
} }
} }
for (final rule in _sortedDrawRules) { for (final rule in _sortedDrawRules) {
if (rule.first == null) { if (rule.first == null) {
continue; continue;
@ -323,6 +408,7 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
break; break;
} }
} }
_firstDrawable = lastDrawable; _firstDrawable = lastDrawable;
} }
} }

View File

@ -3,8 +3,10 @@ export 'package:rive/src/generated/backboard_base.dart';
class Backboard extends BackboardBase { class Backboard extends BackboardBase {
static final Backboard unknown = Backboard(); static final Backboard unknown = Backboard();
@override @override
void onAdded() {} void onAdded() {}
@override @override
void onAddedDirty() {} void onAddedDirty() {}
} }

View File

@ -22,6 +22,9 @@ class Bone extends BoneBase {
return null; return null;
} }
/// Iterate through the child bones. [BoneCallback] returns false if iteration
/// can stop. Returns false if iteration stopped, true if it made it through
/// the whole list.
bool forEachBone(BoneCallback callback) { bool forEachBone(BoneCallback callback) {
for (final child in children) { for (final child in children) {
if (child.coreType == BoneBase.typeKey) { if (child.coreType == BoneBase.typeKey) {
@ -35,6 +38,7 @@ class Bone extends BoneBase {
@override @override
double get x => (parent as Bone).length; double get x => (parent as Bone).length;
@override @override
set x(double value) { set x(double value) {
throw UnsupportedError('not expected to set x on a bone.'); throw UnsupportedError('not expected to set x on a bone.');
@ -42,6 +46,7 @@ class Bone extends BoneBase {
@override @override
double get y => 0; double get y => 0;
@override @override
set y(double value) { set y(double value) {
throw UnsupportedError('not expected to set y on a bone.'); throw UnsupportedError('not expected to set y on a bone.');
@ -49,6 +54,9 @@ class Bone extends BoneBase {
@override @override
bool validate() { bool validate() {
// Bones are only valid if they're parented to other bones. RootBones are a
// special case, but they inherit from bone so we check the concrete type
// here to make sure we evalute this check only for non-root bones.
return super.validate() && (coreType != BoneBase.typeKey || parent is Bone); return super.validate() && (coreType != BoneBase.typeKey || parent is Bone);
} }
} }

View File

@ -5,12 +5,16 @@ export 'package:rive/src/generated/bones/cubic_weight_base.dart';
class CubicWeight extends CubicWeightBase { class CubicWeight extends CubicWeightBase {
final Vec2D inTranslation = Vec2D(); final Vec2D inTranslation = Vec2D();
final Vec2D outTranslation = Vec2D(); final Vec2D outTranslation = Vec2D();
@override @override
void inIndicesChanged(int from, int to) {} void inIndicesChanged(int from, int to) {}
@override @override
void inValuesChanged(int from, int to) {} void inValuesChanged(int from, int to) {}
@override @override
void outIndicesChanged(int from, int to) {} void outIndicesChanged(int from, int to) {}
@override @override
void outValuesChanged(int from, int to) {} void outValuesChanged(int from, int to) {}
} }

View File

@ -1,24 +1,37 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:rive/src/rive_core/bones/skinnable.dart'; import 'package:rive/src/rive_core/bones/skinnable.dart';
import 'package:rive/src/rive_core/bones/tendon.dart'; import 'package:rive/src/rive_core/bones/tendon.dart';
import 'package:rive/src/rive_core/component.dart'; import 'package:rive/src/rive_core/component.dart';
import 'package:rive/src/rive_core/math/mat2d.dart'; import 'package:rive/src/rive_core/math/mat2d.dart';
import 'package:rive/src/rive_core/shapes/path_vertex.dart'; import 'package:rive/src/rive_core/shapes/path_vertex.dart';
import 'package:rive/src/generated/bones/skin_base.dart'; import 'package:rive/src/generated/bones/skin_base.dart';
export 'package:rive/src/generated/bones/skin_base.dart'; export 'package:rive/src/generated/bones/skin_base.dart';
/// Represents a skin deformation of either a Path or an Image Mesh connected to
/// a set of bones.
class Skin extends SkinBase { class Skin extends SkinBase {
final List<Tendon> _tendons = []; final List<Tendon> _tendons = [];
List<Tendon> get tendons => _tendons; List<Tendon> get tendons => _tendons;
Float32List _boneTransforms = Float32List(0); Float32List _boneTransforms = Float32List(0);
final Mat2D _worldTransform = Mat2D(); final Mat2D _worldTransform = Mat2D();
@override @override
void onDirty(int mask) { void onDirty(int mask) {
// When the skin is dirty the deformed skinnable will need to regenerate its
// drawing commands.
// TODO: rename path to topology/surface something common between path &
// mesh.
(parent as Skinnable).markSkinDirty(); (parent as Skinnable).markSkinDirty();
} }
@override @override
void update(int dirt) { void update(int dirt) {
// Any dirt here indicates that the transforms needs to be rebuilt. This
// should only be worldTransform from the bones (recursively passed down) or
// ComponentDirt.path from the PointsPath (set explicitly).
var size = (_tendons.length + 1) * 6; var size = (_tendons.length + 1) * 6;
if (_boneTransforms.length != size) { if (_boneTransforms.length != size) {
_boneTransforms = Float32List(size); _boneTransforms = Float32List(size);
@ -29,6 +42,7 @@ class Skin extends SkinBase {
_boneTransforms[4] = 0; _boneTransforms[4] = 0;
_boneTransforms[5] = 0; _boneTransforms[5] = 0;
} }
var temp = Mat2D(); var temp = Mat2D();
var bidx = 6; var bidx = 6;
for (final tendon in _tendons) { for (final tendon in _tendons) {
@ -79,6 +93,8 @@ class Skin extends SkinBase {
@override @override
void buildDependencies() { void buildDependencies() {
super.buildDependencies(); super.buildDependencies();
// A skin depends on all its bones. N.B. that we don't depend on the parent
// skinnable. The skinnable depends on us.
for (final tendon in _tendons) { for (final tendon in _tendons) {
tendon.bone?.addDependent(this); tendon.bone?.addDependent(this);
} }
@ -110,6 +126,7 @@ class Skin extends SkinBase {
markRebuildDependencies(); markRebuildDependencies();
} }
parent?.markRebuildDependencies(); parent?.markRebuildDependencies();
break; break;
} }
} }

View File

@ -1,18 +1,27 @@
import 'package:rive/src/rive_core/bones/skin.dart'; import 'package:rive/src/rive_core/bones/skin.dart';
import 'package:rive/src/rive_core/component.dart'; import 'package:rive/src/rive_core/component.dart';
/// An abstraction to give a common interface to any container component that
/// can contain a skin to bind bones to.
abstract class Skinnable { abstract class Skinnable {
// _skin is null when this object isn't connected to bones.
Skin? _skin; Skin? _skin;
Skin? get skin => _skin; Skin? get skin => _skin;
void appendChild(Component child); void appendChild(Component child);
// ignore: use_setters_to_change_properties
void addSkin(Skin skin) { void addSkin(Skin skin) {
// Notify old skin/maybe support multiple skins in the future?
_skin = skin; _skin = skin;
markSkinDirty(); markSkinDirty();
} }
void removeSkin(Skin skin) { void removeSkin(Skin skin) {
if (_skin == skin) { if (_skin == skin) {
_skin = null; _skin = null;
markSkinDirty(); markSkinDirty();
} }
} }

View File

@ -8,6 +8,7 @@ class Tendon extends TendonBase {
Mat2D? _inverseBind; Mat2D? _inverseBind;
SkeletalComponent? _bone; SkeletalComponent? _bone;
SkeletalComponent? get bone => _bone; SkeletalComponent? get bone => _bone;
Mat2D get inverseBind { Mat2D get inverseBind {
if (_inverseBind == null) { if (_inverseBind == null) {
_inverseBind = Mat2D(); _inverseBind = Mat2D();
@ -17,11 +18,16 @@ class Tendon extends TendonBase {
} }
@override @override
void boneIdChanged(int from, int to) {} void boneIdChanged(int from, int to) {
// This never happens, or at least it should only happen prior to an
// onAddedDirty call.
}
@override @override
void onAddedDirty() { void onAddedDirty() {
super.onAddedDirty(); super.onAddedDirty();
_bone = context.resolve(boneId); _bone = context.resolve(boneId);
_bind[0] = xx; _bind[0] = xx;
_bind[1] = xy; _bind[1] = xy;
_bind[2] = yx; _bind[2] = yx;
@ -32,6 +38,7 @@ class Tendon extends TendonBase {
@override @override
void update(int dirt) {} void update(int dirt) {}
@override @override
void txChanged(double from, double to) { void txChanged(double from, double to) {
_bind[4] = to; _bind[4] = to;

View File

@ -1,4 +1,5 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:rive/src/rive_core/math/mat2d.dart'; import 'package:rive/src/rive_core/math/mat2d.dart';
import 'package:rive/src/rive_core/math/vec2d.dart'; import 'package:rive/src/rive_core/math/vec2d.dart';
import 'package:rive/src/generated/bones/weight_base.dart'; import 'package:rive/src/generated/bones/weight_base.dart';
@ -6,12 +7,18 @@ export 'package:rive/src/generated/bones/weight_base.dart';
class Weight extends WeightBase { class Weight extends WeightBase {
final Vec2D translation = Vec2D(); final Vec2D translation = Vec2D();
@override @override
void indicesChanged(int from, int to) {} void indicesChanged(int from, int to) {}
@override @override
void update(int dirt) {} void update(int dirt) {
// Intentionally empty. Weights don't update.
}
@override @override
void valuesChanged(int from, int to) {} void valuesChanged(int from, int to) {}
static void deform(double x, double y, int indices, int weights, Mat2D world, static void deform(double x, double y, int indices, int weights, Mat2D world,
Float32List boneTransforms, Vec2D result) { Float32List boneTransforms, Vec2D result) {
double xx = 0, xy = 0, yx = 0, yy = 0, tx = 0, ty = 0; double xx = 0, xy = 0, yx = 0, yy = 0, tx = 0, ty = 0;
@ -22,6 +29,7 @@ class Weight extends WeightBase {
if (weight == 0) { if (weight == 0) {
continue; continue;
} }
double normalizedWeight = weight / 255; double normalizedWeight = weight / 255;
var index = encodedWeightValue(i, indices); var index = encodedWeightValue(i, indices);
var startBoneTransformIndex = index * 6; var startBoneTransformIndex = index * 6;

View File

@ -1,19 +1,31 @@
/// Helper to abstract changing weighted values on a vertex.
abstract class WeightedVertex { abstract class WeightedVertex {
int get weights; int get weights;
int get weightIndices; int get weightIndices;
set weights(int value); set weights(int value);
set weightIndices(int value); set weightIndices(int value);
/// Set the weight of this vertex for a specific tendon.
void setWeight(int tendonIndex, int tendonCount, double weight) { void setWeight(int tendonIndex, int tendonCount, double weight) {
int tendonWeightIndex = int tendonWeightIndex =
_setTendonWeight(tendonIndex, (weight.clamp(0, 1) * 255).round()); _setTendonWeight(tendonIndex, (weight.clamp(0, 1) * 255).round());
// re-normalize the list such that only bones with value are at the
// start and they sum to 100%, if any need to change make sure to give
// priority (not change) tendonIndex which we just tried to set.
var tendonWeights = _tendonWeights; var tendonWeights = _tendonWeights;
int totalWeight = tendonWeights.fold( int totalWeight = tendonWeights.fold(
0, (value, tendonWeight) => value + tendonWeight.weight); 0, (value, tendonWeight) => value + tendonWeight.weight);
var vertexTendons = var vertexTendons =
tendonWeights.where((tendonWeight) => tendonWeight.tendon != 0); tendonWeights.where((tendonWeight) => tendonWeight.tendon != 0);
const maxWeight = 255; const maxWeight = 255;
var remainder = maxWeight - totalWeight; var remainder = maxWeight - totalWeight;
if (vertexTendons.length == 1) { if (vertexTendons.length == 1) {
// User is specifically setting a single tendon to a value, just pick
// the next one up (modulate by the total number of tendons).
var patchTendonIndex = (tendonIndex + 1) % tendonCount; var patchTendonIndex = (tendonIndex + 1) % tendonCount;
_setTendonWeight( _setTendonWeight(
patchTendonIndex, tendonCount == 1 ? maxWeight : remainder); patchTendonIndex, tendonCount == 1 ? maxWeight : remainder);
@ -40,6 +52,8 @@ abstract class WeightedVertex {
void _sortWeights() { void _sortWeights() {
var tendonWeights = _tendonWeights; var tendonWeights = _tendonWeights;
// Sort weights such that tendons with value show up first and any with no
// value (0 weight) are cleared to the 0 (no) tendon.
tendonWeights.sort((a, b) => b.weight.compareTo(a.weight)); tendonWeights.sort((a, b) => b.weight.compareTo(a.weight));
for (int i = 0; i < tendonWeights.length; i++) { for (int i = 0; i < tendonWeights.length; i++) {
final tw = tendonWeights[i]; final tw = tendonWeights[i];
@ -54,14 +68,16 @@ abstract class WeightedVertex {
_WeightHelper(2, (weightIndices >> 16) & 0xFF, _getRawWeight(2)), _WeightHelper(2, (weightIndices >> 16) & 0xFF, _getRawWeight(2)),
_WeightHelper(3, (weightIndices >> 24) & 0xFF, _getRawWeight(3)) _WeightHelper(3, (weightIndices >> 24) & 0xFF, _getRawWeight(3))
]; ];
int _setTendonWeight(int tendonIndex, int weight) { int _setTendonWeight(int tendonIndex, int weight) {
var indices = weightIndices; var indices = weightIndices;
var bonesIndices = [ var bonesIndices = [
indices & 0xFF, indices & 0xFF,
(indices >> 8) & 0xFF, (indices >> 8) & 0xFF,
(indices >> 16) & 0xFF, (indices >> 16) & 0xFF,
(indices >> 24) & 0xFF (indices >> 24) & 0xFF,
]; ];
int setWeightIndex = -1; int setWeightIndex = -1;
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (bonesIndices[i] == tendonIndex + 1) { if (bonesIndices[i] == tendonIndex + 1) {
@ -70,10 +86,14 @@ abstract class WeightedVertex {
break; break;
} }
} }
// This bone wasn't weighted for this vertex, go find the bone with the
// least weight (or a 0 bone) and use it.
if (setWeightIndex == -1) { if (setWeightIndex == -1) {
int lowestWeight = double.maxFinite.toInt(); int lowestWeight = double.maxFinite.toInt();
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (bonesIndices[i] == 0) { if (bonesIndices[i] == 0) {
// this isn't set to a bone yet, use it!
setWeightIndex = i; setWeightIndex = i;
break; break;
} }
@ -83,16 +103,21 @@ abstract class WeightedVertex {
lowestWeight = weight; lowestWeight = weight;
} }
} }
_setTendonIndex(setWeightIndex, tendonIndex + 1); _setTendonIndex(setWeightIndex, tendonIndex + 1);
_rawSetWeight(setWeightIndex, weight); _rawSetWeight(setWeightIndex, weight);
} }
return setWeightIndex; return setWeightIndex;
} }
/// [tendonIndex] of 0 means no bound tendon, when bound to an actual tendon,
/// it should be set to the skin's tendon's index + 1.
void _setTendonIndex(int weightIndex, int tendonIndex) { void _setTendonIndex(int weightIndex, int tendonIndex) {
assert(weightIndex < 4 && weightIndex >= 0); assert(weightIndex < 4 && weightIndex >= 0);
var indexValues = weightIndices; var indexValues = weightIndices;
// Clear the bits for this weight value.
indexValues &= ~(0xFF << (weightIndex * 8)); indexValues &= ~(0xFF << (weightIndex * 8));
// Set the bits for this weight value.
weightIndices = indexValues | (tendonIndex << (weightIndex * 8)); weightIndices = indexValues | (tendonIndex << (weightIndex * 8));
} }
@ -104,11 +129,14 @@ abstract class WeightedVertex {
void _rawSetWeight(int weightIndex, int weightValue) { void _rawSetWeight(int weightIndex, int weightValue) {
assert(weightIndex < 4 && weightIndex >= 0); assert(weightIndex < 4 && weightIndex >= 0);
var weightValues = weights; var weightValues = weights;
// Clear the bits for this weight value.
weightValues &= ~(0xFF << (weightIndex * 8)); weightValues &= ~(0xFF << (weightIndex * 8));
// Set the bits for this weight value.
weights = weightValues | (weightValue << (weightIndex * 8)); weights = weightValues | (weightValue << (weightIndex * 8));
} }
int _getRawWeight(int weightIndex) => (weights >> (weightIndex * 8)) & 0xFF; int _getRawWeight(int weightIndex) => (weights >> (weightIndex * 8)) & 0xFF;
double getWeight(int tendonIndex) { double getWeight(int tendonIndex) {
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (getTendon(i) == tendonIndex + 1) { if (getTendon(i) == tendonIndex + 1) {
@ -123,5 +151,6 @@ class _WeightHelper {
final int index; final int index;
final int tendon; final int tendon;
int weight; int weight;
_WeightHelper(this.index, this.tendon, this.weight); _WeightHelper(this.index, this.tendon, this.weight);
} }

View File

@ -2,6 +2,7 @@ import 'package:rive/src/core/core.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:rive/src/rive_core/artboard.dart'; import 'package:rive/src/rive_core/artboard.dart';
import 'package:rive/src/rive_core/container_component.dart'; import 'package:rive/src/rive_core/container_component.dart';
import 'package:rive/src/generated/component_base.dart'; import 'package:rive/src/generated/component_base.dart';
import 'package:rive/src/utilities/dependency_sorter.dart'; import 'package:rive/src/utilities/dependency_sorter.dart';
import 'package:rive/src/utilities/tops.dart'; import 'package:rive/src/utilities/tops.dart';
@ -11,20 +12,36 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
implements DependencyGraphNode<Component>, Parentable<Component> { implements DependencyGraphNode<Component>, Parentable<Component> {
Artboard? _artboard; Artboard? _artboard;
dynamic _userData; dynamic _userData;
/// Override to true if you want some object inheriting from Component to not
/// have a parent. Most objects will validate that they have a parent during
/// the onAdded callback otherwise they are considered invalid and are culled
/// from core.
bool get canBeOrphaned => false; bool get canBeOrphaned => false;
// Used during update process.
int graphOrder = 0; int graphOrder = 0;
int dirt = 0xFFFF; int dirt = 0xFFFF;
// This is really only for sanity and earlying out of recursive loops.
static const int maxTreeDepth = 5000; static const int maxTreeDepth = 5000;
bool addDirt(int value, {bool recurse = false}) { bool addDirt(int value, {bool recurse = false}) {
if ((dirt & value) == value) { if ((dirt & value) == value) {
// Already marked.
return false; return false;
} }
// Make sure dirt is set before calling anything that can set more dirt.
dirt |= value; dirt |= value;
onDirty(dirt); onDirty(dirt);
artboard?.onComponentDirty(this); artboard?.onComponentDirty(this);
if (!recurse) { if (!recurse) {
return true; return true;
} }
for (final d in dependents) { for (final d in dependents) {
d.addDirt(value, recurse: recurse); d.addDirt(value, recurse: recurse);
} }
@ -33,7 +50,12 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
void onDirty(int mask) {} void onDirty(int mask) {}
void update(int dirt); void update(int dirt);
/// The artboard this component belongs to.
Artboard? get artboard => _artboard; Artboard? get artboard => _artboard;
// Note that this isn't a setter as we don't want anything externally changing
// the artboard.
void _changeArtboard(Artboard? value) { void _changeArtboard(Artboard? value) {
if (_artboard == value) { if (_artboard == value) {
return; return;
@ -43,8 +65,14 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
_artboard?.addComponent(this); _artboard?.addComponent(this);
} }
/// Called whenever we're resolving the artboard, we piggy back on that
/// process to visit ancestors in the tree. This is a good opportunity to
/// check if we have an ancestor of a specific type. For example, a Path needs
/// to know which Shape it's within.
@mustCallSuper @mustCallSuper
void visitAncestor(Component ancestor) {} void visitAncestor(Component ancestor) {}
/// Find the artboard in the hierarchy.
bool resolveArtboard() { bool resolveArtboard() {
int sanity = maxTreeDepth; int sanity = maxTreeDepth;
for (Component? curr = this; for (Component? curr = this;
@ -71,6 +99,7 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
} }
void userDataChanged(dynamic from, dynamic to) {} void userDataChanged(dynamic from, dynamic to) {}
@override @override
void parentIdChanged(int from, int to) { void parentIdChanged(int from, int to) {
parent = context.resolve(to); parent = context.resolve(to);
@ -79,6 +108,7 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
ContainerComponent? _parent; ContainerComponent? _parent;
@override @override
ContainerComponent? get parent => _parent; ContainerComponent? get parent => _parent;
set parent(ContainerComponent? value) { set parent(ContainerComponent? value) {
if (_parent == value) { if (_parent == value) {
return; return;
@ -93,28 +123,40 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
void parentChanged(ContainerComponent? from, ContainerComponent? to) { void parentChanged(ContainerComponent? from, ContainerComponent? to) {
from?.children.remove(this); from?.children.remove(this);
from?.childRemoved(this); from?.childRemoved(this);
to?.children.add(this); to?.children.add(this);
to?.childAdded(this); to?.childAdded(this);
// We need to resolve our artboard.
markRebuildDependencies(); markRebuildDependencies();
} }
/// Components that depend on this component.
final Set<Component> _dependents = {}; final Set<Component> _dependents = {};
/// Components that this component depends on.
final Set<Component> _dependsOn = {}; final Set<Component> _dependsOn = {};
@override @override
Set<Component> get dependents => _dependents; Set<Component> get dependents => _dependents;
bool addDependent(Component dependent) { bool addDependent(Component dependent) {
assert(artboard == dependent.artboard, assert(artboard == dependent.artboard,
'Components must be in the same artboard.'); 'Components must be in the same artboard.');
if (!_dependents.add(dependent)) { if (!_dependents.add(dependent)) {
return false; return false;
} }
dependent._dependsOn.add(this); dependent._dependsOn.add(this);
return true; return true;
} }
bool isValidParent(Component parent) => parent is ContainerComponent; bool isValidParent(Component parent) => parent is ContainerComponent;
void markRebuildDependencies() { void markRebuildDependencies() {
if (!context.markDependenciesDirty(this)) { if (!context.markDependenciesDirty(this)) {
// no context, or already dirty.
return; return;
} }
for (final dependent in _dependents) { for (final dependent in _dependents) {
@ -128,11 +170,18 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
parentDep._dependents.remove(this); parentDep._dependents.remove(this);
} }
_dependsOn.clear(); _dependsOn.clear();
// by default a component depends on nothing (likely it will depend on the
// parent but we leave that for specific implementations to supply).
} }
/// Something we depend on has been removed. It's important to clear out any
/// stored references to that dependency so it can be garbage collected (if
/// necessary).
void onDependencyRemoved(Component dependent) {} void onDependencyRemoved(Component dependent) {}
@override @override
void onAdded() {} void onAdded() {}
@override @override
void onAddedDirty() { void onAddedDirty() {
if (parentId != Core.missingId) { if (parentId != Core.missingId) {
@ -140,6 +189,11 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
} }
} }
/// When a component has been removed from the Core Context, we clean up any
/// dangling references left on the parent and on any other dependent
/// component. It's important for specialization of Component to respond to
/// override [onDependencyRemoved] and clean up any further stored references
/// to that component (for example the target of a Constraint).
@override @override
@mustCallSuper @mustCallSuper
void onRemoved() { void onRemoved() {
@ -148,14 +202,22 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
parentDep._dependents.remove(this); parentDep._dependents.remove(this);
} }
_dependsOn.clear(); _dependsOn.clear();
for (final dependent in _dependents) { for (final dependent in _dependents) {
dependent.onDependencyRemoved(this); dependent.onDependencyRemoved(this);
} }
_dependents.clear(); _dependents.clear();
// silently clear from the parent in order to not cause any further undo
// stack changes
if (parent != null) { if (parent != null) {
parent!.children.remove(this); parent!.children.remove(this);
parent!.childRemoved(this); parent!.childRemoved(this);
} }
// The artboard containing this component will need its dependencies
// re-sorted.
if (artboard != null) { if (artboard != null) {
context.markDependencyOrderDirty(); context.markDependencyOrderDirty();
_changeArtboard(null); _changeArtboard(null);
@ -168,7 +230,10 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
} }
@override @override
void nameChanged(String from, String to) {} void nameChanged(String from, String to) {
/// Changing name doesn't really do anything.
}
@override @override
bool import(ImportStack stack) { bool import(ImportStack stack) {
var artboardImporter = stack.latest<ArtboardImporter>(ArtboardBase.typeKey); var artboardImporter = stack.latest<ArtboardImporter>(ArtboardBase.typeKey);
@ -176,6 +241,7 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
return false; return false;
} }
artboardImporter.addComponent(this); artboardImporter.addComponent(this);
return super.import(stack); return super.import(stack);
} }
} }

View File

@ -1,14 +1,41 @@
class ComponentDirt { class ComponentDirt {
static const int dependents = 1 << 0; static const int dependents = 1 << 0;
/// General flag for components are dirty (if this is up, the update cycle
/// runs). It gets automatically applied with any other dirt.
static const int components = 1 << 1; static const int components = 1 << 1;
/// Draw order needs to be re-computed.
static const int drawOrder = 1 << 2; static const int drawOrder = 1 << 2;
/// Draw order needs to be re-computed.
static const int naturalDrawOrder = 1 << 3; static const int naturalDrawOrder = 1 << 3;
/// Path is dirty and needs to be rebuilt.
static const int path = 1 << 4; static const int path = 1 << 4;
/// Vertices have changed, re-order cached lists.
static const int vertices = 1 << 5; static const int vertices = 1 << 5;
/// Used by any component that needs to recompute their local transform.
/// Usually components that have their transform dirty will also have their
/// worldTransform dirty.
static const int transform = 1 << 6; static const int transform = 1 << 6;
/// Used by any component that needs to update its world transform.
static const int worldTransform = 1 << 7; static const int worldTransform = 1 << 7;
/// Dirt used to mark some stored paint needs to be rebuilt or that we just
/// want to trigger an update cycle so painting occurs.
static const int paint = 1 << 8; static const int paint = 1 << 8;
/// Used by the gradients track when the stops need to be re-ordered.
static const int stops = 1 << 9; static const int stops = 1 << 9;
/// Used by ClippingShape to help Shape know when to recalculate its list of
/// clipping sources.
static const int clip = 1 << 10; static const int clip = 1 << 10;
/// Set when blend modes need to be updated.
static const int blendMode = 1 << 11; static const int blendMode = 1 << 11;
} }

View File

@ -1,4 +1,8 @@
class ComponentFlags { class ComponentFlags {
/// Whether the component should be drawn (at runtime this only used by
/// drawables and paths).
static const int hidden = 1 << 0; static const int hidden = 1 << 0;
// Whether the component was locked for editing in the editor.
static const int locked = 1 << 1; static const int locked = 1 << 1;
} }

View File

@ -19,7 +19,11 @@ abstract class ContainerComponent extends ContainerComponentBase {
@mustCallSuper @mustCallSuper
void childAdded(Component child) {} void childAdded(Component child) {}
void childRemoved(Component child) {} void childRemoved(Component child) {}
// Make sure that the current function can be applied to the current
// [Component], before descending onto all the children.
bool forAll(DescentCallback cb) { bool forAll(DescentCallback cb) {
if (cb(this) == false) { if (cb(this) == false) {
return false; return false;
@ -28,17 +32,27 @@ abstract class ContainerComponent extends ContainerComponentBase {
return true; return true;
} }
// Recursively descend onto all the children in the hierarchy tree.
// If the callback returns false, it won't recurse down a particular branch.
void forEachChild(DescentCallback cb) { void forEachChild(DescentCallback cb) {
for (final child in children) { for (final child in children) {
if (cb(child) == false) { if (cb(child) == false) {
continue; continue;
} }
// TODO: replace with a more robust check.
if (child is ContainerComponent) { if (child is ContainerComponent) {
child.forEachChild(cb); child.forEachChild(cb);
} }
} }
} }
/// Recursive version of [Component.remove]. This should only be called when
/// you know this is the only part of the branch you are removing in your
/// operation. If your operation could remove items from the same branch
/// multiple times, you should consider building up a list of the individual
/// items to remove and then remove them individually to avoid calling remove
/// multiple times on children.
void removeRecursive() { void removeRecursive() {
Set<Component> deathRow = {this}; Set<Component> deathRow = {this};
forEachChild((child) => deathRow.add(child)); forEachChild((child) => deathRow.add(child));

View File

@ -7,10 +7,12 @@ export 'package:rive/src/generated/draw_rules_base.dart';
class DrawRules extends DrawRulesBase { class DrawRules extends DrawRulesBase {
final Set<DrawTarget> _targets = {}; final Set<DrawTarget> _targets = {};
Set<DrawTarget> get targets => _targets; Set<DrawTarget> get targets => _targets;
DrawTarget? _activeTarget; DrawTarget? _activeTarget;
DrawTarget? get activeTarget => _activeTarget; DrawTarget? get activeTarget => _activeTarget;
set activeTarget(DrawTarget? value) => set activeTarget(DrawTarget? value) =>
drawTargetId = value?.id ?? Core.missingId; drawTargetId = value?.id ?? Core.missingId;
@override @override
void drawTargetIdChanged(int from, int to) { void drawTargetIdChanged(int from, int to) {
_activeTarget = context.resolve(to); _activeTarget = context.resolve(to);
@ -25,12 +27,14 @@ class DrawRules extends DrawRulesBase {
@override @override
void update(int dirt) {} void update(int dirt) {}
@override @override
void childAdded(Component child) { void childAdded(Component child) {
super.childAdded(child); super.childAdded(child);
switch (child.coreType) { switch (child.coreType) {
case DrawTargetBase.typeKey: case DrawTargetBase.typeKey:
_targets.add(child as DrawTarget); _targets.add(child as DrawTarget);
break; break;
} }
} }
@ -44,6 +48,7 @@ class DrawRules extends DrawRulesBase {
if (_targets.isEmpty) { if (_targets.isEmpty) {
remove(); remove();
} }
break; break;
} }
} }

View File

@ -6,14 +6,17 @@ export 'package:rive/src/generated/draw_target_base.dart';
enum DrawTargetPlacement { before, after } enum DrawTargetPlacement { before, after }
class DrawTarget extends DrawTargetBase { class DrawTarget extends DrawTargetBase {
// Store first and last drawables that are affected by this target.
Drawable? first; Drawable? first;
Drawable? last; Drawable? last;
Drawable? _drawable; Drawable? _drawable;
Drawable? get drawable => _drawable; Drawable? get drawable => _drawable;
set drawable(Drawable? value) { set drawable(Drawable? value) {
if (_drawable == value) { if (_drawable == value) {
return; return;
} }
_drawable = value; _drawable = value;
drawableId = value?.id ?? Core.missingId; drawableId = value?.id ?? Core.missingId;
} }
@ -21,6 +24,7 @@ class DrawTarget extends DrawTargetBase {
DrawTargetPlacement get placement => DrawTargetPlacement get placement =>
DrawTargetPlacement.values[placementValue]; DrawTargetPlacement.values[placementValue];
set placement(DrawTargetPlacement value) => placementValue = value.index; set placement(DrawTargetPlacement value) => placementValue = value.index;
@override @override
void drawableIdChanged(int from, int to) { void drawableIdChanged(int from, int to) {
drawable = context.resolve(to); drawable = context.resolve(to);

View File

@ -1,4 +1,5 @@
import 'dart:ui'; import 'dart:ui';
import 'package:rive/src/rive_core/component_dirt.dart'; import 'package:rive/src/rive_core/component_dirt.dart';
import 'package:rive/src/rive_core/component_flags.dart'; import 'package:rive/src/rive_core/component_flags.dart';
import 'package:rive/src/rive_core/container_component.dart'; import 'package:rive/src/rive_core/container_component.dart';
@ -9,23 +10,37 @@ import 'package:rive/src/rive_core/transform_component.dart';
export 'package:rive/src/generated/drawable_base.dart'; export 'package:rive/src/generated/drawable_base.dart';
abstract class Drawable extends DrawableBase { abstract class Drawable extends DrawableBase {
/// Flattened rules inherited from parents (or self) so we don't have to look
/// up the tree when re-sorting.
DrawRules? flattenedDrawRules; DrawRules? flattenedDrawRules;
/// The previous drawable in the draw order.
Drawable? prev; Drawable? prev;
/// The next drawable in the draw order.
Drawable? next; Drawable? next;
@override @override
void buildDrawOrder( void buildDrawOrder(
List<Drawable> drawables, DrawRules? rules, List<DrawRules> allRules) { List<Drawable> drawables, DrawRules? rules, List<DrawRules> allRules) {
flattenedDrawRules = drawRules ?? rules; flattenedDrawRules = drawRules ?? rules;
drawables.add(this); drawables.add(this);
super.buildDrawOrder(drawables, rules, allRules); super.buildDrawOrder(drawables, rules, allRules);
} }
/// Draw the contents of this drawable component in world transform space.
void draw(Canvas canvas); void draw(Canvas canvas);
BlendMode get blendMode => BlendMode.values[blendModeValue]; BlendMode get blendMode => BlendMode.values[blendModeValue];
set blendMode(BlendMode value) => blendModeValue = value.index; set blendMode(BlendMode value) => blendModeValue = value.index;
@override @override
void blendModeValueChanged(int from, int to) {} void blendModeValueChanged(int from, int to) {}
List<ClippingShape> _clippingShapes = []; List<ClippingShape> _clippingShapes = [];
bool clip(Canvas canvas) { bool clip(Canvas canvas) {
if (_clippingShapes.isEmpty) { if (_clippingShapes.isEmpty) {
return false; return false;
@ -43,6 +58,8 @@ abstract class Drawable extends DrawableBase {
@override @override
void parentChanged(ContainerComponent? from, ContainerComponent? to) { void parentChanged(ContainerComponent? from, ContainerComponent? to) {
super.parentChanged(from, to); super.parentChanged(from, to);
// Make sure we re-compute clipping shapes when we change parents. Issue
// #1586
addDirt(ComponentDirt.clip); addDirt(ComponentDirt.clip);
} }
@ -50,6 +67,7 @@ abstract class Drawable extends DrawableBase {
void update(int dirt) { void update(int dirt) {
super.update(dirt); super.update(dirt);
if (dirt & ComponentDirt.clip != 0) { if (dirt & ComponentDirt.clip != 0) {
// Find clip in parents.
List<ClippingShape> clippingShapes = []; List<ClippingShape> clippingShapes = [];
for (ContainerComponent? p = this; p != null; p = p.parent) { for (ContainerComponent? p = this; p != null; p = p.parent) {
if (p is TransformComponent) { if (p is TransformComponent) {
@ -62,7 +80,9 @@ abstract class Drawable extends DrawableBase {
} }
} }
// When drawable flags change, repaint.
@override @override
void drawableFlagsChanged(int from, int to) => addDirt(ComponentDirt.paint); void drawableFlagsChanged(int from, int to) => addDirt(ComponentDirt.paint);
bool get isHidden => (drawableFlags & ComponentFlags.hidden) != 0; bool get isHidden => (drawableFlags & ComponentFlags.hidden) != 0;
} }

View File

@ -1,6 +1,8 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
// Just a way to get around the protected notifyListeners so we can use trigger
// multiple events from a single object.
class Event extends ChangeNotifier { class Event extends ChangeNotifier {
void notify() => notifyListeners(); void notify() => notifyListeners();
} }

View File

@ -5,16 +5,19 @@ import 'package:rive/src/rive_core/math/vec2d.dart';
class AABB { class AABB {
Float32List _buffer; Float32List _buffer;
Float32List get values { Float32List get values {
return _buffer; return _buffer;
} }
Vec2D get topLeft => minimum; Vec2D get topLeft => minimum;
Vec2D get topRight { Vec2D get topRight {
return Vec2D.fromValues(_buffer[2], _buffer[1]); return Vec2D.fromValues(_buffer[2], _buffer[1]);
} }
Vec2D get bottomRight => maximum; Vec2D get bottomRight => maximum;
Vec2D get bottomLeft { Vec2D get bottomLeft {
return Vec2D.fromValues(_buffer[0], _buffer[3]); return Vec2D.fromValues(_buffer[0], _buffer[3]);
} }
@ -31,10 +34,14 @@ class AABB {
double get maxX => _buffer[2]; double get maxX => _buffer[2];
double get minY => _buffer[1]; double get minY => _buffer[1];
double get maxY => _buffer[3]; double get maxY => _buffer[3];
AABB() : _buffer = Float32List.fromList([0.0, 0.0, 0.0, 0.0]); AABB() : _buffer = Float32List.fromList([0.0, 0.0, 0.0, 0.0]);
AABB.clone(AABB a) : _buffer = Float32List.fromList(a.values); AABB.clone(AABB a) : _buffer = Float32List.fromList(a.values);
AABB.fromValues(double a, double b, double c, double d) AABB.fromValues(double a, double b, double c, double d)
: _buffer = Float32List.fromList([a, b, c, d]); : _buffer = Float32List.fromList([a, b, c, d]);
AABB.empty() AABB.empty()
: _buffer = Float32List.fromList([ : _buffer = Float32List.fromList([
double.maxFinite, double.maxFinite,
@ -42,6 +49,7 @@ class AABB {
-double.maxFinite, -double.maxFinite,
-double.maxFinite -double.maxFinite
]); ]);
factory AABB.expand(AABB from, double amount) { factory AABB.expand(AABB from, double amount) {
var aabb = AABB.clone(from); var aabb = AABB.clone(from);
if (aabb.width < amount) { if (aabb.width < amount) {
@ -54,6 +62,7 @@ class AABB {
} }
return aabb; return aabb;
} }
factory AABB.pad(AABB from, double amount) { factory AABB.pad(AABB from, double amount) {
var aabb = AABB.clone(from); var aabb = AABB.clone(from);
aabb[0] -= amount; aabb[0] -= amount;
@ -62,7 +71,9 @@ class AABB {
aabb[3] += amount; aabb[3] += amount;
return aabb; return aabb;
} }
bool get isEmpty => !AABB.isValid(this); bool get isEmpty => !AABB.isValid(this);
Vec2D includePoint(Vec2D point, Mat2D? transform) { Vec2D includePoint(Vec2D point, Mat2D? transform) {
var transformedPoint = transform == null var transformedPoint = transform == null
? point ? point
@ -90,12 +101,15 @@ class AABB {
AABB.fromMinMax(Vec2D min, Vec2D max) AABB.fromMinMax(Vec2D min, Vec2D max)
: _buffer = Float32List.fromList([min[0], min[1], max[0], max[1]]); : _buffer = Float32List.fromList([min[0], min[1], max[0], max[1]]);
static bool areEqual(AABB a, AABB b) { static bool areEqual(AABB a, AABB b) {
return a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3]; return a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3];
} }
double get width => _buffer[2] - _buffer[0]; double get width => _buffer[2] - _buffer[0];
double get height => _buffer[3] - _buffer[1]; double get height => _buffer[3] - _buffer[1];
double operator [](int idx) { double operator [](int idx) {
return _buffer[idx]; return _buffer[idx];
} }
@ -162,14 +176,18 @@ class AABB {
static bool testOverlap(AABB a, AABB b) { static bool testOverlap(AABB a, AABB b) {
double d1x = b[0] - a[2]; double d1x = b[0] - a[2];
double d1y = b[1] - a[3]; double d1y = b[1] - a[3];
double d2x = a[0] - b[2]; double d2x = a[0] - b[2];
double d2y = a[1] - b[3]; double d2y = a[1] - b[3];
if (d1x > 0.0 || d1y > 0.0) { if (d1x > 0.0 || d1y > 0.0) {
return false; return false;
} }
if (d2x > 0.0 || d2y > 0.0) { if (d2x > 0.0 || d2y > 0.0) {
return false; return false;
} }
return true; return true;
} }
@ -181,6 +199,7 @@ class AABB {
AABB translate(Vec2D vec) => AABB.fromValues(_buffer[0] + vec[0], AABB translate(Vec2D vec) => AABB.fromValues(_buffer[0] + vec[0],
_buffer[1] + vec[1], _buffer[2] + vec[0], _buffer[3] + vec[1]); _buffer[1] + vec[1], _buffer[2] + vec[0], _buffer[3] + vec[1]);
@override @override
String toString() { String toString() {
return _buffer.toString(); return _buffer.toString();
@ -195,16 +214,23 @@ class AABB {
], transform: matrix); ], transform: matrix);
} }
factory AABB.fromPoints(Iterable<Vec2D> points, /// Compute an AABB from a set of points with an optional [transform] to apply
{Mat2D? transform, double expand = 0}) { /// before computing.
factory AABB.fromPoints(
Iterable<Vec2D> points, {
Mat2D? transform,
double expand = 0,
}) {
double minX = double.maxFinite; double minX = double.maxFinite;
double minY = double.maxFinite; double minY = double.maxFinite;
double maxX = -double.maxFinite; double maxX = -double.maxFinite;
double maxY = -double.maxFinite; double maxY = -double.maxFinite;
for (final point in points) { for (final point in points) {
var p = transform == null var p = transform == null
? point ? point
: Vec2D.transformMat2D(Vec2D(), point, transform); : Vec2D.transformMat2D(Vec2D(), point, transform);
double x = p[0]; double x = p[0];
double y = p[1]; double y = p[1];
if (x < minX) { if (x < minX) {
@ -213,6 +239,7 @@ class AABB {
if (y < minY) { if (y < minY) {
minY = y; minY = y;
} }
if (x > maxX) { if (x > maxX) {
maxX = x; maxX = x;
} }
@ -220,6 +247,8 @@ class AABB {
maxY = y; maxY = y;
} }
} }
// Make sure the box is at least this wide/high
if (expand != 0) { if (expand != 0) {
double width = maxX - minX; double width = maxX - minX;
double diff = expand - width; double diff = expand - width;
@ -230,6 +259,7 @@ class AABB {
} }
double height = maxY - minY; double height = maxY - minY;
diff = expand - height; diff = expand - height;
if (diff > 0) { if (diff > 0) {
diff /= 2; diff /= 2;
minY -= diff; minY -= diff;

View File

@ -1,2 +1,4 @@
/// Use this for perfect rounded corners.
/// https://stackoverflow.com/questions/1734745/how-to-create-circle-with-b%C3%A9zier-curves
const circleConstant = 0.552284749831; const circleConstant = 0.552284749831;
const icircleConstant = 1 - circleConstant; const icircleConstant = 1 - circleConstant;

View File

@ -1,8 +1,11 @@
import 'dart:math'; import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:rive/src/rive_core/math/transform_components.dart'; import 'package:rive/src/rive_core/math/transform_components.dart';
import 'package:rive/src/rive_core/math/vec2d.dart'; import 'package:rive/src/rive_core/math/vec2d.dart';
/// Can't make this constant so we override and disable changing values so we'll
/// throw if something tries to change the identity.
class _Identity extends Mat2D { class _Identity extends Mat2D {
@override @override
void operator []=(int index, double value) => throw UnsupportedError( void operator []=(int index, double value) => throw UnsupportedError(
@ -12,6 +15,7 @@ class _Identity extends Mat2D {
class Mat2D { class Mat2D {
static final Mat2D identity = _Identity(); static final Mat2D identity = _Identity();
final Float32List _buffer; final Float32List _buffer;
Float32List get values { Float32List get values {
return _buffer; return _buffer;
} }
@ -46,15 +50,20 @@ class Mat2D {
} }
Mat2D() : _buffer = Float32List.fromList([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]); Mat2D() : _buffer = Float32List.fromList([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
Mat2D.fromTranslation(Vec2D translation) Mat2D.fromTranslation(Vec2D translation)
: _buffer = Float32List.fromList( : _buffer = Float32List.fromList(
[1.0, 0.0, 0.0, 1.0, translation[0], translation[1]]); [1.0, 0.0, 0.0, 1.0, translation[0], translation[1]]);
Mat2D.fromScaling(Vec2D scaling) Mat2D.fromScaling(Vec2D scaling)
: _buffer = Float32List.fromList([scaling[0], 0, 0, scaling[1], 0, 0]); : _buffer = Float32List.fromList([scaling[0], 0, 0, scaling[1], 0, 0]);
Mat2D.fromMat4(Float64List mat4) Mat2D.fromMat4(Float64List mat4)
: _buffer = Float32List.fromList( : _buffer = Float32List.fromList(
[mat4[0], mat4[1], mat4[4], mat4[5], mat4[12], mat4[13]]); [mat4[0], mat4[1], mat4[4], mat4[5], mat4[12], mat4[13]]);
Mat2D.clone(Mat2D copy) : _buffer = Float32List.fromList(copy.values); Mat2D.clone(Mat2D copy) : _buffer = Float32List.fromList(copy.values);
static Mat2D fromRotation(Mat2D o, double rad) { static Mat2D fromRotation(Mat2D o, double rad) {
double s = sin(rad); double s = sin(rad);
double c = cos(rad); double c = cos(rad);
@ -163,11 +172,13 @@ class Mat2D {
static bool invert(Mat2D o, Mat2D a) { static bool invert(Mat2D o, Mat2D a) {
double aa = a[0], ab = a[1], ac = a[2], ad = a[3], atx = a[4], aty = a[5]; double aa = a[0], ab = a[1], ac = a[2], ad = a[3], atx = a[4], aty = a[5];
double det = aa * ad - ab * ac; double det = aa * ad - ab * ac;
if (det == 0.0) { if (det == 0.0) {
return false; return false;
} }
det = 1.0 / det; det = 1.0 / det;
o[0] = ad * det; o[0] = ad * det;
o[1] = -ab * det; o[1] = -ab * det;
o[2] = -ac * det; o[2] = -ac * det;
@ -181,6 +192,7 @@ class Mat2D {
double x = m[0]; double x = m[0];
double y = m[1]; double y = m[1];
s[0] = x.sign * sqrt(x * x + y * y); s[0] = x.sign * sqrt(x * x + y * y);
x = m[2]; x = m[2];
y = m[3]; y = m[3];
s[1] = y.sign * sqrt(x * x + y * y); s[1] = y.sign * sqrt(x * x + y * y);
@ -203,11 +215,13 @@ class Mat2D {
static void decompose(Mat2D m, TransformComponents result) { static void decompose(Mat2D m, TransformComponents result) {
double m0 = m[0], m1 = m[1], m2 = m[2], m3 = m[3]; double m0 = m[0], m1 = m[1], m2 = m[2], m3 = m[3];
double rotation = atan2(m1, m0); double rotation = atan2(m1, m0);
double denom = m0 * m0 + m1 * m1; double denom = m0 * m0 + m1 * m1;
double scaleX = sqrt(denom); double scaleX = sqrt(denom);
double scaleY = (scaleX == 0) ? 0 : ((m0 * m3 - m2 * m1) / scaleX); double scaleY = (scaleX == 0) ? 0 : ((m0 * m3 - m2 * m1) / scaleX);
double skewX = atan2(m0 * m2 + m1 * m3, denom); double skewX = atan2(m0 * m2 + m1 * m3, denom);
result[0] = m[4]; result[0] = m[4];
result[1] = m[5]; result[1] = m[5];
result[2] = scaleX; result[2] = scaleX;
@ -218,6 +232,7 @@ class Mat2D {
static void compose(Mat2D m, TransformComponents result) { static void compose(Mat2D m, TransformComponents result) {
double r = result[4]; double r = result[4];
if (r != 0.0) { if (r != 0.0) {
Mat2D.fromRotation(m, r); Mat2D.fromRotation(m, r);
} else { } else {
@ -226,6 +241,7 @@ class Mat2D {
m[4] = result[0]; m[4] = result[0];
m[5] = result[1]; m[5] = result[1];
Mat2D.scale(m, m, result.scale); Mat2D.scale(m, m, result.scale);
double sk = result[5]; double sk = result[5];
if (sk != 0.0) { if (sk != 0.0) {
m[2] = m[0] * sk + m[2]; m[2] = m[0] * sk + m[2];

View File

@ -1,18 +1,39 @@
import 'package:rive/src/rive_core/math/vec2d.dart'; import 'package:rive/src/rive_core/math/vec2d.dart';
/// Result of projecting a point onto a segment.
class ProjectionResult { class ProjectionResult {
/// The distance factor from 0-1 along the segment starting at the
/// [Segment2D.start].
final double t; final double t;
/// The actual 2d point in the same space as [Segment2D.start] and
/// [Segment2D.end].
final Vec2D point; final Vec2D point;
ProjectionResult(this.t, this.point); ProjectionResult(this.t, this.point);
} }
/// A line segment with a discrete [start] and [end].
class Segment2D { class Segment2D {
/// The starting point of this line segment.
final Vec2D start; final Vec2D start;
/// The ending point of this line segment.
final Vec2D end; final Vec2D end;
/// Difference from start to end. Nullable so we can compute it only when we
/// need it.
Vec2D? diff; Vec2D? diff;
/// The squared length of this segment.
double lengthSquared = 0; double lengthSquared = 0;
Segment2D(this.start, this.end); Segment2D(this.start, this.end);
/// Find where the given [point] lies on this segment.
ProjectionResult projectPoint(Vec2D point, {bool clamp = true}) { ProjectionResult projectPoint(Vec2D point, {bool clamp = true}) {
// We cache these internally so we can call projectPoint multiple times in
// succession performantly.
if (diff == null) { if (diff == null) {
diff = Vec2D.subtract(Vec2D(), start, end); diff = Vec2D.subtract(Vec2D(), start, end);
lengthSquared = Vec2D.squaredLength(diff!); lengthSquared = Vec2D.squaredLength(diff!);
@ -23,7 +44,9 @@ class Segment2D {
double t = ((point[0] - start[0]) * (end[0] - start[0]) + double t = ((point[0] - start[0]) * (end[0] - start[0]) +
(point[1] - start[1]) * (end[1] - start[1])) / (point[1] - start[1]) * (end[1] - start[1])) /
lengthSquared; lengthSquared;
if (clamp) { if (clamp) {
// Clamp at edges.
if (t < 0.0) { if (t < 0.0) {
return ProjectionResult(0, start); return ProjectionResult(0, start);
} }
@ -31,9 +54,13 @@ class Segment2D {
return ProjectionResult(1, end); return ProjectionResult(1, end);
} }
} }
return ProjectionResult( return ProjectionResult(
t, t,
Vec2D.fromValues(start[0] + t * (end[0] - start[0]), Vec2D.fromValues(
start[1] + t * (end[1] - start[1]))); start[0] + t * (end[0] - start[0]),
start[1] + t * (end[1] - start[1]),
),
);
} }
} }

View File

@ -1,9 +1,11 @@
import 'dart:math'; import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:rive/src/rive_core/math/vec2d.dart'; import 'package:rive/src/rive_core/math/vec2d.dart';
class TransformComponents { class TransformComponents {
final Float32List _buffer; final Float32List _buffer;
Float32List get values { Float32List get values {
return _buffer; return _buffer;
} }
@ -18,8 +20,10 @@ class TransformComponents {
TransformComponents() TransformComponents()
: _buffer = Float32List.fromList([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]); : _buffer = Float32List.fromList([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
TransformComponents.clone(TransformComponents copy) TransformComponents.clone(TransformComponents copy)
: _buffer = Float32List.fromList(copy.values); : _buffer = Float32List.fromList(copy.values);
double get x { double get x {
return _buffer[0]; return _buffer[0];
} }

View File

@ -5,6 +5,7 @@ import 'package:rive/src/utilities/utilities.dart';
class Vec2D { class Vec2D {
final Float32List _buffer; final Float32List _buffer;
Float32List get values { Float32List get values {
return _buffer; return _buffer;
} }
@ -18,8 +19,11 @@ class Vec2D {
} }
Vec2D() : _buffer = Float32List.fromList([0.0, 0.0]); Vec2D() : _buffer = Float32List.fromList([0.0, 0.0]);
Vec2D.clone(Vec2D copy) : _buffer = Float32List.fromList(copy._buffer); Vec2D.clone(Vec2D copy) : _buffer = Float32List.fromList(copy._buffer);
Vec2D.fromValues(double x, double y) : _buffer = Float32List.fromList([x, y]); Vec2D.fromValues(double x, double y) : _buffer = Float32List.fromList([x, y]);
static void copy(Vec2D o, Vec2D a) { static void copy(Vec2D o, Vec2D a) {
o[0] = a[0]; o[0] = a[0];
o[1] = a[1]; o[1] = a[1];
@ -99,6 +103,7 @@ class Vec2D {
static Vec2D negate(Vec2D result, Vec2D a) { static Vec2D negate(Vec2D result, Vec2D a) {
result[0] = -1 * a[0]; result[0] = -1 * a[0];
result[1] = -1 * a[1]; result[1] = -1 * a[1];
return result; return result;
} }
@ -143,9 +148,12 @@ class Vec2D {
if (t >= 1) { if (t >= 1) {
return Vec2D.squaredDistance(segmentPoint2, pt); return Vec2D.squaredDistance(segmentPoint2, pt);
} }
Vec2D ptOnSeg = Vec2D.fromValues( Vec2D ptOnSeg = Vec2D.fromValues(
segmentPoint1[0] + t * (segmentPoint2[0] - segmentPoint1[0]), segmentPoint1[0] + t * (segmentPoint2[0] - segmentPoint1[0]),
segmentPoint1[1] + t * (segmentPoint2[1] - segmentPoint1[1])); segmentPoint1[1] + t * (segmentPoint2[1] - segmentPoint1[1]),
);
return Vec2D.squaredDistance(ptOnSeg, pt); return Vec2D.squaredDistance(ptOnSeg, pt);
} }
@ -165,6 +173,7 @@ class Vec2D {
@override @override
bool operator ==(Object o) => bool operator ==(Object o) =>
o is Vec2D && _buffer[0] == o[0] && _buffer[1] == o[1]; o is Vec2D && _buffer[0] == o[0] && _buffer[1] == o[1];
@override @override
int get hashCode => szudzik(_buffer[0].hashCode, _buffer[1].hashCode); int get hashCode => szudzik(_buffer[0].hashCode, _buffer[1].hashCode);
} }

View File

@ -7,6 +7,8 @@ class _UnknownNode extends Node {}
class Node extends NodeBase { class Node extends NodeBase {
static final Node unknown = _UnknownNode(); static final Node unknown = _UnknownNode();
/// Sets the position of the Node
set translation(Vec2D pos) { set translation(Vec2D pos) {
x = pos[0]; x = pos[0];
y = pos[1]; y = pos[1];

View File

@ -1,9 +1,12 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
/// Abstraction for receiving a per frame callback while isPlaying is true to
/// apply animation based on an elapsed amount of time.
abstract class RiveAnimationController<T> { abstract class RiveAnimationController<T> {
final _isActive = ValueNotifier<bool>(false); final _isActive = ValueNotifier<bool>(false);
ValueListenable<bool> get isActiveChanged => _isActive; ValueListenable<bool> get isActiveChanged => _isActive;
bool get isActive => _isActive.value; bool get isActive => _isActive.value;
set isActive(bool value) { set isActive(bool value) {
if (_isActive.value != value) { if (_isActive.value != value) {
@ -20,7 +23,11 @@ abstract class RiveAnimationController<T> {
void onActivate() {} void onActivate() {}
@protected @protected
void onDeactivate() {} void onDeactivate() {}
/// Apply animation to objects registered in [core]. Note that a [core]
/// context is specified as animations can be applied to instances.
void apply(T core, double elapsedSeconds); void apply(T core, double elapsedSeconds);
bool init(T core) => true; bool init(T core) => true;
void dispose() {} void dispose() {}
} }

View File

@ -1,5 +1,6 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
/// Thrown when a file being read doesn't match the Rive format.
@immutable @immutable
class RiveFormatErrorException implements Exception { class RiveFormatErrorException implements Exception {
final String cause; final String cause;

View File

@ -1,5 +1,7 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
/// Error that occurs when a file being loaded doesn't match the importer's
/// supported version.
@immutable @immutable
class RiveUnsupportedVersionException implements Exception { class RiveUnsupportedVersionException implements Exception {
final int majorVersion; final int majorVersion;
@ -8,6 +10,7 @@ class RiveUnsupportedVersionException implements Exception {
final int fileMinorVersion; final int fileMinorVersion;
const RiveUnsupportedVersionException(this.majorVersion, this.minorVersion, const RiveUnsupportedVersionException(this.majorVersion, this.minorVersion,
this.fileMajorVersion, this.fileMinorVersion); this.fileMajorVersion, this.fileMinorVersion);
@override @override
String toString() { String toString() {
return 'File contains version $fileMajorVersion.$fileMinorVersion. ' return 'File contains version $fileMajorVersion.$fileMinorVersion. '

View File

@ -1,11 +1,15 @@
import 'dart:collection'; import 'dart:collection';
import 'package:rive/src/rive_core/runtime/exceptions/rive_format_error_exception.dart'; import 'package:rive/src/rive_core/runtime/exceptions/rive_format_error_exception.dart';
import 'package:rive/src/rive_core/runtime/exceptions/rive_unsupported_version_exception.dart'; import 'package:rive/src/rive_core/runtime/exceptions/rive_unsupported_version_exception.dart';
import 'package:rive/src/utilities/binary_buffer/binary_reader.dart'; import 'package:rive/src/utilities/binary_buffer/binary_reader.dart';
/// Stores the minor and major version of Rive. Versions with the same major
/// value are backwards and forwards compatible.
class RuntimeVersion { class RuntimeVersion {
final int major; final int major;
final int minor; final int minor;
const RuntimeVersion(this.major, this.minor); const RuntimeVersion(this.major, this.minor);
String versionString() { String versionString() {
return '$major.$minor'; return '$major.$minor';
@ -17,19 +21,26 @@ const riveVersion = RuntimeVersion(7, 0);
class RuntimeHeader { class RuntimeHeader {
static const String fingerprint = 'RIVE'; static const String fingerprint = 'RIVE';
final RuntimeVersion version; final RuntimeVersion version;
final int fileId; final int fileId;
final HashMap<int, int> propertyToFieldIndex; final HashMap<int, int> propertyToFieldIndex;
RuntimeHeader(
{required this.fileId, RuntimeHeader({
required this.version, required this.fileId,
required this.propertyToFieldIndex}); required this.version,
required this.propertyToFieldIndex,
});
factory RuntimeHeader.read(BinaryReader reader) { factory RuntimeHeader.read(BinaryReader reader) {
var fingerprint = RuntimeHeader.fingerprint.codeUnits; var fingerprint = RuntimeHeader.fingerprint.codeUnits;
for (int i = 0; i < fingerprint.length; i++) { for (int i = 0; i < fingerprint.length; i++) {
if (reader.readUint8() != fingerprint[i]) { if (reader.readUint8() != fingerprint[i]) {
throw const RiveFormatErrorException('Fingerprint doesn\'t match.'); throw const RiveFormatErrorException('Fingerprint doesn\'t match.');
} }
} }
int readMajorVersion = reader.readVarUint(); int readMajorVersion = reader.readVarUint();
int readMinorVersion = reader.readVarUint(); int readMinorVersion = reader.readVarUint();
if (readMajorVersion > riveVersion.major) { if (readMajorVersion > riveVersion.major) {
@ -40,7 +51,9 @@ class RuntimeHeader {
reader.readVarUint(); reader.readVarUint();
} }
int fileId = reader.readVarUint(); int fileId = reader.readVarUint();
var propertyFields = HashMap<int, int>(); var propertyFields = HashMap<int, int>();
var propertyKeys = <int>[]; var propertyKeys = <int>[];
for (int propertyKey = reader.readVarUint(); for (int propertyKey = reader.readVarUint();
propertyKey != 0; propertyKey != 0;
@ -58,9 +71,11 @@ class RuntimeHeader {
propertyFields[propertyKey] = fieldIndex; propertyFields[propertyKey] = fieldIndex;
currentBit += 2; currentBit += 2;
} }
return RuntimeHeader( return RuntimeHeader(
fileId: fileId, fileId: fileId,
version: RuntimeVersion(readMajorVersion, readMinorVersion), version: RuntimeVersion(readMajorVersion, readMinorVersion),
propertyToFieldIndex: propertyFields); propertyToFieldIndex: propertyFields,
);
} }
} }

View File

@ -1,4 +1,5 @@
import 'dart:ui'; import 'dart:ui';
import 'package:rive/src/rive_core/component_dirt.dart'; import 'package:rive/src/rive_core/component_dirt.dart';
import 'package:rive/src/rive_core/node.dart'; import 'package:rive/src/rive_core/node.dart';
import 'package:rive/src/rive_core/shapes/shape.dart'; import 'package:rive/src/rive_core/shapes/shape.dart';
@ -10,19 +11,25 @@ class ClippingShape extends ClippingShapeBase {
final List<Shape> _shapes = []; final List<Shape> _shapes = [];
PathFillType get fillType => PathFillType.values[fillRule]; PathFillType get fillType => PathFillType.values[fillRule];
set fillType(PathFillType type) => fillRule = type.index; set fillType(PathFillType type) => fillRule = type.index;
Node _source = Node.unknown; Node _source = Node.unknown;
Node get source => _source; Node get source => _source;
set source(Node value) { set source(Node value) {
if (_source == value) { if (_source == value) {
return; return;
} }
_source = value; _source = value;
sourceId = value.id; sourceId = value.id;
} }
@override @override
void fillRuleChanged(int from, int to) { void fillRuleChanged(int from, int to) {
// In the future, if clipOp can change at runtime (animation), we may want
// the shapes that use this as a clipping source to make them depend on this
// clipping shape so we can add dirt to them directly.
parent?.addDirt(ComponentDirt.clip, recurse: true); parent?.addDirt(ComponentDirt.clip, recurse: true);
addDirt(ComponentDirt.path); addDirt(ComponentDirt.path);
} }
@ -44,10 +51,13 @@ class ClippingShape extends ClippingShapeBase {
_source.forAll((component) { _source.forAll((component) {
if (component is Shape) { if (component is Shape) {
_shapes.add(component); _shapes.add(component);
//component.addDependent(this);
component.pathComposer.addDependent(this); component.pathComposer.addDependent(this);
} }
return true; return true;
}); });
// make sure we rebuild the clipping path.
addDirt(ComponentDirt.path); addDirt(ComponentDirt.path);
} }
@ -60,6 +70,8 @@ class ClippingShape extends ClippingShapeBase {
@override @override
void update(int dirt) { void update(int dirt) {
if (dirt & (ComponentDirt.worldTransform | ComponentDirt.path) != 0) { if (dirt & (ComponentDirt.worldTransform | ComponentDirt.path) != 0) {
// Build the clipping path as one of our dependent shapes changes or we
// added a shape.
clippingPath.reset(); clippingPath.reset();
clippingPath.fillType = fillType; clippingPath.fillType = fillType;
for (final shape in _shapes) { for (final shape in _shapes) {
@ -75,6 +87,7 @@ class ClippingShape extends ClippingShapeBase {
@override @override
void isVisibleChanged(bool from, bool to) { void isVisibleChanged(bool from, bool to) {
// Redraw
_source.addDirt(ComponentDirt.paint); _source.addDirt(ComponentDirt.paint);
} }
} }

View File

@ -1,4 +1,5 @@
import 'dart:math'; import 'dart:math';
import 'package:rive/src/core/core.dart'; import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/component_dirt.dart'; import 'package:rive/src/rive_core/component_dirt.dart';
import 'package:rive/src/rive_core/math/vec2d.dart'; import 'package:rive/src/rive_core/math/vec2d.dart';
@ -10,8 +11,10 @@ class CubicAsymmetricVertex extends CubicAsymmetricVertexBase {
CubicAsymmetricVertex.procedural() { CubicAsymmetricVertex.procedural() {
InternalCoreHelper.markValid(this); InternalCoreHelper.markValid(this);
} }
Vec2D? _inPoint; Vec2D? _inPoint;
Vec2D? _outPoint; Vec2D? _outPoint;
@override @override
Vec2D get outPoint { Vec2D get outPoint {
return _outPoint ??= Vec2D.add( return _outPoint ??= Vec2D.add(

View File

@ -1,4 +1,5 @@
import 'dart:math'; import 'dart:math';
import 'package:rive/src/core/core.dart'; import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/component_dirt.dart'; import 'package:rive/src/rive_core/component_dirt.dart';
import 'package:rive/src/rive_core/math/vec2d.dart'; import 'package:rive/src/rive_core/math/vec2d.dart';
@ -8,16 +9,18 @@ export 'package:rive/src/generated/shapes/cubic_detached_vertex_base.dart';
class CubicDetachedVertex extends CubicDetachedVertexBase { class CubicDetachedVertex extends CubicDetachedVertexBase {
Vec2D? _inPoint; Vec2D? _inPoint;
Vec2D? _outPoint; Vec2D? _outPoint;
CubicDetachedVertex(); CubicDetachedVertex();
CubicDetachedVertex.fromValues( CubicDetachedVertex.fromValues({
{required double x, required double x,
required double y, required double y,
double? inX, double? inX,
double? inY, double? inY,
double? outX, double? outX,
double? outY, double? outY,
Vec2D? inPoint, Vec2D? inPoint,
Vec2D? outPoint}) { Vec2D? outPoint,
}) {
InternalCoreHelper.markValid(this); InternalCoreHelper.markValid(this);
this.x = x; this.x = x;
this.y = y; this.y = y;
@ -25,12 +28,14 @@ class CubicDetachedVertex extends CubicDetachedVertexBase {
this.outPoint = this.outPoint =
Vec2D.fromValues(outX ?? outPoint![0], outY ?? outPoint![1]); Vec2D.fromValues(outX ?? outPoint![0], outY ?? outPoint![1]);
} }
@override @override
Vec2D get outPoint => _outPoint ??= Vec2D.add( Vec2D get outPoint => _outPoint ??= Vec2D.add(
Vec2D(), Vec2D(),
translation, translation,
Vec2D.fromValues( Vec2D.fromValues(
cos(outRotation) * outDistance, sin(outRotation) * outDistance)); cos(outRotation) * outDistance, sin(outRotation) * outDistance));
@override @override
set outPoint(Vec2D value) { set outPoint(Vec2D value) {
_outPoint = Vec2D.clone(value); _outPoint = Vec2D.clone(value);
@ -42,6 +47,7 @@ class CubicDetachedVertex extends CubicDetachedVertexBase {
translation, translation,
Vec2D.fromValues( Vec2D.fromValues(
cos(inRotation) * inDistance, sin(inRotation) * inDistance)); cos(inRotation) * inDistance, sin(inRotation) * inDistance));
@override @override
set inPoint(Vec2D value) { set inPoint(Vec2D value) {
_inPoint = Vec2D.clone(value); _inPoint = Vec2D.clone(value);

View File

@ -1,4 +1,5 @@
import 'dart:math'; import 'dart:math';
import 'package:rive/src/core/core.dart'; import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/component_dirt.dart'; import 'package:rive/src/rive_core/component_dirt.dart';
import 'package:rive/src/rive_core/math/vec2d.dart'; import 'package:rive/src/rive_core/math/vec2d.dart';
@ -7,11 +8,15 @@ export 'package:rive/src/generated/shapes/cubic_mirrored_vertex_base.dart';
class CubicMirroredVertex extends CubicMirroredVertexBase { class CubicMirroredVertex extends CubicMirroredVertexBase {
CubicMirroredVertex(); CubicMirroredVertex();
/// Makes a vertex that is disconnected from core.
CubicMirroredVertex.procedural() { CubicMirroredVertex.procedural() {
InternalCoreHelper.markValid(this); InternalCoreHelper.markValid(this);
} }
Vec2D? _inPoint; Vec2D? _inPoint;
Vec2D? _outPoint; Vec2D? _outPoint;
@override @override
Vec2D get outPoint { Vec2D get outPoint {
return _outPoint ??= Vec2D.add(Vec2D(), translation, return _outPoint ??= Vec2D.add(Vec2D(), translation,

View File

@ -1,4 +1,5 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:rive/src/rive_core/bones/weight.dart'; import 'package:rive/src/rive_core/bones/weight.dart';
import 'package:rive/src/rive_core/math/mat2d.dart'; import 'package:rive/src/rive_core/math/mat2d.dart';
import 'package:rive/src/rive_core/math/vec2d.dart'; import 'package:rive/src/rive_core/math/vec2d.dart';
@ -8,15 +9,20 @@ export 'package:rive/src/generated/shapes/cubic_vertex_base.dart';
abstract class CubicVertex extends CubicVertexBase { abstract class CubicVertex extends CubicVertexBase {
Vec2D get outPoint; Vec2D get outPoint;
Vec2D get inPoint; Vec2D get inPoint;
set outPoint(Vec2D value); set outPoint(Vec2D value);
set inPoint(Vec2D value); set inPoint(Vec2D value);
@override @override
Vec2D get renderTranslation => weight?.translation ?? super.renderTranslation; Vec2D get renderTranslation => weight?.translation ?? super.renderTranslation;
Vec2D get renderIn => weight?.inTranslation ?? inPoint; Vec2D get renderIn => weight?.inTranslation ?? inPoint;
Vec2D get renderOut => weight?.outTranslation ?? outPoint; Vec2D get renderOut => weight?.outTranslation ?? outPoint;
@override @override
void deform(Mat2D world, Float32List boneTransforms) { void deform(Mat2D world, Float32List boneTransforms) {
super.deform(world, boneTransforms); super.deform(world, boneTransforms);
Weight.deform(outPoint[0], outPoint[1], weight!.outIndices, Weight.deform(outPoint[0], outPoint[1], weight!.outIndices,
weight!.outValues, world, boneTransforms, weight!.outTranslation); weight!.outValues, world, boneTransforms, weight!.outTranslation);
Weight.deform(inPoint[0], inPoint[1], weight!.inIndices, weight!.inValues, Weight.deform(inPoint[0], inPoint[1], weight!.inIndices, weight!.inValues,

View File

@ -2,6 +2,7 @@ import 'package:rive/src/rive_core/math/circle_constant.dart';
import 'package:rive/src/rive_core/shapes/cubic_detached_vertex.dart'; import 'package:rive/src/rive_core/shapes/cubic_detached_vertex.dart';
import 'package:rive/src/rive_core/shapes/path_vertex.dart'; import 'package:rive/src/rive_core/shapes/path_vertex.dart';
import 'package:rive/src/generated/shapes/ellipse_base.dart'; import 'package:rive/src/generated/shapes/ellipse_base.dart';
export 'package:rive/src/generated/shapes/ellipse_base.dart'; export 'package:rive/src/generated/shapes/ellipse_base.dart';
class Ellipse extends EllipseBase { class Ellipse extends EllipseBase {
@ -9,35 +10,40 @@ class Ellipse extends EllipseBase {
List<PathVertex> get vertices { List<PathVertex> get vertices {
double ox = -originX * width + radiusX; double ox = -originX * width + radiusX;
double oy = -originY * height + radiusY; double oy = -originY * height + radiusY;
return [ return [
CubicDetachedVertex.fromValues( CubicDetachedVertex.fromValues(
x: ox, x: ox,
y: oy - radiusY, y: oy - radiusY,
inX: ox - radiusX * circleConstant, inX: ox - radiusX * circleConstant,
inY: oy - radiusY, inY: oy - radiusY,
outX: ox + radiusX * circleConstant, outX: ox + radiusX * circleConstant,
outY: oy - radiusY), outY: oy - radiusY,
),
CubicDetachedVertex.fromValues( CubicDetachedVertex.fromValues(
x: ox + radiusX, x: ox + radiusX,
y: oy, y: oy,
inX: ox + radiusX, inX: ox + radiusX,
inY: oy + circleConstant * -radiusY, inY: oy + circleConstant * -radiusY,
outX: ox + radiusX, outX: ox + radiusX,
outY: oy + circleConstant * radiusY), outY: oy + circleConstant * radiusY,
),
CubicDetachedVertex.fromValues( CubicDetachedVertex.fromValues(
x: ox, x: ox,
y: oy + radiusY, y: oy + radiusY,
inX: ox + radiusX * circleConstant, inX: ox + radiusX * circleConstant,
inY: oy + radiusY, inY: oy + radiusY,
outX: ox - radiusX * circleConstant, outX: ox - radiusX * circleConstant,
outY: oy + radiusY), outY: oy + radiusY,
),
CubicDetachedVertex.fromValues( CubicDetachedVertex.fromValues(
x: ox - radiusX, x: ox - radiusX,
y: oy, y: oy,
inX: ox - radiusX, inX: ox - radiusX,
inY: oy + radiusY * circleConstant, inY: oy + radiusY * circleConstant,
outX: ox - radiusX, outX: ox - radiusX,
outY: oy - radiusY * circleConstant) outY: oy - radiusY * circleConstant,
),
]; ];
} }

View File

@ -1,19 +1,28 @@
import 'dart:ui'; import 'dart:ui';
import 'package:rive/src/rive_core/component_dirt.dart'; import 'package:rive/src/rive_core/component_dirt.dart';
import 'package:rive/src/rive_core/shapes/shape_paint_container.dart'; import 'package:rive/src/rive_core/shapes/shape_paint_container.dart';
import 'package:rive/src/generated/shapes/paint/fill_base.dart'; import 'package:rive/src/generated/shapes/paint/fill_base.dart';
export 'package:rive/src/generated/shapes/paint/fill_base.dart'; export 'package:rive/src/generated/shapes/paint/fill_base.dart';
/// A fill Shape painter.
class Fill extends FillBase { class Fill extends FillBase {
@override @override
Paint makePaint() => Paint()..style = PaintingStyle.fill; Paint makePaint() => Paint()..style = PaintingStyle.fill;
PathFillType get fillType => PathFillType.values[fillRule]; PathFillType get fillType => PathFillType.values[fillRule];
set fillType(PathFillType type) => fillRule = type.index; set fillType(PathFillType type) => fillRule = type.index;
@override @override
void fillRuleChanged(int from, int to) => void fillRuleChanged(int from, int to) =>
parent?.addDirt(ComponentDirt.paint); parent?.addDirt(ComponentDirt.paint);
@override @override
void update(int dirt) {} void update(int dirt) {
// Intentionally empty, fill doesn't update.
// Because Fill never adds dependencies, it'll also never get called.
}
@override @override
void onAdded() { void onAdded() {
super.onAdded(); super.onAdded();

View File

@ -1,7 +1,9 @@
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:rive/src/rive_core/container_component.dart'; import 'package:rive/src/rive_core/container_component.dart';
import 'package:rive/src/generated/shapes/paint/gradient_stop_base.dart'; import 'package:rive/src/generated/shapes/paint/gradient_stop_base.dart';
import 'package:rive/src/rive_core/shapes/paint/linear_gradient.dart'; import 'package:rive/src/rive_core/shapes/paint/linear_gradient.dart';
export 'package:rive/src/generated/shapes/paint/gradient_stop_base.dart'; export 'package:rive/src/generated/shapes/paint/gradient_stop_base.dart';
class GradientStop extends GradientStopBase { class GradientStop extends GradientStopBase {
@ -24,8 +26,10 @@ class GradientStop extends GradientStopBase {
@override @override
void update(int dirt) {} void update(int dirt) {}
@override @override
bool validate() => super.validate() && _gradient != null; bool validate() => super.validate() && _gradient != null;
@override @override
void parentChanged(ContainerComponent? from, ContainerComponent? to) { void parentChanged(ContainerComponent? from, ContainerComponent? to) {
super.parentChanged(from, to); super.parentChanged(from, to);

View File

@ -1,15 +1,24 @@
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:rive/src/rive_core/component.dart'; import 'package:rive/src/rive_core/component.dart';
import 'package:rive/src/rive_core/component_dirt.dart'; import 'package:rive/src/rive_core/component_dirt.dart';
import 'package:rive/src/rive_core/math/vec2d.dart'; import 'package:rive/src/rive_core/math/vec2d.dart';
import 'package:rive/src/rive_core/shapes/paint/gradient_stop.dart'; import 'package:rive/src/rive_core/shapes/paint/gradient_stop.dart';
import 'package:rive/src/rive_core/shapes/paint/shape_paint_mutator.dart'; import 'package:rive/src/rive_core/shapes/paint/shape_paint_mutator.dart';
import 'package:rive/src/rive_core/shapes/shape.dart';
import 'package:rive/src/generated/shapes/paint/linear_gradient_base.dart'; import 'package:rive/src/generated/shapes/paint/linear_gradient_base.dart';
export 'package:rive/src/generated/shapes/paint/linear_gradient_base.dart'; export 'package:rive/src/generated/shapes/paint/linear_gradient_base.dart';
/// A core linear gradient. Can be added as a child to a [Shape]'s [Fill] or
/// [Stroke] to paint that Fill or Stroke with a gradient. This is the
/// foundation for the RadialGradient which is very similar but also has a
/// radius value.
class LinearGradient extends LinearGradientBase with ShapePaintMutator { class LinearGradient extends LinearGradientBase with ShapePaintMutator {
/// Stored list of core gradient stops are in the hierarchy as children of
/// this container.
final List<GradientStop> gradientStops = []; final List<GradientStop> gradientStops = [];
bool _paintsInWorldSpace = true; bool _paintsInWorldSpace = true;
bool get paintsInWorldSpace => _paintsInWorldSpace; bool get paintsInWorldSpace => _paintsInWorldSpace;
set paintsInWorldSpace(bool value) { set paintsInWorldSpace(bool value) {
@ -22,8 +31,11 @@ class LinearGradient extends LinearGradientBase with ShapePaintMutator {
Vec2D get start => Vec2D.fromValues(startX, startY); Vec2D get start => Vec2D.fromValues(startX, startY);
Vec2D get end => Vec2D.fromValues(endX, endY); Vec2D get end => Vec2D.fromValues(endX, endY);
ui.Offset get startOffset => ui.Offset(startX, startY); ui.Offset get startOffset => ui.Offset(startX, startY);
ui.Offset get endOffset => ui.Offset(endX, endY); ui.Offset get endOffset => ui.Offset(endX, endY);
/// Gradients depends on their shape.
@override @override
void buildDependencies() { void buildDependencies() {
super.buildDependencies(); super.buildDependencies();
@ -35,6 +47,7 @@ class LinearGradient extends LinearGradientBase with ShapePaintMutator {
super.childAdded(child); super.childAdded(child);
if (child is GradientStop && !gradientStops.contains(child)) { if (child is GradientStop && !gradientStops.contains(child)) {
gradientStops.add(child); gradientStops.add(child);
markStopsDirty(); markStopsDirty();
} }
} }
@ -44,31 +57,48 @@ class LinearGradient extends LinearGradientBase with ShapePaintMutator {
super.childRemoved(child); super.childRemoved(child);
if (child is GradientStop && gradientStops.contains(child)) { if (child is GradientStop && gradientStops.contains(child)) {
gradientStops.remove(child); gradientStops.remove(child);
markStopsDirty(); markStopsDirty();
} }
} }
/// Mark the gradient stops as changed. This will re-sort the stops and
/// rebuild the necessary gradients in the next update cycle.
void markStopsDirty() => addDirt(ComponentDirt.stops | ComponentDirt.paint); void markStopsDirty() => addDirt(ComponentDirt.stops | ComponentDirt.paint);
/// Mark the gradient as needing to be rebuilt. This is a more efficient
/// version of markStopsDirty as it won't re-sort the stops.
void markGradientDirty() => addDirt(ComponentDirt.paint); void markGradientDirty() => addDirt(ComponentDirt.paint);
@override @override
void update(int dirt) { void update(int dirt) {
// Do the stops need to be re-ordered?
bool stopsChanged = dirt & ComponentDirt.stops != 0; bool stopsChanged = dirt & ComponentDirt.stops != 0;
if (stopsChanged) { if (stopsChanged) {
gradientStops.sort((a, b) => a.position.compareTo(b.position)); gradientStops.sort((a, b) => a.position.compareTo(b.position));
} }
bool worldTransformed = dirt & ComponentDirt.worldTransform != 0; bool worldTransformed = dirt & ComponentDirt.worldTransform != 0;
bool localTransformed = dirt & ComponentDirt.transform != 0; bool localTransformed = dirt & ComponentDirt.transform != 0;
// We rebuild the gradient if the gradient is dirty or we paint in world
// space and the world space transform has changed, or the local transform
// has changed. Local transform changes when a stop moves in local space.
var rebuildGradient = dirt & ComponentDirt.paint != 0 || var rebuildGradient = dirt & ComponentDirt.paint != 0 ||
localTransformed || localTransformed ||
(paintsInWorldSpace && worldTransformed); (paintsInWorldSpace && worldTransformed);
if (rebuildGradient) { if (rebuildGradient) {
// build up the color and positions lists
var colors = <ui.Color>[]; var colors = <ui.Color>[];
var colorPositions = <double>[]; var colorPositions = <double>[];
for (final stop in gradientStops) { for (final stop in gradientStops) {
colors.add(stop.color); colors.add(stop.color);
colorPositions.add(stop.position); colorPositions.add(stop.position);
} }
// Check if we need to update the world space gradient.
if (paintsInWorldSpace) { if (paintsInWorldSpace) {
// Get the start and end of the gradient in world coordinates (world
// transform of the shape).
var world = shapePaintContainer!.worldTransform; var world = shapePaintContainer!.worldTransform;
var worldStart = Vec2D.transformMat2D(Vec2D(), start, world); var worldStart = Vec2D.transformMat2D(Vec2D(), start, world);
var worldEnd = Vec2D.transformMat2D(Vec2D(), end, world); var worldEnd = Vec2D.transformMat2D(Vec2D(), end, world);
@ -85,6 +115,7 @@ class LinearGradient extends LinearGradientBase with ShapePaintMutator {
ui.Gradient makeGradient(ui.Offset start, ui.Offset end, ui.Gradient makeGradient(ui.Offset start, ui.Offset end,
List<ui.Color> colors, List<double> colorPositions) => List<ui.Color> colors, List<double> colorPositions) =>
ui.Gradient.linear(start, end, colors, colorPositions); ui.Gradient.linear(start, end, colors, colorPositions);
@override @override
void startXChanged(double from, double to) { void startXChanged(double from, double to) {
addDirt(ComponentDirt.transform); addDirt(ComponentDirt.transform);
@ -114,6 +145,8 @@ class LinearGradient extends LinearGradientBase with ShapePaintMutator {
@override @override
void opacityChanged(double from, double to) { void opacityChanged(double from, double to) {
syncColor(); syncColor();
// We don't need to rebuild anything, just let our shape know we should
// repaint.
shapePaintContainer!.addDirt(ComponentDirt.paint); shapePaintContainer!.addDirt(ComponentDirt.paint);
} }

View File

@ -3,6 +3,8 @@ import 'package:rive/src/generated/shapes/paint/radial_gradient_base.dart';
export 'package:rive/src/generated/shapes/paint/radial_gradient_base.dart'; export 'package:rive/src/generated/shapes/paint/radial_gradient_base.dart';
class RadialGradient extends RadialGradientBase { class RadialGradient extends RadialGradientBase {
/// We override the make gradient operation to create a radial gradient
/// instead of a linear one.
@override @override
ui.Gradient makeGradient(ui.Offset start, ui.Offset end, ui.Gradient makeGradient(ui.Offset start, ui.Offset end,
List<ui.Color> colors, List<double> colorPositions) => List<ui.Color> colors, List<double> colorPositions) =>

View File

@ -1,34 +1,48 @@
import 'dart:ui'; import 'dart:ui';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:rive/src/rive_core/component.dart'; import 'package:rive/src/rive_core/component.dart';
import 'package:rive/src/rive_core/component_dirt.dart'; import 'package:rive/src/rive_core/component_dirt.dart';
import 'package:rive/src/rive_core/container_component.dart'; import 'package:rive/src/rive_core/container_component.dart';
import 'package:rive/src/rive_core/shapes/paint/shape_paint_mutator.dart'; import 'package:rive/src/rive_core/shapes/paint/shape_paint_mutator.dart';
import 'package:rive/src/rive_core/shapes/shape.dart';
import 'package:rive/src/rive_core/shapes/shape_paint_container.dart'; import 'package:rive/src/rive_core/shapes/shape_paint_container.dart';
import 'package:rive/src/generated/shapes/paint/shape_paint_base.dart'; import 'package:rive/src/generated/shapes/paint/shape_paint_base.dart';
export 'package:rive/src/generated/shapes/paint/shape_paint_base.dart'; export 'package:rive/src/generated/shapes/paint/shape_paint_base.dart';
/// Generic ShapePaint that abstracts Stroke and Fill. Automatically hooks up
/// parent [Shape] to child [ShapePaintMutator]s.
abstract class ShapePaint extends ShapePaintBase { abstract class ShapePaint extends ShapePaintBase {
late Paint _paint; late Paint _paint;
Paint get paint => _paint; Paint get paint => _paint;
ShapePaintMutator? _paintMutator; ShapePaintMutator? _paintMutator;
ShapePaintContainer? get shapePaintContainer => ShapePaintContainer? get shapePaintContainer =>
parent is ShapePaintContainer ? parent as ShapePaintContainer : null; parent is ShapePaintContainer ? parent as ShapePaintContainer : null;
ShapePaint() { ShapePaint() {
_paint = makePaint(); _paint = makePaint();
} }
BlendMode get blendMode => _paint.blendMode; BlendMode get blendMode => _paint.blendMode;
set blendMode(BlendMode value) => _paint.blendMode = value; set blendMode(BlendMode value) => _paint.blendMode = value;
double get renderOpacity => _paintMutator!.renderOpacity; double get renderOpacity => _paintMutator!.renderOpacity;
set renderOpacity(double value) => _paintMutator!.renderOpacity = value; set renderOpacity(double value) => _paintMutator!.renderOpacity = value;
ShapePaintMutator? get paintMutator => _paintMutator; ShapePaintMutator? get paintMutator => _paintMutator;
void _changeMutator(ShapePaintMutator? mutator) { void _changeMutator(ShapePaintMutator? mutator) {
_paint = makePaint(); _paint = makePaint();
_paintMutator = mutator; _paintMutator = mutator;
} }
/// Implementing classes are expected to override this to create a paint
/// object. This gets called whenever the mutator is changed in order to not
/// require each mutator to manually reset the paint to some canonical state.
/// Instead, we simply blow out the old one and make a new one.
@protected @protected
Paint makePaint(); Paint makePaint();
@override @override
void childAdded(Component child) { void childAdded(Component child) {
super.childAdded(child); super.childAdded(child);
@ -53,6 +67,7 @@ abstract class ShapePaint extends ShapePaintBase {
super.validate() && super.validate() &&
parent is ShapePaintContainer && parent is ShapePaintContainer &&
_paintMutator != null; _paintMutator != null;
@override @override
void isVisibleChanged(bool from, bool to) { void isVisibleChanged(bool from, bool to) {
shapePaintContainer?.addDirt(ComponentDirt.paint); shapePaintContainer?.addDirt(ComponentDirt.paint);
@ -61,6 +76,8 @@ abstract class ShapePaint extends ShapePaintBase {
@override @override
void childRemoved(Component child) { void childRemoved(Component child) {
super.childRemoved(child); super.childRemoved(child);
// Make sure to clean up any references so that they can be garbage
// collected.
if (child is ShapePaintMutator && if (child is ShapePaintMutator &&
_paintMutator == child as ShapePaintMutator) { _paintMutator == child as ShapePaintMutator) {
_changeMutator(null); _changeMutator(null);
@ -69,5 +86,6 @@ abstract class ShapePaint extends ShapePaintBase {
void _initMutator() => void _initMutator() =>
_paintMutator?.initializePaintMutator(shapePaintContainer!, paint); _paintMutator?.initializePaintMutator(shapePaintContainer!, paint);
void draw(Canvas canvas, Path path); void draw(Canvas canvas, Path path);
} }

View File

@ -1,12 +1,17 @@
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:rive/src/rive_core/shapes/shape_paint_container.dart'; import 'package:rive/src/rive_core/shapes/shape_paint_container.dart';
abstract class ShapePaintMutator { abstract class ShapePaintMutator {
ShapePaintContainer? _shapePaintContainer; ShapePaintContainer? _shapePaintContainer;
Paint _paint = Paint(); Paint _paint = Paint();
/// The container is usually either a Shape or an Artboard, basically any of
/// the various ContainerComponents that can contain Fills or Strokes.
ShapePaintContainer? get shapePaintContainer => _shapePaintContainer; ShapePaintContainer? get shapePaintContainer => _shapePaintContainer;
Paint get paint => _paint; Paint get paint => _paint;
double _renderOpacity = 1; double _renderOpacity = 1;
double get renderOpacity => _renderOpacity; double get renderOpacity => _renderOpacity;
set renderOpacity(double value) { set renderOpacity(double value) {
@ -18,6 +23,7 @@ abstract class ShapePaintMutator {
@protected @protected
void syncColor(); void syncColor();
@mustCallSuper @mustCallSuper
void initializePaintMutator(ShapePaintContainer container, Paint paint) { void initializePaintMutator(ShapePaintContainer container, Paint paint) {
_shapePaintContainer = container; _shapePaintContainer = container;

View File

@ -1,10 +1,12 @@
import 'dart:ui'; import 'dart:ui';
import 'package:rive/src/rive_core/component_dirt.dart'; import 'package:rive/src/rive_core/component_dirt.dart';
import 'package:rive/src/rive_core/shapes/paint/shape_paint.dart'; import 'package:rive/src/rive_core/shapes/paint/shape_paint.dart';
import 'package:rive/src/rive_core/shapes/paint/shape_paint_mutator.dart'; import 'package:rive/src/rive_core/shapes/paint/shape_paint_mutator.dart';
import 'package:rive/src/generated/shapes/paint/solid_color_base.dart'; import 'package:rive/src/generated/shapes/paint/solid_color_base.dart';
export 'package:rive/src/generated/shapes/paint/solid_color_base.dart'; export 'package:rive/src/generated/shapes/paint/solid_color_base.dart';
/// A solid color painter for a shape. Works for both Fill and Stroke.
class SolidColor extends SolidColorBase with ShapePaintMutator { class SolidColor extends SolidColorBase with ShapePaintMutator {
Color get color => Color(colorValue); Color get color => Color(colorValue);
set color(Color c) { set color(Color c) {
@ -13,12 +15,24 @@ class SolidColor extends SolidColorBase with ShapePaintMutator {
@override @override
void colorValueChanged(int from, int to) { void colorValueChanged(int from, int to) {
// Since all we need to do is set the color on the paint, we can just do
// this whenever it changes as it's such a lightweight operation. We don't
// need to schedule it for the next update cycle, which saves us from adding
// SolidColor to the dependencies graph.
syncColor(); syncColor();
// Since we're not in the dependency tree, chuck dirt onto the shape, which
// is. This just ensures we'll paint as soon as possible to show the updated
// color.
shapePaintContainer?.addDirt(ComponentDirt.paint); shapePaintContainer?.addDirt(ComponentDirt.paint);
} }
@override @override
void update(int dirt) {} void update(int dirt) {
// Intentionally empty. SolidColor doesn't need an update cycle and doesn't
// depend on anything.
}
@override @override
void syncColor() { void syncColor() {
paint.color = color paint.color = color
@ -27,6 +41,7 @@ class SolidColor extends SolidColorBase with ShapePaintMutator {
@override @override
bool validate() => super.validate() && parent is ShapePaint; bool validate() => super.validate() && parent is ShapePaint;
@override @override
void onAdded() { void onAdded() {
super.onAdded(); super.onAdded();

Some files were not shown because too many files have changed in this diff Show More