mirror of
https://github.com/rive-app/rive-flutter
synced 2025-07-05 21:55:58 +00:00
Choose events to fire on State and Transition start/end.
Allows selection of event and when to fire it per state and transition. On a state:  On a transition:  I also kept getting the annoying combo-box stuck at the bottom of the screen with these combos so low to the bottom right, so I fixed that too. Which helps the text combos too. Popups with predictable heights will try to open in the direction where they'll have most vertical space: <img width="570" alt="CleanShot 2023-08-16 at 22 30 02@2x" src="https://github.com/rive-app/rive/assets/454182/88802e09-04df-4256-b4c1-2cc2bf490fcd"> Diffs= ad4236501 Choose events to fire on State and Transition start/end. (#5830) abe5aab14 Added a Vello back-end with a custom winit viewer. (#5786) adeebb26a Implement drawImage() in PLS (#5780) Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
This commit is contained in:
@ -1 +1 @@
|
||||
8caa7d377f368af8c667ca209a2ce7aef8679be2
|
||||
ad4236501bdf0218046a93c54f2ee4a379747890
|
||||
|
@ -1,3 +1,7 @@
|
||||
## 0.11.15
|
||||
|
||||
- New event system! Listen to events reported by a StateMachine via StateMachineController.addEventListener.
|
||||
|
||||
## 0.11.14
|
||||
|
||||
- Refactor how hit testing is performed in `RiveAnimation` and `Rive` widgets. Pointer events (listeners) can now be enabled on the `Rive` widget by setting `enablePointerEvents` to `true` (default is false).
|
||||
|
@ -19,11 +19,13 @@ export 'package:rive/src/core/importers/layer_state_importer.dart';
|
||||
export 'package:rive/src/core/importers/linear_animation_importer.dart';
|
||||
export 'package:rive/src/core/importers/nested_state_machine_importer.dart';
|
||||
export 'package:rive/src/core/importers/state_machine_importer.dart';
|
||||
export 'package:rive/src/core/importers/state_machine_layer_component_importer.dart';
|
||||
export 'package:rive/src/core/importers/state_machine_layer_importer.dart';
|
||||
export 'package:rive/src/core/importers/state_machine_listener_importer.dart';
|
||||
export 'package:rive/src/core/importers/state_transition_importer.dart';
|
||||
export 'package:rive/src/event_list.dart';
|
||||
export 'package:rive/src/generated/rive_core_context.dart';
|
||||
export 'package:rive/src/layer_component_events.dart';
|
||||
export 'package:rive/src/listener_actions.dart';
|
||||
export 'package:rive/src/runtime_artboard.dart';
|
||||
export 'package:rive/src/state_machine_components.dart';
|
||||
|
@ -0,0 +1,12 @@
|
||||
import 'package:rive/src/core/importers/artboard_import_stack_object.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_fire_event.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_layer_component.dart';
|
||||
|
||||
class StateMachineLayerComponentImporter extends ArtboardImportStackObject {
|
||||
final StateMachineLayerComponent component;
|
||||
StateMachineLayerComponentImporter(this.component);
|
||||
|
||||
void addFireEvent(StateMachineFireEvent event) {
|
||||
component.internalAddFireEvent(event);
|
||||
}
|
||||
}
|
@ -7,7 +7,6 @@ class StateMachineListenerImporter extends ArtboardImportStackObject {
|
||||
StateMachineListenerImporter(this.listener);
|
||||
|
||||
void addAction(ListenerAction change) {
|
||||
// listener.context.addObject(change);
|
||||
listener.internalAddAction(change);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,71 @@
|
||||
// Core automatically generated
|
||||
// lib/src/generated/animation/state_machine_fire_event_base.dart.
|
||||
// Do not modify manually.
|
||||
|
||||
import 'package:rive/src/core/core.dart';
|
||||
|
||||
abstract class StateMachineFireEventBase<T extends CoreContext>
|
||||
extends Core<T> {
|
||||
static const int typeKey = 169;
|
||||
@override
|
||||
int get coreType => StateMachineFireEventBase.typeKey;
|
||||
@override
|
||||
Set<int> get coreTypes => {StateMachineFireEventBase.typeKey};
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// EventId field with key 392.
|
||||
static const int eventIdInitialValue = -1;
|
||||
int _eventId = eventIdInitialValue;
|
||||
static const int eventIdPropertyKey = 392;
|
||||
|
||||
/// Id of the Event referenced.
|
||||
int get eventId => _eventId;
|
||||
|
||||
/// Change the [_eventId] field value.
|
||||
/// [eventIdChanged] will be invoked only if the field's value has changed.
|
||||
set eventId(int value) {
|
||||
if (_eventId == value) {
|
||||
return;
|
||||
}
|
||||
int from = _eventId;
|
||||
_eventId = value;
|
||||
if (hasValidated) {
|
||||
eventIdChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void eventIdChanged(int from, int to);
|
||||
|
||||
/// --------------------------------------------------------------------------
|
||||
/// OccursValue field with key 393.
|
||||
static const int occursValueInitialValue = 0;
|
||||
int _occursValue = occursValueInitialValue;
|
||||
static const int occursValuePropertyKey = 393;
|
||||
|
||||
/// When the event fires.
|
||||
int get occursValue => _occursValue;
|
||||
|
||||
/// Change the [_occursValue] field value.
|
||||
/// [occursValueChanged] will be invoked only if the field's value has
|
||||
/// changed.
|
||||
set occursValue(int value) {
|
||||
if (_occursValue == value) {
|
||||
return;
|
||||
}
|
||||
int from = _occursValue;
|
||||
_occursValue = value;
|
||||
if (hasValidated) {
|
||||
occursValueChanged(from, value);
|
||||
}
|
||||
}
|
||||
|
||||
void occursValueChanged(int from, int to);
|
||||
|
||||
@override
|
||||
void copy(Core source) {
|
||||
if (source is StateMachineFireEventBase) {
|
||||
_eventId = source._eventId;
|
||||
_occursValue = source._occursValue;
|
||||
}
|
||||
}
|
||||
}
|
@ -69,6 +69,7 @@ import 'package:rive/src/rive_core/animation/nested_state_machine.dart';
|
||||
import 'package:rive/src/rive_core/animation/nested_trigger.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_bool.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_fire_event.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_layer.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_listener.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_number.dart';
|
||||
@ -225,6 +226,8 @@ class RiveCoreContext {
|
||||
return KeyFrameColor();
|
||||
case StateMachineBase.typeKey:
|
||||
return StateMachine();
|
||||
case StateMachineFireEventBase.typeKey:
|
||||
return StateMachineFireEvent();
|
||||
case EntryStateBase.typeKey:
|
||||
return EntryState();
|
||||
case StateMachineTriggerBase.typeKey:
|
||||
@ -851,6 +854,16 @@ class RiveCoreContext {
|
||||
object.value = value;
|
||||
}
|
||||
break;
|
||||
case StateMachineFireEventBase.eventIdPropertyKey:
|
||||
if (object is StateMachineFireEventBase && value is int) {
|
||||
object.eventId = value;
|
||||
}
|
||||
break;
|
||||
case StateMachineFireEventBase.occursValuePropertyKey:
|
||||
if (object is StateMachineFireEventBase && value is int) {
|
||||
object.occursValue = value;
|
||||
}
|
||||
break;
|
||||
case NestedNumberBase.nestedValuePropertyKey:
|
||||
if (object is NestedNumberBase && value is double) {
|
||||
object.nestedValue = value;
|
||||
@ -1686,6 +1699,8 @@ class RiveCoreContext {
|
||||
case StateTransitionBase.exitTimePropertyKey:
|
||||
case StateTransitionBase.interpolationTypePropertyKey:
|
||||
case StateTransitionBase.interpolatorIdPropertyKey:
|
||||
case StateMachineFireEventBase.eventIdPropertyKey:
|
||||
case StateMachineFireEventBase.occursValuePropertyKey:
|
||||
case BlendState1DBase.inputIdPropertyKey:
|
||||
case BlendStateTransitionBase.exitBlendAnimationIdPropertyKey:
|
||||
case StrokeBase.capPropertyKey:
|
||||
@ -2010,6 +2025,10 @@ class RiveCoreContext {
|
||||
return (object as StateTransitionBase).interpolationType;
|
||||
case StateTransitionBase.interpolatorIdPropertyKey:
|
||||
return (object as StateTransitionBase).interpolatorId;
|
||||
case StateMachineFireEventBase.eventIdPropertyKey:
|
||||
return (object as StateMachineFireEventBase).eventId;
|
||||
case StateMachineFireEventBase.occursValuePropertyKey:
|
||||
return (object as StateMachineFireEventBase).occursValue;
|
||||
case BlendState1DBase.inputIdPropertyKey:
|
||||
return (object as BlendState1DBase).inputId;
|
||||
case BlendStateTransitionBase.exitBlendAnimationIdPropertyKey:
|
||||
@ -2710,6 +2729,16 @@ class RiveCoreContext {
|
||||
object.interpolatorId = value;
|
||||
}
|
||||
break;
|
||||
case StateMachineFireEventBase.eventIdPropertyKey:
|
||||
if (object is StateMachineFireEventBase) {
|
||||
object.eventId = value;
|
||||
}
|
||||
break;
|
||||
case StateMachineFireEventBase.occursValuePropertyKey:
|
||||
if (object is StateMachineFireEventBase) {
|
||||
object.occursValue = value;
|
||||
}
|
||||
break;
|
||||
case BlendState1DBase.inputIdPropertyKey:
|
||||
if (object is BlendState1DBase) {
|
||||
object.inputId = value;
|
||||
|
22
lib/src/layer_component_events.dart
Normal file
22
lib/src/layer_component_events.dart
Normal file
@ -0,0 +1,22 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:rive/src/rive_core/animation/state_machine_fire_event.dart';
|
||||
|
||||
class LayerComponentEvents extends ListBase<StateMachineFireEvent> {
|
||||
final List<StateMachineFireEvent?> _values = [];
|
||||
List<StateMachineFireEvent> get values =>
|
||||
_values.cast<StateMachineFireEvent>();
|
||||
|
||||
@override
|
||||
int get length => _values.length;
|
||||
|
||||
@override
|
||||
set length(int value) => _values.length = value;
|
||||
|
||||
@override
|
||||
StateMachineFireEvent operator [](int index) => _values[index]!;
|
||||
|
||||
@override
|
||||
void operator []=(int index, StateMachineFireEvent value) =>
|
||||
_values[index] = value;
|
||||
}
|
38
lib/src/rive_core/animation/state_machine_fire_event.dart
Normal file
38
lib/src/rive_core/animation/state_machine_fire_event.dart
Normal file
@ -0,0 +1,38 @@
|
||||
library rive_core;
|
||||
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/generated/animation/state_machine_fire_event_base.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_layer_component.dart';
|
||||
|
||||
export 'package:rive/src/generated/animation/state_machine_fire_event_base.dart';
|
||||
|
||||
enum StateMachineFireOccurance {
|
||||
atStart,
|
||||
atEnd,
|
||||
}
|
||||
|
||||
class StateMachineFireEvent extends StateMachineFireEventBase {
|
||||
@override
|
||||
void onAddedDirty() {}
|
||||
|
||||
@override
|
||||
void eventIdChanged(int from, int to) {}
|
||||
|
||||
@override
|
||||
void occursValueChanged(int from, int to) {}
|
||||
|
||||
StateMachineFireOccurance get occurs =>
|
||||
StateMachineFireOccurance.values[occursValue];
|
||||
|
||||
@override
|
||||
bool import(ImportStack importStack) {
|
||||
var importer = importStack.latest<StateMachineLayerComponentImporter>(
|
||||
StateMachineLayerComponentBase.typeKey);
|
||||
if (importer == null) {
|
||||
return false;
|
||||
}
|
||||
importer.addFireEvent(this);
|
||||
|
||||
return super.import(importStack);
|
||||
}
|
||||
}
|
@ -2,10 +2,28 @@
|
||||
// linter happy...
|
||||
|
||||
// ignore: unused_import
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:rive/src/core/core.dart';
|
||||
import 'package:rive/src/generated/animation/state_machine_layer_component_base.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_fire_event.dart';
|
||||
|
||||
export 'package:rive/src/generated/animation/state_machine_layer_component_base.dart';
|
||||
|
||||
abstract class StateMachineLayerComponent
|
||||
extends StateMachineLayerComponentBase<RuntimeArtboard> {}
|
||||
extends StateMachineLayerComponentBase<RuntimeArtboard> {
|
||||
final LayerComponentEvents _events = LayerComponentEvents();
|
||||
LayerComponentEvents get events => _events;
|
||||
|
||||
void internalAddFireEvent(StateMachineFireEvent event) {
|
||||
assert(!_events.contains(event), 'shouldn\'t already contain the event');
|
||||
_events.add(event);
|
||||
}
|
||||
|
||||
Iterable<StateMachineFireEvent> eventsAt(
|
||||
StateMachineFireOccurance occurence) =>
|
||||
_events
|
||||
.where((fireEvent) => fireEvent.occurs == occurence)
|
||||
.whereNotNull();
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import 'package:rive/src/rive_core/animation/linear_animation.dart';
|
||||
import 'package:rive/src/rive_core/animation/nested_state_machine.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_instance.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_fire_event.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_layer.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_listener.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_trigger.dart';
|
||||
@ -46,6 +47,7 @@ class LayerController {
|
||||
StateInstance? _stateFrom;
|
||||
bool _holdAnimationFrom = false;
|
||||
StateTransition? _transition;
|
||||
bool _transitionCompleted = false;
|
||||
double _mix = 1.0;
|
||||
double _mixFrom = 1.0;
|
||||
|
||||
@ -65,15 +67,35 @@ class LayerController {
|
||||
_changeState(layer.entryState);
|
||||
}
|
||||
|
||||
void _fireEvents(Iterable<StateMachineFireEvent> fireEvents) {
|
||||
for (final fireEvent in fireEvents) {
|
||||
Event event = core.resolveWithDefault(fireEvent.eventId, Event.unknown);
|
||||
if (event != Event.unknown) {
|
||||
controller.reportEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _changeState(LayerState? state, {StateTransition? transition}) {
|
||||
assert(state is! AnyState,
|
||||
'We don\'t allow making the AnyState an active state.');
|
||||
if (state == _currentState?.state) {
|
||||
return false;
|
||||
}
|
||||
_currentState?.dispose();
|
||||
var currentState = _currentState;
|
||||
if (currentState != null) {
|
||||
_fireEvents(currentState.state.eventsAt(StateMachineFireOccurance.atEnd));
|
||||
currentState.dispose();
|
||||
}
|
||||
var nextState = state;
|
||||
|
||||
if (nextState != null) {
|
||||
_currentState = nextState.makeInstance();
|
||||
_fireEvents(nextState.eventsAt(StateMachineFireOccurance.atStart));
|
||||
} else {
|
||||
_currentState = null;
|
||||
}
|
||||
|
||||
_currentState = state?.makeInstance();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -89,12 +111,16 @@ class LayerController {
|
||||
_mix != 1;
|
||||
|
||||
void _updateMix(double elapsedSeconds) {
|
||||
if (_transition != null &&
|
||||
_stateFrom != null &&
|
||||
_transition!.duration != 0) {
|
||||
_mix = (_mix + elapsedSeconds / _transition!.mixTime(_stateFrom!.state))
|
||||
var transition = _transition;
|
||||
if (transition != null && _stateFrom != null && transition.duration != 0) {
|
||||
_mix = (_mix + elapsedSeconds / transition.mixTime(_stateFrom!.state))
|
||||
.clamp(0, 1)
|
||||
.toDouble();
|
||||
|
||||
if (_mix == 1 && !_transitionCompleted) {
|
||||
_transitionCompleted = true;
|
||||
_fireEvents(transition.eventsAt(StateMachineFireOccurance.atEnd));
|
||||
}
|
||||
} else {
|
||||
_mix = 1;
|
||||
}
|
||||
@ -191,6 +217,15 @@ class LayerController {
|
||||
// Take transition
|
||||
_transition = transition;
|
||||
|
||||
_fireEvents(transition.eventsAt(StateMachineFireOccurance.atStart));
|
||||
// Immediately fire end events if transition has no duration.
|
||||
if (transition.duration == 0) {
|
||||
_transitionCompleted = true;
|
||||
_fireEvents(transition.eventsAt(StateMachineFireOccurance.atEnd));
|
||||
} else {
|
||||
_transitionCompleted = false;
|
||||
}
|
||||
|
||||
_stateFrom = outState;
|
||||
|
||||
// If we had an exit time and wanted to pause on exit, make sure to hold
|
||||
|
@ -25,6 +25,7 @@ import 'package:rive/src/rive_core/animation/linear_animation.dart';
|
||||
import 'package:rive/src/rive_core/animation/nested_state_machine.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_layer.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_layer_component.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_machine_listener.dart';
|
||||
import 'package:rive/src/rive_core/animation/state_transition.dart';
|
||||
import 'package:rive/src/rive_core/artboard.dart';
|
||||
@ -260,6 +261,14 @@ class RiveFile {
|
||||
if (!importStack.makeLatest(stackType, stackObject)) {
|
||||
throw const RiveFormatErrorException('Rive file is corrupt.');
|
||||
}
|
||||
// Special case for StateMachineLayerComponents as the concrete types also
|
||||
// add importers.
|
||||
if (object is StateMachineLayerComponent) {
|
||||
if (!importStack.makeLatest(StateMachineLayerComponentBase.typeKey,
|
||||
StateMachineLayerComponentImporter(object))) {
|
||||
throw const RiveFormatErrorException('Rive file is corrupt.');
|
||||
}
|
||||
}
|
||||
|
||||
// Store all as some may fail to import (will be set to null, but we still
|
||||
// want them to occupy an id).
|
||||
|
@ -1,5 +1,5 @@
|
||||
name: rive
|
||||
version: 0.11.14
|
||||
version: 0.11.15
|
||||
homepage: https://rive.app
|
||||
description: Rive 2 Flutter Runtime. This package provides runtime functionality for playing back and interacting with animations built with the Rive editor available at https://rive.app.
|
||||
repository: https://github.com/rive-app/rive-flutter
|
||||
|
3
test/assets/events_on_states.riv
Normal file
3
test/assets/events_on_states.riv
Normal file
@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6f360a9ba8e224871161a4945d24959c75fba97d9ca81f33be0f73252c31b3c4
|
||||
size 486
|
@ -305,5 +305,48 @@ void main() {
|
||||
expect(receivedEvents, contains('Footstep'));
|
||||
expect(receivedEvents, contains('Event 3'));
|
||||
});
|
||||
|
||||
testWidgets('State & Transition events report',
|
||||
(WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
final riveBytes = loadFile('assets/events_on_states.riv');
|
||||
final riveFile = RiveFile.import(riveBytes);
|
||||
|
||||
var controller = StateMachineController.fromArtboard(
|
||||
riveFile.mainArtboard, 'State Machine 1');
|
||||
expect(controller, isNotNull);
|
||||
|
||||
Set<String> receivedEvents = {};
|
||||
controller!.addEventListener((event) {
|
||||
receivedEvents.add(event.name);
|
||||
});
|
||||
|
||||
BoxFit fit = BoxFit.contain;
|
||||
Alignment alignment = Alignment.topLeft;
|
||||
bool anitaliasing = true;
|
||||
Widget placeholder = _placeHolderWidgetOne;
|
||||
|
||||
const riveKey = Key('riveWidgetKey');
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: RiveAnimation.direct(
|
||||
riveFile,
|
||||
key: riveKey,
|
||||
controllers: [controller],
|
||||
fit: fit,
|
||||
alignment: alignment,
|
||||
antialiasing: anitaliasing,
|
||||
placeHolder: placeholder,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump(Duration.zero);
|
||||
|
||||
expect(receivedEvents, contains('First'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user