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

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

View File

@ -1,5 +1,6 @@
import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/artboard.dart';
import '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
void onAddedDirty() {}
@override
void onAdded() {}
@override
bool validate() => super.validate() && _artboard != null;
@override
void nameChanged(String from, String to) {}
}

View File

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

View File

@ -1,7 +1,9 @@
import 'dart:collection';
import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/keyed_property.dart';
import 'package:rive/src/rive_core/component.dart';
import 'package:rive/src/generated/animation/keyed_object_base.dart';
import 'linear_animation.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> {
final HashMap<int, KeyedProperty> _keyedProperties =
HashMap<int, KeyedProperty>();
Iterable<KeyedProperty> get keyedProperties => _keyedProperties.values;
@override
void onAddedDirty() {}
@override
void onAdded() {}
@override
bool validate() {
if (!super.validate()) {
return false;
}
var component = context.resolve<Component>(objectId);
if (component == null) {
return false;
}
return true;
}
@ -33,14 +41,22 @@ class KeyedObject extends KeyedObjectBase<RuntimeArtboard> {
bool isValidKeyedProperty(KeyedProperty property) {
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) {
return false;
}
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) {
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) {
return false;
}
@ -48,11 +64,17 @@ class KeyedObject extends KeyedObjectBase<RuntimeArtboard> {
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) {
var removed = _keyedProperties.remove(property.propertyKey);
if (_keyedProperties.isEmpty) {
// Remove this keyed property.
context.removeObject(this);
}
// assert(removed == null || removed == property,
// '$removed was not $property or null');
return removed != null;
}
@ -68,6 +90,7 @@ class KeyedObject extends KeyedObjectBase<RuntimeArtboard> {
@override
void objectIdChanged(int from, int to) {}
@override
bool import(ImportStack stack) {
var animationHelper =
@ -76,6 +99,7 @@ class KeyedObject extends KeyedObjectBase<RuntimeArtboard> {
return false;
}
animationHelper.addKeyedObject(this);
return super.import(stack);
}
}

View File

@ -1,6 +1,7 @@
import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/keyed_object.dart';
import 'package:rive/src/rive_core/animation/keyframe.dart';
import '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 = [];
Iterable<T> get keyframes => _keyframes;
set keyframes(Iterable<T> frames) => _keyframes = frames.toList();
/// Get the keyframe immediately following the provided one.
T? after(T keyframe) {
var index = _keyframes.indexOf(keyframe);
if (index != -1 && index + 1 < _keyframes.length) {
@ -20,12 +23,15 @@ class KeyFrameList<T extends KeyFrameInterface> {
return null;
}
/// Find the index in the keyframe list of a specific time frame.
int indexOfFrame(int frame) {
int idx = 0;
// Binary find the keyframe index.
int mid = 0;
int closestFrame = 0;
int start = 0;
int end = _keyframes.length - 1;
while (start <= end) {
mid = (start + end) >> 1;
closestFrame = _keyframes[mid].frame;
@ -37,6 +43,7 @@ class KeyFrameList<T extends KeyFrameInterface> {
idx = start = mid;
break;
}
idx = start;
}
return idx;
@ -49,13 +56,17 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
with KeyFrameList<KeyFrame> {
@override
void onAdded() {}
@override
void onAddedDirty() {}
@override
void 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) {
if (_keyframes.contains(frame)) {
return false;
@ -65,44 +76,64 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
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) {
var removed = _keyframes.remove(frame);
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);
}
return removed;
}
void _checkShouldRemove() {
if (_keyframes.isEmpty) {
// Remove this keyed property.
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() {
context.dirty(_sortAndValidateKeyFrames);
}
void _sortAndValidateKeyFrames() {
sort();
for (int i = 0; i < _keyframes.length - 1; i++) {
var a = _keyframes[i];
var b = _keyframes[i + 1];
if (a.frame == b.frame) {
// N.B. this removes it from the list too.
context.removeObject(a);
// Repeat current.
i--;
}
}
}
/// Number of keyframes for this keyed property.
int get numFrames => _keyframes.length;
KeyFrame getFrameAt(int index) => _keyframes[index];
int closestFrameIndex(double seconds) {
int idx = 0;
// Binary find the keyframe index (use timeInSeconds here as opposed to the
// finder above which operates in frames).
int mid = 0;
double closestSeconds = 0;
int start = 0;
int end = _keyframes.length - 1;
while (start <= end) {
mid = (start + end) >> 1;
closestSeconds = _keyframes[mid].seconds;
@ -123,10 +154,12 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
if (_keyframes.isEmpty) {
return;
}
int idx = closestFrameIndex(seconds);
int pk = propertyKey;
if (idx == 0) {
var first = _keyframes[0];
first.apply(object, pk, mix);
} else {
if (idx < _keyframes.length) {
@ -135,6 +168,8 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
if (seconds == toFrame.seconds) {
toFrame.apply(object, pk, mix);
} else {
/// Equivalent to fromFrame.interpolation ==
/// KeyFrameInterpolation.hold.
if (fromFrame.interpolationType == 0) {
fromFrame.apply(object, pk, mix);
} else {
@ -143,6 +178,7 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
}
} else {
var last = _keyframes[idx - 1];
last.apply(object, pk, mix);
}
}
@ -150,6 +186,7 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
@override
void propertyKeyChanged(int from, int to) {}
@override
bool import(ImportStack stack) {
var importer = stack.latest<KeyedObjectImporter>(KeyedObjectBase.typeKey);
@ -157,6 +194,7 @@ class KeyedProperty extends KeyedPropertyBase<RuntimeArtboard>
return false;
}
importer.addKeyedProperty(this);
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/keyframe_interpolation.dart';
import 'package:rive/src/rive_core/animation/linear_animation.dart';
import 'package:rive/src/generated/animation/keyframe_base.dart';
export 'package:rive/src/generated/animation/keyframe_base.dart';
abstract class KeyFrame extends KeyFrameBase<RuntimeArtboard>
implements KeyFrameInterface {
double _timeInSeconds = 0;
double get seconds => _timeInSeconds;
bool get canInterpolate => true;
KeyFrameInterpolation get interpolation =>
KeyFrameInterpolation.values[interpolationType];
set interpolation(KeyFrameInterpolation value) {
@ -19,13 +23,17 @@ abstract class KeyFrame extends KeyFrameBase<RuntimeArtboard>
@override
void interpolationTypeChanged(int from, int to) {}
@override
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);
}
@override
void onAdded() {}
void computeSeconds(LinearAnimation animation) {
_timeInSeconds = frame / animation.fps;
}
@ -44,15 +52,22 @@ abstract class KeyFrame extends KeyFrameBase<RuntimeArtboard>
@override
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);
/// 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,
covariant KeyFrame nextFrame, double mix);
Interpolator? _interpolator;
Interpolator? get interpolator => _interpolator;
set interpolator(Interpolator? value) {
if (_interpolator == value) {
return;
}
_interpolator = value;
interpolatorId = value?.id ?? Core.missingId;
}
@ -65,6 +80,7 @@ abstract class KeyFrame extends KeyFrameBase<RuntimeArtboard>
return false;
}
keyedPropertyHelper.addKeyFrame(this);
return super.import(importStack);
}
}

View File

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

View File

@ -22,13 +22,16 @@ class KeyFrameDouble extends KeyFrameDoubleBase {
@override
void apply(Core<CoreContext> object, int propertyKey, double mix) =>
_apply(object, propertyKey, mix, value);
@override
void applyInterpolation(Core<CoreContext> object, int propertyKey,
double currentTime, KeyFrameDouble nextFrame, double mix) {
var f = (currentTime - seconds) / (nextFrame.seconds - seconds);
if (interpolator != null) {
f = interpolator!.transform(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 {
@override
bool get canInterpolate => false;
@override
void apply(Core<CoreContext> object, int propertyKey, double mix) {
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/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_transition.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 {
final StateTransitions _transitions = StateTransitions();
StateTransitions get transitions => _transitions;
@override
void onAdded() {}
@override
void onAddedDirty() {}
void internalAddTransition(StateTransition transition) {
assert(!_transitions.contains(transition),
'shouldn\'t already contain the transition');
_transitions.add(transition);
}
@ -24,6 +30,8 @@ abstract class LayerState extends LayerStateBase {
super.onRemoved();
}
StateInstance makeInstance();
@override
bool import(ImportStack stack) {
var importer =
@ -32,6 +40,7 @@ abstract class LayerState extends LayerStateBase {
return false;
}
importer.addState(this);
return super.import(stack);
}
}

View File

@ -1,4 +1,5 @@
import 'dart:collection';
import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/keyed_object.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';
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>();
/// The metadata for the objects that are keyed in this animation.
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) {
if (internalCheckAddKeyedObject(object)) {
_keyedObjects[object.objectId] = object;
@ -19,6 +28,9 @@ class LinearAnimation extends LinearAnimationBase {
bool internalCheckAddKeyedObject(KeyedObject object) {
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) {
return false;
}
@ -29,6 +41,13 @@ class LinearAnimation extends LinearAnimationBase {
double get endSeconds =>
(enableWorkArea ? workEnd : duration).toDouble() / fps;
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}) {
for (final keyedObject in _keyedObjects.values) {
keyedObject.apply(time, mix, coreContext);
@ -37,20 +56,28 @@ class LinearAnimation extends LinearAnimationBase {
Loop get loop => Loop.values[loopValue];
set loop(Loop value) => loopValue = value.index;
@override
void durationChanged(int from, int to) {}
@override
void enableWorkAreaChanged(bool from, bool to) {}
@override
void fpsChanged(int from, int to) {}
@override
void loopValueChanged(int from, int to) {}
@override
void speedChanged(double from, double to) {}
@override
void workEndChanged(int from, int to) {}
@override
void workStartChanged(int from, int to) {}
@override
bool import(ImportStack stack) {
var artboardImporter = stack.latest<ArtboardImporter>(ArtboardBase.typeKey);
@ -58,6 +85,7 @@ class LinearAnimation extends LinearAnimationBase {
return false;
}
artboardImporter.addAnimation(this);
return super.import(stack);
}
}

View File

@ -11,47 +11,73 @@ class LinearAnimationInstance {
bool get didLoop => _didLoop;
double _spilledTime = 0;
double get spilledTime => _spilledTime;
double get totalTime => _totalTime;
double get lastTotalTime => _lastTotalTime;
LinearAnimationInstance(this.animation)
: _time =
(animation.enableWorkArea ? animation.workStart : 0).toDouble() /
animation.fps;
/// Note that when time is set, the direction will be changed to 1
set time(double value) {
if (_time == value) {
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;
_time = _totalTime = value;
_lastTotalTime = _totalTime - diff;
_direction = 1;
}
/// Returns the current time position of the animation in seconds
double get time => _time;
/// Direction should only be +1 or -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;
/// Returns the end time of the animation in seconds
double get endTime =>
(animation.enableWorkArea ? animation.workEnd : animation.duration)
.toDouble() /
animation.fps;
/// Returns the start time of the animation in seconds
double get startTime =>
(animation.enableWorkArea ? animation.workStart : 0).toDouble() /
animation.fps;
double get progress => (_time - startTime) / (endTime - startTime);
/// Resets the animation to the starting frame
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 advance(double elapsedSeconds) {
var deltaSeconds = elapsedSeconds * animation.speed * _direction;
_lastTotalTime = _totalTime;
_totalTime += deltaSeconds;
_time += deltaSeconds;
double frames = _time * animation.fps;
var start = animation.enableWorkArea ? animation.workStart : 0;
var end = animation.enableWorkArea ? animation.workEnd : animation.duration;
var range = end - start;
bool keepGoing = true;
_didLoop = false;
_spilledTime = 0;
switch (animation.loop) {
case Loop.oneShot:
if (frames > end) {
@ -72,6 +98,7 @@ class LinearAnimationInstance {
}
break;
case Loop.pingPong:
// ignore: literal_only_boolean_expressions
while (true) {
if (_direction == 1 && frames >= end) {
_spilledTime = (frames - end) / animation.fps;
@ -86,6 +113,11 @@ class LinearAnimationInstance {
_time = frames / animation.fps;
_didLoop = true;
} 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;
}
}

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>();
final StateMachineComponents<StateMachineLayer> layers =
StateMachineComponents<StateMachineLayer>();
@override
bool import(ImportStack stack) {
var artboardImporter = stack.latest<ArtboardImporter>(ArtboardBase.typeKey);
@ -17,6 +18,7 @@ class StateMachine extends StateMachineBase {
return false;
}
artboardImporter.addStateMachine(this);
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 {
@override
void valueChanged(bool from, bool to) {}
@override
bool isValidType<T>() => T == bool;
@override
dynamic get controllerValue => value;
}

View File

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

View File

@ -1,4 +1,5 @@
import 'dart:collection';
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/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 {
static final StateMachineInput unknown = _StateMachineUnknownInput();
@override
ListBase<StateMachineComponent> machineComponentList(StateMachine machine) =>
machine.inputs;
bool isValidType<T>() => false;
dynamic get controllerValue => null;
}

View File

@ -1,4 +1,5 @@
import 'dart:collection';
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/exit_state.dart';
@ -13,12 +14,17 @@ class StateMachineLayer extends StateMachineLayerBase {
LayerState? _entryState;
LayerState? _anyState;
LayerState? _exitState;
LayerState? get entryState => _entryState;
LayerState? get anyState => _anyState;
LayerState? get exitState => _exitState;
@override
ListBase<StateMachineComponent> machineComponentList(StateMachine machine) =>
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) {
switch (state.coreType) {
case AnyStateBase.typeKey:
@ -31,6 +37,7 @@ class StateMachineLayer extends StateMachineLayerBase {
_entryState = state;
break;
}
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/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 {
@override
void valueChanged(double from, double to) {}
@override
bool isValidType<T>() => T == double;
@override
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 {
bool _triggered = false;
bool get triggered => _triggered;
void fire() {
_triggered = true;
}
@ -14,6 +15,7 @@ class StateMachineTrigger extends StateMachineTriggerBase {
@override
bool isValidType<T>() => T == bool;
@override
dynamic get controllerValue => _triggered;
}

View File

@ -1,24 +1,40 @@
import 'dart:collection';
import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/animation_state.dart';
import 'package:rive/src/rive_core/animation/animation_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/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/rive_core/state_transition_flags.dart';
export 'package:rive/src/generated/animation/state_transition_base.dart';
enum AllowTransition { no, waitingForExit, yes }
class StateTransition extends StateTransitionBase {
final StateTransitionConditions conditions = StateTransitionConditions();
LayerState? stateTo;
static final StateTransition unknown = StateTransition();
@override
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
void onAdded() {}
@override
void onAddedDirty() {}
@override
void onRemoved() {
super.onRemoved();
@ -27,6 +43,11 @@ class StateTransition extends StateTransitionBase {
bool get isDisabled => (flags & StateTransitionFlags.disabled) != 0;
bool get pauseOnExit => (flags & StateTransitionFlags.pauseOnExit) != 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) {
if (duration == 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}) {
if ((flags & StateTransitionFlags.exitTimeIsPercentage) != 0) {
var animationDuration = 0.0;
var start = 0.0;
if (stateFrom is AnimationState) {
start = absolute ? stateFrom.animation?.startSeconds ?? 0 : 0;
animationDuration = stateFrom.animation?.durationSeconds ?? 0;
var exitAnimation = exitTimeAnimation(stateFrom);
if (exitAnimation != null) {
start = absolute ? exitAnimation.startSeconds : 0;
animationDuration = exitAnimation.durationSeconds;
}
return start + exitTime / 100 * animationDuration;
} else {
return exitTime / 1000;
@ -64,28 +101,89 @@ class StateTransition extends StateTransitionBase {
return false;
}
importer.addTransition(this);
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) {
if (conditions.contains(condition)) {
return false;
}
conditions.add(condition);
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) {
var removed = conditions.remove(condition);
return removed;
}
@override
void flagsChanged(int from, int to) {}
@override
void durationChanged(int from, int to) {}
@override
void exitTimeChanged(int from, int to) {}
@override
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 {
@override
bool validate() => super.validate() && (input is StateMachineBool);
@override
bool evaluate(HashMap<int, dynamic> values) {
if (input is! StateMachineBool) {

View File

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

View File

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

View File

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

View File

@ -4,6 +4,9 @@ export 'package:rive/src/generated/animation/transition_value_condition_base.dar
abstract class TransitionValueCondition extends TransitionValueConditionBase {
TransitionConditionOp get op => TransitionConditionOp.values[opValue];
@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 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/animation.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/shape_paint_container.dart';
import 'package:rive/src/utilities/dependency_sorter.dart';
import 'package:rive/src/generated/artboard_base.dart';
export 'package:rive/src/generated/artboard_base.dart';
class Artboard extends ArtboardBase with ShapePaintContainer {
/// Artboard are one of the few (only?) components that can be orphaned.
@override
bool get canBeOrphaned => true;
final Path path = Path();
List<Component> _dependencyOrder = [];
final List<Drawable> _drawables = [];
final List<DrawRules> _rules = [];
List<DrawTarget> _sortedDrawRules = [];
final Set<Component> _components = {};
List<Drawable> get drawables => _drawables;
final AnimationList _animations = AnimationList();
/// List of animations in this artboard.
AnimationList get animations => _animations;
/// Does this artboard have animations?
bool get hasAnimations => _animations.isNotEmpty;
int _dirtDepth = 0;
int _dirt = 255;
void forEachComponent(void Function(Component) callback) =>
_components.forEach(callback);
@override
Artboard get artboard => this;
Vec2D get originWorld {
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 didUpdate = false;
if ((_dirt & ComponentDirt.drawOrder) != 0) {
sortDrawOrder();
_dirt &= ~ComponentDirt.drawOrder;
@ -51,6 +70,8 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
int count = _dependencyOrder.length;
while ((_dirt & ComponentDirt.components) != 0 && step < maxSteps) {
_dirt &= ~ComponentDirt.components;
// Track dirt depth here so that if something else marks
// dirty, we restart.
for (int i = 0; i < count; i++) {
Component component = _dependencyOrder[i];
_dirtDepth = i;
@ -71,6 +92,7 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
return didUpdate;
}
/// Update any dirty components in this artboard.
bool advance(double elapsedSeconds) {
bool didUpdate = false;
for (final controller in _animationControllers) {
@ -93,6 +115,10 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
context.markNeedsAdvance();
_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) {
_dirtDepth = component.graphOrder;
}
@ -100,17 +126,24 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
@override
bool resolveArtboard() => true;
/// Sort the DAG for resolution in order of dependencies such that dependent
/// compnents process after their dependencies.
void sortDependencies() {
var optimistic = DependencySorter<Component>();
var order = optimistic.sort(this);
if (order.isEmpty) {
// cycle detected, use a more robust solver
var robust = TarjansDependencySorter<Component>();
order = robust.sort(this);
}
_dependencyOrder = order;
for (final component in _dependencyOrder) {
component.graphOrder = graphOrder++;
// component.dirt = 255;
}
_dirt |= ComponentDirt.components;
}
@ -145,16 +178,23 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
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) {
if (!_components.add(component)) {
return;
}
}
/// Remove a component from the artboard and its various tracked lists of
/// components.
void removeComponent(Component component) {
_components.remove(component);
}
/// Let the artboard know that the drawables need to be resorted before
/// drawing next.
void markDrawOrderDirty() {
if ((dirt & ComponentDirt.drawOrder) == 0) {
context.markNeedsAdvance();
@ -162,13 +202,25 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
}
}
/// Draw the drawable components in this artboard.
void draw(Canvas canvas) {
canvas.save();
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);
for (final fill in fills) {
fill.draw(canvas, path);
}
for (var drawable = _firstDrawable;
drawable != null;
drawable = drawable.prev) {
@ -180,8 +232,10 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
canvas.restore();
}
/// Our world transform is always the identity. Artboard defines world space.
@override
Mat2D get worldTransform => Mat2D();
@override
void originXChanged(double from, double to) {
addDirt(ComponentDirt.worldTransform);
@ -192,20 +246,31 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
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) {
if (_animations.contains(animation)) {
return false;
}
_animations.add(animation);
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 removed = _animations.remove(animation);
return removed;
}
/// The animation controllers that are called back whenever the artboard
/// advances.
final Set<RiveAnimationController> _animationControllers = {};
/// Add an animation controller to this artboard. Playing will be scheduled if
/// it's already playing.
bool addController(RiveAnimationController controller) {
if (_animationControllers.contains(controller) ||
!controller.init(context)) {
@ -219,6 +284,7 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
return true;
}
/// Remove an animation controller form this artboard.
bool removeController(RiveAnimationController controller) {
if (_animationControllers.remove(controller)) {
controller.isActiveChanged.removeListener(_onControllerPlayingChanged);
@ -229,25 +295,37 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
}
void _onControllerPlayingChanged() => context.markNeedsAdvance();
@override
void onFillsChanged() {}
@override
void onPaintMutatorChanged(ShapePaintMutator mutator) {}
@override
void onStrokesChanged() {}
@override
Vec2D get worldTranslation => Vec2D();
Drawable? _firstDrawable;
void computeDrawOrder() {
_drawables.clear();
_rules.clear();
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();
// Make sure all dependents are empty.
for (final nodeRules in _rules) {
for (final target in nodeRules.targets) {
target.dependents.clear();
}
}
// Now build up the dependencies.
for (final nodeRules in _rules) {
for (final target in nodeRules.targets) {
root.dependents.add(target);
@ -259,19 +337,25 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
}
}
}
var sorter = DependencySorter<Component>();
_sortedDrawRules = sorter.sort(root).cast<DrawTarget>().skip(1).toList();
sortDrawOrder();
}
void sortDrawOrder() {
// Clear out rule first/last items.
for (final rule in _sortedDrawRules) {
rule.first = rule.last = null;
}
_firstDrawable = null;
Drawable? lastDrawable;
for (final drawable in _drawables) {
var rules = drawable.flattenedDrawRules;
var target = rules?.activeTarget;
if (target != null) {
if (target.first == null) {
@ -294,6 +378,7 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
}
}
}
for (final rule in _sortedDrawRules) {
if (rule.first == null) {
continue;
@ -323,6 +408,7 @@ class Artboard extends ArtboardBase with ShapePaintContainer {
break;
}
}
_firstDrawable = lastDrawable;
}
}

View File

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

View File

@ -22,6 +22,9 @@ class Bone extends BoneBase {
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) {
for (final child in children) {
if (child.coreType == BoneBase.typeKey) {
@ -35,6 +38,7 @@ class Bone extends BoneBase {
@override
double get x => (parent as Bone).length;
@override
set x(double value) {
throw UnsupportedError('not expected to set x on a bone.');
@ -42,6 +46,7 @@ class Bone extends BoneBase {
@override
double get y => 0;
@override
set y(double value) {
throw UnsupportedError('not expected to set y on a bone.');
@ -49,6 +54,9 @@ class Bone extends BoneBase {
@override
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);
}
}

View File

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

View File

@ -1,24 +1,37 @@
import 'dart:typed_data';
import 'package:rive/src/rive_core/bones/skinnable.dart';
import 'package:rive/src/rive_core/bones/tendon.dart';
import 'package:rive/src/rive_core/component.dart';
import 'package:rive/src/rive_core/math/mat2d.dart';
import 'package:rive/src/rive_core/shapes/path_vertex.dart';
import '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 {
final List<Tendon> _tendons = [];
List<Tendon> get tendons => _tendons;
Float32List _boneTransforms = Float32List(0);
final Mat2D _worldTransform = Mat2D();
@override
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();
}
@override
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;
if (_boneTransforms.length != size) {
_boneTransforms = Float32List(size);
@ -29,6 +42,7 @@ class Skin extends SkinBase {
_boneTransforms[4] = 0;
_boneTransforms[5] = 0;
}
var temp = Mat2D();
var bidx = 6;
for (final tendon in _tendons) {
@ -79,6 +93,8 @@ class Skin extends SkinBase {
@override
void 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) {
tendon.bone?.addDependent(this);
}
@ -110,6 +126,7 @@ class Skin extends SkinBase {
markRebuildDependencies();
}
parent?.markRebuildDependencies();
break;
}
}

View File

@ -1,18 +1,27 @@
import 'package:rive/src/rive_core/bones/skin.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 {
// _skin is null when this object isn't connected to bones.
Skin? _skin;
Skin? get skin => _skin;
void appendChild(Component child);
// ignore: use_setters_to_change_properties
void addSkin(Skin skin) {
// Notify old skin/maybe support multiple skins in the future?
_skin = skin;
markSkinDirty();
}
void removeSkin(Skin skin) {
if (_skin == skin) {
_skin = null;
markSkinDirty();
}
}

View File

@ -8,6 +8,7 @@ class Tendon extends TendonBase {
Mat2D? _inverseBind;
SkeletalComponent? _bone;
SkeletalComponent? get bone => _bone;
Mat2D get inverseBind {
if (_inverseBind == null) {
_inverseBind = Mat2D();
@ -17,11 +18,16 @@ class Tendon extends TendonBase {
}
@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
void onAddedDirty() {
super.onAddedDirty();
_bone = context.resolve(boneId);
_bind[0] = xx;
_bind[1] = xy;
_bind[2] = yx;
@ -32,6 +38,7 @@ class Tendon extends TendonBase {
@override
void update(int dirt) {}
@override
void txChanged(double from, double to) {
_bind[4] = to;

View File

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

View File

@ -1,19 +1,31 @@
/// Helper to abstract changing weighted values on a vertex.
abstract class WeightedVertex {
int get weights;
int get weightIndices;
set weights(int value);
set weightIndices(int value);
/// Set the weight of this vertex for a specific tendon.
void setWeight(int tendonIndex, int tendonCount, double weight) {
int tendonWeightIndex =
_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;
int totalWeight = tendonWeights.fold(
0, (value, tendonWeight) => value + tendonWeight.weight);
var vertexTendons =
tendonWeights.where((tendonWeight) => tendonWeight.tendon != 0);
const maxWeight = 255;
var remainder = maxWeight - totalWeight;
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;
_setTendonWeight(
patchTendonIndex, tendonCount == 1 ? maxWeight : remainder);
@ -40,6 +52,8 @@ abstract class WeightedVertex {
void _sortWeights() {
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));
for (int i = 0; i < tendonWeights.length; i++) {
final tw = tendonWeights[i];
@ -54,14 +68,16 @@ abstract class WeightedVertex {
_WeightHelper(2, (weightIndices >> 16) & 0xFF, _getRawWeight(2)),
_WeightHelper(3, (weightIndices >> 24) & 0xFF, _getRawWeight(3))
];
int _setTendonWeight(int tendonIndex, int weight) {
var indices = weightIndices;
var bonesIndices = [
indices & 0xFF,
(indices >> 8) & 0xFF,
(indices >> 16) & 0xFF,
(indices >> 24) & 0xFF
(indices >> 24) & 0xFF,
];
int setWeightIndex = -1;
for (int i = 0; i < 4; i++) {
if (bonesIndices[i] == tendonIndex + 1) {
@ -70,10 +86,14 @@ abstract class WeightedVertex {
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) {
int lowestWeight = double.maxFinite.toInt();
for (int i = 0; i < 4; i++) {
if (bonesIndices[i] == 0) {
// this isn't set to a bone yet, use it!
setWeightIndex = i;
break;
}
@ -83,16 +103,21 @@ abstract class WeightedVertex {
lowestWeight = weight;
}
}
_setTendonIndex(setWeightIndex, tendonIndex + 1);
_rawSetWeight(setWeightIndex, weight);
}
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) {
assert(weightIndex < 4 && weightIndex >= 0);
var indexValues = weightIndices;
// Clear the bits for this weight value.
indexValues &= ~(0xFF << (weightIndex * 8));
// Set the bits for this weight value.
weightIndices = indexValues | (tendonIndex << (weightIndex * 8));
}
@ -104,11 +129,14 @@ abstract class WeightedVertex {
void _rawSetWeight(int weightIndex, int weightValue) {
assert(weightIndex < 4 && weightIndex >= 0);
var weightValues = weights;
// Clear the bits for this weight value.
weightValues &= ~(0xFF << (weightIndex * 8));
// Set the bits for this weight value.
weights = weightValues | (weightValue << (weightIndex * 8));
}
int _getRawWeight(int weightIndex) => (weights >> (weightIndex * 8)) & 0xFF;
double getWeight(int tendonIndex) {
for (int i = 0; i < 4; i++) {
if (getTendon(i) == tendonIndex + 1) {
@ -123,5 +151,6 @@ class _WeightHelper {
final int index;
final int tendon;
int 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:rive/src/rive_core/artboard.dart';
import 'package:rive/src/rive_core/container_component.dart';
import 'package:rive/src/generated/component_base.dart';
import 'package:rive/src/utilities/dependency_sorter.dart';
import 'package:rive/src/utilities/tops.dart';
@ -11,20 +12,36 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
implements DependencyGraphNode<Component>, Parentable<Component> {
Artboard? _artboard;
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;
// Used during update process.
int graphOrder = 0;
int dirt = 0xFFFF;
// This is really only for sanity and earlying out of recursive loops.
static const int maxTreeDepth = 5000;
bool addDirt(int value, {bool recurse = false}) {
if ((dirt & value) == value) {
// Already marked.
return false;
}
// Make sure dirt is set before calling anything that can set more dirt.
dirt |= value;
onDirty(dirt);
artboard?.onComponentDirty(this);
if (!recurse) {
return true;
}
for (final d in dependents) {
d.addDirt(value, recurse: recurse);
}
@ -33,7 +50,12 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
void onDirty(int mask) {}
void update(int dirt);
/// The artboard this component belongs to.
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) {
if (_artboard == value) {
return;
@ -43,8 +65,14 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
_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
void visitAncestor(Component ancestor) {}
/// Find the artboard in the hierarchy.
bool resolveArtboard() {
int sanity = maxTreeDepth;
for (Component? curr = this;
@ -71,6 +99,7 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
}
void userDataChanged(dynamic from, dynamic to) {}
@override
void parentIdChanged(int from, int to) {
parent = context.resolve(to);
@ -79,6 +108,7 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
ContainerComponent? _parent;
@override
ContainerComponent? get parent => _parent;
set parent(ContainerComponent? value) {
if (_parent == value) {
return;
@ -93,28 +123,40 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
void parentChanged(ContainerComponent? from, ContainerComponent? to) {
from?.children.remove(this);
from?.childRemoved(this);
to?.children.add(this);
to?.childAdded(this);
// We need to resolve our artboard.
markRebuildDependencies();
}
/// Components that depend on this component.
final Set<Component> _dependents = {};
/// Components that this component depends on.
final Set<Component> _dependsOn = {};
@override
Set<Component> get dependents => _dependents;
bool addDependent(Component dependent) {
assert(artboard == dependent.artboard,
'Components must be in the same artboard.');
if (!_dependents.add(dependent)) {
return false;
}
dependent._dependsOn.add(this);
return true;
}
bool isValidParent(Component parent) => parent is ContainerComponent;
void markRebuildDependencies() {
if (!context.markDependenciesDirty(this)) {
// no context, or already dirty.
return;
}
for (final dependent in _dependents) {
@ -128,11 +170,18 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
parentDep._dependents.remove(this);
}
_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) {}
@override
void onAdded() {}
@override
void onAddedDirty() {
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
@mustCallSuper
void onRemoved() {
@ -148,14 +202,22 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
parentDep._dependents.remove(this);
}
_dependsOn.clear();
for (final dependent in _dependents) {
dependent.onDependencyRemoved(this);
}
_dependents.clear();
// silently clear from the parent in order to not cause any further undo
// stack changes
if (parent != null) {
parent!.children.remove(this);
parent!.childRemoved(this);
}
// The artboard containing this component will need its dependencies
// re-sorted.
if (artboard != null) {
context.markDependencyOrderDirty();
_changeArtboard(null);
@ -168,7 +230,10 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
}
@override
void nameChanged(String from, String to) {}
void nameChanged(String from, String to) {
/// Changing name doesn't really do anything.
}
@override
bool import(ImportStack stack) {
var artboardImporter = stack.latest<ArtboardImporter>(ArtboardBase.typeKey);
@ -176,6 +241,7 @@ abstract class Component extends ComponentBase<RuntimeArtboard>
return false;
}
artboardImporter.addComponent(this);
return super.import(stack);
}
}

View File

@ -1,14 +1,41 @@
class ComponentDirt {
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;
/// Draw order needs to be re-computed.
static const int drawOrder = 1 << 2;
/// Draw order needs to be re-computed.
static const int naturalDrawOrder = 1 << 3;
/// Path is dirty and needs to be rebuilt.
static const int path = 1 << 4;
/// Vertices have changed, re-order cached lists.
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;
/// Used by any component that needs to update its world transform.
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;
/// Used by the gradients track when the stops need to be re-ordered.
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;
/// Set when blend modes need to be updated.
static const int blendMode = 1 << 11;
}

View File

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

View File

@ -19,7 +19,11 @@ abstract class ContainerComponent extends ContainerComponentBase {
@mustCallSuper
void childAdded(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) {
if (cb(this) == false) {
return false;
@ -28,17 +32,27 @@ abstract class ContainerComponent extends ContainerComponentBase {
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) {
for (final child in children) {
if (cb(child) == false) {
continue;
}
// TODO: replace with a more robust check.
if (child is ContainerComponent) {
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() {
Set<Component> deathRow = {this};
forEachChild((child) => deathRow.add(child));

View File

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

View File

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

View File

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

View File

@ -1,6 +1,8 @@
import 'package:flutter/foundation.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 {
void notify() => notifyListeners();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,12 @@
import 'package:flutter/foundation.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> {
final _isActive = ValueNotifier<bool>(false);
ValueListenable<bool> get isActiveChanged => _isActive;
bool get isActive => _isActive.value;
set isActive(bool value) {
if (_isActive.value != value) {
@ -20,7 +23,11 @@ abstract class RiveAnimationController<T> {
void onActivate() {}
@protected
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);
bool init(T core) => true;
void dispose() {}
}

View File

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

View File

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

View File

@ -1,11 +1,15 @@
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_unsupported_version_exception.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 {
final int major;
final int minor;
const RuntimeVersion(this.major, this.minor);
String versionString() {
return '$major.$minor';
@ -17,19 +21,26 @@ const riveVersion = RuntimeVersion(7, 0);
class RuntimeHeader {
static const String fingerprint = 'RIVE';
final RuntimeVersion version;
final int fileId;
final HashMap<int, int> propertyToFieldIndex;
RuntimeHeader(
{required this.fileId,
RuntimeHeader({
required this.fileId,
required this.version,
required this.propertyToFieldIndex});
required this.propertyToFieldIndex,
});
factory RuntimeHeader.read(BinaryReader reader) {
var fingerprint = RuntimeHeader.fingerprint.codeUnits;
for (int i = 0; i < fingerprint.length; i++) {
if (reader.readUint8() != fingerprint[i]) {
throw const RiveFormatErrorException('Fingerprint doesn\'t match.');
}
}
int readMajorVersion = reader.readVarUint();
int readMinorVersion = reader.readVarUint();
if (readMajorVersion > riveVersion.major) {
@ -40,7 +51,9 @@ class RuntimeHeader {
reader.readVarUint();
}
int fileId = reader.readVarUint();
var propertyFields = HashMap<int, int>();
var propertyKeys = <int>[];
for (int propertyKey = reader.readVarUint();
propertyKey != 0;
@ -58,9 +71,11 @@ class RuntimeHeader {
propertyFields[propertyKey] = fieldIndex;
currentBit += 2;
}
return RuntimeHeader(
fileId: fileId,
version: RuntimeVersion(readMajorVersion, readMinorVersion),
propertyToFieldIndex: propertyFields);
propertyToFieldIndex: propertyFields,
);
}
}

View File

@ -1,4 +1,5 @@
import 'dart:ui';
import 'package:rive/src/rive_core/component_dirt.dart';
import 'package:rive/src/rive_core/node.dart';
import 'package:rive/src/rive_core/shapes/shape.dart';
@ -10,19 +11,25 @@ class ClippingShape extends ClippingShapeBase {
final List<Shape> _shapes = [];
PathFillType get fillType => PathFillType.values[fillRule];
set fillType(PathFillType type) => fillRule = type.index;
Node _source = Node.unknown;
Node get source => _source;
set source(Node value) {
if (_source == value) {
return;
}
_source = value;
sourceId = value.id;
}
@override
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);
addDirt(ComponentDirt.path);
}
@ -44,10 +51,13 @@ class ClippingShape extends ClippingShapeBase {
_source.forAll((component) {
if (component is Shape) {
_shapes.add(component);
//component.addDependent(this);
component.pathComposer.addDependent(this);
}
return true;
});
// make sure we rebuild the clipping path.
addDirt(ComponentDirt.path);
}
@ -60,6 +70,8 @@ class ClippingShape extends ClippingShapeBase {
@override
void update(int dirt) {
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.fillType = fillType;
for (final shape in _shapes) {
@ -75,6 +87,7 @@ class ClippingShape extends ClippingShapeBase {
@override
void isVisibleChanged(bool from, bool to) {
// Redraw
_source.addDirt(ComponentDirt.paint);
}
}

View File

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

View File

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

View File

@ -1,4 +1,5 @@
import 'dart:math';
import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/component_dirt.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 {
CubicMirroredVertex();
/// Makes a vertex that is disconnected from core.
CubicMirroredVertex.procedural() {
InternalCoreHelper.markValid(this);
}
Vec2D? _inPoint;
Vec2D? _outPoint;
@override
Vec2D get outPoint {
return _outPoint ??= Vec2D.add(Vec2D(), translation,

View File

@ -1,4 +1,5 @@
import 'dart:typed_data';
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/vec2d.dart';
@ -8,15 +9,20 @@ export 'package:rive/src/generated/shapes/cubic_vertex_base.dart';
abstract class CubicVertex extends CubicVertexBase {
Vec2D get outPoint;
Vec2D get inPoint;
set outPoint(Vec2D value);
set inPoint(Vec2D value);
@override
Vec2D get renderTranslation => weight?.translation ?? super.renderTranslation;
Vec2D get renderIn => weight?.inTranslation ?? inPoint;
Vec2D get renderOut => weight?.outTranslation ?? outPoint;
@override
void deform(Mat2D world, Float32List boneTransforms) {
super.deform(world, boneTransforms);
Weight.deform(outPoint[0], outPoint[1], weight!.outIndices,
weight!.outValues, world, boneTransforms, weight!.outTranslation);
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/path_vertex.dart';
import 'package:rive/src/generated/shapes/ellipse_base.dart';
export 'package:rive/src/generated/shapes/ellipse_base.dart';
class Ellipse extends EllipseBase {
@ -9,6 +10,7 @@ class Ellipse extends EllipseBase {
List<PathVertex> get vertices {
double ox = -originX * width + radiusX;
double oy = -originY * height + radiusY;
return [
CubicDetachedVertex.fromValues(
x: ox,
@ -16,28 +18,32 @@ class Ellipse extends EllipseBase {
inX: ox - radiusX * circleConstant,
inY: oy - radiusY,
outX: ox + radiusX * circleConstant,
outY: oy - radiusY),
outY: oy - radiusY,
),
CubicDetachedVertex.fromValues(
x: ox + radiusX,
y: oy,
inX: ox + radiusX,
inY: oy + circleConstant * -radiusY,
outX: ox + radiusX,
outY: oy + circleConstant * radiusY),
outY: oy + circleConstant * radiusY,
),
CubicDetachedVertex.fromValues(
x: ox,
y: oy + radiusY,
inX: ox + radiusX * circleConstant,
inY: oy + radiusY,
outX: ox - radiusX * circleConstant,
outY: oy + radiusY),
outY: oy + radiusY,
),
CubicDetachedVertex.fromValues(
x: ox - radiusX,
y: oy,
inX: ox - radiusX,
inY: oy + radiusY * circleConstant,
outX: ox - radiusX,
outY: oy - radiusY * circleConstant)
outY: oy - radiusY * circleConstant,
),
];
}

View File

@ -1,19 +1,28 @@
import 'dart:ui';
import 'package:rive/src/rive_core/component_dirt.dart';
import 'package:rive/src/rive_core/shapes/shape_paint_container.dart';
import '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 {
@override
Paint makePaint() => Paint()..style = PaintingStyle.fill;
PathFillType get fillType => PathFillType.values[fillRule];
set fillType(PathFillType type) => fillRule = type.index;
@override
void fillRuleChanged(int from, int to) =>
parent?.addDirt(ComponentDirt.paint);
@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
void onAdded() {
super.onAdded();

View File

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

View File

@ -1,15 +1,24 @@
import 'dart:ui' as ui;
import 'package:meta/meta.dart';
import 'package:rive/src/rive_core/component.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/shapes/paint/gradient_stop.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';
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 {
/// Stored list of core gradient stops are in the hierarchy as children of
/// this container.
final List<GradientStop> gradientStops = [];
bool _paintsInWorldSpace = true;
bool get paintsInWorldSpace => _paintsInWorldSpace;
set paintsInWorldSpace(bool value) {
@ -22,8 +31,11 @@ class LinearGradient extends LinearGradientBase with ShapePaintMutator {
Vec2D get start => Vec2D.fromValues(startX, startY);
Vec2D get end => Vec2D.fromValues(endX, endY);
ui.Offset get startOffset => ui.Offset(startX, startY);
ui.Offset get endOffset => ui.Offset(endX, endY);
/// Gradients depends on their shape.
@override
void buildDependencies() {
super.buildDependencies();
@ -35,6 +47,7 @@ class LinearGradient extends LinearGradientBase with ShapePaintMutator {
super.childAdded(child);
if (child is GradientStop && !gradientStops.contains(child)) {
gradientStops.add(child);
markStopsDirty();
}
}
@ -44,31 +57,48 @@ class LinearGradient extends LinearGradientBase with ShapePaintMutator {
super.childRemoved(child);
if (child is GradientStop && gradientStops.contains(child)) {
gradientStops.remove(child);
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);
/// 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);
@override
void update(int dirt) {
// Do the stops need to be re-ordered?
bool stopsChanged = dirt & ComponentDirt.stops != 0;
if (stopsChanged) {
gradientStops.sort((a, b) => a.position.compareTo(b.position));
}
bool worldTransformed = dirt & ComponentDirt.worldTransform != 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 ||
localTransformed ||
(paintsInWorldSpace && worldTransformed);
if (rebuildGradient) {
// build up the color and positions lists
var colors = <ui.Color>[];
var colorPositions = <double>[];
for (final stop in gradientStops) {
colors.add(stop.color);
colorPositions.add(stop.position);
}
// Check if we need to update the world space gradient.
if (paintsInWorldSpace) {
// Get the start and end of the gradient in world coordinates (world
// transform of the shape).
var world = shapePaintContainer!.worldTransform;
var worldStart = Vec2D.transformMat2D(Vec2D(), start, 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,
List<ui.Color> colors, List<double> colorPositions) =>
ui.Gradient.linear(start, end, colors, colorPositions);
@override
void startXChanged(double from, double to) {
addDirt(ComponentDirt.transform);
@ -114,6 +145,8 @@ class LinearGradient extends LinearGradientBase with ShapePaintMutator {
@override
void opacityChanged(double from, double to) {
syncColor();
// We don't need to rebuild anything, just let our shape know we should
// repaint.
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';
class RadialGradient extends RadialGradientBase {
/// We override the make gradient operation to create a radial gradient
/// instead of a linear one.
@override
ui.Gradient makeGradient(ui.Offset start, ui.Offset end,
List<ui.Color> colors, List<double> colorPositions) =>

View File

@ -1,34 +1,48 @@
import 'dart:ui';
import 'package:meta/meta.dart';
import 'package:rive/src/rive_core/component.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/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/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 {
late Paint _paint;
Paint get paint => _paint;
ShapePaintMutator? _paintMutator;
ShapePaintContainer? get shapePaintContainer =>
parent is ShapePaintContainer ? parent as ShapePaintContainer : null;
ShapePaint() {
_paint = makePaint();
}
BlendMode get blendMode => _paint.blendMode;
set blendMode(BlendMode value) => _paint.blendMode = value;
double get renderOpacity => _paintMutator!.renderOpacity;
set renderOpacity(double value) => _paintMutator!.renderOpacity = value;
ShapePaintMutator? get paintMutator => _paintMutator;
void _changeMutator(ShapePaintMutator? mutator) {
_paint = makePaint();
_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
Paint makePaint();
@override
void childAdded(Component child) {
super.childAdded(child);
@ -53,6 +67,7 @@ abstract class ShapePaint extends ShapePaintBase {
super.validate() &&
parent is ShapePaintContainer &&
_paintMutator != null;
@override
void isVisibleChanged(bool from, bool to) {
shapePaintContainer?.addDirt(ComponentDirt.paint);
@ -61,6 +76,8 @@ abstract class ShapePaint extends ShapePaintBase {
@override
void childRemoved(Component child) {
super.childRemoved(child);
// Make sure to clean up any references so that they can be garbage
// collected.
if (child is ShapePaintMutator &&
_paintMutator == child as ShapePaintMutator) {
_changeMutator(null);
@ -69,5 +86,6 @@ abstract class ShapePaint extends ShapePaintBase {
void _initMutator() =>
_paintMutator?.initializePaintMutator(shapePaintContainer!, paint);
void draw(Canvas canvas, Path path);
}

View File

@ -1,12 +1,17 @@
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:rive/src/rive_core/shapes/shape_paint_container.dart';
abstract class ShapePaintMutator {
ShapePaintContainer? _shapePaintContainer;
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;
Paint get paint => _paint;
double _renderOpacity = 1;
double get renderOpacity => _renderOpacity;
set renderOpacity(double value) {
@ -18,6 +23,7 @@ abstract class ShapePaintMutator {
@protected
void syncColor();
@mustCallSuper
void initializePaintMutator(ShapePaintContainer container, Paint paint) {
_shapePaintContainer = container;

View File

@ -1,10 +1,12 @@
import 'dart:ui';
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_mutator.dart';
import '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 {
Color get color => Color(colorValue);
set color(Color c) {
@ -13,12 +15,24 @@ class SolidColor extends SolidColorBase with ShapePaintMutator {
@override
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();
// 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);
}
@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
void syncColor() {
paint.color = color
@ -27,6 +41,7 @@ class SolidColor extends SolidColorBase with ShapePaintMutator {
@override
bool validate() => super.validate() && parent is ShapePaint;
@override
void onAdded() {
super.onAdded();

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