mirror of https://github.com/flutter/pinball.git
feat: implemented rendering with `ZIndex` (#282)
* feat: defined PinballCavas * refactor: remove unawaited * feat: defined renderTree * refactor: made LaunchRamp a Component * refactor: removed RenderPriority from FlutterForest * feat: implementing PinballCanvasComponent * refactor: migrated to zIndex * refactor: removed old pinball_canvas.dart * refactor: cleaned PinballCanvasComponent * refactor: reordered children additions * refactor: adjusted priorities * refactor: adjusted RenderPriority * refactor: improved rendering algorithm * refactor: removes Blueprint * test: migrating blueprint tests * docs: removed Blueprint references * refactor: renamed PinballCanvas to ZCanvas * test: tested z_canvas_component * refactor: renamed LayerSensor to use zIndex * refactor: renamed RenderPriority to ZIndexes * refactor: removed priority instances * refactor: removed priority occurences * fix: plunger and tapping positions * test: correctly testes SparkyScorch * test: adjusted ScoringBehavior test * docs: documented z_canvas_component * test: fixed ScoringBehavior tests * refactor: renamed to ZCanvas * refactor: applied PR suggestionspull/287/head
parent
d748188286
commit
95fe7a62aa
@ -1,21 +1,20 @@
|
|||||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
import 'package:flame/components.dart';
|
||||||
import 'package:pinball/game/components/components.dart';
|
import 'package:pinball/game/components/components.dart';
|
||||||
import 'package:pinball_components/pinball_components.dart' hide Assets;
|
import 'package:pinball_components/pinball_components.dart' hide Assets;
|
||||||
import 'package:pinball_flame/pinball_flame.dart';
|
|
||||||
|
|
||||||
/// {@template launcher}
|
/// {@template launcher}
|
||||||
/// A [Blueprint] which creates the [Plunger], [RocketSpriteComponent] and
|
/// Channel on the right side of the board containing the [LaunchRamp],
|
||||||
/// [LaunchRamp].
|
/// [Plunger], and [RocketSpriteComponent].
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
class Launcher extends Blueprint {
|
class Launcher extends Component {
|
||||||
/// {@macro launcher}
|
/// {@macro launcher}
|
||||||
Launcher()
|
Launcher()
|
||||||
: super(
|
: super(
|
||||||
components: [
|
children: [
|
||||||
|
LaunchRamp(),
|
||||||
ControlledPlunger(compressionDistance: 10.5)
|
ControlledPlunger(compressionDistance: 10.5)
|
||||||
..initialPosition = Vector2(41.1, 43),
|
..initialPosition = Vector2(41.1, 43),
|
||||||
RocketSpriteComponent()..position = Vector2(43, 62.3),
|
RocketSpriteComponent()..position = Vector2(43, 62.3),
|
||||||
],
|
],
|
||||||
blueprints: [LaunchRamp()],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,119 +0,0 @@
|
|||||||
// ignore_for_file: public_member_api_docs
|
|
||||||
|
|
||||||
import 'package:pinball_components/pinball_components.dart';
|
|
||||||
|
|
||||||
/// {@template render_priority}
|
|
||||||
/// Priorities for the component rendering order in the pinball game.
|
|
||||||
/// {@endtemplate}
|
|
||||||
// TODO(allisonryan0002): find alternative to section comments.
|
|
||||||
abstract class RenderPriority {
|
|
||||||
static const _base = 0;
|
|
||||||
static const _above = 1;
|
|
||||||
static const _below = -1;
|
|
||||||
|
|
||||||
// Ball
|
|
||||||
|
|
||||||
/// Render priority for the [Ball] while it's on the board.
|
|
||||||
static const int ballOnBoard = _base;
|
|
||||||
|
|
||||||
/// Render priority for the [Ball] while it's on the [SpaceshipRamp].
|
|
||||||
static const int ballOnSpaceshipRamp =
|
|
||||||
_above + spaceshipRampBackgroundRailing;
|
|
||||||
|
|
||||||
/// Render priority for the [Ball] while it's on the [AndroidSpaceship].
|
|
||||||
static const int ballOnSpaceship = _above + spaceshipSaucer;
|
|
||||||
|
|
||||||
/// Render priority for the [Ball] while it's on the [SpaceshipRail].
|
|
||||||
static const int ballOnSpaceshipRail = _above + spaceshipRail;
|
|
||||||
|
|
||||||
/// Render priority for the [Ball] while it's on the [LaunchRamp].
|
|
||||||
static const int ballOnLaunchRamp = launchRamp;
|
|
||||||
|
|
||||||
// Background
|
|
||||||
|
|
||||||
// TODO(allisonryan0002): fix this magic priority. Could bump all priorities
|
|
||||||
// so there are no negatives.
|
|
||||||
static const int boardBackground = 3 * _below + _base;
|
|
||||||
|
|
||||||
// Boundaries
|
|
||||||
|
|
||||||
static const int bottomBoundary = _above + dinoBottomWall;
|
|
||||||
|
|
||||||
static const int outerBoundary = _above + boardBackground;
|
|
||||||
|
|
||||||
static const int outerBottomBoundary = _above + rocket;
|
|
||||||
|
|
||||||
// Bottom Group
|
|
||||||
|
|
||||||
static const int bottomGroup = _above + ballOnBoard;
|
|
||||||
|
|
||||||
// Launcher
|
|
||||||
|
|
||||||
static const int launchRamp = _above + outerBoundary;
|
|
||||||
|
|
||||||
static const int launchRampForegroundRailing = ballOnBoard;
|
|
||||||
|
|
||||||
static const int plunger = _above + launchRamp;
|
|
||||||
|
|
||||||
static const int rocket = _below + bottomBoundary;
|
|
||||||
|
|
||||||
// Dino Desert
|
|
||||||
|
|
||||||
static const int dinoTopWall = _above + ballOnBoard;
|
|
||||||
|
|
||||||
static const int dino = _above + dinoTopWall;
|
|
||||||
|
|
||||||
static const int dinoBottomWall = _above + dino;
|
|
||||||
|
|
||||||
static const int slingshot = _above + dinoBottomWall;
|
|
||||||
|
|
||||||
// Flutter Forest
|
|
||||||
|
|
||||||
static const int flutterForest = _above + launchRampForegroundRailing;
|
|
||||||
|
|
||||||
// Sparky Scorch
|
|
||||||
|
|
||||||
static const int computerBase = _below + ballOnBoard;
|
|
||||||
|
|
||||||
static const int computerTop = _above + ballOnBoard;
|
|
||||||
|
|
||||||
static const int computerGlow = _above + ballOnBoard;
|
|
||||||
|
|
||||||
static const int sparkyAnimatronic = _above + spaceshipRampForegroundRailing;
|
|
||||||
|
|
||||||
static const int sparkyBumper = _above + ballOnBoard;
|
|
||||||
|
|
||||||
static const int turboChargeFlame = _above + ballOnBoard;
|
|
||||||
|
|
||||||
// Android Acres
|
|
||||||
|
|
||||||
static const int spaceshipRail = _above + bottomGroup;
|
|
||||||
|
|
||||||
static const int spaceshipRailExit = _above + ballOnSpaceshipRail;
|
|
||||||
|
|
||||||
static const int spaceshipSaucer = _above + ballOnSpaceshipRail;
|
|
||||||
|
|
||||||
static const int spaceshipLightBeam = _below + spaceshipSaucer;
|
|
||||||
|
|
||||||
static const int androidHead = _above + spaceshipSaucer;
|
|
||||||
|
|
||||||
static const int spaceshipRamp = _above + ballOnBoard;
|
|
||||||
|
|
||||||
static const int spaceshipRampBackgroundRailing = _above + spaceshipRamp;
|
|
||||||
|
|
||||||
static const int spaceshipRampArrow = _above + spaceshipRamp;
|
|
||||||
|
|
||||||
static const int spaceshipRampForegroundRailing =
|
|
||||||
_above + ballOnSpaceshipRamp;
|
|
||||||
|
|
||||||
static const int spaceshipRampBoardOpening = _below + ballOnBoard;
|
|
||||||
|
|
||||||
static const int androidBumper = _above + ballOnBoard;
|
|
||||||
|
|
||||||
// Score Text
|
|
||||||
|
|
||||||
static const int scoreText = _above + spaceshipRampForegroundRailing;
|
|
||||||
|
|
||||||
// Debug information
|
|
||||||
static const int debugInfo = _above + scoreText;
|
|
||||||
}
|
|
@ -0,0 +1,110 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
|
/// Z-Indexes for the component rendering order in the pinball game.
|
||||||
|
// TODO(allisonryan0002): find alternative to section comments.
|
||||||
|
abstract class ZIndexes {
|
||||||
|
static const _base = 0;
|
||||||
|
static const _above = 1;
|
||||||
|
static const _below = -1;
|
||||||
|
|
||||||
|
// Ball
|
||||||
|
|
||||||
|
static const ballOnBoard = _base;
|
||||||
|
|
||||||
|
static const ballOnSpaceshipRamp = _above + spaceshipRampBackgroundRailing;
|
||||||
|
|
||||||
|
static const ballOnSpaceship = _above + spaceshipSaucer;
|
||||||
|
|
||||||
|
static const ballOnSpaceshipRail = _above + spaceshipRail;
|
||||||
|
|
||||||
|
static const ballOnLaunchRamp = _above + launchRamp;
|
||||||
|
|
||||||
|
// Background
|
||||||
|
|
||||||
|
// TODO(allisonryan0002): fix this magic zindex. Could bump all priorities so
|
||||||
|
// there are no negatives.
|
||||||
|
static const boardBackground = 3 * _below + _base;
|
||||||
|
|
||||||
|
static const decal = _above + boardBackground;
|
||||||
|
|
||||||
|
// Boundaries
|
||||||
|
|
||||||
|
static const bottomBoundary = _above + dinoBottomWall;
|
||||||
|
|
||||||
|
static const outerBoundary = _above + boardBackground;
|
||||||
|
|
||||||
|
static const outerBottomBoundary = _above + rocket;
|
||||||
|
|
||||||
|
// Bottom Group
|
||||||
|
|
||||||
|
static const bottomGroup = _above + ballOnBoard;
|
||||||
|
|
||||||
|
// Launcher
|
||||||
|
|
||||||
|
static const launchRamp = _above + outerBoundary;
|
||||||
|
|
||||||
|
static const launchRampForegroundRailing = _above + ballOnLaunchRamp;
|
||||||
|
|
||||||
|
static const plunger = _above + launchRamp;
|
||||||
|
|
||||||
|
static const rocket = _below + bottomBoundary;
|
||||||
|
|
||||||
|
// Dino Desert
|
||||||
|
|
||||||
|
static const dinoTopWall = _above + ballOnBoard;
|
||||||
|
|
||||||
|
static const dino = _above + dinoTopWall;
|
||||||
|
|
||||||
|
static const dinoBottomWall = _above + dino;
|
||||||
|
|
||||||
|
static const slingshots = _above + dinoBottomWall;
|
||||||
|
|
||||||
|
// Flutter Forest
|
||||||
|
|
||||||
|
static const flutterForest = _above + launchRampForegroundRailing;
|
||||||
|
|
||||||
|
// Sparky Scorch
|
||||||
|
|
||||||
|
static const computerBase = _below + ballOnBoard;
|
||||||
|
|
||||||
|
static const computerTop = _above + ballOnBoard;
|
||||||
|
|
||||||
|
static const computerGlow = _above + ballOnBoard;
|
||||||
|
|
||||||
|
static const sparkyAnimatronic = _above + spaceshipRampForegroundRailing;
|
||||||
|
|
||||||
|
static const sparkyBumper = _above + ballOnBoard;
|
||||||
|
|
||||||
|
static const turboChargeFlame = _above + ballOnBoard;
|
||||||
|
|
||||||
|
// Android Acres
|
||||||
|
|
||||||
|
static const spaceshipRail = _above + bottomGroup;
|
||||||
|
|
||||||
|
static const spaceshipRailExit = _above + ballOnSpaceshipRail;
|
||||||
|
|
||||||
|
static const spaceshipSaucer = _above + ballOnSpaceshipRail;
|
||||||
|
|
||||||
|
static const spaceshipLightBeam = _below + spaceshipSaucer;
|
||||||
|
|
||||||
|
static const androidHead = _above + ballOnSpaceship;
|
||||||
|
|
||||||
|
static const spaceshipRamp = _above + sparkyBumper;
|
||||||
|
|
||||||
|
static const spaceshipRampBackgroundRailing = _above + spaceshipRamp;
|
||||||
|
|
||||||
|
static const spaceshipRampArrow = _above + spaceshipRamp;
|
||||||
|
|
||||||
|
static const spaceshipRampForegroundRailing = _above + ballOnSpaceshipRamp;
|
||||||
|
|
||||||
|
static const spaceshipRampBoardOpening = _below + ballOnBoard;
|
||||||
|
|
||||||
|
static const androidBumper = _above + ballOnBoard;
|
||||||
|
|
||||||
|
// Score Text
|
||||||
|
|
||||||
|
static const scoreText = _above + spaceshipRampForegroundRailing;
|
||||||
|
|
||||||
|
// Debug information
|
||||||
|
static const debugInfo = _above + scoreText;
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
library pinball_flame;
|
library pinball_flame;
|
||||||
|
|
||||||
export 'src/blueprint.dart';
|
|
||||||
export 'src/component_controller.dart';
|
export 'src/component_controller.dart';
|
||||||
export 'src/contact_behavior.dart';
|
export 'src/contact_behavior.dart';
|
||||||
export 'src/keyboard_input_controller.dart';
|
export 'src/keyboard_input_controller.dart';
|
||||||
export 'src/parent_is_a.dart';
|
export 'src/parent_is_a.dart';
|
||||||
export 'src/sprite_animation.dart';
|
export 'src/sprite_animation.dart';
|
||||||
|
export 'src/z_canvas_component.dart';
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
import 'package:flame/components.dart';
|
|
||||||
import 'package:flame/game.dart';
|
|
||||||
|
|
||||||
// TODO(erickzanardo): Keeping this inside our code base so we can experiment
|
|
||||||
// with the idea, but this is a potential upstream change on Flame.
|
|
||||||
|
|
||||||
/// {@template blueprint}
|
|
||||||
/// A [Blueprint] is a virtual way of grouping [Component]s that are related.
|
|
||||||
/// {@endtemplate blueprint}
|
|
||||||
class Blueprint {
|
|
||||||
/// {@macro blueprint}
|
|
||||||
Blueprint({
|
|
||||||
Iterable<Component>? components,
|
|
||||||
Iterable<Blueprint>? blueprints,
|
|
||||||
}) {
|
|
||||||
if (components != null) _components.addAll(components);
|
|
||||||
if (blueprints != null) {
|
|
||||||
_blueprints.addAll(blueprints);
|
|
||||||
for (final blueprint in blueprints) {
|
|
||||||
_components.addAll(blueprint.components);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<Component> _components = [];
|
|
||||||
|
|
||||||
final List<Blueprint> _blueprints = [];
|
|
||||||
|
|
||||||
Future<void> _addToParent(Component parent) async {
|
|
||||||
await parent.addAll(_components);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a copy of the components built by this blueprint.
|
|
||||||
List<Component> get components => List.unmodifiable(_components);
|
|
||||||
|
|
||||||
/// Returns a copy of the blueprints built by this blueprint.
|
|
||||||
List<Blueprint> get blueprints => List.unmodifiable(_blueprints);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds helper methods regarding [Blueprint]s to [FlameGame].
|
|
||||||
extension FlameGameBlueprint on Component {
|
|
||||||
/// Shortcut to add a [Blueprint]s components to its parent.
|
|
||||||
Future<void> addFromBlueprint(Blueprint blueprint) async {
|
|
||||||
await blueprint._addToParent(this);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,249 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
|
||||||
|
/// {@template z_canvas_component}
|
||||||
|
/// Draws [ZIndex] components after the all non-[ZIndex] components have been
|
||||||
|
/// drawn.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class ZCanvasComponent extends Component {
|
||||||
|
/// {@macro z_canvas_component}
|
||||||
|
ZCanvasComponent({
|
||||||
|
Iterable<Component>? children,
|
||||||
|
}) : _zCanvas = ZCanvas(),
|
||||||
|
super(children: children);
|
||||||
|
|
||||||
|
final ZCanvas _zCanvas;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void renderTree(Canvas canvas) {
|
||||||
|
_zCanvas.canvas = canvas;
|
||||||
|
super.renderTree(_zCanvas);
|
||||||
|
_zCanvas.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply to any [Component] that will be rendered according to a
|
||||||
|
/// [ZIndex.zIndex].
|
||||||
|
///
|
||||||
|
/// [ZIndex] components must be descendants of a [ZCanvasComponent].
|
||||||
|
///
|
||||||
|
/// {@macro z_canvas.render}
|
||||||
|
mixin ZIndex on Component {
|
||||||
|
/// The z-index of this component.
|
||||||
|
///
|
||||||
|
/// The higher the value, the later the component will be drawn. Hence,
|
||||||
|
/// rendering in front of [Component]s with lower [zIndex] values.
|
||||||
|
int zIndex = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void renderTree(
|
||||||
|
Canvas canvas,
|
||||||
|
) {
|
||||||
|
if (canvas is ZCanvas) {
|
||||||
|
canvas.buffer(this);
|
||||||
|
} else {
|
||||||
|
super.renderTree(canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The [ZCanvas] allows to postpone the rendering of [ZIndex] components.
|
||||||
|
///
|
||||||
|
/// You should not use this class directly.
|
||||||
|
class ZCanvas implements Canvas {
|
||||||
|
/// The [Canvas] to render to.
|
||||||
|
///
|
||||||
|
/// This is set by [ZCanvasComponent] when rendering.
|
||||||
|
late Canvas canvas;
|
||||||
|
|
||||||
|
final List<ZIndex> _zBuffer = [];
|
||||||
|
|
||||||
|
/// Postpones the rendering of [ZIndex] component and its children.
|
||||||
|
void buffer(ZIndex component) => _zBuffer.add(component);
|
||||||
|
|
||||||
|
/// Renders all [ZIndex] components and their children.
|
||||||
|
///
|
||||||
|
/// {@template z_canvas.render}
|
||||||
|
/// The rendering order is defined by the parent [ZIndex]. The children of
|
||||||
|
/// the same parent are rendered in the order they were added.
|
||||||
|
///
|
||||||
|
/// If two [Component]s ever overlap each other, and have the same
|
||||||
|
/// [ZIndex.zIndex], there is no guarantee that the first one will be rendered
|
||||||
|
/// before the second one.
|
||||||
|
/// {@endtemplate}
|
||||||
|
void render() => _zBuffer
|
||||||
|
..sort((a, b) => a.zIndex.compareTo(b.zIndex))
|
||||||
|
..whereType<Component>().forEach(_render)
|
||||||
|
..clear();
|
||||||
|
|
||||||
|
void _render(Component component) => component.renderTree(canvas);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void clipPath(Path path, {bool doAntiAlias = true}) =>
|
||||||
|
canvas.clipPath(path, doAntiAlias: doAntiAlias);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void clipRRect(RRect rrect, {bool doAntiAlias = true}) =>
|
||||||
|
canvas.clipRRect(rrect, doAntiAlias: doAntiAlias);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void clipRect(
|
||||||
|
Rect rect, {
|
||||||
|
ClipOp clipOp = ClipOp.intersect,
|
||||||
|
bool doAntiAlias = true,
|
||||||
|
}) =>
|
||||||
|
canvas.clipRect(rect, clipOp: clipOp, doAntiAlias: doAntiAlias);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawArc(
|
||||||
|
Rect rect,
|
||||||
|
double startAngle,
|
||||||
|
double sweepAngle,
|
||||||
|
bool useCenter,
|
||||||
|
Paint paint,
|
||||||
|
) =>
|
||||||
|
canvas.drawArc(rect, startAngle, sweepAngle, useCenter, paint);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawAtlas(
|
||||||
|
Image atlas,
|
||||||
|
List<RSTransform> transforms,
|
||||||
|
List<Rect> rects,
|
||||||
|
List<Color>? colors,
|
||||||
|
BlendMode? blendMode,
|
||||||
|
Rect? cullRect,
|
||||||
|
Paint paint,
|
||||||
|
) =>
|
||||||
|
canvas.drawAtlas(
|
||||||
|
atlas,
|
||||||
|
transforms,
|
||||||
|
rects,
|
||||||
|
colors,
|
||||||
|
blendMode,
|
||||||
|
cullRect,
|
||||||
|
paint,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawCircle(Offset c, double radius, Paint paint) => canvas.drawCircle(
|
||||||
|
c,
|
||||||
|
radius,
|
||||||
|
paint,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawColor(Color color, BlendMode blendMode) =>
|
||||||
|
canvas.drawColor(color, blendMode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawDRRect(RRect outer, RRect inner, Paint paint) =>
|
||||||
|
canvas.drawDRRect(outer, inner, paint);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawImage(Image image, Offset offset, Paint paint) =>
|
||||||
|
canvas.drawImage(image, offset, paint);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawImageNine(Image image, Rect center, Rect dst, Paint paint) =>
|
||||||
|
canvas.drawImageNine(image, center, dst, paint);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawImageRect(Image image, Rect src, Rect dst, Paint paint) =>
|
||||||
|
canvas.drawImageRect(image, src, dst, paint);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawLine(Offset p1, Offset p2, Paint paint) =>
|
||||||
|
canvas.drawLine(p1, p2, paint);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawOval(Rect rect, Paint paint) => canvas.drawOval(rect, paint);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawPaint(Paint paint) => canvas.drawPaint(paint);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawParagraph(Paragraph paragraph, Offset offset) =>
|
||||||
|
canvas.drawParagraph(paragraph, offset);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawPath(Path path, Paint paint) => canvas.drawPath(path, paint);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawPicture(Picture picture) => canvas.drawPicture(picture);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawPoints(PointMode pointMode, List<Offset> points, Paint paint) =>
|
||||||
|
canvas.drawPoints(pointMode, points, paint);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawRRect(RRect rrect, Paint paint) => canvas.drawRRect(rrect, paint);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawRawAtlas(
|
||||||
|
Image atlas,
|
||||||
|
Float32List rstTransforms,
|
||||||
|
Float32List rects,
|
||||||
|
Int32List? colors,
|
||||||
|
BlendMode? blendMode,
|
||||||
|
Rect? cullRect,
|
||||||
|
Paint paint,
|
||||||
|
) =>
|
||||||
|
canvas.drawRawAtlas(
|
||||||
|
atlas,
|
||||||
|
rstTransforms,
|
||||||
|
rects,
|
||||||
|
colors,
|
||||||
|
blendMode,
|
||||||
|
cullRect,
|
||||||
|
paint,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawRawPoints(PointMode pointMode, Float32List points, Paint paint) =>
|
||||||
|
canvas.drawRawPoints(pointMode, points, paint);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawRect(Rect rect, Paint paint) => canvas.drawRect(rect, paint);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawShadow(
|
||||||
|
Path path,
|
||||||
|
Color color,
|
||||||
|
double elevation,
|
||||||
|
bool transparentOccluder,
|
||||||
|
) =>
|
||||||
|
canvas.drawShadow(path, color, elevation, transparentOccluder);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void drawVertices(Vertices vertices, BlendMode blendMode, Paint paint) =>
|
||||||
|
canvas.drawVertices(vertices, blendMode, paint);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int getSaveCount() => canvas.getSaveCount();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void restore() => canvas.restore();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void rotate(double radians) => canvas.rotate(radians);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void save() => canvas.save();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void saveLayer(Rect? bounds, Paint paint) => canvas.saveLayer(bounds, paint);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void scale(double sx, [double? sy]) => canvas.scale(sx, sy);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void skew(double sx, double sy) => canvas.skew(sx, sy);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void transform(Float64List matrix4) => canvas.transform(matrix4);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void translate(double dx, double dy) => canvas.translate(dx, dy);
|
||||||
|
}
|
@ -1,86 +0,0 @@
|
|||||||
// ignore_for_file: cascade_invocations
|
|
||||||
|
|
||||||
import 'package:flame/components.dart';
|
|
||||||
import 'package:flame/game.dart';
|
|
||||||
import 'package:flame_test/flame_test.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:pinball_flame/pinball_flame.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
|
||||||
|
|
||||||
group('Blueprint', () {
|
|
||||||
final flameTester = FlameTester(FlameGame.new);
|
|
||||||
|
|
||||||
test('correctly sets and gets components', () {
|
|
||||||
final component1 = Component();
|
|
||||||
final component2 = Component();
|
|
||||||
final blueprint = Blueprint(
|
|
||||||
components: [
|
|
||||||
component1,
|
|
||||||
component2,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(blueprint.components.length, 2);
|
|
||||||
expect(blueprint.components, contains(component1));
|
|
||||||
expect(blueprint.components, contains(component2));
|
|
||||||
});
|
|
||||||
|
|
||||||
test('correctly sets and gets blueprints', () {
|
|
||||||
final blueprint2 = Blueprint(
|
|
||||||
components: [Component()],
|
|
||||||
);
|
|
||||||
final blueprint1 = Blueprint(
|
|
||||||
components: [Component()],
|
|
||||||
blueprints: [blueprint2],
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(blueprint1.blueprints, contains(blueprint2));
|
|
||||||
});
|
|
||||||
|
|
||||||
flameTester.test('adds the components to parent on attach', (game) async {
|
|
||||||
final blueprint = Blueprint(
|
|
||||||
components: [
|
|
||||||
Component(),
|
|
||||||
Component(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
await game.addFromBlueprint(blueprint);
|
|
||||||
await game.ready();
|
|
||||||
|
|
||||||
for (final component in blueprint.components) {
|
|
||||||
expect(game.children.contains(component), isTrue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
flameTester.test('adds components from a child Blueprint', (game) async {
|
|
||||||
final childBlueprint = Blueprint(
|
|
||||||
components: [
|
|
||||||
Component(),
|
|
||||||
Component(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
final parentBlueprint = Blueprint(
|
|
||||||
components: [
|
|
||||||
Component(),
|
|
||||||
Component(),
|
|
||||||
],
|
|
||||||
blueprints: [
|
|
||||||
childBlueprint,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
await game.addFromBlueprint(parentBlueprint);
|
|
||||||
await game.ready();
|
|
||||||
|
|
||||||
for (final component in childBlueprint.components) {
|
|
||||||
expect(game.children, contains(component));
|
|
||||||
expect(parentBlueprint.components, contains(component));
|
|
||||||
}
|
|
||||||
for (final component in parentBlueprint.components) {
|
|
||||||
expect(game.children, contains(component));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 22 KiB |
@ -0,0 +1,385 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter/material.dart' hide Image;
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
|
||||||
|
class _TestCircleComponent extends CircleComponent with ZIndex {
|
||||||
|
_TestCircleComponent(Color color)
|
||||||
|
: super(
|
||||||
|
paint: Paint()..color = color,
|
||||||
|
radius: 10,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MockCanvas extends Mock implements Canvas {}
|
||||||
|
|
||||||
|
class _MockImage extends Mock implements Image {}
|
||||||
|
|
||||||
|
class _MockPicture extends Mock implements Picture {}
|
||||||
|
|
||||||
|
class _MockParagraph extends Mock implements Paragraph {}
|
||||||
|
|
||||||
|
class _MockVertices extends Mock implements Vertices {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final flameTester = FlameTester(FlameGame.new);
|
||||||
|
const goldenPrefix = 'golden/rendering/';
|
||||||
|
|
||||||
|
group('ZCanvasComponent', () {
|
||||||
|
flameTester.test('loads correctly', (game) async {
|
||||||
|
final component = ZCanvasComponent();
|
||||||
|
await game.ensureAdd(component);
|
||||||
|
expect(game.contains(component), isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'red circle renders behind blue circle',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final canvas = ZCanvasComponent(
|
||||||
|
children: [
|
||||||
|
_TestCircleComponent(Colors.blue)..zIndex = 1,
|
||||||
|
_TestCircleComponent(Colors.red)..zIndex = 0,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
await game.ensureAdd(canvas);
|
||||||
|
|
||||||
|
game.camera.followVector2(Vector2.zero());
|
||||||
|
},
|
||||||
|
verify: (game, tester) async {
|
||||||
|
await expectLater(
|
||||||
|
find.byGame<FlameGame>(),
|
||||||
|
matchesGoldenFile('${goldenPrefix}red_blue.png'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.testGameWidget(
|
||||||
|
'blue circle renders behind red circle',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
final canvas = ZCanvasComponent(
|
||||||
|
children: [
|
||||||
|
_TestCircleComponent(Colors.blue)..zIndex = 0,
|
||||||
|
_TestCircleComponent(Colors.red)..zIndex = 1
|
||||||
|
],
|
||||||
|
);
|
||||||
|
await game.ensureAdd(canvas);
|
||||||
|
|
||||||
|
game.camera.followVector2(Vector2.zero());
|
||||||
|
},
|
||||||
|
verify: (game, tester) async {
|
||||||
|
await expectLater(
|
||||||
|
find.byGame<FlameGame>(),
|
||||||
|
matchesGoldenFile('${goldenPrefix}blue_red.png'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('ZCanvas', () {
|
||||||
|
late Canvas canvas;
|
||||||
|
late Path path;
|
||||||
|
late RRect rRect;
|
||||||
|
late Rect rect;
|
||||||
|
late Paint paint;
|
||||||
|
late Image atlas;
|
||||||
|
late BlendMode blendMode;
|
||||||
|
late Color color;
|
||||||
|
late Offset offset;
|
||||||
|
late Float64List float64list;
|
||||||
|
late Float32List float32list;
|
||||||
|
late Int32List int32list;
|
||||||
|
late Picture picture;
|
||||||
|
late Paragraph paragraph;
|
||||||
|
late Vertices vertices;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
canvas = _MockCanvas();
|
||||||
|
path = Path();
|
||||||
|
rRect = RRect.zero;
|
||||||
|
rect = Rect.zero;
|
||||||
|
paint = Paint();
|
||||||
|
atlas = _MockImage();
|
||||||
|
blendMode = BlendMode.clear;
|
||||||
|
color = Colors.black;
|
||||||
|
offset = Offset.zero;
|
||||||
|
float64list = Float64List(1);
|
||||||
|
float32list = Float32List(1);
|
||||||
|
int32list = Int32List(1);
|
||||||
|
picture = _MockPicture();
|
||||||
|
paragraph = _MockParagraph();
|
||||||
|
vertices = _MockVertices();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("clipPath calls Canvas's clipPath", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.clipPath(path, doAntiAlias: false);
|
||||||
|
verify(
|
||||||
|
() => canvas.clipPath(path, doAntiAlias: false),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("clipRRect calls Canvas's clipRRect", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.clipRRect(rRect, doAntiAlias: false);
|
||||||
|
verify(
|
||||||
|
() => canvas.clipRRect(rRect, doAntiAlias: false),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("clipRect calls Canvas's clipRect", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.clipRect(rect, doAntiAlias: false);
|
||||||
|
verify(
|
||||||
|
() => canvas.clipRect(rect, doAntiAlias: false),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawArc calls Canvas's drawArc", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawArc(rect, 0, 1, false, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawArc(rect, 0, 1, false, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawAtlas calls Canvas's drawAtlas", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawAtlas(atlas, [], [], [], blendMode, rect, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawAtlas(atlas, [], [], [], blendMode, rect, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawCircle calls Canvas's drawCircle", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawCircle(offset, 0, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawCircle(offset, 0, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawColor calls Canvas's drawColor", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawColor(color, blendMode);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawColor(color, blendMode),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawDRRect calls Canvas's drawDRRect", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawDRRect(rRect, rRect, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawDRRect(rRect, rRect, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawImage calls Canvas's drawImage", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawImage(atlas, offset, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawImage(atlas, offset, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawImageNine calls Canvas's drawImageNine", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawImageNine(atlas, rect, rect, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawImageNine(atlas, rect, rect, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawImageRect calls Canvas's drawImageRect", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawImageRect(atlas, rect, rect, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawImageRect(atlas, rect, rect, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawLine calls Canvas's drawLine", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawLine(offset, offset, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawLine(offset, offset, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawOval calls Canvas's drawOval", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawOval(rect, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawOval(rect, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawPaint calls Canvas's drawPaint", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawPaint(paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawPaint(paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawParagraph calls Canvas's drawParagraph", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawParagraph(paragraph, offset);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawParagraph(paragraph, offset),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawPath calls Canvas's drawPath", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawPath(path, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawPath(path, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawPicture calls Canvas's drawPicture", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawPicture(picture);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawPicture(picture),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawPoints calls Canvas's drawPoints", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawPoints(PointMode.points, [offset], paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawPoints(PointMode.points, [offset], paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawRRect calls Canvas's drawRRect", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawRRect(rRect, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawRRect(rRect, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawRawAtlas calls Canvas's drawRawAtlas", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawRawAtlas(
|
||||||
|
atlas,
|
||||||
|
float32list,
|
||||||
|
float32list,
|
||||||
|
int32list,
|
||||||
|
BlendMode.clear,
|
||||||
|
rect,
|
||||||
|
paint,
|
||||||
|
);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawRawAtlas(
|
||||||
|
atlas,
|
||||||
|
float32list,
|
||||||
|
float32list,
|
||||||
|
int32list,
|
||||||
|
BlendMode.clear,
|
||||||
|
rect,
|
||||||
|
paint,
|
||||||
|
),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawRawPoints calls Canvas's drawRawPoints", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawRawPoints(PointMode.points, float32list, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawRawPoints(PointMode.points, float32list, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawRect calls Canvas's drawRect", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawRect(rect, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawRect(rect, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawShadow calls Canvas's drawShadow", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawShadow(path, color, 0, false);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawShadow(path, color, 0, false),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("drawVertices calls Canvas's drawVertices", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.drawVertices(vertices, blendMode, paint);
|
||||||
|
verify(
|
||||||
|
() => canvas.drawVertices(vertices, blendMode, paint),
|
||||||
|
).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getSaveCount calls Canvas's getSaveCount", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
when(() => canvas.getSaveCount()).thenReturn(1);
|
||||||
|
zcanvas.getSaveCount();
|
||||||
|
verify(() => canvas.getSaveCount()).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("restore calls Canvas's restore", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.restore();
|
||||||
|
verify(() => canvas.restore()).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("rotate calls Canvas's rotate", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.rotate(0);
|
||||||
|
verify(() => canvas.rotate(0)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("save calls Canvas's save", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.save();
|
||||||
|
verify(() => canvas.save()).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("saveLayer calls Canvas's saveLayer", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.saveLayer(rect, paint);
|
||||||
|
verify(() => canvas.saveLayer(rect, paint)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("scale calls Canvas's scale", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.scale(0, 0);
|
||||||
|
verify(() => canvas.scale(0, 0)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("skew calls Canvas's skew", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.skew(0, 0);
|
||||||
|
verify(() => canvas.skew(0, 0)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("transform calls Canvas's transform", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.transform(float64list);
|
||||||
|
verify(() => canvas.transform(float64list)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("translate calls Canvas's translate", () {
|
||||||
|
final zcanvas = ZCanvas()..canvas = canvas;
|
||||||
|
zcanvas.translate(0, 0);
|
||||||
|
verify(() => canvas.translate(0, 0)).called(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in new issue