diff --git a/example/lib/main.dart b/example/lib/main.dart index eeb8dd9..057570c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -6,6 +6,7 @@ import 'package:rive_example/little_machine.dart'; import 'package:rive_example/play_one_shot_animation.dart'; import 'package:rive_example/play_pause_animation.dart'; import 'package:rive_example/simple_animation.dart'; +import 'package:rive_example/simple_machine_action.dart'; import 'package:rive_example/simple_state_machine.dart'; import 'package:rive_example/state_machine_skills.dart'; @@ -148,6 +149,20 @@ class Home extends StatelessWidget { ); }, ), + const SizedBox( + height: 10, + ), + ElevatedButton( + child: const Text('State Machine with Action'), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const StateMachineAction(), + ), + ); + }, + ), ], ), ), diff --git a/example/lib/simple_machine_action.dart b/example/lib/simple_machine_action.dart new file mode 100644 index 0000000..aebd943 --- /dev/null +++ b/example/lib/simple_machine_action.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:rive/rive.dart'; + +class StateMachineAction extends StatefulWidget { + const StateMachineAction({Key? key}) : super(key: key); + + @override + _StateMachineActionState createState() => _StateMachineActionState(); +} + +class _StateMachineActionState extends State { + void _onRiveInit(Artboard artboard) { + final controller = StateMachineController.fromArtboard(artboard, 'Switch'); + artboard.addController(controller!); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Light Switch'), + ), + body: Center( + child: RiveAnimation.asset( + 'assets/light_switch.riv', + fit: BoxFit.contain, + onInit: _onRiveInit, + ), + ), + ); + } +} diff --git a/lib/src/rive.dart b/lib/src/rive.dart index 821ddf4..03aed12 100644 --- a/lib/src/rive.dart +++ b/lib/src/rive.dart @@ -112,6 +112,7 @@ class RiveRenderObject extends RiveRenderBox { @override void draw(Canvas canvas, Mat2D viewTransform) { + canvas.transform(viewTransform.mat4); artboard.draw(canvas); } } diff --git a/lib/src/rive_core/artboard.dart b/lib/src/rive_core/artboard.dart index 2aea45d..5c89140 100644 --- a/lib/src/rive_core/artboard.dart +++ b/lib/src/rive_core/artboard.dart @@ -360,6 +360,10 @@ class Artboard extends ArtboardBase with ShapePaintContainer { /// advances. final Set _animationControllers = {}; + /// Access a read-only iterator of currently applied animation controllers. + Iterable get animationControllers => + _animationControllers; + /// Add an animation controller to this artboard. Playing will be scheduled if /// it's already playing. bool addController(RiveAnimationController controller) { diff --git a/lib/src/rive_file.dart b/lib/src/rive_file.dart index 0853f8e..fb85bb7 100644 --- a/lib/src/rive_file.dart +++ b/lib/src/rive_file.dart @@ -20,6 +20,7 @@ import 'package:rive/src/rive_core/animation/keyed_property.dart'; import 'package:rive/src/rive_core/animation/layer_state.dart'; import 'package:rive/src/rive_core/animation/linear_animation.dart'; import 'package:rive/src/rive_core/animation/state_machine.dart'; +import 'package:rive/src/rive_core/animation/state_machine_event.dart'; import 'package:rive/src/rive_core/animation/state_machine_layer.dart'; import 'package:rive/src/rive_core/animation/state_transition.dart'; import 'package:rive/src/rive_core/artboard.dart'; @@ -158,7 +159,9 @@ class RiveFile { case StateMachineLayerBase.typeKey: stackObject = StateMachineLayerImporter(object as StateMachineLayer); break; - + case StateMachineEventBase.typeKey: + stackObject = StateMachineEventImporter(object as StateMachineEvent); + break; case EntryStateBase.typeKey: case AnyStateBase.typeKey: case ExitStateBase.typeKey: diff --git a/lib/src/rive_render_box.dart b/lib/src/rive_render_box.dart index 1cd3ebe..7fe40e0 100644 --- a/lib/src/rive_render_box.dart +++ b/lib/src/rive_render_box.dart @@ -194,30 +194,25 @@ abstract class RiveRenderBox extends RenderBox { /// be called after advancing. Return true to prevent regular paint. bool customPaint(PaintingContext context, Offset offset) => false; - @protected - @override - void paint(PaintingContext context, Offset offset) { - _frameCallbackId = -1; - if (advance(_elapsedSeconds)) { - scheduleRepaint(); - } else { - _stopwatch.stop(); + Vec2D globalToArtboard(Offset globalPosition) { + var local = globalToLocal(globalPosition); + var alignArtboard = computeAlignment(); + var localToArtboard = Mat2D(); + var localAsVec = Vec2D.fromValues(local.dx, local.dy); + if (!Mat2D.invert(localToArtboard, alignArtboard)) { + return localAsVec; } - _elapsedSeconds = 0; - - if (customPaint(context, offset)) { - return; - } - - final Canvas canvas = context.canvas; + return Vec2D.transformMat2D(Vec2D(), localAsVec, localToArtboard); + } + Mat2D computeAlignment([Offset offset = Offset.zero]) { AABB bounds = aabb; double contentWidth = bounds[2] - bounds[0]; double contentHeight = bounds[3] - bounds[1]; if (contentWidth == 0 || contentHeight == 0) { - return; + return Mat2D(); } double x = -1 * bounds[0] - @@ -229,9 +224,6 @@ abstract class RiveRenderBox extends RenderBox { double scaleX = 1.0, scaleY = 1.0; - canvas.save(); - beforeDraw(canvas, offset); - switch (_fit) { case BoxFit.fill: scaleX = size.width / contentWidth; @@ -278,14 +270,30 @@ abstract class RiveRenderBox extends RenderBox { center[4] = x; center[5] = y; Mat2D.multiply(transform, transform, center); + return transform; + } - canvas.translate( - offset.dx + size.width / 2.0 + (_alignment.x * size.width / 2.0), - offset.dy + size.height / 2.0 + (_alignment.y * size.height / 2.0), - ); + @protected + @override + void paint(PaintingContext context, Offset offset) { + _frameCallbackId = -1; + if (advance(_elapsedSeconds)) { + scheduleRepaint(); + } else { + _stopwatch.stop(); + } + _elapsedSeconds = 0; - canvas.scale(scaleX, scaleY); - canvas.translate(x, y); + if (customPaint(context, offset)) { + return; + } + + final Canvas canvas = context.canvas; + + canvas.save(); + beforeDraw(canvas, offset); + + var transform = computeAlignment(offset); draw(canvas, transform); diff --git a/lib/src/widgets/rive_animation.dart b/lib/src/widgets/rive_animation.dart index 8f73d8f..c945a41 100644 --- a/lib/src/widgets/rive_animation.dart +++ b/lib/src/widgets/rive_animation.dart @@ -1,5 +1,6 @@ import 'package:flutter/widgets.dart'; import 'package:rive/rive.dart'; +import 'package:rive/src/rive_core/math/vec2d.dart'; /// Specifies whether a source is from an asset bundle or http enum _Source { @@ -137,7 +138,8 @@ class _RiveAnimationState extends State { // controller specified, select a default animation final animationNames = widget.animations.isEmpty && widget.stateMachines.isEmpty && - widget.controllers.isEmpty + widget.controllers.isEmpty && + widget.onInit == null ? [artboard.animations.first.name] : widget.animations; @@ -169,13 +171,84 @@ class _RiveAnimationState extends State { super.dispose(); } + Vec2D? _toArtboard(Offset local) { + RiveRenderObject? riveRenderer; + var renderObject = context.findRenderObject(); + if (renderObject is! RenderBox) { + return null; + } + renderObject.visitChildren( + (child) { + if (child is RiveRenderObject) { + riveRenderer = child; + } + }, + ); + if (riveRenderer == null) { + return null; + } + var globalCoordinates = renderObject.localToGlobal(local); + + return riveRenderer!.globalToArtboard(globalCoordinates); + } + + Widget _optionalHitTester(BuildContext context, Widget child) { + assert(_artboard != null); + var hasHitTesting = _artboard!.animationControllers.any((controller) => + controller is StateMachineController && + controller.hitShapes.isNotEmpty); + + if (hasHitTesting) { + void hitHelper(PointerEvent event, + void Function(StateMachineController, Vec2D) callback) { + var artboardPosition = _toArtboard(event.localPosition); + if (artboardPosition != null) { + var stateMachineControllers = _artboard!.animationControllers + .whereType(); + for (final stateMachineController in stateMachineControllers) { + callback(stateMachineController, artboardPosition); + } + } + } + + return Listener( + onPointerDown: (details) => hitHelper( + details, + (controller, artboardPosition) => + controller.pointerDown(artboardPosition), + ), + onPointerUp: (details) => hitHelper( + details, + (controller, artboardPosition) => + controller.pointerUp(artboardPosition), + ), + onPointerHover: (details) => hitHelper( + details, + (controller, artboardPosition) => + controller.pointerMove(artboardPosition), + ), + onPointerMove: (details) => hitHelper( + details, + (controller, artboardPosition) => + controller.pointerMove(artboardPosition), + ), + child: child, + ); + } + + return child; + } + @override Widget build(BuildContext context) => _artboard != null - ? Rive( - artboard: _artboard!, - fit: widget.fit, - alignment: widget.alignment, - antialiasing: widget.antialiasing, + ? _optionalHitTester( + context, + Rive( + artboard: _artboard!, + fit: widget.fit, + alignment: widget.alignment, + antialiasing: widget.antialiasing, + ), ) : widget.placeHolder ?? const SizedBox(); }