mirror of https://github.com/flutter/pinball.git
chore: SpriteAnimationWidget from pre release flame (#228)
parent
1f72dbfd4a
commit
20529c1406
@ -0,0 +1,102 @@
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/image_composition.dart';
|
||||
import 'package:flutter/material.dart' hide Animation;
|
||||
|
||||
/// {@template flame.widgets.sprite_animation_widget}
|
||||
/// A [StatelessWidget] that renders a [SpriteAnimation].
|
||||
/// {@endtemplate}
|
||||
// TODO(arturplaczek): Remove when this PR will be merged.
|
||||
// https://github.com/flame-engine/flame/pull/1552
|
||||
class SpriteAnimationWidget extends StatelessWidget {
|
||||
/// {@macro flame.widgets.sprite_animation_widget}
|
||||
const SpriteAnimationWidget({
|
||||
required this.controller,
|
||||
this.anchor = Anchor.topLeft,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
/// The positioning [Anchor].
|
||||
final Anchor anchor;
|
||||
|
||||
final SpriteAnimationController controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: controller,
|
||||
builder: (_, __) {
|
||||
return CustomPaint(
|
||||
painter: SpritePainter(
|
||||
controller.animation.getSprite(),
|
||||
anchor,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SpriteAnimationController extends AnimationController {
|
||||
SpriteAnimationController({
|
||||
required TickerProvider vsync,
|
||||
required this.animation,
|
||||
}) : super(vsync: vsync) {
|
||||
duration = Duration(seconds: animation.totalDuration().ceil());
|
||||
}
|
||||
|
||||
final SpriteAnimation animation;
|
||||
|
||||
double? _lastUpdated;
|
||||
|
||||
@override
|
||||
void notifyListeners() {
|
||||
super.notifyListeners();
|
||||
|
||||
final now = DateTime.now().millisecond.toDouble();
|
||||
final dt = max<double>(0, (now - (_lastUpdated ?? 0)) / 1000);
|
||||
animation.update(dt);
|
||||
_lastUpdated = now;
|
||||
}
|
||||
}
|
||||
|
||||
class SpritePainter extends CustomPainter {
|
||||
SpritePainter(
|
||||
this._sprite,
|
||||
this._anchor, {
|
||||
double angle = 0,
|
||||
}) : _angle = angle;
|
||||
|
||||
final Sprite _sprite;
|
||||
final Anchor _anchor;
|
||||
final double _angle;
|
||||
|
||||
@override
|
||||
bool shouldRepaint(SpritePainter oldDelegate) {
|
||||
return oldDelegate._sprite != _sprite ||
|
||||
oldDelegate._anchor != _anchor ||
|
||||
oldDelegate._angle != _angle;
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final boxSize = size.toVector2();
|
||||
final rate = boxSize.clone()..divide(_sprite.srcSize);
|
||||
final minRate = min(rate.x, rate.y);
|
||||
final paintSize = _sprite.srcSize * minRate;
|
||||
final anchorPosition = _anchor.toVector2();
|
||||
final boxAnchorPosition = boxSize.clone()..multiply(anchorPosition);
|
||||
final spriteAnchorPosition = anchorPosition..multiply(paintSize);
|
||||
|
||||
canvas
|
||||
..translateVector(boxAnchorPosition..sub(spriteAnchorPosition))
|
||||
..renderRotated(
|
||||
_angle,
|
||||
spriteAnchorPosition,
|
||||
(canvas) => _sprite.render(canvas, size: paintSize),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:pinball_flame/pinball_flame.dart';
|
||||
|
||||
class MockSpriteAnimationController extends Mock
|
||||
implements SpriteAnimationController {}
|
||||
|
||||
class MockSpriteAnimation extends Mock implements SpriteAnimation {}
|
||||
|
||||
class MockSprite extends Mock implements Sprite {}
|
||||
|
||||
// TODO(arturplaczek): Remove when this PR will be merged.
|
||||
// https://github.com/flame-engine/flame/pull/1552
|
||||
|
||||
void main() {
|
||||
group('PinballSpriteAnimationWidget', () {
|
||||
late SpriteAnimationController controller;
|
||||
late SpriteAnimation animation;
|
||||
late Sprite sprite;
|
||||
|
||||
setUp(() {
|
||||
controller = MockSpriteAnimationController();
|
||||
animation = MockSpriteAnimation();
|
||||
sprite = MockSprite();
|
||||
|
||||
when(() => controller.animation).thenAnswer((_) => animation);
|
||||
|
||||
when(() => animation.totalDuration()).thenAnswer((_) => 1);
|
||||
when(() => animation.getSprite()).thenAnswer((_) => sprite);
|
||||
when(() => sprite.srcSize).thenAnswer((_) => Vector2(1, 1));
|
||||
when(() => sprite.srcSize).thenAnswer((_) => Vector2(1, 1));
|
||||
});
|
||||
|
||||
testWidgets('renders correctly', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
SpriteAnimationWidget(
|
||||
controller: controller,
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(SpriteAnimationWidget), findsOneWidget);
|
||||
});
|
||||
|
||||
test('SpriteAnimationController is updating animations', () {
|
||||
SpriteAnimationController(
|
||||
vsync: const TestVSync(),
|
||||
animation: animation,
|
||||
).notifyListeners();
|
||||
|
||||
verify(() => animation.update(any())).called(1);
|
||||
});
|
||||
|
||||
testWidgets('SpritePainter shouldRepaint returns true when Sprite changed',
|
||||
(tester) async {
|
||||
final spritePainter = SpritePainter(
|
||||
sprite,
|
||||
Anchor.center,
|
||||
angle: 45,
|
||||
);
|
||||
|
||||
final anotherPainter = SpritePainter(
|
||||
sprite,
|
||||
Anchor.center,
|
||||
angle: 30,
|
||||
);
|
||||
|
||||
expect(spritePainter.shouldRepaint(anotherPainter), isTrue);
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in new issue