From 95939391e7c3fe4888b4fb5dde792771d1d14824 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sat, 25 Jun 2022 12:42:14 -0700 Subject: [PATCH 01/54] initial Page class --- .../flame/lib/src/components/navigator.dart | 5 +++ packages/flame/lib/src/components/page.dart | 34 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 packages/flame/lib/src/components/navigator.dart create mode 100644 packages/flame/lib/src/components/page.dart diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator.dart new file mode 100644 index 00000000000..5312e320be8 --- /dev/null +++ b/packages/flame/lib/src/components/navigator.dart @@ -0,0 +1,5 @@ +import 'package:flame/src/components/component.dart'; + +class Navigator extends Component { + +} diff --git a/packages/flame/lib/src/components/page.dart b/packages/flame/lib/src/components/page.dart new file mode 100644 index 00000000000..115075388ac --- /dev/null +++ b/packages/flame/lib/src/components/page.dart @@ -0,0 +1,34 @@ + +import 'package:flame/src/components/component.dart'; +import 'package:flame/src/components/mixins/parent_is_a.dart'; +import 'package:flame/src/components/navigator.dart'; +import 'package:flame/src/components/position_component.dart'; +import 'package:meta/meta.dart'; + +class Page extends PositionComponent with ParentIsA { + Page({ + required this.name, + this.builder, + this.transparent = false, + }); + + final String name; + + final Component Function()? builder; + + /// If true, then the page below this one will continue to be rendered when + /// this page becomes active. If false, then this page is assumed to + /// completely obscure any page that would be underneath, and therefore the + /// page underneath doesn't need to be rendered. + final bool transparent; + + @internal + Component build() { + assert( + builder != null, + 'Either provide `builder` in the constructor, or override the build() ' + 'method', + ); + return builder!(); + } +} From 2bbe115cf894ded50207d118cc8eb3218e9dd03c Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sat, 25 Jun 2022 15:23:51 -0700 Subject: [PATCH 02/54] work on example game --- doc/flame/examples/lib/main.dart | 5 + doc/flame/examples/lib/navigator.dart | 122 ++++++++++++++++++ packages/flame/lib/game.dart | 2 + .../flame/lib/src/components/navigator.dart | 39 ++++++ packages/flame/lib/src/components/page.dart | 18 ++- 5 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 doc/flame/examples/lib/navigator.dart diff --git a/doc/flame/examples/lib/main.dart b/doc/flame/examples/lib/main.dart index 76200327a96..155e16c860a 100644 --- a/doc/flame/examples/lib/main.dart +++ b/doc/flame/examples/lib/main.dart @@ -1,6 +1,7 @@ import 'dart:html'; // ignore: avoid_web_libraries_in_flutter import 'package:doc_flame_examples/drag_events.dart'; +import 'package:doc_flame_examples/navigator.dart'; import 'package:doc_flame_examples/tap_events.dart'; import 'package:flame/game.dart'; import 'package:flutter/widgets.dart'; @@ -18,6 +19,10 @@ void main() { case 'drag_events': game = DragEventsGame(); break; + case 'navigator': + default: + game = NavigatorGame(); + break; } if (game != null) { runApp(GameWidget(game: game)); diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart new file mode 100644 index 00000000000..2f7a7a198ad --- /dev/null +++ b/doc/flame/examples/lib/navigator.dart @@ -0,0 +1,122 @@ +import 'package:flame/components.dart'; +import 'package:flame/experimental.dart'; +import 'package:flame/game.dart'; +import 'package:flutter/rendering.dart'; + +class NavigatorGame extends FlameGame with HasTappableComponents { + late final Navigator navigator; + + @override + Future onLoad() async { + navigator = Navigator( + pages: { + 'home': Page(builder: StartPageImpl.new), + }, + initialPage: 'home', + )..addToParent(this); + } +} + +class StartPageImpl extends Component with HasGameRef { + StartPageImpl() + : _logo = TextPaint( + style: const TextStyle( + fontSize: 64, + color: Color(0xFFFFFFFF), + fontWeight: FontWeight.w800, + ), + ).toTextPainter('Syzygy'), + _button1 = RoundedButton( + text: 'Level 1', + action: () {}, + color: const Color(0xffadde6c), + borderColor: const Color(0xffedffab), + ), + _button2 = RoundedButton( + text: 'Level 2', + action: () {}, + color: const Color(0xffdebe6c), + borderColor: const Color(0xfffff4c7), + ) { + add(_button1); + add(_button2); + } + + final TextPainter _logo; + late Offset _logoOffset; + final RoundedButton _button1; + final RoundedButton _button2; + + @override + void onGameResize(Vector2 size) { + super.onGameResize(size); + _logoOffset = Offset((size.x - _logo.width) / 2, size.y / 3 - _logo.height); + _button1.position = Vector2(size.x / 2, _logoOffset.dy + 140); + _button2.position = Vector2(size.x / 2, _logoOffset.dy + 200); + } + + @override + void render(Canvas canvas) { + canvas.drawColor(const Color(0xff063e67), BlendMode.src); + _logo.paint(canvas, _logoOffset); + } +} + +class RoundedButton extends PositionComponent with TapCallbacks { + RoundedButton({ + required this.text, + required this.action, + required Color color, + required Color borderColor, + super.anchor = Anchor.center, + }) : _textDrawable = TextPaint( + style: const TextStyle( + fontSize: 20, + color: Color(0xFF000000), + fontWeight: FontWeight.w800, + ), + ).toTextPainter(text) { + size = Vector2(150, 40); + _textOffset = Offset( + (size.x - _textDrawable.width) / 2, + (size.y - _textDrawable.height) / 2, + ); + _rrect = RRect.fromLTRBR(0, 0, size.x, size.y, Radius.circular(size.y / 2)); + _bgPaint = Paint()..color = color; + _borderPaint = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 2 + ..color = borderColor; + } + + final String text; + final void Function() action; + final TextPainter _textDrawable; + late final Offset _textOffset; + late final RRect _rrect; + late final Paint _borderPaint; + late final Paint _bgPaint; + + @override + void render(Canvas canvas) { + canvas.drawRRect(_rrect, _bgPaint); + canvas.drawRRect(_rrect, _borderPaint); + _textDrawable.paint(canvas, _textOffset); + } + + @override + void onTapDown(TapDownEvent event) { + scale = Vector2.all(1.05); + } + + @override + void onTapUp(TapUpEvent event) { + scale = Vector2.all(1.0); + action(); + } + + @override + void onTapCancel(TapCancelEvent event) { + scale = Vector2.all(1.0); + } +} diff --git a/packages/flame/lib/game.dart b/packages/flame/lib/game.dart index 44e84efc193..2505e84f700 100644 --- a/packages/flame/lib/game.dart +++ b/packages/flame/lib/game.dart @@ -1,6 +1,8 @@ /// {@canonicalFor text.TextPaint} /// {@canonicalFor text.TextRenderer} export 'src/collisions/has_collision_detection.dart'; +export 'src/components/navigator.dart' show Navigator; +export 'src/components/page.dart' show Page; export 'src/extensions/vector2.dart'; export 'src/game/camera/camera.dart'; export 'src/game/camera/viewport.dart'; diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator.dart index 5312e320be8..0e00885f336 100644 --- a/packages/flame/lib/src/components/navigator.dart +++ b/packages/flame/lib/src/components/navigator.dart @@ -1,5 +1,44 @@ +import 'package:collection/collection.dart'; import 'package:flame/src/components/component.dart'; +import 'package:flame/src/components/page.dart'; class Navigator extends Component { + Navigator({ + required Map pages, + required this.initialPage, + }) : _pages = pages; + final String initialPage; + final Map _pages; + final List _currentPages = []; + + void showPage(String name) { + final page = _pages[name]; + assert(page != null, 'Page "$name" is not known to the Navigator'); + final activePage = _currentPages.lastOrNull; + if (page! == activePage) { + return; + } + if (!page.isMounted) { + add(page); + } + _currentPages.remove(page); + _currentPages.add(page); + _fixPageOrder(); + activePage?.deactivate(); + page.activate(); + } + + void _fixPageOrder() { + for (var i = 0; i < _currentPages.length; i++) { + _currentPages[i].changePriorityWithoutResorting(i); + } + reorderChildren(); + } + + @override + void onMount() { + super.onMount(); + showPage(initialPage); + } } diff --git a/packages/flame/lib/src/components/page.dart b/packages/flame/lib/src/components/page.dart index 115075388ac..c94752d9ef1 100644 --- a/packages/flame/lib/src/components/page.dart +++ b/packages/flame/lib/src/components/page.dart @@ -7,13 +7,10 @@ import 'package:meta/meta.dart'; class Page extends PositionComponent with ParentIsA { Page({ - required this.name, this.builder, this.transparent = false, }); - final String name; - final Component Function()? builder; /// If true, then the page below this one will continue to be rendered when @@ -22,6 +19,10 @@ class Page extends PositionComponent with ParentIsA { /// page underneath doesn't need to be rendered. final bool transparent; + void onActivate() {} + + void deactivate() {} + @internal Component build() { assert( @@ -31,4 +32,15 @@ class Page extends PositionComponent with ParentIsA { ); return builder!(); } + + @internal + bool get isBuilt => _child != null; + + Component? _child; + + @internal + void activate() { + _child ??= build()..addToParent(this); + onActivate(); + } } From 26c76a3621d6adb89eea9896c5287d95fcac3286 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sat, 25 Jun 2022 15:40:42 -0700 Subject: [PATCH 03/54] start implementing levels --- doc/flame/examples/lib/navigator.dart | 83 +++++++++++++++++++-------- 1 file changed, 60 insertions(+), 23 deletions(-) diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index 2f7a7a198ad..583b5d4ca80 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -11,6 +11,8 @@ class NavigatorGame extends FlameGame with HasTappableComponents { navigator = Navigator( pages: { 'home': Page(builder: StartPageImpl.new), + 'level1': Page(builder: Level1PageImpl.new), + 'level2': Page(builder: Level2PageImpl.new), }, initialPage: 'home', )..addToParent(this); @@ -18,34 +20,34 @@ class NavigatorGame extends FlameGame with HasTappableComponents { } class StartPageImpl extends Component with HasGameRef { - StartPageImpl() - : _logo = TextPaint( - style: const TextStyle( - fontSize: 64, - color: Color(0xFFFFFFFF), - fontWeight: FontWeight.w800, - ), - ).toTextPainter('Syzygy'), - _button1 = RoundedButton( - text: 'Level 1', - action: () {}, - color: const Color(0xffadde6c), - borderColor: const Color(0xffedffab), - ), - _button2 = RoundedButton( - text: 'Level 2', - action: () {}, - color: const Color(0xffdebe6c), - borderColor: const Color(0xfffff4c7), - ) { + StartPageImpl() { + _logo = TextPaint( + style: const TextStyle( + fontSize: 64, + color: Color(0xFFFFFFFF), + fontWeight: FontWeight.w800, + ), + ).toTextPainter('Syzygy'); + _button1 = RoundedButton( + text: 'Level 1', + action: () => gameRef.navigator.showPage('level1'), + color: const Color(0xffadde6c), + borderColor: const Color(0xffedffab), + ); + _button2 = RoundedButton( + text: 'Level 2', + action: () => gameRef.navigator.showPage('level2'), + color: const Color(0xffdebe6c), + borderColor: const Color(0xfffff4c7), + ); add(_button1); add(_button2); } - final TextPainter _logo; + late final TextPainter _logo; late Offset _logoOffset; - final RoundedButton _button1; - final RoundedButton _button2; + late final RoundedButton _button1; + late final RoundedButton _button2; @override void onGameResize(Vector2 size) { @@ -120,3 +122,38 @@ class RoundedButton extends PositionComponent with TapCallbacks { scale = Vector2.all(1.0); } } + +class Level1PageImpl extends Component { + @override + Future onLoad() async { + final game = findGame()!; + add( + Planet(radius: 40, color: const Color(0xFFFFFFEE)) + ..position = game.size / 2, + ); + } + + @override + void render(Canvas canvas) { + canvas.drawColor(const Color(0xaa845fb0), BlendMode.srcATop); + } +} + +class Level2PageImpl extends Component {} + +class Planet extends PositionComponent { + Planet({required this.radius, required this.color}) + : _paint = Paint()..color = color, + super(anchor: Anchor.center, size: Vector2.all(2 * radius)); + + final double radius; + final Color color; + final Paint _paint; + + @override + void render(Canvas canvas) { + canvas.drawCircle(Offset(radius, radius), radius, _paint); + } +} + +class Orbit extends PositionComponent {} From d80617c6fd907ac916a96ccdf3cfbbd6b2018006 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sat, 25 Jun 2022 15:48:23 -0700 Subject: [PATCH 04/54] do not render hidden pages --- packages/flame/lib/src/components/navigator.dart | 16 +++++++++++++++- packages/flame/lib/src/components/page.dart | 3 +++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator.dart index 0e00885f336..e70fad2e956 100644 --- a/packages/flame/lib/src/components/navigator.dart +++ b/packages/flame/lib/src/components/navigator.dart @@ -1,3 +1,5 @@ +import 'dart:ui'; + import 'package:collection/collection.dart'; import 'package:flame/src/components/component.dart'; import 'package:flame/src/components/page.dart'; @@ -30,8 +32,11 @@ class Navigator extends Component { } void _fixPageOrder() { - for (var i = 0; i < _currentPages.length; i++) { + var render = true; + for (var i = _currentPages.length - 1; i >= 0; i--) { _currentPages[i].changePriorityWithoutResorting(i); + _currentPages[i].isRendered = render; + render &= _currentPages[i].transparent; } reorderChildren(); } @@ -41,4 +46,13 @@ class Navigator extends Component { super.onMount(); showPage(initialPage); } + + @override + void renderTree(Canvas canvas) { + children.forEach((child) { + if (child is Page && child.isRendered) { + child.renderTree(canvas); + } + }); + } } diff --git a/packages/flame/lib/src/components/page.dart b/packages/flame/lib/src/components/page.dart index c94752d9ef1..3ffab65c878 100644 --- a/packages/flame/lib/src/components/page.dart +++ b/packages/flame/lib/src/components/page.dart @@ -36,6 +36,9 @@ class Page extends PositionComponent with ParentIsA { @internal bool get isBuilt => _child != null; + @internal + bool isRendered = true; + Component? _child; @internal From 1ea9c3e776df9ad0d3b267f578c72dd6eb09259e Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sat, 25 Jun 2022 16:05:18 -0700 Subject: [PATCH 05/54] finish level 1 --- doc/flame/examples/lib/navigator.dart | 66 ++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index 583b5d4ca80..a1e1859b7c1 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -128,8 +128,28 @@ class Level1PageImpl extends Component { Future onLoad() async { final game = findGame()!; add( - Planet(radius: 40, color: const Color(0xFFFFFFEE)) - ..position = game.size / 2, + Planet( + radius: 40, + color: const Color(0xFFFFFFEE), + position: game.size / 2, + children: [ + Orbit( + radius: 150, + revolutionPeriod: 6, + planet: Planet( + radius: 15, + color: const Color(0xff54d7b1), + children: [ + Orbit( + radius: 40, + revolutionPeriod: 5, + planet: Planet(radius: 5, color: const Color(0xFFcccccc)), + ), + ], + ), + ), + ], + ), ); } @@ -142,9 +162,12 @@ class Level1PageImpl extends Component { class Level2PageImpl extends Component {} class Planet extends PositionComponent { - Planet({required this.radius, required this.color}) - : _paint = Paint()..color = color, - super(anchor: Anchor.center, size: Vector2.all(2 * radius)); + Planet({ + required this.radius, + required this.color, + super.position, + super.children, + }) : _paint = Paint()..color = color; final double radius; final Color color; @@ -152,8 +175,37 @@ class Planet extends PositionComponent { @override void render(Canvas canvas) { - canvas.drawCircle(Offset(radius, radius), radius, _paint); + canvas.drawCircle(Offset.zero, radius, _paint); } } -class Orbit extends PositionComponent {} +class Orbit extends PositionComponent { + Orbit({ + required this.radius, + required this.planet, + required this.revolutionPeriod, + double initialAngle = 0, + }) : _paint = Paint() + ..style = PaintingStyle.stroke + ..color = const Color(0x888888aa), + _angle = initialAngle { + add(planet); + } + + final double radius; + final double revolutionPeriod; + final Planet planet; + final Paint _paint; + double _angle; + + @override + void render(Canvas canvas) { + canvas.drawCircle(Offset.zero, radius, _paint); + } + + @override + void update(double dt) { + _angle += dt / revolutionPeriod * Transform2D.tau; + planet.position = Vector2(radius, 0)..rotate(_angle); + } +} From 8b7856435e8fdc8afacf572e2c9ca057c78e6792 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sat, 25 Jun 2022 16:59:40 -0700 Subject: [PATCH 06/54] ensure point events dont propagate inappropriately --- doc/flame/examples/lib/navigator.dart | 2 +- .../flame/lib/src/components/navigator.dart | 11 ++----- packages/flame/lib/src/components/page.dart | 33 +++++++++++++++++-- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index a1e1859b7c1..720800f6ac9 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -155,7 +155,7 @@ class Level1PageImpl extends Component { @override void render(Canvas canvas) { - canvas.drawColor(const Color(0xaa845fb0), BlendMode.srcATop); + canvas.drawColor(const Color(0xbb5f358d), BlendMode.srcATop); } } diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator.dart index e70fad2e956..fec91be91ad 100644 --- a/packages/flame/lib/src/components/navigator.dart +++ b/packages/flame/lib/src/components/navigator.dart @@ -2,7 +2,9 @@ import 'dart:ui'; import 'package:collection/collection.dart'; import 'package:flame/src/components/component.dart'; +import 'package:flame/src/components/mixins/coordinate_transform.dart'; import 'package:flame/src/components/page.dart'; +import 'package:vector_math/vector_math_64.dart'; class Navigator extends Component { Navigator({ @@ -46,13 +48,4 @@ class Navigator extends Component { super.onMount(); showPage(initialPage); } - - @override - void renderTree(Canvas canvas) { - children.forEach((child) { - if (child is Page && child.isRendered) { - child.renderTree(canvas); - } - }); - } } diff --git a/packages/flame/lib/src/components/page.dart b/packages/flame/lib/src/components/page.dart index 3ffab65c878..3fb5ba6b654 100644 --- a/packages/flame/lib/src/components/page.dart +++ b/packages/flame/lib/src/components/page.dart @@ -1,9 +1,11 @@ +import 'dart:ui' show Canvas; import 'package:flame/src/components/component.dart'; import 'package:flame/src/components/mixins/parent_is_a.dart'; import 'package:flame/src/components/navigator.dart'; import 'package:flame/src/components/position_component.dart'; import 'package:meta/meta.dart'; +import 'package:vector_math/vector_math_64.dart'; class Page extends PositionComponent with ParentIsA { Page({ @@ -21,9 +23,8 @@ class Page extends PositionComponent with ParentIsA { void onActivate() {} - void deactivate() {} + void onDeactivate() {} - @internal Component build() { assert( builder != null, @@ -33,6 +34,8 @@ class Page extends PositionComponent with ParentIsA { return builder!(); } + //#region Implementation methods + @internal bool get isBuilt => _child != null; @@ -46,4 +49,30 @@ class Page extends PositionComponent with ParentIsA { _child ??= build()..addToParent(this); onActivate(); } + + @internal + void deactivate() { + onDeactivate(); + } + + @override + void renderTree(Canvas canvas) { + if (isRendered) { + super.renderTree(canvas); + } + } + + @override + Iterable componentsAtPoint( + Vector2 point, [ + List? nestedPoints, + ]) { + if (isRendered) { + return super.componentsAtPoint(point, nestedPoints); + } else { + return const Iterable.empty(); + } + } + + //#endregion } From 794f10a2dadd33a392be9b47e96031d097bf178c Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sat, 25 Jun 2022 17:08:10 -0700 Subject: [PATCH 07/54] added level 2 --- doc/flame/examples/lib/navigator.dart | 73 +++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index 720800f6ac9..fa8d14e0f25 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -64,6 +64,16 @@ class StartPageImpl extends Component with HasGameRef { } } +class Background extends Component { + Background(this.color); + final Color color; + + @override + void render(Canvas canvas) { + canvas.drawColor(color, BlendMode.srcATop); + } +} + class RoundedButton extends PositionComponent with TapCallbacks { RoundedButton({ required this.text, @@ -127,7 +137,8 @@ class Level1PageImpl extends Component { @override Future onLoad() async { final game = findGame()!; - add( + addAll([ + Background(const Color(0xbb5f358d)), Planet( radius: 40, color: const Color(0xFFFFFFEE), @@ -141,26 +152,70 @@ class Level1PageImpl extends Component { color: const Color(0xff54d7b1), children: [ Orbit( - radius: 40, - revolutionPeriod: 5, - planet: Planet(radius: 5, color: const Color(0xFFcccccc)), + radius: 40, + revolutionPeriod: 5, + planet: Planet(radius: 5, color: const Color(0xFFcccccc)), ), ], ), ), ], ), - ); + ]); } +} +class Level2PageImpl extends Component { @override - void render(Canvas canvas) { - canvas.drawColor(const Color(0xbb5f358d), BlendMode.srcATop); + Future onLoad() async { + final game = findGame()!; + addAll([ + Background(const Color(0xbb074825)), + Planet( + radius: 30, + color: const Color(0xFFFFFFff), + position: game.size / 2, + children: [ + Orbit( + radius: 100, + revolutionPeriod: 5, + planet: Planet( + radius: 15, + color: const Color(0xffcbce7b), + ), + ), + Orbit( + radius: 180, + revolutionPeriod: 11, + planet: Planet( + radius: 20, + color: const Color(0xfff32727), + children: [ + Orbit( + radius: 32, + revolutionPeriod: 3, + planet: Planet( + radius: 6, + color: const Color(0xffffdb00), + ), + ), + Orbit( + radius: 45, + revolutionPeriod: 4, + planet: Planet( + radius: 4, + color: const Color(0xffdc00ff), + ), + ), + ], + ), + ), + ], + ), + ]); } } -class Level2PageImpl extends Component {} - class Planet extends PositionComponent { Planet({ required this.radius, From 367c1216b9edd1deeb172bb5e01b1440639afb58 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sat, 25 Jun 2022 17:30:10 -0700 Subject: [PATCH 08/54] added pop page functionality --- doc/flame/examples/lib/navigator.dart | 76 +++++++++++++++---- .../flame/lib/src/components/navigator.dart | 10 +++ 2 files changed, 71 insertions(+), 15 deletions(-) diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index fa8d14e0f25..76b45c18290 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -133,15 +133,60 @@ class RoundedButton extends PositionComponent with TapCallbacks { } } +class BackButton extends PositionComponent + with TapCallbacks, HasGameRef { + BackButton() : super(size: Vector2.all(40), position: Vector2.all(10)); + + final Paint _borderPaint = Paint() + ..style = PaintingStyle.stroke + ..color = const Color(0x66ffffff); + final Paint _arrowPaint = Paint() + ..style = PaintingStyle.stroke + ..color = const Color(0xffaaaaaa) + ..strokeWidth = 7; + final Path _arrowPath = Path() + ..moveTo(22, 8) + ..lineTo(10, 20) + ..lineTo(22, 32) + ..moveTo(12, 20) + ..lineTo(34, 20); + + @override + void render(Canvas canvas) { + canvas.drawRRect( + RRect.fromRectAndRadius(size.toRect(), Radius.circular(5)), + _borderPaint, + ); + canvas.drawPath(_arrowPath, _arrowPaint); + } + + @override + void onTapDown(TapDownEvent event) { + _arrowPaint.color = const Color(0xffffffff); + } + + @override + void onTapUp(TapUpEvent event) { + _arrowPaint.color = const Color(0xffaaaaaa); + gameRef.navigator.popPage(); + } + + @override + void onTapCancel(TapCancelEvent event) { + _arrowPaint.color = const Color(0xffaaaaaa); + } +} + class Level1PageImpl extends Component { @override Future onLoad() async { final game = findGame()!; addAll([ Background(const Color(0xbb5f358d)), + BackButton(), Planet( radius: 40, - color: const Color(0xFFFFFFEE), + color: const Color(0xfffff188), position: game.size / 2, children: [ Orbit( @@ -171,6 +216,7 @@ class Level2PageImpl extends Component { final game = findGame()!; addAll([ Background(const Color(0xbb074825)), + BackButton(), Planet( radius: 30, color: const Color(0xFFFFFFff), @@ -181,31 +227,31 @@ class Level2PageImpl extends Component { revolutionPeriod: 5, planet: Planet( radius: 15, - color: const Color(0xffcbce7b), + color: const Color(0xffc9ce0d), ), ), Orbit( radius: 180, - revolutionPeriod: 11, + revolutionPeriod: 10, planet: Planet( radius: 20, color: const Color(0xfff32727), children: [ Orbit( - radius: 32, - revolutionPeriod: 3, - planet: Planet( - radius: 6, - color: const Color(0xffffdb00), - ), + radius: 32, + revolutionPeriod: 3, + planet: Planet( + radius: 6, + color: const Color(0xffffdb00), + ), ), Orbit( - radius: 45, - revolutionPeriod: 4, - planet: Planet( - radius: 4, - color: const Color(0xffdc00ff), - ), + radius: 45, + revolutionPeriod: 4, + planet: Planet( + radius: 4, + color: const Color(0xffdc00ff), + ), ), ], ), diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator.dart index fec91be91ad..b0e9b068ba1 100644 --- a/packages/flame/lib/src/components/navigator.dart +++ b/packages/flame/lib/src/components/navigator.dart @@ -33,6 +33,16 @@ class Navigator extends Component { page.activate(); } + void popPage() { + if (_currentPages.isEmpty) { + return; + } + final poppedPage = _currentPages.removeLast()..removeFromParent(); + _fixPageOrder(); + poppedPage.deactivate(); + _currentPages.lastOrNull?.activate(); + } + void _fixPageOrder() { var render = true; for (var i = _currentPages.length - 1; i >= 0; i--) { From 52bf6554995bf2ee46b9058cee8af3caa4c46fb1 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sat, 25 Jun 2022 17:49:05 -0700 Subject: [PATCH 09/54] added pause page --- doc/flame/examples/lib/navigator.dart | 76 +++++++++++++++---- .../flame/lib/src/components/navigator.dart | 4 - 2 files changed, 60 insertions(+), 20 deletions(-) diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index 76b45c18290..5ebaa19de6c 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -13,6 +13,7 @@ class NavigatorGame extends FlameGame with HasTappableComponents { 'home': Page(builder: StartPageImpl.new), 'level1': Page(builder: Level1PageImpl.new), 'level2': Page(builder: Level2PageImpl.new), + 'pause': Page(builder: PausePageImpl.new, transparent: true), }, initialPage: 'home', )..addToParent(this); @@ -133,50 +134,76 @@ class RoundedButton extends PositionComponent with TapCallbacks { } } -class BackButton extends PositionComponent - with TapCallbacks, HasGameRef { - BackButton() : super(size: Vector2.all(40), position: Vector2.all(10)); +abstract class SimpleButton extends PositionComponent with TapCallbacks { + SimpleButton(this._iconPath, {super.position}) : super(size: Vector2.all(40)); final Paint _borderPaint = Paint() ..style = PaintingStyle.stroke ..color = const Color(0x66ffffff); - final Paint _arrowPaint = Paint() + final Paint _iconPaint = Paint() ..style = PaintingStyle.stroke ..color = const Color(0xffaaaaaa) ..strokeWidth = 7; - final Path _arrowPath = Path() - ..moveTo(22, 8) - ..lineTo(10, 20) - ..lineTo(22, 32) - ..moveTo(12, 20) - ..lineTo(34, 20); + final Path _iconPath; + + void action(); @override void render(Canvas canvas) { canvas.drawRRect( - RRect.fromRectAndRadius(size.toRect(), Radius.circular(5)), + RRect.fromRectAndRadius(size.toRect(), const Radius.circular(8)), _borderPaint, ); - canvas.drawPath(_arrowPath, _arrowPaint); + canvas.drawPath(_iconPath, _iconPaint); } @override void onTapDown(TapDownEvent event) { - _arrowPaint.color = const Color(0xffffffff); + _iconPaint.color = const Color(0xffffffff); } @override void onTapUp(TapUpEvent event) { - _arrowPaint.color = const Color(0xffaaaaaa); - gameRef.navigator.popPage(); + _iconPaint.color = const Color(0xffaaaaaa); + action(); } @override void onTapCancel(TapCancelEvent event) { - _arrowPaint.color = const Color(0xffaaaaaa); + _iconPaint.color = const Color(0xffaaaaaa); } } +class BackButton extends SimpleButton with HasGameRef { + BackButton() + : super( + Path() + ..moveTo(22, 8) + ..lineTo(10, 20) + ..lineTo(22, 32) + ..moveTo(12, 20) + ..lineTo(34, 20), + position: Vector2.all(10), + ); + + @override + void action() => gameRef.navigator.popPage(); +} + +class PauseButton extends SimpleButton with HasGameRef { + PauseButton() + : super( + Path() + ..moveTo(14, 10) + ..lineTo(14, 30) + ..moveTo(26, 10) + ..lineTo(26, 30), + position: Vector2(60, 10), + ); + @override + void action() => gameRef.navigator.showPage('pause'); +} + class Level1PageImpl extends Component { @override Future onLoad() async { @@ -184,6 +211,7 @@ class Level1PageImpl extends Component { addAll([ Background(const Color(0xbb5f358d)), BackButton(), + PauseButton(), Planet( radius: 40, color: const Color(0xfffff188), @@ -217,6 +245,7 @@ class Level2PageImpl extends Component { addAll([ Background(const Color(0xbb074825)), BackButton(), + PauseButton(), Planet( radius: 30, color: const Color(0xFFFFFFff), @@ -310,3 +339,18 @@ class Orbit extends PositionComponent { planet.position = Vector2(radius, 0)..rotate(_angle); } } + +class PausePageImpl extends Component { + @override + Future onLoad() async { + final game = findGame()!; + addAll([ + Background(const Color(0x55000000)), + TextComponent( + text: 'PAUSED', + position: game.canvasSize / 2, + anchor: Anchor.center, + ), + ]); + } +} diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator.dart index b0e9b068ba1..a171b0a664d 100644 --- a/packages/flame/lib/src/components/navigator.dart +++ b/packages/flame/lib/src/components/navigator.dart @@ -1,10 +1,6 @@ -import 'dart:ui'; - import 'package:collection/collection.dart'; import 'package:flame/src/components/component.dart'; -import 'package:flame/src/components/mixins/coordinate_transform.dart'; import 'package:flame/src/components/page.dart'; -import 'package:vector_math/vector_math_64.dart'; class Navigator extends Component { Navigator({ From 840bdfc2f1977e64f3186371fa4945e3d29d6eda Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sat, 25 Jun 2022 17:59:51 -0700 Subject: [PATCH 10/54] pause page now actually pauses time --- doc/flame/examples/lib/navigator.dart | 40 +++++++++++++++++++-- packages/flame/lib/src/components/page.dart | 14 ++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index 5ebaa19de6c..071f23d22d4 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -1,4 +1,5 @@ import 'package:flame/components.dart'; +import 'package:flame/effects.dart'; import 'package:flame/experimental.dart'; import 'package:flame/game.dart'; import 'package:flutter/rendering.dart'; @@ -11,8 +12,8 @@ class NavigatorGame extends FlameGame with HasTappableComponents { navigator = Navigator( pages: { 'home': Page(builder: StartPageImpl.new), - 'level1': Page(builder: Level1PageImpl.new), - 'level2': Page(builder: Level2PageImpl.new), + 'level1': Level1Page(), + 'level2': Level2Page(), 'pause': Page(builder: PausePageImpl.new, transparent: true), }, initialPage: 'home', @@ -204,6 +205,22 @@ class PauseButton extends SimpleButton with HasGameRef { void action() => gameRef.navigator.showPage('pause'); } +class Level1Page extends Page { + @override + Component build() => Level1PageImpl(); + + @override + void onDeactivate() => stopTime(); +} + +class Level2Page extends Page { + @override + Component build() => Level2PageImpl(); + + @override + void onDeactivate() => stopTime(); +} + class Level1PageImpl extends Component { @override Future onLoad() async { @@ -340,7 +357,8 @@ class Orbit extends PositionComponent { } } -class PausePageImpl extends Component { +class PausePageImpl extends Component + with TapCallbacks, HasGameRef { @override Future onLoad() async { final game = findGame()!; @@ -350,7 +368,23 @@ class PausePageImpl extends Component { text: 'PAUSED', position: game.canvasSize / 2, anchor: Anchor.center, + children: [ + ScaleEffect.to( + Vector2.all(1.1), + EffectController( + duration: 0.3, + alternate: true, + infinite: true, + ), + ) + ], ), ]); } + + @override + bool containsLocalPoint(Vector2 point) => true; + + @override + void onTapUp(TapUpEvent event) => gameRef.navigator.popPage(); } diff --git a/packages/flame/lib/src/components/page.dart b/packages/flame/lib/src/components/page.dart index 3fb5ba6b654..6bdc603d6ae 100644 --- a/packages/flame/lib/src/components/page.dart +++ b/packages/flame/lib/src/components/page.dart @@ -34,8 +34,14 @@ class Page extends PositionComponent with ParentIsA { return builder!(); } + void stopTime() { + _timeMultiplier = 0; + } + //#region Implementation methods + double _timeMultiplier = 1.0; + @internal bool get isBuilt => _child != null; @@ -47,6 +53,7 @@ class Page extends PositionComponent with ParentIsA { @internal void activate() { _child ??= build()..addToParent(this); + _timeMultiplier = 1.0; onActivate(); } @@ -62,6 +69,13 @@ class Page extends PositionComponent with ParentIsA { } } + @override + void updateTree(double dt) { + if (_timeMultiplier > 0) { + super.updateTree(dt * _timeMultiplier); + } + } + @override Iterable componentsAtPoint( Vector2 point, [ From 432a346b743699245947f768590c9af8dca4e862 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sat, 25 Jun 2022 18:54:48 -0700 Subject: [PATCH 11/54] added render effects --- doc/flame/examples/lib/navigator.dart | 29 ++++++++------- packages/flame/lib/game.dart | 1 + packages/flame/lib/src/components/page.dart | 32 +++++++++++------ .../flame/lib/src/page_render_effect.dart | 35 +++++++++++++++++++ 4 files changed, 74 insertions(+), 23 deletions(-) create mode 100644 packages/flame/lib/src/page_render_effect.dart diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index 071f23d22d4..4070e989b9a 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -12,8 +12,8 @@ class NavigatorGame extends FlameGame with HasTappableComponents { navigator = Navigator( pages: { 'home': Page(builder: StartPageImpl.new), - 'level1': Level1Page(), - 'level2': Level2Page(), + 'level1': LevelPage(builder: Level1PageImpl.new), + 'level2': LevelPage(builder: Level2PageImpl.new), 'pause': Page(builder: PausePageImpl.new, transparent: true), }, initialPage: 'home', @@ -205,20 +205,24 @@ class PauseButton extends SimpleButton with HasGameRef { void action() => gameRef.navigator.showPage('pause'); } -class Level1Page extends Page { - @override - Component build() => Level1PageImpl(); - - @override - void onDeactivate() => stopTime(); -} +class LevelPage extends Page { + LevelPage({super.builder}); -class Level2Page extends Page { @override - Component build() => Level2PageImpl(); + void onActivate() { + resumeTime(); + removeRenderEffect(); + } @override - void onDeactivate() => stopTime(); + void onDeactivate() { + stopTime(); + addRenderEffect( + PaintRenderEffect() + ..addDesaturation(opacity: 0.5) + ..addBlur(3.0), + ); + } } class Level1PageImpl extends Component { @@ -363,7 +367,6 @@ class PausePageImpl extends Component Future onLoad() async { final game = findGame()!; addAll([ - Background(const Color(0x55000000)), TextComponent( text: 'PAUSED', position: game.canvasSize / 2, diff --git a/packages/flame/lib/game.dart b/packages/flame/lib/game.dart index c01be6e74c2..c2b3f5516a8 100644 --- a/packages/flame/lib/game.dart +++ b/packages/flame/lib/game.dart @@ -17,4 +17,5 @@ export 'src/game/mixins/single_game_instance.dart'; export 'src/game/notifying_vector2.dart'; export 'src/game/projector.dart'; export 'src/game/transform2d.dart'; +export 'src/page_render_effect.dart'; export 'src/text/text_paint.dart'; diff --git a/packages/flame/lib/src/components/page.dart b/packages/flame/lib/src/components/page.dart index 6bdc603d6ae..b4723901004 100644 --- a/packages/flame/lib/src/components/page.dart +++ b/packages/flame/lib/src/components/page.dart @@ -1,9 +1,10 @@ -import 'dart:ui' show Canvas; +import 'dart:ui'; import 'package:flame/src/components/component.dart'; import 'package:flame/src/components/mixins/parent_is_a.dart'; import 'package:flame/src/components/navigator.dart'; import 'package:flame/src/components/position_component.dart'; +import 'package:flame/src/page_render_effect.dart'; import 'package:meta/meta.dart'; import 'package:vector_math/vector_math_64.dart'; @@ -34,13 +35,17 @@ class Page extends PositionComponent with ParentIsA { return builder!(); } - void stopTime() { - _timeMultiplier = 0; - } + double timeSpeed = 1.0; - //#region Implementation methods + void stopTime() => timeSpeed = 0; + + void resumeTime() => timeSpeed = 1.0; + + void addRenderEffect(PageRenderEffect effect) => _renderEffect = effect; - double _timeMultiplier = 1.0; + void removeRenderEffect() => _renderEffect = null; + + //#region Implementation methods @internal bool get isBuilt => _child != null; @@ -50,10 +55,11 @@ class Page extends PositionComponent with ParentIsA { Component? _child; + PageRenderEffect? _renderEffect; + @internal void activate() { _child ??= build()..addToParent(this); - _timeMultiplier = 1.0; onActivate(); } @@ -65,14 +71,20 @@ class Page extends PositionComponent with ParentIsA { @override void renderTree(Canvas canvas) { if (isRendered) { - super.renderTree(canvas); + if (_renderEffect != null) { + final useCanvas = _renderEffect!.preprocessCanvas(canvas); + super.renderTree(useCanvas); + _renderEffect!.postprocessCanvas(canvas); + } else { + super.renderTree(canvas); + } } } @override void updateTree(double dt) { - if (_timeMultiplier > 0) { - super.updateTree(dt * _timeMultiplier); + if (timeSpeed > 0) { + super.updateTree(dt * timeSpeed); } } diff --git a/packages/flame/lib/src/page_render_effect.dart b/packages/flame/lib/src/page_render_effect.dart new file mode 100644 index 00000000000..10044c0f94c --- /dev/null +++ b/packages/flame/lib/src/page_render_effect.dart @@ -0,0 +1,35 @@ +import 'dart:ui'; + +abstract class PageRenderEffect { + Canvas preprocessCanvas(Canvas canvas); + void postprocessCanvas(Canvas canvas); +} + +class PaintRenderEffect extends PageRenderEffect { + final _paint = Paint(); + + void addBlur(double amount, [double? amountY]) { + _paint.imageFilter = + ImageFilter.blur(sigmaX: amount, sigmaY: amountY ?? amount); + } + + void addTint(Color color) { + _paint.colorFilter = ColorFilter.mode(color, BlendMode.srcATop); + } + + void addDesaturation({double opacity = 1.0}) { + _paint.blendMode = BlendMode.luminosity; + _paint.color = Color.fromARGB((255 * opacity).toInt(), 0, 0, 0); + } + + @override + Canvas preprocessCanvas(Canvas canvas) { + canvas.saveLayer(null, _paint); + return canvas; + } + + @override + void postprocessCanvas(Canvas canvas) { + canvas.restore(); + } +} From dd372bd0ab114ff18a8722a2c39b64fcaf06436b Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sun, 26 Jun 2022 10:30:42 -0700 Subject: [PATCH 12/54] embed Navigator example as an infobox into docs --- doc/_sphinx/extensions/flutter_app.css | 27 +++++++++++++ doc/_sphinx/extensions/flutter_app.py | 52 +++++++++++++++----------- doc/_sphinx/theme/flames.css | 6 +++ doc/flame/examples/lib/navigator.dart | 43 +++++++++------------ doc/flame/flame.md | 1 + doc/flame/navigator.md | 20 ++++++++++ 6 files changed, 102 insertions(+), 47 deletions(-) create mode 100644 doc/flame/navigator.md diff --git a/doc/_sphinx/extensions/flutter_app.css b/doc/_sphinx/extensions/flutter_app.css index a542479665e..54af42ca36e 100644 --- a/doc/_sphinx/extensions/flutter_app.css +++ b/doc/_sphinx/extensions/flutter_app.css @@ -130,3 +130,30 @@ button.flutter-app-button:after { height: 350px; width: 100%; } + +.flutter-app-infobox { + background: #282828; + border: 1px solid #555555; + border-radius: 6px; + float: right; + margin-left: 6pt; + padding: 8px; + width: 280px; +} + +.flutter-app-infobox button.flutter-app-iframe { + height: 400px; +} + +.flutter-app-infobox button.flutter-app-button { + float: right; + font-size: 0.85em; + margin-bottom: 0; + margin-right: 0; + min-height: 14pt; + min-width: 50pt; +} + +.flutter-app-infobox p:last-child { + margin-bottom: 0; +} diff --git a/doc/_sphinx/extensions/flutter_app.py b/doc/_sphinx/extensions/flutter_app.py index 7c7e5eae224..4f917aa0329 100644 --- a/doc/_sphinx/extensions/flutter_app.py +++ b/doc/_sphinx/extensions/flutter_app.py @@ -44,13 +44,16 @@ class FlutterAppDirective(SphinxDirective): with the matching name. :show: - a list of one or more run modes, which could include "widget", - "popup", and "code". Each of these modes produces a different output: + "popup", "code", and "infobox". Each of these modes produces a different + output: "widget" - an iframe shown directly inside the docs page; "popup" - a [Run] button which opens the app to (almost) fullscreen; "code" - a [Code] button which opens a popup with the code that was compiled. + "infobox" - the content will be displayed as an infobox floating on + the right-hand side of the page """ - has_content = False + has_content = True required_arguments = 0 optional_arguments = 0 option_spec = { @@ -103,6 +106,11 @@ def run(self): classes=['flutter-app-button', 'code'], onclick=f'open_code_listings("{code_id}")', )) + if 'infobox' in self.modes: + self.state.nested_parse(self.content, 0, result) + result = [ + nodes.container('', *result, classes=['flutter-app-infobox']) + ] return result def _process_show_option(self): @@ -110,7 +118,7 @@ def _process_show_option(self): if argument: values = argument.split() for value in values: - if value not in ['widget', 'popup', 'code']: + if value not in ['widget', 'popup', 'code', 'infobox']: raise self.error('Invalid :show: value ' + value) self.modes = values else: @@ -228,30 +236,31 @@ def _doc_root(): # ------------------------------------------------------------------------------ class IFrame(nodes.Element, nodes.General): - pass + def visit(self, node): + self.body.append( + self.starttag(node, 'iframe', src=node.attributes['src'])) - -def visit_iframe(self, node): - self.body.append(self.starttag(node, 'iframe', src=node.attributes['src'])) - - -def depart_iframe(self, _): - self.body.append('') + def depart(self, _): + self.body.append('') class Button(nodes.Element, nodes.General): - pass + def visit(self, node): + attrs = {} + if 'onclick' in node.attributes: + attrs['onclick'] = node.attributes['onclick'] + self.body.append(self.starttag(node, 'button', **attrs).strip()) + def depart(self, _): + self.body.append('') -def visit_button(self, node): - attrs = {} - if 'onclick' in node.attributes: - attrs['onclick'] = node.attributes['onclick'] - self.body.append(self.starttag(node, 'button', **attrs).strip()) +class Div(nodes.Element, nodes.General): + def visit(self, node): + self.body.append(self.starttag(node, 'div').strip()) -def depart_button(self, _): - self.body.append('') + def depart(self, _): + self.body.append('') # ------------------------------------------------------------------------------ @@ -265,8 +274,9 @@ def setup(app): shutil.copy(os.path.join(base_dir, 'flutter_app.js'), target_dir) shutil.copy(os.path.join(base_dir, 'flutter_app.css'), target_dir) - app.add_node(IFrame, html=(visit_iframe, depart_iframe)) - app.add_node(Button, html=(visit_button, depart_button)) + app.add_node(IFrame, html=(IFrame.visit, IFrame.depart)) + app.add_node(Button, html=(Button.visit, Button.depart)) + app.add_node(Div, html=(Div.visit, Div.depart)) app.add_directive('flutter-app', FlutterAppDirective) app.add_js_file('flutter_app.js') app.add_css_file('flutter_app.css') diff --git a/doc/_sphinx/theme/flames.css b/doc/_sphinx/theme/flames.css index 78d927b8a95..499eb87ba06 100644 --- a/doc/_sphinx/theme/flames.css +++ b/doc/_sphinx/theme/flames.css @@ -482,6 +482,12 @@ div.document { padding: var(--document-padding); } +div.document::after { /* clearfix */ + content: ''; + display: block; + clear: both; +} + div.copyright { font-size: 12px; margin-top: 3px; diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index 4070e989b9a..4a969d95be2 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -26,7 +26,7 @@ class StartPageImpl extends Component with HasGameRef { _logo = TextPaint( style: const TextStyle( fontSize: 64, - color: Color(0xFFFFFFFF), + color: Color(0xFFC8FFF5), fontWeight: FontWeight.w800, ), ).toTextPainter('Syzygy'); @@ -61,7 +61,7 @@ class StartPageImpl extends Component with HasGameRef { @override void render(Canvas canvas) { - canvas.drawColor(const Color(0xff063e67), BlendMode.src); + // canvas.drawColor(const Color(0xff063e67), BlendMode.src); _logo.paint(canvas, _logoOffset); } } @@ -230,25 +230,25 @@ class Level1PageImpl extends Component { Future onLoad() async { final game = findGame()!; addAll([ - Background(const Color(0xbb5f358d)), + Background(const Color(0xbb2a074f)), BackButton(), PauseButton(), Planet( - radius: 40, + radius: 25, color: const Color(0xfffff188), position: game.size / 2, children: [ Orbit( - radius: 150, + radius: 110, revolutionPeriod: 6, planet: Planet( - radius: 15, + radius: 10, color: const Color(0xff54d7b1), children: [ Orbit( - radius: 40, + radius: 25, revolutionPeriod: 5, - planet: Planet(radius: 5, color: const Color(0xFFcccccc)), + planet: Planet(radius: 3, color: const Color(0xFFcccccc)), ), ], ), @@ -264,7 +264,7 @@ class Level2PageImpl extends Component { Future onLoad() async { final game = findGame()!; addAll([ - Background(const Color(0xbb074825)), + Background(const Color(0xff052b44)), BackButton(), PauseButton(), Planet( @@ -273,35 +273,26 @@ class Level2PageImpl extends Component { position: game.size / 2, children: [ Orbit( - radius: 100, + radius: 60, revolutionPeriod: 5, - planet: Planet( - radius: 15, - color: const Color(0xffc9ce0d), - ), + planet: Planet(radius: 10, color: const Color(0xffc9ce0d)), ), Orbit( - radius: 180, + radius: 110, revolutionPeriod: 10, planet: Planet( - radius: 20, + radius: 14, color: const Color(0xfff32727), children: [ Orbit( - radius: 32, + radius: 26, revolutionPeriod: 3, - planet: Planet( - radius: 6, - color: const Color(0xffffdb00), - ), + planet: Planet(radius: 5, color: const Color(0xffffdb00)), ), Orbit( - radius: 45, + radius: 35, revolutionPeriod: 4, - planet: Planet( - radius: 4, - color: const Color(0xffdc00ff), - ), + planet: Planet(radius: 3, color: const Color(0xffdc00ff)), ), ], ), diff --git a/doc/flame/flame.md b/doc/flame/flame.md index 066594ebb0d..631ee54dc60 100644 --- a/doc/flame/flame.md +++ b/doc/flame/flame.md @@ -3,6 +3,7 @@ ```{toctree} File structure GameWidget +Navigator Game loop Components Platforms diff --git a/doc/flame/navigator.md b/doc/flame/navigator.md new file mode 100644 index 00000000000..51b85baaa69 --- /dev/null +++ b/doc/flame/navigator.md @@ -0,0 +1,20 @@ + +```{flutter-app} +:sources: ../flame/examples +:page: navigator +:show: widget code infobox + +This example app shows the use of the `Navigator` component to move across multiple screens within +the game. In addition, the "pause" button stops time and applies visual effects to the content of +the page below it. +``` + +# Navigator + +The **Navigator** is a component whose job is to manage navigation across multiple screens within +the game. It is similar in spirit to Flutter's [Navigator][Flutter Navigator] class, except that it +works with Flame components instead of Flutter widgets. + + + +[Flutter Navigator]: https://api.flutter.dev/flutter/widgets/Navigator-class.html From c44c1b6086678c9e1d0caacf158a91b9d700a299 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sun, 26 Jun 2022 11:00:12 -0700 Subject: [PATCH 13/54] added splash screen --- doc/flame/examples/lib/navigator.dart | 30 ++++++++++++++++++++++++++- doc/flame/navigator.md | 7 +++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index 4a969d95be2..c9bd9915602 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -11,16 +11,44 @@ class NavigatorGame extends FlameGame with HasTappableComponents { Future onLoad() async { navigator = Navigator( pages: { + 'splash': Page(builder: SplashScreen.new), 'home': Page(builder: StartPageImpl.new), 'level1': LevelPage(builder: Level1PageImpl.new), 'level2': LevelPage(builder: Level2PageImpl.new), 'pause': Page(builder: PausePageImpl.new, transparent: true), }, - initialPage: 'home', + initialPage: 'splash', )..addToParent(this); } } +class SplashScreen extends Component + with TapCallbacks, HasGameRef { + @override + Future onLoad() async { + addAll([ + Background(const Color(0xff282828)), + TextBoxComponent( + text: '[Navigator demo]', + textRenderer: TextPaint( + style: const TextStyle( + color: Color(0x66ffffff), + fontSize: 16, + ), + ), + align: Anchor.center, + size: gameRef.canvasSize, + ), + ]); + } + + @override + bool containsLocalPoint(Vector2 point) => true; + + @override + void onTapUp(TapUpEvent event) => gameRef.navigator.showPage('home'); +} + class StartPageImpl extends Component with HasGameRef { StartPageImpl() { _logo = TextPaint( diff --git a/doc/flame/navigator.md b/doc/flame/navigator.md index 51b85baaa69..1abeee723ed 100644 --- a/doc/flame/navigator.md +++ b/doc/flame/navigator.md @@ -15,6 +15,13 @@ The **Navigator** is a component whose job is to manage navigation across multip the game. It is similar in spirit to Flutter's [Navigator][Flutter Navigator] class, except that it works with Flame components instead of Flutter widgets. +A typical game will usually consists of multiple pages: the splash screen, the starting menu page, +the settings page, credits, the main game page, several pop-ups, etc. The `Navigator` will organize +all these destinations and allow you to transition between them. + +Internally, the `Navigator` contains a stack of pages. When you request it to show a page, that +page will be placed on top of all other pages in the stack. Later you can `popPage()` to remove the +topmost page from the stack. [Flutter Navigator]: https://api.flutter.dev/flutter/widgets/Navigator-class.html From 837df247b3eb54b717e737878983527e900fb671 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sun, 26 Jun 2022 13:19:07 -0700 Subject: [PATCH 14/54] more docs --- doc/flame/navigator.md | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/doc/flame/navigator.md b/doc/flame/navigator.md index 1abeee723ed..06410910ff7 100644 --- a/doc/flame/navigator.md +++ b/doc/flame/navigator.md @@ -9,6 +9,7 @@ the game. In addition, the "pause" button stops time and applies visual effects the page below it. ``` + # Navigator The **Navigator** is a component whose job is to manage navigation across multiple screens within @@ -21,7 +22,42 @@ all these destinations and allow you to transition between them. Internally, the `Navigator` contains a stack of pages. When you request it to show a page, that page will be placed on top of all other pages in the stack. Later you can `popPage()` to remove the -topmost page from the stack. - +topmost page from the stack. The pages of the Navigator are addressed by their unique names. + +Each Page in the Navigator can be either transparent or opaque. If a page is opaque, then the pages +below it in the stack are not rendered, and do not receive pointer events (such as taps or drags). +On the contrary, if a page is transparent, then the page below it will be rendered and receive +events normally. Such transparent pages are useful for implementing modal dialogs, inventory or +dialogue UIs, etc. + + +Usage example: +```dart +class MyGame extends FlameGame { + late final Navigator navigator; + + @override + Future onLoad() async { + navigator = Navigator( + pages: { + 'home': Page(builder: HomePageComponent()), + 'level-selector': Page(builder: LevelSelectorPageComponent()), + 'settings': Page(builder: SettingsPageComponent(), transparent: true), + 'pause': PausePage(), + }, + initialPage: 'home', + ); + } +} +``` [Flutter Navigator]: https://api.flutter.dev/flutter/widgets/Navigator-class.html + + +## Page + +The **Page** component holds information about the content of a particular screen. `Page`s are +mounted as children to the `Navigator`. + +The main property of a `Page` is its `builder` -- the function that creates the component which is +the content of this page. From 1e09e5f5bf1feb65c2ac4540792cece7abb5b9c1 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sun, 26 Jun 2022 13:27:01 -0700 Subject: [PATCH 15/54] remove the Div node --- doc/_sphinx/extensions/flutter_app.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/doc/_sphinx/extensions/flutter_app.py b/doc/_sphinx/extensions/flutter_app.py index 4f917aa0329..68b4949b3d0 100644 --- a/doc/_sphinx/extensions/flutter_app.py +++ b/doc/_sphinx/extensions/flutter_app.py @@ -255,14 +255,6 @@ def depart(self, _): self.body.append('') -class Div(nodes.Element, nodes.General): - def visit(self, node): - self.body.append(self.starttag(node, 'div').strip()) - - def depart(self, _): - self.body.append('') - - # ------------------------------------------------------------------------------ # Extension setup # ------------------------------------------------------------------------------ @@ -276,7 +268,6 @@ def setup(app): app.add_node(IFrame, html=(IFrame.visit, IFrame.depart)) app.add_node(Button, html=(Button.visit, Button.depart)) - app.add_node(Div, html=(Div.visit, Div.depart)) app.add_directive('flutter-app', FlutterAppDirective) app.add_js_file('flutter_app.js') app.add_css_file('flutter_app.css') From f11524c5f3f727a57046d49bf22036fad1dfa20a Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sun, 26 Jun 2022 13:27:59 -0700 Subject: [PATCH 16/54] remove temp code --- doc/flame/examples/lib/main.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/flame/examples/lib/main.dart b/doc/flame/examples/lib/main.dart index 155e16c860a..7eda332acc3 100644 --- a/doc/flame/examples/lib/main.dart +++ b/doc/flame/examples/lib/main.dart @@ -20,7 +20,6 @@ void main() { game = DragEventsGame(); break; case 'navigator': - default: game = NavigatorGame(); break; } From 7738381aad6c7a5501cfbb9931dd3ee68c256b6c Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sun, 26 Jun 2022 13:31:29 -0700 Subject: [PATCH 17/54] analysis issues --- .../game/game_widget/game_widget_controlled_lifecycle_test.dart | 2 +- .../flame/test/game/game_widget/game_widget_keyboard_test.dart | 2 +- .../flame/test/game/game_widget/game_widget_lifecycle_test.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/flame/test/game/game_widget/game_widget_controlled_lifecycle_test.dart b/packages/flame/test/game/game_widget/game_widget_controlled_lifecycle_test.dart index 27e614d8f3f..0901630d2e4 100644 --- a/packages/flame/test/game/game_widget/game_widget_controlled_lifecycle_test.dart +++ b/packages/flame/test/game/game_widget/game_widget_controlled_lifecycle_test.dart @@ -1,5 +1,5 @@ import 'package:flame/components.dart'; -import 'package:flame/game.dart'; +import 'package:flame/game.dart' hide Navigator; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/flame/test/game/game_widget/game_widget_keyboard_test.dart b/packages/flame/test/game/game_widget/game_widget_keyboard_test.dart index ad2dcded13b..cb2a60ab213 100644 --- a/packages/flame/test/game/game_widget/game_widget_keyboard_test.dart +++ b/packages/flame/test/game/game_widget/game_widget_keyboard_test.dart @@ -1,5 +1,5 @@ import 'package:flame/components.dart'; -import 'package:flame/game.dart'; +import 'package:flame/game.dart' hide Navigator; import 'package:flame/input.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; diff --git a/packages/flame/test/game/game_widget/game_widget_lifecycle_test.dart b/packages/flame/test/game/game_widget/game_widget_lifecycle_test.dart index 16914e64725..a020c716138 100644 --- a/packages/flame/test/game/game_widget/game_widget_lifecycle_test.dart +++ b/packages/flame/test/game/game_widget/game_widget_lifecycle_test.dart @@ -1,5 +1,5 @@ import 'package:flame/components.dart'; -import 'package:flame/game.dart'; +import 'package:flame/game.dart' hide Navigator; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; From f34309b74b1f290c41d7c54a05c7384e7e8b6645 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sun, 26 Jun 2022 15:39:44 -0700 Subject: [PATCH 18/54] remove temp code --- doc/flame/examples/lib/navigator.dart | 1 - packages/flame/lib/src/components/navigator.dart | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index c9bd9915602..ac9d0788371 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -89,7 +89,6 @@ class StartPageImpl extends Component with HasGameRef { @override void render(Canvas canvas) { - // canvas.drawColor(const Color(0xff063e67), BlendMode.src); _logo.paint(canvas, _logoOffset); } } diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator.dart index a171b0a664d..c19c11fd2a6 100644 --- a/packages/flame/lib/src/components/navigator.dart +++ b/packages/flame/lib/src/components/navigator.dart @@ -2,6 +2,9 @@ import 'package:collection/collection.dart'; import 'package:flame/src/components/component.dart'; import 'package:flame/src/components/page.dart'; +/// [Navigator] is a component that handles transitions between multiple [Page]s +/// of your game. +/// class Navigator extends Component { Navigator({ required Map pages, From c42eb952575173d6938d54e39e6b67e4d2dea50f Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sun, 26 Jun 2022 15:41:03 -0700 Subject: [PATCH 19/54] use add() with assignment --- doc/flame/examples/lib/navigator.dart | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index ac9d0788371..c3e50ffc9a8 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -9,16 +9,18 @@ class NavigatorGame extends FlameGame with HasTappableComponents { @override Future onLoad() async { - navigator = Navigator( - pages: { - 'splash': Page(builder: SplashScreen.new), - 'home': Page(builder: StartPageImpl.new), - 'level1': LevelPage(builder: Level1PageImpl.new), - 'level2': LevelPage(builder: Level2PageImpl.new), - 'pause': Page(builder: PausePageImpl.new, transparent: true), - }, - initialPage: 'splash', - )..addToParent(this); + add( + navigator = Navigator( + pages: { + 'splash': Page(builder: SplashScreen.new), + 'home': Page(builder: StartPageImpl.new), + 'level1': LevelPage(builder: Level1PageImpl.new), + 'level2': LevelPage(builder: Level2PageImpl.new), + 'pause': Page(builder: PausePageImpl.new, transparent: true), + }, + initialPage: 'splash', + ), + ); } } From 2e56132e53168eff0e9d762b224eccdb527c70c2 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sun, 26 Jun 2022 15:50:40 -0700 Subject: [PATCH 20/54] simplify StartPageImpl --- doc/flame/examples/lib/main.dart | 1 + doc/flame/examples/lib/navigator.dart | 58 +++++++++++++-------------- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/doc/flame/examples/lib/main.dart b/doc/flame/examples/lib/main.dart index 7eda332acc3..155e16c860a 100644 --- a/doc/flame/examples/lib/main.dart +++ b/doc/flame/examples/lib/main.dart @@ -20,6 +20,7 @@ void main() { game = DragEventsGame(); break; case 'navigator': + default: game = NavigatorGame(); break; } diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index c3e50ffc9a8..955435a941a 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -53,45 +53,43 @@ class SplashScreen extends Component class StartPageImpl extends Component with HasGameRef { StartPageImpl() { - _logo = TextPaint( - style: const TextStyle( - fontSize: 64, - color: Color(0xFFC8FFF5), - fontWeight: FontWeight.w800, + addAll([ + _logo = TextComponent( + text: 'Syzygy', + textRenderer: TextPaint( + style: const TextStyle( + fontSize: 64, + color: Color(0xFFC8FFF5), + fontWeight: FontWeight.w800, + ), + ), + anchor: Anchor.center, ), - ).toTextPainter('Syzygy'); - _button1 = RoundedButton( - text: 'Level 1', - action: () => gameRef.navigator.showPage('level1'), - color: const Color(0xffadde6c), - borderColor: const Color(0xffedffab), - ); - _button2 = RoundedButton( - text: 'Level 2', - action: () => gameRef.navigator.showPage('level2'), - color: const Color(0xffdebe6c), - borderColor: const Color(0xfffff4c7), - ); - add(_button1); - add(_button2); + _button1 = RoundedButton( + text: 'Level 1', + action: () => gameRef.navigator.showPage('level1'), + color: const Color(0xffadde6c), + borderColor: const Color(0xffedffab), + ), + _button2 = RoundedButton( + text: 'Level 2', + action: () => gameRef.navigator.showPage('level2'), + color: const Color(0xffdebe6c), + borderColor: const Color(0xfffff4c7), + ), + ]); } - late final TextPainter _logo; - late Offset _logoOffset; + late final TextComponent _logo; late final RoundedButton _button1; late final RoundedButton _button2; @override void onGameResize(Vector2 size) { super.onGameResize(size); - _logoOffset = Offset((size.x - _logo.width) / 2, size.y / 3 - _logo.height); - _button1.position = Vector2(size.x / 2, _logoOffset.dy + 140); - _button2.position = Vector2(size.x / 2, _logoOffset.dy + 200); - } - - @override - void render(Canvas canvas) { - _logo.paint(canvas, _logoOffset); + _logo.position = Vector2(size.x / 2, size.y / 3); + _button1.position = Vector2(size.x / 2, _logo.y + 80); + _button2.position = Vector2(size.x / 2, _logo.y + 140); } } From c31fa31be85284a7631ad4a1cc4cf56d481f66bd Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sun, 26 Jun 2022 16:01:17 -0700 Subject: [PATCH 21/54] remove temp code --- doc/flame/examples/lib/main.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/flame/examples/lib/main.dart b/doc/flame/examples/lib/main.dart index 155e16c860a..7eda332acc3 100644 --- a/doc/flame/examples/lib/main.dart +++ b/doc/flame/examples/lib/main.dart @@ -20,7 +20,6 @@ void main() { game = DragEventsGame(); break; case 'navigator': - default: game = NavigatorGame(); break; } From 1e6b2fe5238e61d62d7a4e39b3c0ab7ab710e555 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sun, 26 Jun 2022 16:24:20 -0700 Subject: [PATCH 22/54] implement factory pages --- .../flame/lib/src/components/navigator.dart | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator.dart index c19c11fd2a6..c4807c2c436 100644 --- a/packages/flame/lib/src/components/navigator.dart +++ b/packages/flame/lib/src/components/navigator.dart @@ -9,17 +9,21 @@ class Navigator extends Component { Navigator({ required Map pages, required this.initialPage, - }) : _pages = pages; + Map? pageFactories, + this.onUnknownPage, + }) : _pages = pages, + _pageFactories = pageFactories ?? {}; final String initialPage; final Map _pages; + final Map _pageFactories; final List _currentPages = []; + final _PageFactory? onUnknownPage; void showPage(String name) { - final page = _pages[name]; - assert(page != null, 'Page "$name" is not known to the Navigator'); + final page = _resolvePage(name); final activePage = _currentPages.lastOrNull; - if (page! == activePage) { + if (page == activePage) { return; } if (!page.isMounted) { @@ -42,6 +46,28 @@ class Navigator extends Component { _currentPages.lastOrNull?.activate(); } + Page _resolvePage(String name) { + final existingPage = _pages[name]; + if (existingPage != null) { + return existingPage; + } + if (name.contains('/')) { + final i = name.indexOf('/'); + final factoryName = name.substring(0, i); + final factory = _pageFactories[factoryName]; + if (factory != null) { + final argument = name.substring(i + 1); + final generatedPage = factory(argument); + _pages[name] = generatedPage; + return generatedPage; + } + } + if (onUnknownPage != null) { + return onUnknownPage!(name); + } + throw ArgumentError('Page "$name" could not be resolved by the Navigator'); + } + void _fixPageOrder() { var render = true; for (var i = _currentPages.length - 1; i >= 0; i--) { @@ -58,3 +84,5 @@ class Navigator extends Component { showPage(initialPage); } } + +typedef _PageFactory = Page Function(String parameter); From 5d3f84e0cd70974252b825a7c0f50b5612156f34 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sun, 26 Jun 2022 16:24:37 -0700 Subject: [PATCH 23/54] format --- packages/flame/lib/src/components/navigator.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator.dart index c4807c2c436..06a59c74082 100644 --- a/packages/flame/lib/src/components/navigator.dart +++ b/packages/flame/lib/src/components/navigator.dart @@ -11,8 +11,8 @@ class Navigator extends Component { required this.initialPage, Map? pageFactories, this.onUnknownPage, - }) : _pages = pages, - _pageFactories = pageFactories ?? {}; + }) : _pages = pages, + _pageFactories = pageFactories ?? {}; final String initialPage; final Map _pages; From c0feb6591b5a22cc49295e385c9c42e66b5f7a63 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sun, 26 Jun 2022 16:25:40 -0700 Subject: [PATCH 24/54] rename showPage -> pushPage --- doc/flame/examples/lib/navigator.dart | 8 ++++---- packages/flame/lib/src/components/navigator.dart | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index 955435a941a..e5c1f99bde3 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -48,7 +48,7 @@ class SplashScreen extends Component bool containsLocalPoint(Vector2 point) => true; @override - void onTapUp(TapUpEvent event) => gameRef.navigator.showPage('home'); + void onTapUp(TapUpEvent event) => gameRef.navigator.pushPage('home'); } class StartPageImpl extends Component with HasGameRef { @@ -67,13 +67,13 @@ class StartPageImpl extends Component with HasGameRef { ), _button1 = RoundedButton( text: 'Level 1', - action: () => gameRef.navigator.showPage('level1'), + action: () => gameRef.navigator.pushPage('level1'), color: const Color(0xffadde6c), borderColor: const Color(0xffedffab), ), _button2 = RoundedButton( text: 'Level 2', - action: () => gameRef.navigator.showPage('level2'), + action: () => gameRef.navigator.pushPage('level2'), color: const Color(0xffdebe6c), borderColor: const Color(0xfffff4c7), ), @@ -229,7 +229,7 @@ class PauseButton extends SimpleButton with HasGameRef { position: Vector2(60, 10), ); @override - void action() => gameRef.navigator.showPage('pause'); + void action() => gameRef.navigator.pushPage('pause'); } class LevelPage extends Page { diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator.dart index 06a59c74082..9fbd976cdc4 100644 --- a/packages/flame/lib/src/components/navigator.dart +++ b/packages/flame/lib/src/components/navigator.dart @@ -20,7 +20,7 @@ class Navigator extends Component { final List _currentPages = []; final _PageFactory? onUnknownPage; - void showPage(String name) { + void pushPage(String name) { final page = _resolvePage(name); final activePage = _currentPages.lastOrNull; if (page == activePage) { @@ -81,7 +81,7 @@ class Navigator extends Component { @override void onMount() { super.onMount(); - showPage(initialPage); + pushPage(initialPage); } } From e262741ce914abc41a4d4c6c87e4484c9eb272a0 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Mon, 27 Jun 2022 11:12:47 -0700 Subject: [PATCH 25/54] wip --- doc/flame/examples/lib/navigator.dart | 48 ++++++------- .../flame/lib/src/components/navigator.dart | 67 +++++++++++++------ packages/flame/lib/src/components/page.dart | 30 +++++---- 3 files changed, 89 insertions(+), 56 deletions(-) diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index e5c1f99bde3..10fe02c2fcc 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -14,9 +14,9 @@ class NavigatorGame extends FlameGame with HasTappableComponents { pages: { 'splash': Page(builder: SplashScreen.new), 'home': Page(builder: StartPageImpl.new), - 'level1': LevelPage(builder: Level1PageImpl.new), - 'level2': LevelPage(builder: Level2PageImpl.new), - 'pause': Page(builder: PausePageImpl.new, transparent: true), + 'level1': Page(builder: Level1PageImpl.new), + 'level2': Page(builder: Level2PageImpl.new), + 'pause': PausePage(), }, initialPage: 'splash', ), @@ -232,26 +232,6 @@ class PauseButton extends SimpleButton with HasGameRef { void action() => gameRef.navigator.pushPage('pause'); } -class LevelPage extends Page { - LevelPage({super.builder}); - - @override - void onActivate() { - resumeTime(); - removeRenderEffect(); - } - - @override - void onDeactivate() { - stopTime(); - addRenderEffect( - PaintRenderEffect() - ..addDesaturation(opacity: 0.5) - ..addBlur(3.0), - ); - } -} - class Level1PageImpl extends Component { @override Future onLoad() async { @@ -379,6 +359,28 @@ class Orbit extends PositionComponent { } } +class PausePage extends Page { + PausePage() : super(builder: PausePageImpl.new, transparent: true); + + @override + void onPush(Page? previousPage) { + previousPage! + ..stopTime() + ..addRenderEffect( + PaintRenderEffect() + ..addDesaturation(opacity: 0.5) + ..addBlur(3.0), + ); + } + + @override + void onPop(Page previousPage) { + previousPage + ..resumeTime() + ..removeRenderEffect(); + } +} + class PausePageImpl extends Component with TapCallbacks, HasGameRef { @override diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator.dart index 9fbd976cdc4..8136e599d8d 100644 --- a/packages/flame/lib/src/components/navigator.dart +++ b/packages/flame/lib/src/components/navigator.dart @@ -1,4 +1,3 @@ -import 'package:collection/collection.dart'; import 'package:flame/src/components/component.dart'; import 'package:flame/src/components/page.dart'; @@ -20,30 +19,41 @@ class Navigator extends Component { final List _currentPages = []; final _PageFactory? onUnknownPage; + /// Puts the page [name] on top of the navigation stack. + /// + /// If the page is already in the stack, it will be simply moved on top; + /// otherwise the page will be built, mounted, and added at the top. If the + /// page is already on the top, this method will be a noop. + /// + /// void pushPage(String name) { final page = _resolvePage(name); - final activePage = _currentPages.lastOrNull; - if (page == activePage) { + final currentActivePage = _currentPages.last; + if (page == currentActivePage) { return; } - if (!page.isMounted) { + if (_currentPages.contains(page)) { + _currentPages.remove(page); + _currentPages.add(page); + } else { + _currentPages.add(page); add(page); } - _currentPages.remove(page); - _currentPages.add(page); - _fixPageOrder(); - activePage?.deactivate(); - page.activate(); + _adjustPageOrder(); + page.didPush(currentActivePage); + _execute(action: _adjustPageVisibility, delay: page.pushTransitionDuration); } void popPage() { - if (_currentPages.isEmpty) { - return; - } - final poppedPage = _currentPages.removeLast()..removeFromParent(); - _fixPageOrder(); - poppedPage.deactivate(); - _currentPages.lastOrNull?.activate(); + assert( + _currentPages.length > 1, + 'Cannot pop the last page from the Navigator', + ); + final page = _currentPages.removeLast(); + _adjustPageOrder(); + _adjustPageVisibility(); + page.didPop(_currentPages.last); + _execute(action: page.removeFromParent, delay: page.popTransitionDuration); } Page _resolvePage(String name) { @@ -68,20 +78,37 @@ class Navigator extends Component { throw ArgumentError('Page "$name" could not be resolved by the Navigator'); } - void _fixPageOrder() { + void _adjustPageOrder() { + for (var i = 0; i < _currentPages.length; i++) { + _currentPages[i].changePriorityWithoutResorting(i); + } + reorderChildren(); + } + + void _adjustPageVisibility() { var render = true; for (var i = _currentPages.length - 1; i >= 0; i--) { - _currentPages[i].changePriorityWithoutResorting(i); _currentPages[i].isRendered = render; render &= _currentPages[i].transparent; } - reorderChildren(); + } + + void _execute({required void Function() action, required double delay}) { + if (delay > 0) { + Future.delayed(Duration(microseconds: (delay * 1e6).toInt())) + .then((_) => action()); + } else { + action(); + } } @override void onMount() { super.onMount(); - pushPage(initialPage); + final page = _resolvePage(initialPage); + _currentPages.add(page); + add(page); + page.didPush(null); } } diff --git a/packages/flame/lib/src/components/page.dart b/packages/flame/lib/src/components/page.dart index b4723901004..77ba5305098 100644 --- a/packages/flame/lib/src/components/page.dart +++ b/packages/flame/lib/src/components/page.dart @@ -10,11 +10,9 @@ import 'package:vector_math/vector_math_64.dart'; class Page extends PositionComponent with ParentIsA { Page({ - this.builder, + Component Function()? builder, this.transparent = false, - }); - - final Component Function()? builder; + }) : _builder = builder; /// If true, then the page below this one will continue to be rendered when /// this page becomes active. If false, then this page is assumed to @@ -22,17 +20,25 @@ class Page extends PositionComponent with ParentIsA { /// page underneath doesn't need to be rendered. final bool transparent; - void onActivate() {} + double pushTransitionDuration = 0; + + double popTransitionDuration = 0; + + final Component Function()? _builder; - void onDeactivate() {} + /// This method is invoked when the page is pushed on top of the [Navigator]'s + /// stack. + void onPush(Page? previousPage) {} + + void onPop(Page previousPage) {} Component build() { assert( - builder != null, + _builder != null, 'Either provide `builder` in the constructor, or override the build() ' 'method', ); - return builder!(); + return _builder!(); } double timeSpeed = 1.0; @@ -58,15 +64,13 @@ class Page extends PositionComponent with ParentIsA { PageRenderEffect? _renderEffect; @internal - void activate() { + void didPush(Page? previousPage) { _child ??= build()..addToParent(this); - onActivate(); + onPush(previousPage); } @internal - void deactivate() { - onDeactivate(); - } + void didPop(Page previousPage) => onPop(previousPage); @override void renderTree(Canvas canvas) { From c5f7d41c6c1f42f5c329761504daf5f7da5b3cf5 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 29 Jun 2022 18:07:20 -0700 Subject: [PATCH 26/54] rename Page -> Route --- doc/flame/examples/lib/navigator.dart | 42 +++++----- packages/flame/lib/game.dart | 2 +- .../flame/lib/src/components/navigator.dart | 82 +++++++++---------- .../src/components/{page.dart => route.dart} | 14 ++-- 4 files changed, 70 insertions(+), 70 deletions(-) rename packages/flame/lib/src/components/{page.dart => route.dart} (89%) diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index 10fe02c2fcc..32d8cf0b0c0 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -11,14 +11,14 @@ class NavigatorGame extends FlameGame with HasTappableComponents { Future onLoad() async { add( navigator = Navigator( - pages: { - 'splash': Page(builder: SplashScreen.new), - 'home': Page(builder: StartPageImpl.new), - 'level1': Page(builder: Level1PageImpl.new), - 'level2': Page(builder: Level2PageImpl.new), + routes: { + 'splash': Route(builder: SplashScreen.new), + 'home': Route(builder: StartPage.new), + 'level1': Route(builder: Level1Page.new), + 'level2': Route(builder: Level2Page.new), 'pause': PausePage(), }, - initialPage: 'splash', + initialRoute: 'splash', ), ); } @@ -48,11 +48,11 @@ class SplashScreen extends Component bool containsLocalPoint(Vector2 point) => true; @override - void onTapUp(TapUpEvent event) => gameRef.navigator.pushPage('home'); + void onTapUp(TapUpEvent event) => gameRef.navigator.pushRoute('home'); } -class StartPageImpl extends Component with HasGameRef { - StartPageImpl() { +class StartPage extends Component with HasGameRef { + StartPage() { addAll([ _logo = TextComponent( text: 'Syzygy', @@ -67,13 +67,13 @@ class StartPageImpl extends Component with HasGameRef { ), _button1 = RoundedButton( text: 'Level 1', - action: () => gameRef.navigator.pushPage('level1'), + action: () => gameRef.navigator.pushRoute('level1'), color: const Color(0xffadde6c), borderColor: const Color(0xffedffab), ), _button2 = RoundedButton( text: 'Level 2', - action: () => gameRef.navigator.pushPage('level2'), + action: () => gameRef.navigator.pushRoute('level2'), color: const Color(0xffdebe6c), borderColor: const Color(0xfffff4c7), ), @@ -215,7 +215,7 @@ class BackButton extends SimpleButton with HasGameRef { ); @override - void action() => gameRef.navigator.popPage(); + void action() => gameRef.navigator.popRoute(); } class PauseButton extends SimpleButton with HasGameRef { @@ -229,10 +229,10 @@ class PauseButton extends SimpleButton with HasGameRef { position: Vector2(60, 10), ); @override - void action() => gameRef.navigator.pushPage('pause'); + void action() => gameRef.navigator.pushRoute('pause'); } -class Level1PageImpl extends Component { +class Level1Page extends Component { @override Future onLoad() async { final game = findGame()!; @@ -266,7 +266,7 @@ class Level1PageImpl extends Component { } } -class Level2PageImpl extends Component { +class Level2Page extends Component { @override Future onLoad() async { final game = findGame()!; @@ -359,12 +359,12 @@ class Orbit extends PositionComponent { } } -class PausePage extends Page { +class PausePage extends Route { PausePage() : super(builder: PausePageImpl.new, transparent: true); @override - void onPush(Page? previousPage) { - previousPage! + void onPush(Route? previousRoute) { + previousRoute! ..stopTime() ..addRenderEffect( PaintRenderEffect() @@ -374,8 +374,8 @@ class PausePage extends Page { } @override - void onPop(Page previousPage) { - previousPage + void onPop(Route previousRoute) { + previousRoute ..resumeTime() ..removeRenderEffect(); } @@ -409,5 +409,5 @@ class PausePageImpl extends Component bool containsLocalPoint(Vector2 point) => true; @override - void onTapUp(TapUpEvent event) => gameRef.navigator.popPage(); + void onTapUp(TapUpEvent event) => gameRef.navigator.popRoute(); } diff --git a/packages/flame/lib/game.dart b/packages/flame/lib/game.dart index c2b3f5516a8..be9806b2b7e 100644 --- a/packages/flame/lib/game.dart +++ b/packages/flame/lib/game.dart @@ -2,7 +2,7 @@ /// {@canonicalFor text.TextRenderer} export 'src/collisions/has_collision_detection.dart'; export 'src/components/navigator.dart' show Navigator; -export 'src/components/page.dart' show Page; +export 'src/components/route.dart' show Route; export 'src/extensions/vector2.dart'; export 'src/game/camera/camera.dart'; export 'src/game/camera/viewport.dart'; diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator.dart index 8136e599d8d..2990fab7d7a 100644 --- a/packages/flame/lib/src/components/navigator.dart +++ b/packages/flame/lib/src/components/navigator.dart @@ -1,23 +1,23 @@ import 'package:flame/src/components/component.dart'; -import 'package:flame/src/components/page.dart'; +import 'package:flame/src/components/route.dart'; -/// [Navigator] is a component that handles transitions between multiple [Page]s -/// of your game. +/// [Navigator] is a component that handles transitions between multiple +/// [Route]s of your game. /// class Navigator extends Component { Navigator({ - required Map pages, - required this.initialPage, - Map? pageFactories, - this.onUnknownPage, - }) : _pages = pages, - _pageFactories = pageFactories ?? {}; + required Map routes, + required this.initialRoute, + Map? routeFactories, + this.onUnknownRoute, + }) : _routes = routes, + _routeFactories = routeFactories ?? {}; - final String initialPage; - final Map _pages; - final Map _pageFactories; - final List _currentPages = []; - final _PageFactory? onUnknownPage; + final String initialRoute; + final Map _routes; + final Map _routeFactories; + final List _currentRoutes = []; + final _RouteFactory? onUnknownRoute; /// Puts the page [name] on top of the navigation stack. /// @@ -26,17 +26,17 @@ class Navigator extends Component { /// page is already on the top, this method will be a noop. /// /// - void pushPage(String name) { - final page = _resolvePage(name); - final currentActivePage = _currentPages.last; + void pushRoute(String name) { + final page = _resolveRoute(name); + final currentActivePage = _currentRoutes.last; if (page == currentActivePage) { return; } - if (_currentPages.contains(page)) { - _currentPages.remove(page); - _currentPages.add(page); + if (_currentRoutes.contains(page)) { + _currentRoutes.remove(page); + _currentRoutes.add(page); } else { - _currentPages.add(page); + _currentRoutes.add(page); add(page); } _adjustPageOrder(); @@ -44,52 +44,52 @@ class Navigator extends Component { _execute(action: _adjustPageVisibility, delay: page.pushTransitionDuration); } - void popPage() { + void popRoute() { assert( - _currentPages.length > 1, + _currentRoutes.length > 1, 'Cannot pop the last page from the Navigator', ); - final page = _currentPages.removeLast(); + final page = _currentRoutes.removeLast(); _adjustPageOrder(); _adjustPageVisibility(); - page.didPop(_currentPages.last); + page.didPop(_currentRoutes.last); _execute(action: page.removeFromParent, delay: page.popTransitionDuration); } - Page _resolvePage(String name) { - final existingPage = _pages[name]; + Route _resolveRoute(String name) { + final existingPage = _routes[name]; if (existingPage != null) { return existingPage; } if (name.contains('/')) { final i = name.indexOf('/'); final factoryName = name.substring(0, i); - final factory = _pageFactories[factoryName]; + final factory = _routeFactories[factoryName]; if (factory != null) { final argument = name.substring(i + 1); final generatedPage = factory(argument); - _pages[name] = generatedPage; + _routes[name] = generatedPage; return generatedPage; } } - if (onUnknownPage != null) { - return onUnknownPage!(name); + if (onUnknownRoute != null) { + return onUnknownRoute!(name); } throw ArgumentError('Page "$name" could not be resolved by the Navigator'); } void _adjustPageOrder() { - for (var i = 0; i < _currentPages.length; i++) { - _currentPages[i].changePriorityWithoutResorting(i); + for (var i = 0; i < _currentRoutes.length; i++) { + _currentRoutes[i].changePriorityWithoutResorting(i); } reorderChildren(); } void _adjustPageVisibility() { var render = true; - for (var i = _currentPages.length - 1; i >= 0; i--) { - _currentPages[i].isRendered = render; - render &= _currentPages[i].transparent; + for (var i = _currentRoutes.length - 1; i >= 0; i--) { + _currentRoutes[i].isRendered = render; + render &= _currentRoutes[i].transparent; } } @@ -105,11 +105,11 @@ class Navigator extends Component { @override void onMount() { super.onMount(); - final page = _resolvePage(initialPage); - _currentPages.add(page); - add(page); - page.didPush(null); + final route = _resolveRoute(initialRoute); + _currentRoutes.add(route); + add(route); + route.didPush(null); } } -typedef _PageFactory = Page Function(String parameter); +typedef _RouteFactory = Route Function(String parameter); diff --git a/packages/flame/lib/src/components/page.dart b/packages/flame/lib/src/components/route.dart similarity index 89% rename from packages/flame/lib/src/components/page.dart rename to packages/flame/lib/src/components/route.dart index 77ba5305098..0185a901ee1 100644 --- a/packages/flame/lib/src/components/page.dart +++ b/packages/flame/lib/src/components/route.dart @@ -8,8 +8,8 @@ import 'package:flame/src/page_render_effect.dart'; import 'package:meta/meta.dart'; import 'package:vector_math/vector_math_64.dart'; -class Page extends PositionComponent with ParentIsA { - Page({ +class Route extends PositionComponent with ParentIsA { + Route({ Component Function()? builder, this.transparent = false, }) : _builder = builder; @@ -28,9 +28,9 @@ class Page extends PositionComponent with ParentIsA { /// This method is invoked when the page is pushed on top of the [Navigator]'s /// stack. - void onPush(Page? previousPage) {} + void onPush(Route? previousRoute) {} - void onPop(Page previousPage) {} + void onPop(Route previousRoute) {} Component build() { assert( @@ -64,13 +64,13 @@ class Page extends PositionComponent with ParentIsA { PageRenderEffect? _renderEffect; @internal - void didPush(Page? previousPage) { + void didPush(Route? previousRoute) { _child ??= build()..addToParent(this); - onPush(previousPage); + onPush(previousRoute); } @internal - void didPop(Page previousPage) => onPop(previousPage); + void didPop(Route previousRoute) => onPop(previousRoute); @override void renderTree(Canvas canvas) { From a392e694d211420a8008c862b5a1ac52b5e9d109 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 29 Jun 2022 20:32:42 -0700 Subject: [PATCH 27/54] finish renaming --- doc/flame/examples/lib/navigator.dart | 12 ++++++------ packages/flame/lib/src/components/route.dart | 18 +++++++++--------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index 32d8cf0b0c0..c68a1a75cfc 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -12,11 +12,11 @@ class NavigatorGame extends FlameGame with HasTappableComponents { add( navigator = Navigator( routes: { - 'splash': Route(builder: SplashScreen.new), + 'splash': Route(builder: SplashScreenPage.new), 'home': Route(builder: StartPage.new), 'level1': Route(builder: Level1Page.new), 'level2': Route(builder: Level2Page.new), - 'pause': PausePage(), + 'pause': PauseRoute(), }, initialRoute: 'splash', ), @@ -24,7 +24,7 @@ class NavigatorGame extends FlameGame with HasTappableComponents { } } -class SplashScreen extends Component +class SplashScreenPage extends Component with TapCallbacks, HasGameRef { @override Future onLoad() async { @@ -359,8 +359,8 @@ class Orbit extends PositionComponent { } } -class PausePage extends Route { - PausePage() : super(builder: PausePageImpl.new, transparent: true); +class PauseRoute extends Route { + PauseRoute() : super(builder: PausePage.new, transparent: true); @override void onPush(Route? previousRoute) { @@ -381,7 +381,7 @@ class PausePage extends Route { } } -class PausePageImpl extends Component +class PausePage extends Component with TapCallbacks, HasGameRef { @override Future onLoad() async { diff --git a/packages/flame/lib/src/components/route.dart b/packages/flame/lib/src/components/route.dart index 0185a901ee1..03144b7be12 100644 --- a/packages/flame/lib/src/components/route.dart +++ b/packages/flame/lib/src/components/route.dart @@ -14,10 +14,10 @@ class Route extends PositionComponent with ParentIsA { this.transparent = false, }) : _builder = builder; - /// If true, then the page below this one will continue to be rendered when - /// this page becomes active. If false, then this page is assumed to - /// completely obscure any page that would be underneath, and therefore the - /// page underneath doesn't need to be rendered. + /// If true, then the route below this one will continue to be rendered when + /// this route becomes active. If false, then this route is assumed to + /// completely obscure any route that would be underneath, and therefore the + /// route underneath doesn't need to be rendered. final bool transparent; double pushTransitionDuration = 0; @@ -26,8 +26,8 @@ class Route extends PositionComponent with ParentIsA { final Component Function()? _builder; - /// This method is invoked when the page is pushed on top of the [Navigator]'s - /// stack. + /// This method is invoked when the route is pushed on top of the + /// [Navigator]'s stack. void onPush(Route? previousRoute) {} void onPop(Route previousRoute) {} @@ -54,18 +54,18 @@ class Route extends PositionComponent with ParentIsA { //#region Implementation methods @internal - bool get isBuilt => _child != null; + bool get isBuilt => _page != null; @internal bool isRendered = true; - Component? _child; + Component? _page; PageRenderEffect? _renderEffect; @internal void didPush(Route? previousRoute) { - _child ??= build()..addToParent(this); + _page ??= build()..addToParent(this); onPush(previousRoute); } From 55a0fa3e5176fc804e992fb275dea79ac0c1f9ab Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 29 Jun 2022 20:37:20 -0700 Subject: [PATCH 28/54] update docs --- doc/flame/navigator.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/doc/flame/navigator.md b/doc/flame/navigator.md index 06410910ff7..790376244dd 100644 --- a/doc/flame/navigator.md +++ b/doc/flame/navigator.md @@ -20,8 +20,8 @@ A typical game will usually consists of multiple pages: the splash screen, the s the settings page, credits, the main game page, several pop-ups, etc. The `Navigator` will organize all these destinations and allow you to transition between them. -Internally, the `Navigator` contains a stack of pages. When you request it to show a page, that -page will be placed on top of all other pages in the stack. Later you can `popPage()` to remove the +Internally, the `Navigator` contains a stack of routes. When you request it to show a route, it +will be placed on top of all other pages in the stack. Later you can `popPage()` to remove the topmost page from the stack. The pages of the Navigator are addressed by their unique names. Each Page in the Navigator can be either transparent or opaque. If a page is opaque, then the pages @@ -30,7 +30,6 @@ On the contrary, if a page is transparent, then the page below it will be render events normally. Such transparent pages are useful for implementing modal dialogs, inventory or dialogue UIs, etc. - Usage example: ```dart class MyGame extends FlameGame { @@ -38,14 +37,16 @@ class MyGame extends FlameGame { @override Future onLoad() async { - navigator = Navigator( - pages: { - 'home': Page(builder: HomePageComponent()), - 'level-selector': Page(builder: LevelSelectorPageComponent()), - 'settings': Page(builder: SettingsPageComponent(), transparent: true), - 'pause': PausePage(), - }, - initialPage: 'home', + add( + navigator = Navigator( + routes: { + 'home': Route(builder: HomePage()), + 'level-selector': Route(builder: LevelSelectorPage()), + 'settings': Route(builder: SettingsPage(), transparent: true), + 'pause': PauseRoute(), + }, + initialRoute: 'home', + ), ); } } @@ -54,10 +55,10 @@ class MyGame extends FlameGame { [Flutter Navigator]: https://api.flutter.dev/flutter/widgets/Navigator-class.html -## Page +## Route -The **Page** component holds information about the content of a particular screen. `Page`s are +The **Route** component holds information about the content of a particular page. `Route`s are mounted as children to the `Navigator`. -The main property of a `Page` is its `builder` -- the function that creates the component which is -the content of this page. +The main property of a `Route` is its `builder` -- the function that creates the component with +the content of its page. From 696703d9f79a5ebccc3e3eb6126fe32167d88366 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 29 Jun 2022 20:38:55 -0700 Subject: [PATCH 29/54] remove transition-related logic --- packages/flame/lib/src/components/navigator.dart | 13 ++----------- packages/flame/lib/src/components/route.dart | 4 ---- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator.dart index 2990fab7d7a..90cbbc0cbcb 100644 --- a/packages/flame/lib/src/components/navigator.dart +++ b/packages/flame/lib/src/components/navigator.dart @@ -41,7 +41,7 @@ class Navigator extends Component { } _adjustPageOrder(); page.didPush(currentActivePage); - _execute(action: _adjustPageVisibility, delay: page.pushTransitionDuration); + _adjustPageVisibility(); } void popRoute() { @@ -53,7 +53,7 @@ class Navigator extends Component { _adjustPageOrder(); _adjustPageVisibility(); page.didPop(_currentRoutes.last); - _execute(action: page.removeFromParent, delay: page.popTransitionDuration); + page.removeFromParent(); } Route _resolveRoute(String name) { @@ -93,15 +93,6 @@ class Navigator extends Component { } } - void _execute({required void Function() action, required double delay}) { - if (delay > 0) { - Future.delayed(Duration(microseconds: (delay * 1e6).toInt())) - .then((_) => action()); - } else { - action(); - } - } - @override void onMount() { super.onMount(); diff --git a/packages/flame/lib/src/components/route.dart b/packages/flame/lib/src/components/route.dart index 03144b7be12..20cc7a50b37 100644 --- a/packages/flame/lib/src/components/route.dart +++ b/packages/flame/lib/src/components/route.dart @@ -20,10 +20,6 @@ class Route extends PositionComponent with ParentIsA { /// route underneath doesn't need to be rendered. final bool transparent; - double pushTransitionDuration = 0; - - double popTransitionDuration = 0; - final Component Function()? _builder; /// This method is invoked when the route is pushed on top of the From 341e636a49307727c922261b60added2bfa4de8c Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 29 Jun 2022 21:28:44 -0700 Subject: [PATCH 30/54] update docs --- .../flame/lib/src/components/navigator.dart | 80 ++++++++++++------- packages/flame/lib/src/components/route.dart | 3 + 2 files changed, 54 insertions(+), 29 deletions(-) diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator.dart index 90cbbc0cbcb..61256852993 100644 --- a/packages/flame/lib/src/components/navigator.dart +++ b/packages/flame/lib/src/components/navigator.dart @@ -1,13 +1,35 @@ import 'package:flame/src/components/component.dart'; import 'package:flame/src/components/route.dart'; -/// [Navigator] is a component that handles transitions between multiple -/// [Route]s of your game. +/// [Navigator] is a component that handles transitions between multiple pages +/// of your game. /// +/// The term **page** is used descriptively here: it is any full-screen (or +/// partial-screen) component. For example: a starting page, a settings page, +/// the main game world page, and so on. A page can also be any individual piece +/// of UI, such as a confirmation dialog box, or a user inventory pop-up. +/// +/// The [Navigator] doesn't handle the pages directly -- instead, it operates +/// a stack of [Route]s. Each route, in turn, manages a single page component. +/// However, routes are "lazy": they will only build their pages when they +/// become active. +/// +/// Internally, the Navigator maintains a stack of Routes. In the beginning, +/// the stack will contain the [initialRoute]. New routes can be added via the +/// [pushRoute] method, and removed with [popRoute]. However, the stack must be +/// kept non-empty: it is an error to attempt to remove the only remaining route +/// from the stack. +/// +/// Routes that are on the stack are mounted as components. When a route is +/// popped, it is removed from the stack and unmounted. Routes can be either +/// transparent or opaque. An opaque route prevents all routes below it from +/// rendering, and also stops pointer events. In addition, routes are able to +/// stop or slow down time for the pages that they control, or to apply visual +/// effects to those pages. class Navigator extends Component { Navigator({ - required Map routes, required this.initialRoute, + required Map routes, Map? routeFactories, this.onUnknownRoute, }) : _routes = routes, @@ -27,39 +49,39 @@ class Navigator extends Component { /// /// void pushRoute(String name) { - final page = _resolveRoute(name); - final currentActivePage = _currentRoutes.last; - if (page == currentActivePage) { + final route = _resolveRoute(name); + final currentActiveRoute = _currentRoutes.last; + if (route == currentActiveRoute) { return; } - if (_currentRoutes.contains(page)) { - _currentRoutes.remove(page); - _currentRoutes.add(page); + if (_currentRoutes.contains(route)) { + _currentRoutes.remove(route); + _currentRoutes.add(route); } else { - _currentRoutes.add(page); - add(page); + _currentRoutes.add(route); + add(route); } - _adjustPageOrder(); - page.didPush(currentActivePage); - _adjustPageVisibility(); + _adjustRoutesOrder(); + route.didPush(currentActiveRoute); + _adjustRoutesVisibility(); } void popRoute() { assert( _currentRoutes.length > 1, - 'Cannot pop the last page from the Navigator', + 'Cannot pop the last route from the Navigator', ); - final page = _currentRoutes.removeLast(); - _adjustPageOrder(); - _adjustPageVisibility(); - page.didPop(_currentRoutes.last); - page.removeFromParent(); + final route = _currentRoutes.removeLast(); + _adjustRoutesOrder(); + _adjustRoutesVisibility(); + route.didPop(_currentRoutes.last); + route.removeFromParent(); } Route _resolveRoute(String name) { - final existingPage = _routes[name]; - if (existingPage != null) { - return existingPage; + final existingRoute = _routes[name]; + if (existingRoute != null) { + return existingRoute; } if (name.contains('/')) { final i = name.indexOf('/'); @@ -67,25 +89,25 @@ class Navigator extends Component { final factory = _routeFactories[factoryName]; if (factory != null) { final argument = name.substring(i + 1); - final generatedPage = factory(argument); - _routes[name] = generatedPage; - return generatedPage; + final generatedRoute = factory(argument); + _routes[name] = generatedRoute; + return generatedRoute; } } if (onUnknownRoute != null) { return onUnknownRoute!(name); } - throw ArgumentError('Page "$name" could not be resolved by the Navigator'); + throw ArgumentError('Route "$name" could not be resolved by the Navigator'); } - void _adjustPageOrder() { + void _adjustRoutesOrder() { for (var i = 0; i < _currentRoutes.length; i++) { _currentRoutes[i].changePriorityWithoutResorting(i); } reorderChildren(); } - void _adjustPageVisibility() { + void _adjustRoutesVisibility() { var render = true; for (var i = _currentRoutes.length - 1; i >= 0; i--) { _currentRoutes[i].isRendered = render; diff --git a/packages/flame/lib/src/components/route.dart b/packages/flame/lib/src/components/route.dart index 20cc7a50b37..89ad1bde45d 100644 --- a/packages/flame/lib/src/components/route.dart +++ b/packages/flame/lib/src/components/route.dart @@ -8,6 +8,9 @@ import 'package:flame/src/page_render_effect.dart'; import 'package:meta/meta.dart'; import 'package:vector_math/vector_math_64.dart'; +/// A [Route] is a light-weight component whose primary job is to build its page +/// when necessary (most routes are created when the game is initialized, and +/// therefore they should avoid doing any potentially costly operations). class Route extends PositionComponent with ParentIsA { Route({ Component Function()? builder, From 946e01e852858855ce68bb94580298f4139c3f66 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 29 Jun 2022 21:30:25 -0700 Subject: [PATCH 31/54] format --- doc/flame/examples/lib/navigator.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index c68a1a75cfc..ae645b0521c 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -381,8 +381,7 @@ class PauseRoute extends Route { } } -class PausePage extends Component - with TapCallbacks, HasGameRef { +class PausePage extends Component with TapCallbacks, HasGameRef { @override Future onLoad() async { final game = findGame()!; From 46ef20723e368e953d7cabdddf04e775659fd9e0 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 29 Jun 2022 22:36:43 -0700 Subject: [PATCH 32/54] more docs --- .../flame/lib/src/components/navigator.dart | 76 ++++++++++++++----- 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator.dart index 61256852993..4102fd7a863 100644 --- a/packages/flame/lib/src/components/navigator.dart +++ b/packages/flame/lib/src/components/navigator.dart @@ -35,30 +35,56 @@ class Navigator extends Component { }) : _routes = routes, _routeFactories = routeFactories ?? {}; + /// Route that will be placed on the stack in the beginning. final String initialRoute; + + /// The stack of all currently active routes. This stack must not be empty + /// (it will be populated with the [initialRoute] in the beginning). + /// + /// The routes in this list are also added to the Navigator as child + /// components. However, due to the fact that children are usually added or + /// removed with a delay, there could be temporary discrepancies between this + /// list and the list of children. + final List _routeStack = []; + + /// The map of all routes known to the Navigator, each route will have a + /// unique name. This map is initialized in the constructor; in addition, any + /// routes produced by the [_routeFactories] will also be cached here. final Map _routes; + + /// Set of functions that are able to resolve routes dynamically. + /// + /// Route factories will be used to resolve pages with names like + /// "prefix/arg". For such a name, we will call the factory "prefix" with the + /// argument "arg". The produced route will be cached in the main [_routes] + /// map, and then built and mounted normally. final Map _routeFactories; - final List _currentRoutes = []; + + /// Function that will be called to resolve any route names that couldn't be + /// resolved via [_routes] or [_routeFactories]. Unlike with routeFactories, + /// the route returned by this function will not be cached. final _RouteFactory? onUnknownRoute; - /// Puts the page [name] on top of the navigation stack. - /// - /// If the page is already in the stack, it will be simply moved on top; - /// otherwise the page will be built, mounted, and added at the top. If the - /// page is already on the top, this method will be a noop. + /// Puts the route [name] on top of the navigation stack. /// + /// If the route is already in the stack, it will be simply moved to the top. + /// Otherwise the route will be mounted and added at the top. We will also + /// initiate building the route's page if it hasn't been built before. If the + /// route is already on top of the stack, this method will do nothing. /// + /// The method calls the [Route.didPush] callback for the newly activated + /// route. void pushRoute(String name) { final route = _resolveRoute(name); - final currentActiveRoute = _currentRoutes.last; + final currentActiveRoute = _routeStack.last; if (route == currentActiveRoute) { return; } - if (_currentRoutes.contains(route)) { - _currentRoutes.remove(route); - _currentRoutes.add(route); + if (_routeStack.contains(route)) { + _routeStack.remove(route); + _routeStack.add(route); } else { - _currentRoutes.add(route); + _routeStack.add(route); add(route); } _adjustRoutesOrder(); @@ -66,18 +92,28 @@ class Navigator extends Component { _adjustRoutesVisibility(); } + /// Removes the topmost route from the stack, and also removes it as a child + /// of the Navigator. + /// + /// The method calls [Route.didPop] for the route that was removed. + /// + /// It is an error to attempt to pop the last remaining route on the stack. void popRoute() { assert( - _currentRoutes.length > 1, + _routeStack.length > 1, 'Cannot pop the last route from the Navigator', ); - final route = _currentRoutes.removeLast(); + final route = _routeStack.removeLast(); _adjustRoutesOrder(); _adjustRoutesVisibility(); - route.didPop(_currentRoutes.last); + route.didPop(_routeStack.last); route.removeFromParent(); } + /// Attempts to resolve the route with the given [name] by searching in the + /// [_routes] map, or invoking one of the [_routeFactories], or, lastly, + /// falling back to the [onUnknownRoute] function. If none of these methods + /// is able to produce a valid [Route], an exception will be raised. Route _resolveRoute(String name) { final existingRoute = _routes[name]; if (existingRoute != null) { @@ -101,17 +137,17 @@ class Navigator extends Component { } void _adjustRoutesOrder() { - for (var i = 0; i < _currentRoutes.length; i++) { - _currentRoutes[i].changePriorityWithoutResorting(i); + for (var i = 0; i < _routeStack.length; i++) { + _routeStack[i].changePriorityWithoutResorting(i); } reorderChildren(); } void _adjustRoutesVisibility() { var render = true; - for (var i = _currentRoutes.length - 1; i >= 0; i--) { - _currentRoutes[i].isRendered = render; - render &= _currentRoutes[i].transparent; + for (var i = _routeStack.length - 1; i >= 0; i--) { + _routeStack[i].isRendered = render; + render &= _routeStack[i].transparent; } } @@ -119,7 +155,7 @@ class Navigator extends Component { void onMount() { super.onMount(); final route = _resolveRoute(initialRoute); - _currentRoutes.add(route); + _routeStack.add(route); add(route); route.didPush(null); } From bdb6609fc2dabe9a952cf26436078dbca31bd880 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 29 Jun 2022 23:50:32 -0700 Subject: [PATCH 33/54] docs for Route --- packages/flame/lib/src/components/route.dart | 73 +++++++++++++++++--- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/packages/flame/lib/src/components/route.dart b/packages/flame/lib/src/components/route.dart index 89ad1bde45d..bfb00a9b176 100644 --- a/packages/flame/lib/src/components/route.dart +++ b/packages/flame/lib/src/components/route.dart @@ -4,13 +4,25 @@ import 'package:flame/src/components/component.dart'; import 'package:flame/src/components/mixins/parent_is_a.dart'; import 'package:flame/src/components/navigator.dart'; import 'package:flame/src/components/position_component.dart'; +import 'package:flame/src/effects/effect.dart'; import 'package:flame/src/page_render_effect.dart'; import 'package:meta/meta.dart'; import 'package:vector_math/vector_math_64.dart'; -/// A [Route] is a light-weight component whose primary job is to build its page -/// when necessary (most routes are created when the game is initialized, and -/// therefore they should avoid doing any potentially costly operations). +/// [Route] is a light-weight component that builds and manages a page. +/// +/// The "page" is a generic concept here: it is any component that comprises a +/// distinct UI arrangement. Pages are usually full-screen, for example: a +/// splash-screen page, a loading page, a game menu page, a level selection +/// page, a character creation page, and so on. Pages may also occupy less than +/// the full screen. These can be: a confirmation popup, an "enter name" dialog +/// box, a character inventory panel, a UI for dialogue with an NPC, etc. +/// +/// Most routes are created when the game is initialized, and thus they should +/// try to be as lightweight as possible. In particular, a [Route] should avoid +/// any potentially costly initialization operations. +/// +/// Routes are managed by the [Navigator] component. class Route extends PositionComponent with ParentIsA { Route({ Component Function()? builder, @@ -23,14 +35,32 @@ class Route extends PositionComponent with ParentIsA { /// route underneath doesn't need to be rendered. final bool transparent; + /// The function that will be invoked in order to build the page component + /// when this route first becomes active. This function may also be `null`, + /// in which case the user must override the [build] method. final Component Function()? _builder; /// This method is invoked when the route is pushed on top of the /// [Navigator]'s stack. + /// + /// The argument for this method is the route that was on top of the stack + /// before the push. It can be null if the current route becomes the first + /// element of the navigation stack. void onPush(Route? previousRoute) {} - void onPop(Route previousRoute) {} - + /// This method is called when the route is popped off the top of the + /// [Navigator]'s stack. + /// + /// The argument for this method is the route that will become the next + /// top-most route on the stack. Thus, the argument in [onPop] will always be + /// the same as was given previously in [onPush]. + void onPop(Route nextRoute) {} + + /// Creates the page component managed by this page. + /// + /// Overriding this method is an alternative to supplying the explicit builder + /// function in the constructor. + @protected Component build() { assert( _builder != null, @@ -40,34 +70,61 @@ class Route extends PositionComponent with ParentIsA { return _builder!(); } + /// The time "speed" factor. + /// + /// The value of 1 means that the time on this page runs normally. The value + /// less than 1 corresponds to time running slower than normal. The speed of + /// zero means the time on this page is stopped. double timeSpeed = 1.0; + /// Completely stops time for the managed page. + /// + /// When the time is stopped, the [updateTree] method of the page is not + /// called at all, which can save computational resources. However, this also + /// means that the lifecycle events on the page will not be processed, and + /// therefore no components will be able to be added or removed from the + /// page. void stopTime() => timeSpeed = 0; + /// Resumes normal time progression for the page, if it was previously slowed + /// down or stopped. void resumeTime() => timeSpeed = 1.0; + /// Applies the provided [PageRenderEffect] to the page. + /// + /// Render effects should not be confused with regular [Effect]s. Examples of + /// the render effects include: whole-page blur, convert into grayscale, + /// apply color tint, etc. void addRenderEffect(PageRenderEffect effect) => _renderEffect = effect; + /// Removes current [PageRenderEffect], is any. void removeRenderEffect() => _renderEffect = null; //#region Implementation methods - @internal - bool get isBuilt => _page != null; - + /// If true, the page must be rendered normally. If false, the page should + /// not be rendered, because it is completely obscured by another route which + /// is on top of it. This variable is set by the [Navigator]. @internal bool isRendered = true; + /// The page that was built and is now owned by this route. This page will + /// also be added as a child component. Component? _page; + /// Additional visual effect that may be applied to the page during rendering. PageRenderEffect? _renderEffect; + /// Invoked by the [Navigator] when this route is pushed to the top of the + /// navigation stack. @internal void didPush(Route? previousRoute) { _page ??= build()..addToParent(this); onPush(previousRoute); } + /// Invoked by the [Navigator] when this route is popped off the top of the + /// navigation stack. @internal void didPop(Route previousRoute) => onPop(previousRoute); From ae137aa22aba3912af2a30508c66120199f7a247 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Wed, 29 Jun 2022 23:56:17 -0700 Subject: [PATCH 34/54] Added names for the Routes --- packages/flame/lib/src/components/navigator.dart | 8 +++++--- packages/flame/lib/src/components/route.dart | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator.dart index 4102fd7a863..c16420e1671 100644 --- a/packages/flame/lib/src/components/navigator.dart +++ b/packages/flame/lib/src/components/navigator.dart @@ -33,7 +33,9 @@ class Navigator extends Component { Map? routeFactories, this.onUnknownRoute, }) : _routes = routes, - _routeFactories = routeFactories ?? {}; + _routeFactories = routeFactories ?? {} { + routes.forEach((name, route) => route.name = name); + } /// Route that will be placed on the stack in the beginning. final String initialRoute; @@ -125,13 +127,13 @@ class Navigator extends Component { final factory = _routeFactories[factoryName]; if (factory != null) { final argument = name.substring(i + 1); - final generatedRoute = factory(argument); + final generatedRoute = factory(argument)..name = name; _routes[name] = generatedRoute; return generatedRoute; } } if (onUnknownRoute != null) { - return onUnknownRoute!(name); + return onUnknownRoute!(name)..name = name; } throw ArgumentError('Route "$name" could not be resolved by the Navigator'); } diff --git a/packages/flame/lib/src/components/route.dart b/packages/flame/lib/src/components/route.dart index bfb00a9b176..b7da503dcbb 100644 --- a/packages/flame/lib/src/components/route.dart +++ b/packages/flame/lib/src/components/route.dart @@ -35,6 +35,9 @@ class Route extends PositionComponent with ParentIsA { /// route underneath doesn't need to be rendered. final bool transparent; + /// The name of the route (set by the [Navigator]). + late final String name; + /// The function that will be invoked in order to build the page component /// when this route first becomes active. This function may also be `null`, /// in which case the user must override the [build] method. From 8fc0f71da17d1b2326fdf1d5e3dea83877f1a47d Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sat, 2 Jul 2022 12:25:39 -0700 Subject: [PATCH 35/54] added getters .currentRoute and .previousRoute --- packages/flame/lib/src/components/navigator.dart | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator.dart index c16420e1671..72711b18d3b 100644 --- a/packages/flame/lib/src/components/navigator.dart +++ b/packages/flame/lib/src/components/navigator.dart @@ -67,6 +67,14 @@ class Navigator extends Component { /// the route returned by this function will not be cached. final _RouteFactory? onUnknownRoute; + /// Returns the route that is currently at the top of the stack. + Route get currentRoute => _routeStack.last; + + /// Returns the route that is below the current topmost route, if it exists. + Route? get previousRoute { + return _routeStack.length >=2 ? _routeStack[_routeStack.length - 2] : null; + } + /// Puts the route [name] on top of the navigation stack. /// /// If the route is already in the stack, it will be simply moved to the top. @@ -78,19 +86,17 @@ class Navigator extends Component { /// route. void pushRoute(String name) { final route = _resolveRoute(name); - final currentActiveRoute = _routeStack.last; - if (route == currentActiveRoute) { + if (route == currentRoute) { return; } if (_routeStack.contains(route)) { _routeStack.remove(route); - _routeStack.add(route); } else { - _routeStack.add(route); add(route); } + _routeStack.add(route); _adjustRoutesOrder(); - route.didPush(currentActiveRoute); + route.didPush(previousRoute); _adjustRoutesVisibility(); } From e815b2303f92dd766ba043cadef91adc2bebf7e6 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sat, 2 Jul 2022 12:52:43 -0700 Subject: [PATCH 36/54] Renamed RenderEffect -> Decorator --- doc/flame/examples/lib/navigator.dart | 2 +- packages/flame/lib/game.dart | 3 ++- packages/flame/lib/src/components/route.dart | 10 +++++----- packages/flame/lib/src/decorator.dart | 6 ++++++ .../{page_render_effect.dart => paint_decorator.dart} | 7 ++----- 5 files changed, 16 insertions(+), 12 deletions(-) create mode 100644 packages/flame/lib/src/decorator.dart rename packages/flame/lib/src/{page_render_effect.dart => paint_decorator.dart} (79%) diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index ae645b0521c..d5f9810be60 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -367,7 +367,7 @@ class PauseRoute extends Route { previousRoute! ..stopTime() ..addRenderEffect( - PaintRenderEffect() + PaintDecorator() ..addDesaturation(opacity: 0.5) ..addBlur(3.0), ); diff --git a/packages/flame/lib/game.dart b/packages/flame/lib/game.dart index be9806b2b7e..8b4a6c78ff5 100644 --- a/packages/flame/lib/game.dart +++ b/packages/flame/lib/game.dart @@ -3,6 +3,7 @@ export 'src/collisions/has_collision_detection.dart'; export 'src/components/navigator.dart' show Navigator; export 'src/components/route.dart' show Route; +export 'src/decorator.dart' show Decorator; export 'src/extensions/vector2.dart'; export 'src/game/camera/camera.dart'; export 'src/game/camera/viewport.dart'; @@ -17,5 +18,5 @@ export 'src/game/mixins/single_game_instance.dart'; export 'src/game/notifying_vector2.dart'; export 'src/game/projector.dart'; export 'src/game/transform2d.dart'; -export 'src/page_render_effect.dart'; +export 'src/paint_decorator.dart' show PaintDecorator; export 'src/text/text_paint.dart'; diff --git a/packages/flame/lib/src/components/route.dart b/packages/flame/lib/src/components/route.dart index b7da503dcbb..83f27e7c321 100644 --- a/packages/flame/lib/src/components/route.dart +++ b/packages/flame/lib/src/components/route.dart @@ -5,7 +5,7 @@ import 'package:flame/src/components/mixins/parent_is_a.dart'; import 'package:flame/src/components/navigator.dart'; import 'package:flame/src/components/position_component.dart'; import 'package:flame/src/effects/effect.dart'; -import 'package:flame/src/page_render_effect.dart'; +import 'package:flame/src/decorator.dart'; import 'package:meta/meta.dart'; import 'package:vector_math/vector_math_64.dart'; @@ -93,14 +93,14 @@ class Route extends PositionComponent with ParentIsA { /// down or stopped. void resumeTime() => timeSpeed = 1.0; - /// Applies the provided [PageRenderEffect] to the page. + /// Applies the provided [Decorator] to the page. /// /// Render effects should not be confused with regular [Effect]s. Examples of /// the render effects include: whole-page blur, convert into grayscale, /// apply color tint, etc. - void addRenderEffect(PageRenderEffect effect) => _renderEffect = effect; + void addRenderEffect(Decorator effect) => _renderEffect = effect; - /// Removes current [PageRenderEffect], is any. + /// Removes current [Decorator], is any. void removeRenderEffect() => _renderEffect = null; //#region Implementation methods @@ -116,7 +116,7 @@ class Route extends PositionComponent with ParentIsA { Component? _page; /// Additional visual effect that may be applied to the page during rendering. - PageRenderEffect? _renderEffect; + Decorator? _renderEffect; /// Invoked by the [Navigator] when this route is pushed to the top of the /// navigation stack. diff --git a/packages/flame/lib/src/decorator.dart b/packages/flame/lib/src/decorator.dart new file mode 100644 index 00000000000..f93a0aa0fd3 --- /dev/null +++ b/packages/flame/lib/src/decorator.dart @@ -0,0 +1,6 @@ +import 'dart:ui'; + +abstract class Decorator { + Canvas preprocessCanvas(Canvas canvas); + void postprocessCanvas(Canvas canvas); +} diff --git a/packages/flame/lib/src/page_render_effect.dart b/packages/flame/lib/src/paint_decorator.dart similarity index 79% rename from packages/flame/lib/src/page_render_effect.dart rename to packages/flame/lib/src/paint_decorator.dart index 10044c0f94c..85c865c28df 100644 --- a/packages/flame/lib/src/page_render_effect.dart +++ b/packages/flame/lib/src/paint_decorator.dart @@ -1,11 +1,8 @@ import 'dart:ui'; -abstract class PageRenderEffect { - Canvas preprocessCanvas(Canvas canvas); - void postprocessCanvas(Canvas canvas); -} +import 'package:flame/src/decorator.dart'; -class PaintRenderEffect extends PageRenderEffect { +class PaintDecorator extends Decorator { final _paint = Paint(); void addBlur(double amount, [double? amountY]) { From 51b716e3cb7331d1c24202f34dbf01587c1b5a1e Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sat, 2 Jul 2022 12:57:03 -0700 Subject: [PATCH 37/54] Renamed pushRoute -> pushNamed --- doc/flame/examples/lib/navigator.dart | 8 ++++---- packages/flame/lib/src/components/navigator.dart | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index d5f9810be60..11d435e45f5 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -48,7 +48,7 @@ class SplashScreenPage extends Component bool containsLocalPoint(Vector2 point) => true; @override - void onTapUp(TapUpEvent event) => gameRef.navigator.pushRoute('home'); + void onTapUp(TapUpEvent event) => gameRef.navigator.pushNamed('home'); } class StartPage extends Component with HasGameRef { @@ -67,13 +67,13 @@ class StartPage extends Component with HasGameRef { ), _button1 = RoundedButton( text: 'Level 1', - action: () => gameRef.navigator.pushRoute('level1'), + action: () => gameRef.navigator.pushNamed('level1'), color: const Color(0xffadde6c), borderColor: const Color(0xffedffab), ), _button2 = RoundedButton( text: 'Level 2', - action: () => gameRef.navigator.pushRoute('level2'), + action: () => gameRef.navigator.pushNamed('level2'), color: const Color(0xffdebe6c), borderColor: const Color(0xfffff4c7), ), @@ -229,7 +229,7 @@ class PauseButton extends SimpleButton with HasGameRef { position: Vector2(60, 10), ); @override - void action() => gameRef.navigator.pushRoute('pause'); + void action() => gameRef.navigator.pushNamed('pause'); } class Level1Page extends Component { diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator.dart index 72711b18d3b..441aec84745 100644 --- a/packages/flame/lib/src/components/navigator.dart +++ b/packages/flame/lib/src/components/navigator.dart @@ -16,7 +16,7 @@ import 'package:flame/src/components/route.dart'; /// /// Internally, the Navigator maintains a stack of Routes. In the beginning, /// the stack will contain the [initialRoute]. New routes can be added via the -/// [pushRoute] method, and removed with [popRoute]. However, the stack must be +/// [pushNamed] method, and removed with [popRoute]. However, the stack must be /// kept non-empty: it is an error to attempt to remove the only remaining route /// from the stack. /// @@ -84,7 +84,7 @@ class Navigator extends Component { /// /// The method calls the [Route.didPush] callback for the newly activated /// route. - void pushRoute(String name) { + void pushNamed(String name) { final route = _resolveRoute(name); if (route == currentRoute) { return; From 7903b38fa5fd16ffab8b665458bffbf4e30bb2ed Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sat, 2 Jul 2022 12:57:31 -0700 Subject: [PATCH 38/54] rename popRoute -> pop --- doc/flame/examples/lib/navigator.dart | 4 ++-- packages/flame/lib/src/components/navigator.dart | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index 11d435e45f5..8032f9a9845 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -215,7 +215,7 @@ class BackButton extends SimpleButton with HasGameRef { ); @override - void action() => gameRef.navigator.popRoute(); + void action() => gameRef.navigator.pop(); } class PauseButton extends SimpleButton with HasGameRef { @@ -408,5 +408,5 @@ class PausePage extends Component with TapCallbacks, HasGameRef { bool containsLocalPoint(Vector2 point) => true; @override - void onTapUp(TapUpEvent event) => gameRef.navigator.popRoute(); + void onTapUp(TapUpEvent event) => gameRef.navigator.pop(); } diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator.dart index 441aec84745..9b5a74565cb 100644 --- a/packages/flame/lib/src/components/navigator.dart +++ b/packages/flame/lib/src/components/navigator.dart @@ -16,7 +16,7 @@ import 'package:flame/src/components/route.dart'; /// /// Internally, the Navigator maintains a stack of Routes. In the beginning, /// the stack will contain the [initialRoute]. New routes can be added via the -/// [pushNamed] method, and removed with [popRoute]. However, the stack must be +/// [pushNamed] method, and removed with [pop]. However, the stack must be /// kept non-empty: it is an error to attempt to remove the only remaining route /// from the stack. /// @@ -106,7 +106,7 @@ class Navigator extends Component { /// The method calls [Route.didPop] for the route that was removed. /// /// It is an error to attempt to pop the last remaining route on the stack. - void popRoute() { + void pop() { assert( _routeStack.length > 1, 'Cannot pop the last route from the Navigator', From 28102b90e35ef18e3f3cb83ab04b3e7aa26d09a9 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sat, 2 Jul 2022 14:16:32 -0700 Subject: [PATCH 39/54] added pushRoute() method --- packages/flame/lib/src/components/navigator.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator.dart index 9b5a74565cb..31ead3c72bd 100644 --- a/packages/flame/lib/src/components/navigator.dart +++ b/packages/flame/lib/src/components/navigator.dart @@ -100,6 +100,17 @@ class Navigator extends Component { _adjustRoutesVisibility(); } + /// Puts an unnamed [route] on top of the navigation stack. + /// + /// The method calls [Route.didPush] for this new route after it is added. + void pushRoute(Route route) { + add(route); + _routeStack.add(route); + _adjustRoutesOrder(); + route.didPush(previousRoute); + _adjustRoutesVisibility(); + } + /// Removes the topmost route from the stack, and also removes it as a child /// of the Navigator. /// From e26ad438f2165341d609c526fb0f21f907e3c962 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sat, 2 Jul 2022 14:19:38 -0700 Subject: [PATCH 40/54] renamed Navigator -> NavigatorComponent --- doc/flame/examples/lib/navigator.dart | 4 ++-- packages/flame/lib/game.dart | 2 +- ...navigator.dart => navigator_component.dart} | 9 ++++----- packages/flame/lib/src/components/route.dart | 18 +++++++++--------- .../game_widget_controlled_lifecycle_test.dart | 2 +- .../game_widget/game_widget_keyboard_test.dart | 2 +- .../game_widget_lifecycle_test.dart | 2 +- 7 files changed, 19 insertions(+), 20 deletions(-) rename packages/flame/lib/src/components/{navigator.dart => navigator_component.dart} (96%) diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index 8032f9a9845..e033d3e9446 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -5,12 +5,12 @@ import 'package:flame/game.dart'; import 'package:flutter/rendering.dart'; class NavigatorGame extends FlameGame with HasTappableComponents { - late final Navigator navigator; + late final NavigatorComponent navigator; @override Future onLoad() async { add( - navigator = Navigator( + navigator = NavigatorComponent( routes: { 'splash': Route(builder: SplashScreenPage.new), 'home': Route(builder: StartPage.new), diff --git a/packages/flame/lib/game.dart b/packages/flame/lib/game.dart index 8b4a6c78ff5..453ba5d71be 100644 --- a/packages/flame/lib/game.dart +++ b/packages/flame/lib/game.dart @@ -1,7 +1,7 @@ /// {@canonicalFor text.TextPaint} /// {@canonicalFor text.TextRenderer} export 'src/collisions/has_collision_detection.dart'; -export 'src/components/navigator.dart' show Navigator; +export 'src/components/navigator_component.dart' show NavigatorComponent; export 'src/components/route.dart' show Route; export 'src/decorator.dart' show Decorator; export 'src/extensions/vector2.dart'; diff --git a/packages/flame/lib/src/components/navigator.dart b/packages/flame/lib/src/components/navigator_component.dart similarity index 96% rename from packages/flame/lib/src/components/navigator.dart rename to packages/flame/lib/src/components/navigator_component.dart index 31ead3c72bd..15972446ace 100644 --- a/packages/flame/lib/src/components/navigator.dart +++ b/packages/flame/lib/src/components/navigator_component.dart @@ -1,15 +1,14 @@ import 'package:flame/src/components/component.dart'; import 'package:flame/src/components/route.dart'; -/// [Navigator] is a component that handles transitions between multiple pages -/// of your game. +/// [NavigatorComponent] handles transitions between multiple pages of a game. /// /// The term **page** is used descriptively here: it is any full-screen (or /// partial-screen) component. For example: a starting page, a settings page, /// the main game world page, and so on. A page can also be any individual piece /// of UI, such as a confirmation dialog box, or a user inventory pop-up. /// -/// The [Navigator] doesn't handle the pages directly -- instead, it operates +/// The navigator doesn't handle the pages directly -- instead, it operates /// a stack of [Route]s. Each route, in turn, manages a single page component. /// However, routes are "lazy": they will only build their pages when they /// become active. @@ -26,8 +25,8 @@ import 'package:flame/src/components/route.dart'; /// rendering, and also stops pointer events. In addition, routes are able to /// stop or slow down time for the pages that they control, or to apply visual /// effects to those pages. -class Navigator extends Component { - Navigator({ +class NavigatorComponent extends Component { + NavigatorComponent({ required this.initialRoute, required Map routes, Map? routeFactories, diff --git a/packages/flame/lib/src/components/route.dart b/packages/flame/lib/src/components/route.dart index 83f27e7c321..6223ee808ec 100644 --- a/packages/flame/lib/src/components/route.dart +++ b/packages/flame/lib/src/components/route.dart @@ -2,7 +2,7 @@ import 'dart:ui'; import 'package:flame/src/components/component.dart'; import 'package:flame/src/components/mixins/parent_is_a.dart'; -import 'package:flame/src/components/navigator.dart'; +import 'package:flame/src/components/navigator_component.dart'; import 'package:flame/src/components/position_component.dart'; import 'package:flame/src/effects/effect.dart'; import 'package:flame/src/decorator.dart'; @@ -22,8 +22,8 @@ import 'package:vector_math/vector_math_64.dart'; /// try to be as lightweight as possible. In particular, a [Route] should avoid /// any potentially costly initialization operations. /// -/// Routes are managed by the [Navigator] component. -class Route extends PositionComponent with ParentIsA { +/// Routes are managed by the [NavigatorComponent] component. +class Route extends PositionComponent with ParentIsA { Route({ Component Function()? builder, this.transparent = false, @@ -35,7 +35,7 @@ class Route extends PositionComponent with ParentIsA { /// route underneath doesn't need to be rendered. final bool transparent; - /// The name of the route (set by the [Navigator]). + /// The name of the route (set by the [NavigatorComponent]). late final String name; /// The function that will be invoked in order to build the page component @@ -44,7 +44,7 @@ class Route extends PositionComponent with ParentIsA { final Component Function()? _builder; /// This method is invoked when the route is pushed on top of the - /// [Navigator]'s stack. + /// [NavigatorComponent]'s stack. /// /// The argument for this method is the route that was on top of the stack /// before the push. It can be null if the current route becomes the first @@ -52,7 +52,7 @@ class Route extends PositionComponent with ParentIsA { void onPush(Route? previousRoute) {} /// This method is called when the route is popped off the top of the - /// [Navigator]'s stack. + /// [NavigatorComponent]'s stack. /// /// The argument for this method is the route that will become the next /// top-most route on the stack. Thus, the argument in [onPop] will always be @@ -107,7 +107,7 @@ class Route extends PositionComponent with ParentIsA { /// If true, the page must be rendered normally. If false, the page should /// not be rendered, because it is completely obscured by another route which - /// is on top of it. This variable is set by the [Navigator]. + /// is on top of it. This variable is set by the [NavigatorComponent]. @internal bool isRendered = true; @@ -118,7 +118,7 @@ class Route extends PositionComponent with ParentIsA { /// Additional visual effect that may be applied to the page during rendering. Decorator? _renderEffect; - /// Invoked by the [Navigator] when this route is pushed to the top of the + /// Invoked by the [NavigatorComponent] when this route is pushed to the top of the /// navigation stack. @internal void didPush(Route? previousRoute) { @@ -126,7 +126,7 @@ class Route extends PositionComponent with ParentIsA { onPush(previousRoute); } - /// Invoked by the [Navigator] when this route is popped off the top of the + /// Invoked by the [NavigatorComponent] when this route is popped off the top of the /// navigation stack. @internal void didPop(Route previousRoute) => onPop(previousRoute); diff --git a/packages/flame/test/game/game_widget/game_widget_controlled_lifecycle_test.dart b/packages/flame/test/game/game_widget/game_widget_controlled_lifecycle_test.dart index 0901630d2e4..27e614d8f3f 100644 --- a/packages/flame/test/game/game_widget/game_widget_controlled_lifecycle_test.dart +++ b/packages/flame/test/game/game_widget/game_widget_controlled_lifecycle_test.dart @@ -1,5 +1,5 @@ import 'package:flame/components.dart'; -import 'package:flame/game.dart' hide Navigator; +import 'package:flame/game.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/flame/test/game/game_widget/game_widget_keyboard_test.dart b/packages/flame/test/game/game_widget/game_widget_keyboard_test.dart index cb2a60ab213..ad2dcded13b 100644 --- a/packages/flame/test/game/game_widget/game_widget_keyboard_test.dart +++ b/packages/flame/test/game/game_widget/game_widget_keyboard_test.dart @@ -1,5 +1,5 @@ import 'package:flame/components.dart'; -import 'package:flame/game.dart' hide Navigator; +import 'package:flame/game.dart'; import 'package:flame/input.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; diff --git a/packages/flame/test/game/game_widget/game_widget_lifecycle_test.dart b/packages/flame/test/game/game_widget/game_widget_lifecycle_test.dart index a020c716138..16914e64725 100644 --- a/packages/flame/test/game/game_widget/game_widget_lifecycle_test.dart +++ b/packages/flame/test/game/game_widget/game_widget_lifecycle_test.dart @@ -1,5 +1,5 @@ import 'package:flame/components.dart'; -import 'package:flame/game.dart' hide Navigator; +import 'package:flame/game.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; From 177f740e126ee457a1649b610c87e48b7540be84 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sat, 2 Jul 2022 14:20:36 -0700 Subject: [PATCH 41/54] format --- .../flame/lib/src/components/navigator_component.dart | 2 +- packages/flame/lib/src/components/route.dart | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/flame/lib/src/components/navigator_component.dart b/packages/flame/lib/src/components/navigator_component.dart index 15972446ace..6ee10540773 100644 --- a/packages/flame/lib/src/components/navigator_component.dart +++ b/packages/flame/lib/src/components/navigator_component.dart @@ -71,7 +71,7 @@ class NavigatorComponent extends Component { /// Returns the route that is below the current topmost route, if it exists. Route? get previousRoute { - return _routeStack.length >=2 ? _routeStack[_routeStack.length - 2] : null; + return _routeStack.length >= 2 ? _routeStack[_routeStack.length - 2] : null; } /// Puts the route [name] on top of the navigation stack. diff --git a/packages/flame/lib/src/components/route.dart b/packages/flame/lib/src/components/route.dart index 6223ee808ec..8bdba091177 100644 --- a/packages/flame/lib/src/components/route.dart +++ b/packages/flame/lib/src/components/route.dart @@ -4,8 +4,8 @@ import 'package:flame/src/components/component.dart'; import 'package:flame/src/components/mixins/parent_is_a.dart'; import 'package:flame/src/components/navigator_component.dart'; import 'package:flame/src/components/position_component.dart'; -import 'package:flame/src/effects/effect.dart'; import 'package:flame/src/decorator.dart'; +import 'package:flame/src/effects/effect.dart'; import 'package:meta/meta.dart'; import 'package:vector_math/vector_math_64.dart'; @@ -118,16 +118,16 @@ class Route extends PositionComponent with ParentIsA { /// Additional visual effect that may be applied to the page during rendering. Decorator? _renderEffect; - /// Invoked by the [NavigatorComponent] when this route is pushed to the top of the - /// navigation stack. + /// Invoked by the [NavigatorComponent] when this route is pushed to the top + /// of the navigation stack. @internal void didPush(Route? previousRoute) { _page ??= build()..addToParent(this); onPush(previousRoute); } - /// Invoked by the [NavigatorComponent] when this route is popped off the top of the - /// navigation stack. + /// Invoked by the [NavigatorComponent] when this route is popped off the top + /// of the navigation stack. @internal void didPop(Route previousRoute) => onPop(previousRoute); From 7d9655ae05268530990da76eb5110e87c2e1b170 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sat, 2 Jul 2022 15:49:17 -0700 Subject: [PATCH 42/54] decorators now have a simple apply() method --- packages/flame/lib/src/components/route.dart | 4 +--- packages/flame/lib/src/decorator.dart | 24 ++++++++++++++++++-- packages/flame/lib/src/paint_decorator.dart | 12 +++++----- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/packages/flame/lib/src/components/route.dart b/packages/flame/lib/src/components/route.dart index 8bdba091177..28aed791a44 100644 --- a/packages/flame/lib/src/components/route.dart +++ b/packages/flame/lib/src/components/route.dart @@ -135,9 +135,7 @@ class Route extends PositionComponent with ParentIsA { void renderTree(Canvas canvas) { if (isRendered) { if (_renderEffect != null) { - final useCanvas = _renderEffect!.preprocessCanvas(canvas); - super.renderTree(useCanvas); - _renderEffect!.postprocessCanvas(canvas); + _renderEffect!.apply(super.renderTree, canvas); } else { super.renderTree(canvas); } diff --git a/packages/flame/lib/src/decorator.dart b/packages/flame/lib/src/decorator.dart index f93a0aa0fd3..9ded9cb84ac 100644 --- a/packages/flame/lib/src/decorator.dart +++ b/packages/flame/lib/src/decorator.dart @@ -1,6 +1,26 @@ import 'dart:ui'; +import 'package:flame/src/paint_decorator.dart'; + +/// [Decorator] is an abstract class that encapsulates a particular visual +/// effect that should apply to drawing commands wrapped by this class. +/// +/// The simplest way to apply a [Decorator] to a component is to override its +/// `renderTree` method like this: +/// ```dart +/// @override +/// void renderTree(Canvas canvas) { +/// decorator.apply(super.renderTree, canvas); +/// } +/// ``` +/// +/// The following implementations are available: +/// - [PaintDecorator] abstract class Decorator { - Canvas preprocessCanvas(Canvas canvas); - void postprocessCanvas(Canvas canvas); + /// Applies visual effect while [draw]ing on the [canvas]. + /// + /// A no-op decorator would simply call `draw(canvas)`. Any other non-trivial + /// decorator can transform the canvas before drawing, or perform any other + /// adjustment. + void apply(void Function(Canvas) draw, Canvas canvas); } diff --git a/packages/flame/lib/src/paint_decorator.dart b/packages/flame/lib/src/paint_decorator.dart index 85c865c28df..d1f7e619f1a 100644 --- a/packages/flame/lib/src/paint_decorator.dart +++ b/packages/flame/lib/src/paint_decorator.dart @@ -2,6 +2,10 @@ import 'dart:ui'; import 'package:flame/src/decorator.dart'; +/// [PaintDecorator] applies a paint filter to a group of drawing operations. +/// +/// Specifically, the following flavors are available: +/// - class PaintDecorator extends Decorator { final _paint = Paint(); @@ -20,13 +24,9 @@ class PaintDecorator extends Decorator { } @override - Canvas preprocessCanvas(Canvas canvas) { + void apply(void Function(Canvas) draw, Canvas canvas) { canvas.saveLayer(null, _paint); - return canvas; - } - - @override - void postprocessCanvas(Canvas canvas) { + draw(canvas); canvas.restore(); } } From 9b2a2ba862dcb47fd4b7bc79975741104916d250 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sun, 3 Jul 2022 23:33:16 -0700 Subject: [PATCH 43/54] tests for NavigatorComponent --- .../src/components/navigator_component.dart | 28 ++++- .../components/navigator_component_test.dart | 108 ++++++++++++++++++ 2 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 packages/flame/test/components/navigator_component_test.dart diff --git a/packages/flame/lib/src/components/navigator_component.dart b/packages/flame/lib/src/components/navigator_component.dart index 6ee10540773..5d18dc56cf9 100644 --- a/packages/flame/lib/src/components/navigator_component.dart +++ b/packages/flame/lib/src/components/navigator_component.dart @@ -1,5 +1,6 @@ import 'package:flame/src/components/component.dart'; import 'package:flame/src/components/route.dart'; +import 'package:meta/meta.dart'; /// [NavigatorComponent] handles transitions between multiple pages of a game. /// @@ -47,10 +48,13 @@ class NavigatorComponent extends Component { /// removed with a delay, there could be temporary discrepancies between this /// list and the list of children. final List _routeStack = []; + @visibleForTesting + List get stack => _routeStack; /// The map of all routes known to the Navigator, each route will have a /// unique name. This map is initialized in the constructor; in addition, any /// routes produced by the [_routeFactories] will also be cached here. + Map get routes => _routes; final Map _routes; /// Set of functions that are able to resolve routes dynamically. @@ -99,10 +103,18 @@ class NavigatorComponent extends Component { _adjustRoutesVisibility(); } - /// Puts an unnamed [route] on top of the navigation stack. + /// Puts a new [route] on top of the navigation stack. + /// + /// The route may also be given a [name], in which case it will be cached in + /// the [routes] map under this name (if there was already a route with the + /// same name, it will be overwritten). /// /// The method calls [Route.didPush] for this new route after it is added. - void pushRoute(Route route) { + void pushRoute(Route route, {String name = ''}) { + route.name = name; + if (name.isNotEmpty) { + _routes[name] = route; + } add(route); _routeStack.add(route); _adjustRoutesOrder(); @@ -128,6 +140,18 @@ class NavigatorComponent extends Component { route.removeFromParent(); } + /// Removes routes from the top of the stack until reaches the route with the + /// given [name]. + /// + /// After this method, the route [name] will be at the top of the stack. An + /// error will occur if this method is run when there is no route with the + /// specified name on the stack. + void popUntilNamed(String name) { + while (currentRoute.name != name) { + pop(); + } + } + /// Attempts to resolve the route with the given [name] by searching in the /// [_routes] map, or invoking one of the [_routeFactories], or, lastly, /// falling back to the [onUnknownRoute] function. If none of these methods diff --git a/packages/flame/test/components/navigator_component_test.dart b/packages/flame/test/components/navigator_component_test.dart new file mode 100644 index 00000000000..8f3fd621052 --- /dev/null +++ b/packages/flame/test/components/navigator_component_test.dart @@ -0,0 +1,108 @@ +import 'package:flame/components.dart'; +import 'package:flame/game.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('NavigatorComponent', () { + testWithFlameGame('normal route pushing/popping', (game) async { + final navigator = NavigatorComponent( + routes: { + 'A': Route(builder: _ComponentA.new), + 'B': Route(builder: _ComponentB.new), + 'C': Route(builder: _ComponentC.new), + }, + initialRoute: 'A', + ); + game.add(navigator); + await game.ready(); + + expect(navigator.currentRoute.name, 'A'); + expect(navigator.currentRoute.children.length, 1); + expect(navigator.currentRoute.children.first, isA<_ComponentA>()); + + navigator.pushNamed('B'); + await game.ready(); + expect(navigator.currentRoute.name, 'B'); + expect(navigator.currentRoute.children.length, 1); + expect(navigator.currentRoute.children.first, isA<_ComponentB>()); + + navigator.pop(); + await game.ready(); + expect(navigator.currentRoute.name, 'A'); + + navigator.pushRoute(Route(builder: _ComponentD.new), name: 'Dee'); + await game.ready(); + expect(navigator.currentRoute.name, 'Dee'); + expect(navigator.currentRoute.children.length, 1); + expect(navigator.currentRoute.children.first, isA<_ComponentD>()); + }); + + testWithFlameGame('Route factories', (game) async { + final navigator = NavigatorComponent( + initialRoute: 'initial', + routes: {'initial': Route(builder: _ComponentD.new)}, + routeFactories: { + 'a': (arg) => Route(builder: _ComponentA.new), + 'b': (arg) => Route(builder: _ComponentB.new), + }, + ); + game.add(navigator); + await game.ready(); + + expect(navigator.currentRoute.name, 'initial'); + + navigator.pushNamed('a/101'); + await game.ready(); + expect(navigator.currentRoute.name, 'a/101'); + expect(navigator.currentRoute.children.first, isA<_ComponentA>()); + + navigator.pushNamed('b/something'); + await game.ready(); + expect(navigator.currentRoute.name, 'b/something'); + expect(navigator.currentRoute.children.first, isA<_ComponentB>()); + }); + + testWithFlameGame('onUnknownRoute', (game) async { + final navigator = NavigatorComponent( + initialRoute: 'home', + routes: {'home': Route(builder: _ComponentA.new)}, + onUnknownRoute: (name) => Route(builder: _ComponentD.new), + )..addToParent(game); + await game.ready(); + + navigator.pushNamed('hello'); + await game.ready(); + expect(navigator.currentRoute.name, 'hello'); + expect(navigator.currentRoute.children.first, isA<_ComponentD>()); + }); + + testWithFlameGame('default unknown route handling', (game) async { + final navigator = NavigatorComponent( + initialRoute: 'home', + routes: {'home': Route(builder: _ComponentA.new)}, + )..addToParent(game); + await game.ready(); + + expect( + () => navigator.pushNamed('hello'), + throwsA( + predicate( + (e) => + e is ArgumentError && + e.message == + 'Route "hello" could not be resolved by the Navigator', + ), + ), + ); + }); + }); +} + +class _ComponentA extends Component {} + +class _ComponentB extends Component {} + +class _ComponentC extends Component {} + +class _ComponentD extends Component {} From 0eadcecc6cbd2ceb4e76b3f7f1ddd1b0c4ed00ff Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Sun, 3 Jul 2022 23:51:21 -0700 Subject: [PATCH 44/54] more tests --- .../components/navigator_component_test.dart | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/packages/flame/test/components/navigator_component_test.dart b/packages/flame/test/components/navigator_component_test.dart index 8f3fd621052..5afa97e874e 100644 --- a/packages/flame/test/components/navigator_component_test.dart +++ b/packages/flame/test/components/navigator_component_test.dart @@ -17,6 +17,7 @@ void main() { game.add(navigator); await game.ready(); + expect(navigator.routes.length, 3); expect(navigator.currentRoute.name, 'A'); expect(navigator.currentRoute.children.length, 1); expect(navigator.currentRoute.children.first, isA<_ComponentA>()); @@ -26,16 +27,20 @@ void main() { expect(navigator.currentRoute.name, 'B'); expect(navigator.currentRoute.children.length, 1); expect(navigator.currentRoute.children.first, isA<_ComponentB>()); + expect(navigator.stack.length, 2); navigator.pop(); await game.ready(); expect(navigator.currentRoute.name, 'A'); + expect(navigator.stack.length, 1); navigator.pushRoute(Route(builder: _ComponentD.new), name: 'Dee'); await game.ready(); + expect(navigator.routes.length, 4); expect(navigator.currentRoute.name, 'Dee'); expect(navigator.currentRoute.children.length, 1); expect(navigator.currentRoute.children.first, isA<_ComponentD>()); + expect(navigator.stack.length, 2); }); testWithFlameGame('Route factories', (game) async { @@ -63,6 +68,37 @@ void main() { expect(navigator.currentRoute.children.first, isA<_ComponentB>()); }); + testWithFlameGame('push an existing route', (game) async { + final navigator = NavigatorComponent( + routes: { + 'A': Route(builder: _ComponentA.new), + 'B': Route(builder: _ComponentB.new), + 'C': Route(builder: _ComponentC.new), + }, + initialRoute: 'A', + )..addToParent(game); + await game.ready(); + + navigator.pushNamed('A'); + await game.ready(); + expect(navigator.stack.length, 1); + + navigator.pushNamed('B'); + navigator.pushNamed('C'); + expect(navigator.stack.length, 3); + + navigator.pushNamed('B'); + expect(navigator.stack.length, 3); + navigator.pushNamed('A'); + expect(navigator.stack.length, 3); + + await game.ready(); + expect(navigator.children.length, 3); + expect((navigator.children.elementAt(0) as Route).name, 'C'); + expect((navigator.children.elementAt(1) as Route).name, 'B'); + expect((navigator.children.elementAt(2) as Route).name, 'A'); + }); + testWithFlameGame('onUnknownRoute', (game) async { final navigator = NavigatorComponent( initialRoute: 'home', @@ -96,6 +132,19 @@ void main() { ), ); }); + + testWithFlameGame('cannot pop last remaining route', (game) async { + final navigator = NavigatorComponent( + initialRoute: 'home', + routes: {'home': Route(builder: _ComponentA.new)}, + )..addToParent(game); + await game.ready(); + + expect( + navigator.pop, + failsAssert('Cannot pop the last route from the Navigator'), + ); + }); }); } From 25496a6b0c9355922a6988b8ed39e8e73cc02eff Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Mon, 4 Jul 2022 00:02:18 -0700 Subject: [PATCH 45/54] one more test --- .../components/navigator_component_test.dart | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/flame/test/components/navigator_component_test.dart b/packages/flame/test/components/navigator_component_test.dart index 5afa97e874e..021f52a2d7c 100644 --- a/packages/flame/test/components/navigator_component_test.dart +++ b/packages/flame/test/components/navigator_component_test.dart @@ -145,6 +145,31 @@ void main() { failsAssert('Cannot pop the last route from the Navigator'), ); }); + + testWithFlameGame('popUntilNamed', (game) async { + final navigator = NavigatorComponent( + routes: { + 'A': Route(builder: _ComponentA.new), + 'B': Route(builder: _ComponentB.new), + 'C': Route(builder: _ComponentC.new), + }, + initialRoute: 'A', + ); + game.add(navigator); + await game.ready(); + + navigator.pushNamed('B'); + navigator.pushNamed('C'); + await game.ready(); + expect(navigator.stack.length, 3); + expect(navigator.children.length, 3); + + navigator.popUntilNamed('A'); + await game.ready(); + expect(navigator.stack.length, 1); + expect(navigator.children.length, 1); + expect(navigator.currentRoute.name, 'A'); + }); }); } From 12bd10260b4f31a3f9969fb58bac96faabcb754f Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Mon, 4 Jul 2022 01:10:48 -0700 Subject: [PATCH 46/54] tests for Route --- packages/flame/.min_coverage | 2 +- .../test/_goldens/route_decorator_removed.png | Bin 0 -> 342 bytes packages/flame/test/_goldens/route_opaque.png | Bin 0 -> 364 bytes .../flame/test/_goldens/route_transparent.png | Bin 0 -> 365 bytes .../test/_goldens/route_with_decorators.png | Bin 0 -> 375 bytes .../flame/test/components/route_test.dart | 335 ++++++++++++++++++ 6 files changed, 336 insertions(+), 1 deletion(-) create mode 100644 packages/flame/test/_goldens/route_decorator_removed.png create mode 100644 packages/flame/test/_goldens/route_opaque.png create mode 100644 packages/flame/test/_goldens/route_transparent.png create mode 100644 packages/flame/test/_goldens/route_with_decorators.png create mode 100644 packages/flame/test/components/route_test.dart diff --git a/packages/flame/.min_coverage b/packages/flame/.min_coverage index 9ddeb3cd0c7..c7a703d9234 100644 --- a/packages/flame/.min_coverage +++ b/packages/flame/.min_coverage @@ -1 +1 @@ -76.5 +78.0 diff --git a/packages/flame/test/_goldens/route_decorator_removed.png b/packages/flame/test/_goldens/route_decorator_removed.png new file mode 100644 index 0000000000000000000000000000000000000000..eb1a5a07a25c54bb7f518c499f2c615fa4ce0bfb GIT binary patch literal 342 zcmeAS@N?(olHy`uVBq!ia0vp^DImy|Y5=M6U{13b) zNH`|3bPB4t!5Gh~e;3sk9JRmkZthQ*7+i9&Ga`+P_t-Yc%L^%c^e7lM9pOMQikS{9 XVm=Wb@jYrYSY|-s2L_ zyL-;>7k@BY!LaEFhmf*I4~%if$hsou_}prnd%M;L7wf6GO^|R*V(AndDvV|SSDydJ g{M;Oy%bzi=d&RcMtA2wtFz^^WUHx3vIVCg!0JWoYNB{r; literal 0 HcmV?d00001 diff --git a/packages/flame/test/components/route_test.dart b/packages/flame/test/components/route_test.dart new file mode 100644 index 00000000000..75d5a02c650 --- /dev/null +++ b/packages/flame/test/components/route_test.dart @@ -0,0 +1,335 @@ +import 'dart:ui'; + +import 'package:flame/components.dart'; +import 'package:flame/game.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Route', () { + testWithFlameGame('Route without a builder', (game) async { + final navigator = NavigatorComponent( + initialRoute: 'start', + routes: { + 'start': Route(builder: Component.new), + 'new': Route(), + }, + )..addToParent(game); + await game.ready(); + expect( + () => navigator.pushNamed('new'), + failsAssert( + 'Either provide `builder` in the constructor, or override the ' + 'build() method', + ), + ); + }); + + testWithFlameGame('onPush and onPop methods', (game) async { + var onPushCalled = 0; + var onPopCalled = 0; + var buildCalled = 0; + Route? previousRoute; + final navigator = NavigatorComponent( + initialRoute: 'start', + routes: { + 'start': Route(builder: Component.new), + 'new': CustomRoute( + onPush: (self, prevRoute) { + onPushCalled++; + previousRoute = prevRoute; + }, + onPop: (self, prevRoute) { + onPopCalled++; + previousRoute = prevRoute; + }, + build: (self) { + buildCalled++; + return PositionComponent(); + }, + ), + }, + )..addToParent(game); + await game.ready(); + + navigator.pushNamed('new'); + expect(buildCalled, 1); + await game.ready(); + expect(navigator.currentRoute.name, 'new'); + expect(navigator.currentRoute.children.first, isA()); + expect(onPushCalled, 1); + expect(onPopCalled, 0); + expect(previousRoute!.name, 'start'); + + previousRoute = null; + navigator.pop(); + await game.ready(); + expect(onPushCalled, 1); + expect(onPopCalled, 1); + expect(previousRoute!.name, 'start'); + expect(navigator.currentRoute.name, 'start'); + }); + + testWithFlameGame('Stop and resume time', (game) async { + final navigator = NavigatorComponent( + initialRoute: 'start', + routes: { + 'start': Route(builder: _TimerComponent.new), + 'pause': CustomRoute( + builder: Component.new, + onPush: (self, route) => route?.stopTime(), + onPop: (self, route) => route.resumeTime(), + ), + }, + )..addToParent(game); + await game.ready(); + + final timer = navigator.currentRoute.children.first as _TimerComponent; + expect(timer.elapsedTime, 0); + + game.update(1); + expect(timer.elapsedTime, 1); + + navigator.pushNamed('pause'); + await game.ready(); + expect(navigator.currentRoute.name, 'pause'); + expect(navigator.previousRoute!.timeSpeed, 0); + + game.update(10); + expect(timer.elapsedTime, 1); + + navigator.previousRoute!.timeSpeed = 0.1; + game.update(10); + expect(timer.elapsedTime, 2); + + navigator.pop(); + await game.ready(); + expect(navigator.currentRoute.name, 'start'); + expect(navigator.currentRoute.timeSpeed, 1); + + game.update(10); + expect(timer.elapsedTime, 12); + }); + + testGolden( + 'Rendering of opaque routes', + (game) async { + final navigator = NavigatorComponent( + initialRoute: 'initial', + routes: { + 'initial': Route( + builder: () => _ColoredComponent( + color: const Color(0xFFFF0000), + size: Vector2.all(100), + ), + ), + 'green': Route( + builder: () => _ColoredComponent( + color: const Color(0x8800FF00), + position: Vector2.all(10), + size: Vector2.all(80), + ), + ), + }, + )..addToParent(game); + await game.ready(); + navigator.pushNamed('green'); + }, + size: Vector2(100, 100), + goldenFile: '../_goldens/route_opaque.png', + ); + + testGolden( + 'Rendering of transparent routes', + (game) async { + final navigator = NavigatorComponent( + initialRoute: 'initial', + routes: { + 'initial': Route( + builder: () => _ColoredComponent( + color: const Color(0xFFFF0000), + size: Vector2.all(100), + ), + ), + 'green': Route( + builder: () => _ColoredComponent( + color: const Color(0x8800FF00), + position: Vector2.all(10), + size: Vector2.all(80), + ), + transparent: true, + ), + }, + )..addToParent(game); + await game.ready(); + navigator.pushNamed('green'); + }, + size: Vector2(100, 100), + goldenFile: '../_goldens/route_transparent.png', + ); + + testGolden( + 'Rendering of transparent routes with decorators', + (game) async { + final navigator = NavigatorComponent( + initialRoute: 'initial', + routes: { + 'initial': Route( + builder: () => _ColoredComponent( + color: const Color(0xFFFF0000), + size: Vector2.all(100), + ), + ), + 'green': CustomRoute( + builder: () => _ColoredComponent( + color: const Color(0x8800FF00), + position: Vector2.all(10), + size: Vector2.all(80), + ), + transparent: true, + onPush: (self, route) => + route!.addRenderEffect(PaintDecorator()..addDesaturation()), + onPop: (self, route) => route.removeRenderEffect(), + ), + }, + )..addToParent(game); + await game.ready(); + navigator.pushNamed('green'); + }, + size: Vector2(100, 100), + goldenFile: '../_goldens/route_with_decorators.png', + ); + + testGolden( + 'Rendering effect can be removed', + (game) async { + final navigator = NavigatorComponent( + initialRoute: 'initial', + routes: { + 'initial': Route( + builder: () => _ColoredComponent( + color: const Color(0xFFFF0000), + size: Vector2.all(100), + ), + ), + 'green': CustomRoute( + builder: () => _ColoredComponent( + color: const Color(0x8800FF00), + position: Vector2.all(10), + size: Vector2.all(80), + ), + transparent: true, + onPush: (self, route) => + route!.addRenderEffect(PaintDecorator()..addDesaturation()), + onPop: (self, route) => route.removeRenderEffect(), + ), + }, + )..addToParent(game); + await game.ready(); + navigator.pushNamed('green'); + await game.ready(); + navigator.pop(); + }, + size: Vector2(100, 100), + goldenFile: '../_goldens/route_decorator_removed.png', + ); + + testWithFlameGame('componentsAtPoint for opaque route', (game) async { + final navigator = NavigatorComponent( + initialRoute: 'initial', + routes: { + 'initial': Route( + builder: () => PositionComponent(size: Vector2.all(100)), + ), + 'new': Route( + builder: () => PositionComponent(size: Vector2.all(100)), + ), + }, + )..addToParent(game); + await game.ready(); + + navigator.pushNamed('new'); + await game.ready(); + expect( + game.componentsAtPoint(Vector2(50, 50)).toList(), + [navigator.currentRoute.children.first, game], + ); + }); + + testWithFlameGame('componentsAtPoint for transparent route', (game) async { + final navigator = NavigatorComponent( + initialRoute: 'initial', + routes: { + 'initial': Route( + builder: () => PositionComponent(size: Vector2.all(100)), + ), + 'new': Route( + builder: () => PositionComponent(size: Vector2.all(100)), + transparent: true, + ), + }, + )..addToParent(game); + await game.ready(); + + navigator.pushNamed('new'); + await game.ready(); + expect( + game.componentsAtPoint(Vector2(50, 50)).toList(), + [ + navigator.currentRoute.children.first, + navigator.previousRoute!.children.first, + game, + ], + ); + }); + }); +} + +class CustomRoute extends Route { + CustomRoute({ + super.builder, + super.transparent, + void Function(Route, Route?)? onPush, + void Function(Route, Route)? onPop, + Component Function(Route)? build, + }) : _onPush = onPush, + _onPop = onPop, + _build = build; + + final void Function(Route, Route?)? _onPush; + final void Function(Route, Route)? _onPop; + final Component Function(Route)? _build; + + @override + void onPush(Route? route) => _onPush?.call(this, route); + + @override + void onPop(Route route) => _onPop?.call(this, route); + + @override + Component build() => _build?.call(this) ?? super.build(); +} + +class _TimerComponent extends Component { + double elapsedTime = 0; + + @override + void update(double dt) { + elapsedTime += dt; + } +} + +class _ColoredComponent extends PositionComponent { + _ColoredComponent({ + required Color color, + super.position, + super.size, + }) : _paint = Paint()..color = color; + + final Paint _paint; + + @override + void render(Canvas canvas) { + canvas.drawRect(size.toRect(), _paint); + } +} From 0ae89b210891844a08238afd6add2006f9f1145a Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Mon, 4 Jul 2022 01:14:53 -0700 Subject: [PATCH 47/54] update the Decorator API --- doc/flame/examples/lib/navigator.dart | 4 +-- packages/flame/lib/game.dart | 4 +-- packages/flame/lib/src/components/route.dart | 2 +- .../lib/src/{ => rendering}/decorator.dart | 2 +- .../src/{ => rendering}/paint_decorator.dart | 31 ++++++++++++------- .../flame/test/components/route_test.dart | 4 +-- 6 files changed, 27 insertions(+), 20 deletions(-) rename packages/flame/lib/src/{ => rendering}/decorator.dart (93%) rename packages/flame/lib/src/{ => rendering}/paint_decorator.dart (50%) diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index e033d3e9446..6ad9ea03572 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -367,9 +367,7 @@ class PauseRoute extends Route { previousRoute! ..stopTime() ..addRenderEffect( - PaintDecorator() - ..addDesaturation(opacity: 0.5) - ..addBlur(3.0), + PaintDecorator.grayscale(opacity: 0.5)..addBlur(3.0), ); } diff --git a/packages/flame/lib/game.dart b/packages/flame/lib/game.dart index 453ba5d71be..fd9c8197dd9 100644 --- a/packages/flame/lib/game.dart +++ b/packages/flame/lib/game.dart @@ -3,7 +3,7 @@ export 'src/collisions/has_collision_detection.dart'; export 'src/components/navigator_component.dart' show NavigatorComponent; export 'src/components/route.dart' show Route; -export 'src/decorator.dart' show Decorator; +export 'src/rendering/decorator.dart' show Decorator; export 'src/extensions/vector2.dart'; export 'src/game/camera/camera.dart'; export 'src/game/camera/viewport.dart'; @@ -18,5 +18,5 @@ export 'src/game/mixins/single_game_instance.dart'; export 'src/game/notifying_vector2.dart'; export 'src/game/projector.dart'; export 'src/game/transform2d.dart'; -export 'src/paint_decorator.dart' show PaintDecorator; +export 'src/rendering/paint_decorator.dart' show PaintDecorator; export 'src/text/text_paint.dart'; diff --git a/packages/flame/lib/src/components/route.dart b/packages/flame/lib/src/components/route.dart index 28aed791a44..edf91040d51 100644 --- a/packages/flame/lib/src/components/route.dart +++ b/packages/flame/lib/src/components/route.dart @@ -4,7 +4,7 @@ import 'package:flame/src/components/component.dart'; import 'package:flame/src/components/mixins/parent_is_a.dart'; import 'package:flame/src/components/navigator_component.dart'; import 'package:flame/src/components/position_component.dart'; -import 'package:flame/src/decorator.dart'; +import 'package:flame/src/rendering/decorator.dart'; import 'package:flame/src/effects/effect.dart'; import 'package:meta/meta.dart'; import 'package:vector_math/vector_math_64.dart'; diff --git a/packages/flame/lib/src/decorator.dart b/packages/flame/lib/src/rendering/decorator.dart similarity index 93% rename from packages/flame/lib/src/decorator.dart rename to packages/flame/lib/src/rendering/decorator.dart index 9ded9cb84ac..52e8a266c74 100644 --- a/packages/flame/lib/src/decorator.dart +++ b/packages/flame/lib/src/rendering/decorator.dart @@ -1,6 +1,6 @@ import 'dart:ui'; -import 'package:flame/src/paint_decorator.dart'; +import 'package:flame/src/rendering/paint_decorator.dart'; /// [Decorator] is an abstract class that encapsulates a particular visual /// effect that should apply to drawing commands wrapped by this class. diff --git a/packages/flame/lib/src/paint_decorator.dart b/packages/flame/lib/src/rendering/paint_decorator.dart similarity index 50% rename from packages/flame/lib/src/paint_decorator.dart rename to packages/flame/lib/src/rendering/paint_decorator.dart index d1f7e619f1a..8fb7a0a0799 100644 --- a/packages/flame/lib/src/paint_decorator.dart +++ b/packages/flame/lib/src/rendering/paint_decorator.dart @@ -1,26 +1,35 @@ import 'dart:ui'; -import 'package:flame/src/decorator.dart'; +import 'package:flame/src/rendering/decorator.dart'; /// [PaintDecorator] applies a paint filter to a group of drawing operations. /// -/// Specifically, the following flavors are available: -/// - +/// Specifically, the following filters are available: +/// - [PaintDecorator.blur] adds Gaussian blur to the image, as if your vision +/// became blurry and out of focus; +/// - [PaintDecorator.tinted] tints the picture with the specified color, as if +/// looking through a colored glass; +/// - [PaintDecorator.grayscale] removes all color from the picture, as if it +/// was a black-and-white photo. class PaintDecorator extends Decorator { - final _paint = Paint(); - - void addBlur(double amount, [double? amountY]) { - _paint.imageFilter = - ImageFilter.blur(sigmaX: amount, sigmaY: amountY ?? amount); + PaintDecorator.blur(double amount, [double? amountY]) { + addBlur(amount, amountY ?? amount); } - void addTint(Color color) { + PaintDecorator.tinted(Color color) { _paint.colorFilter = ColorFilter.mode(color, BlendMode.srcATop); } - void addDesaturation({double opacity = 1.0}) { - _paint.blendMode = BlendMode.luminosity; + PaintDecorator.grayscale({double opacity = 1.0}) { _paint.color = Color.fromARGB((255 * opacity).toInt(), 0, 0, 0); + _paint.blendMode = BlendMode.luminosity; + } + + final _paint = Paint(); + + void addBlur(double amount, [double? amountY]) { + _paint.imageFilter = + ImageFilter.blur(sigmaX: amount, sigmaY: amountY ?? amount); } @override diff --git a/packages/flame/test/components/route_test.dart b/packages/flame/test/components/route_test.dart index 75d5a02c650..9ab2a08520b 100644 --- a/packages/flame/test/components/route_test.dart +++ b/packages/flame/test/components/route_test.dart @@ -188,7 +188,7 @@ void main() { ), transparent: true, onPush: (self, route) => - route!.addRenderEffect(PaintDecorator()..addDesaturation()), + route!.addRenderEffect(PaintDecorator.grayscale()), onPop: (self, route) => route.removeRenderEffect(), ), }, @@ -220,7 +220,7 @@ void main() { ), transparent: true, onPush: (self, route) => - route!.addRenderEffect(PaintDecorator()..addDesaturation()), + route!.addRenderEffect(PaintDecorator.grayscale()), onPop: (self, route) => route.removeRenderEffect(), ), }, From 5f5f9e487b5962f0ceff4f0bde83213c1be174a0 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Mon, 4 Jul 2022 01:16:36 -0700 Subject: [PATCH 48/54] update exports --- doc/flame/examples/lib/navigator.dart | 1 + packages/flame/lib/game.dart | 2 -- packages/flame/lib/rendering.dart | 2 ++ packages/flame/test/components/route_test.dart | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 packages/flame/lib/rendering.dart diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index 6ad9ea03572..62dabebbe2b 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -2,6 +2,7 @@ import 'package:flame/components.dart'; import 'package:flame/effects.dart'; import 'package:flame/experimental.dart'; import 'package:flame/game.dart'; +import 'package:flame/rendering.dart'; import 'package:flutter/rendering.dart'; class NavigatorGame extends FlameGame with HasTappableComponents { diff --git a/packages/flame/lib/game.dart b/packages/flame/lib/game.dart index fd9c8197dd9..933a4a81412 100644 --- a/packages/flame/lib/game.dart +++ b/packages/flame/lib/game.dart @@ -3,7 +3,6 @@ export 'src/collisions/has_collision_detection.dart'; export 'src/components/navigator_component.dart' show NavigatorComponent; export 'src/components/route.dart' show Route; -export 'src/rendering/decorator.dart' show Decorator; export 'src/extensions/vector2.dart'; export 'src/game/camera/camera.dart'; export 'src/game/camera/viewport.dart'; @@ -18,5 +17,4 @@ export 'src/game/mixins/single_game_instance.dart'; export 'src/game/notifying_vector2.dart'; export 'src/game/projector.dart'; export 'src/game/transform2d.dart'; -export 'src/rendering/paint_decorator.dart' show PaintDecorator; export 'src/text/text_paint.dart'; diff --git a/packages/flame/lib/rendering.dart b/packages/flame/lib/rendering.dart new file mode 100644 index 00000000000..057934c9391 --- /dev/null +++ b/packages/flame/lib/rendering.dart @@ -0,0 +1,2 @@ +export 'src/rendering/decorator.dart' show Decorator; +export 'src/rendering/paint_decorator.dart' show PaintDecorator; diff --git a/packages/flame/test/components/route_test.dart b/packages/flame/test/components/route_test.dart index 9ab2a08520b..dd329293c7c 100644 --- a/packages/flame/test/components/route_test.dart +++ b/packages/flame/test/components/route_test.dart @@ -2,6 +2,7 @@ import 'dart:ui'; import 'package:flame/components.dart'; import 'package:flame/game.dart'; +import 'package:flame/rendering.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; From e8318b9ee69e802b5ac00d9f0da7feaa9e374d0a Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Mon, 4 Jul 2022 01:20:14 -0700 Subject: [PATCH 49/54] format --- packages/flame/lib/src/components/route.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flame/lib/src/components/route.dart b/packages/flame/lib/src/components/route.dart index edf91040d51..13e0923abe7 100644 --- a/packages/flame/lib/src/components/route.dart +++ b/packages/flame/lib/src/components/route.dart @@ -4,8 +4,8 @@ import 'package:flame/src/components/component.dart'; import 'package:flame/src/components/mixins/parent_is_a.dart'; import 'package:flame/src/components/navigator_component.dart'; import 'package:flame/src/components/position_component.dart'; -import 'package:flame/src/rendering/decorator.dart'; import 'package:flame/src/effects/effect.dart'; +import 'package:flame/src/rendering/decorator.dart'; import 'package:meta/meta.dart'; import 'package:vector_math/vector_math_64.dart'; From f8218a3b857c205be4e289ba098640aea40645a3 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Tue, 9 Aug 2022 20:31:57 -0700 Subject: [PATCH 50/54] rename NavigatorComponent -> RouterComponent --- doc/flame/examples/lib/main.dart | 2 +- doc/flame/examples/lib/navigator.dart | 28 +++++++++---------- doc/flame/flame.md | 2 +- doc/flame/{navigator.md => router.md} | 28 +++++++++---------- packages/flame/lib/game.dart | 2 +- packages/flame/lib/src/components/route.dart | 18 ++++++------ ...r_component.dart => router_component.dart} | 17 ++++++----- .../components/navigator_component_test.dart | 14 +++++----- .../flame/test/components/route_test.dart | 18 ++++++------ 9 files changed, 64 insertions(+), 65 deletions(-) rename doc/flame/{navigator.md => router.md} (61%) rename packages/flame/lib/src/components/{navigator_component.dart => router_component.dart} (93%) diff --git a/doc/flame/examples/lib/main.dart b/doc/flame/examples/lib/main.dart index 4efb3ad22d9..a7e94b35c63 100644 --- a/doc/flame/examples/lib/main.dart +++ b/doc/flame/examples/lib/main.dart @@ -23,7 +23,7 @@ void main() { 'decorator_shadow3d': DecoratorShadowGame.new, 'decorator_tint': DecoratorTintGame.new, 'drag_events': DragEventsGame.new, - 'navigator': NavigatorGame.new, + 'router': RouterGame.new, 'tap_events': TapEventsGame.new, }; final game = routes[page]?.call(); diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/navigator.dart index 62dabebbe2b..24f79cb71f9 100644 --- a/doc/flame/examples/lib/navigator.dart +++ b/doc/flame/examples/lib/navigator.dart @@ -5,13 +5,13 @@ import 'package:flame/game.dart'; import 'package:flame/rendering.dart'; import 'package:flutter/rendering.dart'; -class NavigatorGame extends FlameGame with HasTappableComponents { - late final NavigatorComponent navigator; +class RouterGame extends FlameGame with HasTappableComponents { + late final RouterComponent router; @override Future onLoad() async { add( - navigator = NavigatorComponent( + router = RouterComponent( routes: { 'splash': Route(builder: SplashScreenPage.new), 'home': Route(builder: StartPage.new), @@ -26,7 +26,7 @@ class NavigatorGame extends FlameGame with HasTappableComponents { } class SplashScreenPage extends Component - with TapCallbacks, HasGameRef { + with TapCallbacks, HasGameRef { @override Future onLoad() async { addAll([ @@ -49,10 +49,10 @@ class SplashScreenPage extends Component bool containsLocalPoint(Vector2 point) => true; @override - void onTapUp(TapUpEvent event) => gameRef.navigator.pushNamed('home'); + void onTapUp(TapUpEvent event) => gameRef.router.pushNamed('home'); } -class StartPage extends Component with HasGameRef { +class StartPage extends Component with HasGameRef { StartPage() { addAll([ _logo = TextComponent( @@ -68,13 +68,13 @@ class StartPage extends Component with HasGameRef { ), _button1 = RoundedButton( text: 'Level 1', - action: () => gameRef.navigator.pushNamed('level1'), + action: () => gameRef.router.pushNamed('level1'), color: const Color(0xffadde6c), borderColor: const Color(0xffedffab), ), _button2 = RoundedButton( text: 'Level 2', - action: () => gameRef.navigator.pushNamed('level2'), + action: () => gameRef.router.pushNamed('level2'), color: const Color(0xffdebe6c), borderColor: const Color(0xfffff4c7), ), @@ -203,7 +203,7 @@ abstract class SimpleButton extends PositionComponent with TapCallbacks { } } -class BackButton extends SimpleButton with HasGameRef { +class BackButton extends SimpleButton with HasGameRef { BackButton() : super( Path() @@ -216,10 +216,10 @@ class BackButton extends SimpleButton with HasGameRef { ); @override - void action() => gameRef.navigator.pop(); + void action() => gameRef.router.pop(); } -class PauseButton extends SimpleButton with HasGameRef { +class PauseButton extends SimpleButton with HasGameRef { PauseButton() : super( Path() @@ -230,7 +230,7 @@ class PauseButton extends SimpleButton with HasGameRef { position: Vector2(60, 10), ); @override - void action() => gameRef.navigator.pushNamed('pause'); + void action() => gameRef.router.pushNamed('pause'); } class Level1Page extends Component { @@ -380,7 +380,7 @@ class PauseRoute extends Route { } } -class PausePage extends Component with TapCallbacks, HasGameRef { +class PausePage extends Component with TapCallbacks, HasGameRef { @override Future onLoad() async { final game = findGame()!; @@ -407,5 +407,5 @@ class PausePage extends Component with TapCallbacks, HasGameRef { bool containsLocalPoint(Vector2 point) => true; @override - void onTapUp(TapUpEvent event) => gameRef.navigator.pop(); + void onTapUp(TapUpEvent event) => gameRef.router.pop(); } diff --git a/doc/flame/flame.md b/doc/flame/flame.md index 631ee54dc60..0cf6d52721a 100644 --- a/doc/flame/flame.md +++ b/doc/flame/flame.md @@ -3,7 +3,7 @@ ```{toctree} File structure GameWidget -Navigator +Router Game loop Components Platforms diff --git a/doc/flame/navigator.md b/doc/flame/router.md similarity index 61% rename from doc/flame/navigator.md rename to doc/flame/router.md index 790376244dd..5b287c1cfd5 100644 --- a/doc/flame/navigator.md +++ b/doc/flame/router.md @@ -1,30 +1,30 @@ ```{flutter-app} :sources: ../flame/examples -:page: navigator +:page: router :show: widget code infobox -This example app shows the use of the `Navigator` component to move across multiple screens within +This example app shows the use of the `RouterComponent` to move across multiple screens within the game. In addition, the "pause" button stops time and applies visual effects to the content of the page below it. ``` -# Navigator +# RouterComponent -The **Navigator** is a component whose job is to manage navigation across multiple screens within -the game. It is similar in spirit to Flutter's [Navigator][Flutter Navigator] class, except that it -works with Flame components instead of Flutter widgets. +The **RouterComponent**'s job is to manage navigation across multiple screens within the game. It is +similar in spirit to Flutter's [Navigator][Flutter Navigator] class, except that it works with Flame +components instead of Flutter widgets. A typical game will usually consists of multiple pages: the splash screen, the starting menu page, -the settings page, credits, the main game page, several pop-ups, etc. The `Navigator` will organize +the settings page, credits, the main game page, several pop-ups, etc. The router will organize all these destinations and allow you to transition between them. -Internally, the `Navigator` contains a stack of routes. When you request it to show a route, it -will be placed on top of all other pages in the stack. Later you can `popPage()` to remove the -topmost page from the stack. The pages of the Navigator are addressed by their unique names. +Internally, the `RouterComponent` contains a stack of routes. When you request it to show a route, +it will be placed on top of all other pages in the stack. Later you can `popPage()` to remove the +topmost page from the stack. The pages of the router are addressed by their unique names. -Each Page in the Navigator can be either transparent or opaque. If a page is opaque, then the pages +Each page in the router can be either transparent or opaque. If a page is opaque, then the pages below it in the stack are not rendered, and do not receive pointer events (such as taps or drags). On the contrary, if a page is transparent, then the page below it will be rendered and receive events normally. Such transparent pages are useful for implementing modal dialogs, inventory or @@ -33,12 +33,12 @@ dialogue UIs, etc. Usage example: ```dart class MyGame extends FlameGame { - late final Navigator navigator; + late final RouterComponent navigator; @override Future onLoad() async { add( - navigator = Navigator( + router = RouterComponent( routes: { 'home': Route(builder: HomePage()), 'level-selector': Route(builder: LevelSelectorPage()), @@ -58,7 +58,7 @@ class MyGame extends FlameGame { ## Route The **Route** component holds information about the content of a particular page. `Route`s are -mounted as children to the `Navigator`. +mounted as children to the `RouterComponent`. The main property of a `Route` is its `builder` -- the function that creates the component with the content of its page. diff --git a/packages/flame/lib/game.dart b/packages/flame/lib/game.dart index 933a4a81412..efcbb0a9961 100644 --- a/packages/flame/lib/game.dart +++ b/packages/flame/lib/game.dart @@ -1,7 +1,7 @@ /// {@canonicalFor text.TextPaint} /// {@canonicalFor text.TextRenderer} export 'src/collisions/has_collision_detection.dart'; -export 'src/components/navigator_component.dart' show NavigatorComponent; +export 'src/components/router_component.dart' show RouterComponent; export 'src/components/route.dart' show Route; export 'src/extensions/vector2.dart'; export 'src/game/camera/camera.dart'; diff --git a/packages/flame/lib/src/components/route.dart b/packages/flame/lib/src/components/route.dart index 13e0923abe7..4a98434e685 100644 --- a/packages/flame/lib/src/components/route.dart +++ b/packages/flame/lib/src/components/route.dart @@ -2,7 +2,7 @@ import 'dart:ui'; import 'package:flame/src/components/component.dart'; import 'package:flame/src/components/mixins/parent_is_a.dart'; -import 'package:flame/src/components/navigator_component.dart'; +import 'package:flame/src/components/router_component.dart'; import 'package:flame/src/components/position_component.dart'; import 'package:flame/src/effects/effect.dart'; import 'package:flame/src/rendering/decorator.dart'; @@ -22,8 +22,8 @@ import 'package:vector_math/vector_math_64.dart'; /// try to be as lightweight as possible. In particular, a [Route] should avoid /// any potentially costly initialization operations. /// -/// Routes are managed by the [NavigatorComponent] component. -class Route extends PositionComponent with ParentIsA { +/// Routes are managed by the [RouterComponent] component. +class Route extends PositionComponent with ParentIsA { Route({ Component Function()? builder, this.transparent = false, @@ -35,7 +35,7 @@ class Route extends PositionComponent with ParentIsA { /// route underneath doesn't need to be rendered. final bool transparent; - /// The name of the route (set by the [NavigatorComponent]). + /// The name of the route (set by the [RouterComponent]). late final String name; /// The function that will be invoked in order to build the page component @@ -44,7 +44,7 @@ class Route extends PositionComponent with ParentIsA { final Component Function()? _builder; /// This method is invoked when the route is pushed on top of the - /// [NavigatorComponent]'s stack. + /// [RouterComponent]'s stack. /// /// The argument for this method is the route that was on top of the stack /// before the push. It can be null if the current route becomes the first @@ -52,7 +52,7 @@ class Route extends PositionComponent with ParentIsA { void onPush(Route? previousRoute) {} /// This method is called when the route is popped off the top of the - /// [NavigatorComponent]'s stack. + /// [RouterComponent]'s stack. /// /// The argument for this method is the route that will become the next /// top-most route on the stack. Thus, the argument in [onPop] will always be @@ -107,7 +107,7 @@ class Route extends PositionComponent with ParentIsA { /// If true, the page must be rendered normally. If false, the page should /// not be rendered, because it is completely obscured by another route which - /// is on top of it. This variable is set by the [NavigatorComponent]. + /// is on top of it. This variable is set by the [RouterComponent]. @internal bool isRendered = true; @@ -118,7 +118,7 @@ class Route extends PositionComponent with ParentIsA { /// Additional visual effect that may be applied to the page during rendering. Decorator? _renderEffect; - /// Invoked by the [NavigatorComponent] when this route is pushed to the top + /// Invoked by the [RouterComponent] when this route is pushed to the top /// of the navigation stack. @internal void didPush(Route? previousRoute) { @@ -126,7 +126,7 @@ class Route extends PositionComponent with ParentIsA { onPush(previousRoute); } - /// Invoked by the [NavigatorComponent] when this route is popped off the top + /// Invoked by the [RouterComponent] when this route is popped off the top /// of the navigation stack. @internal void didPop(Route previousRoute) => onPop(previousRoute); diff --git a/packages/flame/lib/src/components/navigator_component.dart b/packages/flame/lib/src/components/router_component.dart similarity index 93% rename from packages/flame/lib/src/components/navigator_component.dart rename to packages/flame/lib/src/components/router_component.dart index 5d18dc56cf9..bfd1d2c3893 100644 --- a/packages/flame/lib/src/components/navigator_component.dart +++ b/packages/flame/lib/src/components/router_component.dart @@ -2,19 +2,18 @@ import 'package:flame/src/components/component.dart'; import 'package:flame/src/components/route.dart'; import 'package:meta/meta.dart'; -/// [NavigatorComponent] handles transitions between multiple pages of a game. +/// [RouterComponent] handles transitions between multiple pages of a game. /// /// The term **page** is used descriptively here: it is any full-screen (or /// partial-screen) component. For example: a starting page, a settings page, /// the main game world page, and so on. A page can also be any individual piece /// of UI, such as a confirmation dialog box, or a user inventory pop-up. /// -/// The navigator doesn't handle the pages directly -- instead, it operates -/// a stack of [Route]s. Each route, in turn, manages a single page component. -/// However, routes are "lazy": they will only build their pages when they -/// become active. +/// The router doesn't handle the pages directly -- instead, it operates a stack +/// of [Route]s. Each route, in turn, manages a single page component. However, +/// routes are lazy: they will only build their pages when they become active. /// -/// Internally, the Navigator maintains a stack of Routes. In the beginning, +/// Internally, the router maintains a stack of Routes. In the beginning, /// the stack will contain the [initialRoute]. New routes can be added via the /// [pushNamed] method, and removed with [pop]. However, the stack must be /// kept non-empty: it is an error to attempt to remove the only remaining route @@ -25,9 +24,9 @@ import 'package:meta/meta.dart'; /// transparent or opaque. An opaque route prevents all routes below it from /// rendering, and also stops pointer events. In addition, routes are able to /// stop or slow down time for the pages that they control, or to apply visual -/// effects to those pages. -class NavigatorComponent extends Component { - NavigatorComponent({ +/// effects (via decorators) to those pages. +class RouterComponent extends Component { + RouterComponent({ required this.initialRoute, required Map routes, Map? routeFactories, diff --git a/packages/flame/test/components/navigator_component_test.dart b/packages/flame/test/components/navigator_component_test.dart index 021f52a2d7c..af769fd443a 100644 --- a/packages/flame/test/components/navigator_component_test.dart +++ b/packages/flame/test/components/navigator_component_test.dart @@ -6,7 +6,7 @@ import 'package:flutter_test/flutter_test.dart'; void main() { group('NavigatorComponent', () { testWithFlameGame('normal route pushing/popping', (game) async { - final navigator = NavigatorComponent( + final navigator = RouterComponent( routes: { 'A': Route(builder: _ComponentA.new), 'B': Route(builder: _ComponentB.new), @@ -44,7 +44,7 @@ void main() { }); testWithFlameGame('Route factories', (game) async { - final navigator = NavigatorComponent( + final navigator = RouterComponent( initialRoute: 'initial', routes: {'initial': Route(builder: _ComponentD.new)}, routeFactories: { @@ -69,7 +69,7 @@ void main() { }); testWithFlameGame('push an existing route', (game) async { - final navigator = NavigatorComponent( + final navigator = RouterComponent( routes: { 'A': Route(builder: _ComponentA.new), 'B': Route(builder: _ComponentB.new), @@ -100,7 +100,7 @@ void main() { }); testWithFlameGame('onUnknownRoute', (game) async { - final navigator = NavigatorComponent( + final navigator = RouterComponent( initialRoute: 'home', routes: {'home': Route(builder: _ComponentA.new)}, onUnknownRoute: (name) => Route(builder: _ComponentD.new), @@ -114,7 +114,7 @@ void main() { }); testWithFlameGame('default unknown route handling', (game) async { - final navigator = NavigatorComponent( + final navigator = RouterComponent( initialRoute: 'home', routes: {'home': Route(builder: _ComponentA.new)}, )..addToParent(game); @@ -134,7 +134,7 @@ void main() { }); testWithFlameGame('cannot pop last remaining route', (game) async { - final navigator = NavigatorComponent( + final navigator = RouterComponent( initialRoute: 'home', routes: {'home': Route(builder: _ComponentA.new)}, )..addToParent(game); @@ -147,7 +147,7 @@ void main() { }); testWithFlameGame('popUntilNamed', (game) async { - final navigator = NavigatorComponent( + final navigator = RouterComponent( routes: { 'A': Route(builder: _ComponentA.new), 'B': Route(builder: _ComponentB.new), diff --git a/packages/flame/test/components/route_test.dart b/packages/flame/test/components/route_test.dart index dd329293c7c..cb595a32037 100644 --- a/packages/flame/test/components/route_test.dart +++ b/packages/flame/test/components/route_test.dart @@ -9,7 +9,7 @@ import 'package:flutter_test/flutter_test.dart'; void main() { group('Route', () { testWithFlameGame('Route without a builder', (game) async { - final navigator = NavigatorComponent( + final navigator = RouterComponent( initialRoute: 'start', routes: { 'start': Route(builder: Component.new), @@ -31,7 +31,7 @@ void main() { var onPopCalled = 0; var buildCalled = 0; Route? previousRoute; - final navigator = NavigatorComponent( + final navigator = RouterComponent( initialRoute: 'start', routes: { 'start': Route(builder: Component.new), @@ -72,7 +72,7 @@ void main() { }); testWithFlameGame('Stop and resume time', (game) async { - final navigator = NavigatorComponent( + final navigator = RouterComponent( initialRoute: 'start', routes: { 'start': Route(builder: _TimerComponent.new), @@ -115,7 +115,7 @@ void main() { testGolden( 'Rendering of opaque routes', (game) async { - final navigator = NavigatorComponent( + final navigator = RouterComponent( initialRoute: 'initial', routes: { 'initial': Route( @@ -143,7 +143,7 @@ void main() { testGolden( 'Rendering of transparent routes', (game) async { - final navigator = NavigatorComponent( + final navigator = RouterComponent( initialRoute: 'initial', routes: { 'initial': Route( @@ -172,7 +172,7 @@ void main() { testGolden( 'Rendering of transparent routes with decorators', (game) async { - final navigator = NavigatorComponent( + final navigator = RouterComponent( initialRoute: 'initial', routes: { 'initial': Route( @@ -204,7 +204,7 @@ void main() { testGolden( 'Rendering effect can be removed', (game) async { - final navigator = NavigatorComponent( + final navigator = RouterComponent( initialRoute: 'initial', routes: { 'initial': Route( @@ -236,7 +236,7 @@ void main() { ); testWithFlameGame('componentsAtPoint for opaque route', (game) async { - final navigator = NavigatorComponent( + final navigator = RouterComponent( initialRoute: 'initial', routes: { 'initial': Route( @@ -258,7 +258,7 @@ void main() { }); testWithFlameGame('componentsAtPoint for transparent route', (game) async { - final navigator = NavigatorComponent( + final navigator = RouterComponent( initialRoute: 'initial', routes: { 'initial': Route( From e5c180e69fc5278e0610189edd86bf321f303ddc Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Tue, 9 Aug 2022 20:33:51 -0700 Subject: [PATCH 51/54] analyze --- packages/flame/lib/game.dart | 2 +- packages/flame/lib/src/components/route.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/flame/lib/game.dart b/packages/flame/lib/game.dart index efcbb0a9961..583831b0524 100644 --- a/packages/flame/lib/game.dart +++ b/packages/flame/lib/game.dart @@ -1,8 +1,8 @@ /// {@canonicalFor text.TextPaint} /// {@canonicalFor text.TextRenderer} export 'src/collisions/has_collision_detection.dart'; -export 'src/components/router_component.dart' show RouterComponent; export 'src/components/route.dart' show Route; +export 'src/components/router_component.dart' show RouterComponent; export 'src/extensions/vector2.dart'; export 'src/game/camera/camera.dart'; export 'src/game/camera/viewport.dart'; diff --git a/packages/flame/lib/src/components/route.dart b/packages/flame/lib/src/components/route.dart index 4a98434e685..fd5d721e035 100644 --- a/packages/flame/lib/src/components/route.dart +++ b/packages/flame/lib/src/components/route.dart @@ -2,8 +2,8 @@ import 'dart:ui'; import 'package:flame/src/components/component.dart'; import 'package:flame/src/components/mixins/parent_is_a.dart'; -import 'package:flame/src/components/router_component.dart'; import 'package:flame/src/components/position_component.dart'; +import 'package:flame/src/components/router_component.dart'; import 'package:flame/src/effects/effect.dart'; import 'package:flame/src/rendering/decorator.dart'; import 'package:meta/meta.dart'; From 92536c8aee366c5dee98f9ca798bb94563a14262 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Tue, 9 Aug 2022 20:37:50 -0700 Subject: [PATCH 52/54] more renames --- doc/flame/examples/lib/main.dart | 2 +- .../lib/{navigator.dart => router.dart} | 0 ...t_test.dart => router_component_test.dart} | 124 +++++++++--------- 3 files changed, 63 insertions(+), 63 deletions(-) rename doc/flame/examples/lib/{navigator.dart => router.dart} (100%) rename packages/flame/test/components/{navigator_component_test.dart => router_component_test.dart} (51%) diff --git a/doc/flame/examples/lib/main.dart b/doc/flame/examples/lib/main.dart index a7e94b35c63..a9893fcd3d7 100644 --- a/doc/flame/examples/lib/main.dart +++ b/doc/flame/examples/lib/main.dart @@ -6,7 +6,7 @@ import 'package:doc_flame_examples/decorator_rotate3d.dart'; import 'package:doc_flame_examples/decorator_shadow3d.dart'; import 'package:doc_flame_examples/decorator_tint.dart'; import 'package:doc_flame_examples/drag_events.dart'; -import 'package:doc_flame_examples/navigator.dart'; +import 'package:doc_flame_examples/router.dart'; import 'package:doc_flame_examples/tap_events.dart'; import 'package:flame/game.dart'; import 'package:flutter/widgets.dart'; diff --git a/doc/flame/examples/lib/navigator.dart b/doc/flame/examples/lib/router.dart similarity index 100% rename from doc/flame/examples/lib/navigator.dart rename to doc/flame/examples/lib/router.dart diff --git a/packages/flame/test/components/navigator_component_test.dart b/packages/flame/test/components/router_component_test.dart similarity index 51% rename from packages/flame/test/components/navigator_component_test.dart rename to packages/flame/test/components/router_component_test.dart index af769fd443a..ec1a75c1a70 100644 --- a/packages/flame/test/components/navigator_component_test.dart +++ b/packages/flame/test/components/router_component_test.dart @@ -4,9 +4,9 @@ import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - group('NavigatorComponent', () { + group('RouterComponent', () { testWithFlameGame('normal route pushing/popping', (game) async { - final navigator = RouterComponent( + final router = RouterComponent( routes: { 'A': Route(builder: _ComponentA.new), 'B': Route(builder: _ComponentB.new), @@ -14,37 +14,37 @@ void main() { }, initialRoute: 'A', ); - game.add(navigator); + game.add(router); await game.ready(); - expect(navigator.routes.length, 3); - expect(navigator.currentRoute.name, 'A'); - expect(navigator.currentRoute.children.length, 1); - expect(navigator.currentRoute.children.first, isA<_ComponentA>()); + expect(router.routes.length, 3); + expect(router.currentRoute.name, 'A'); + expect(router.currentRoute.children.length, 1); + expect(router.currentRoute.children.first, isA<_ComponentA>()); - navigator.pushNamed('B'); + router.pushNamed('B'); await game.ready(); - expect(navigator.currentRoute.name, 'B'); - expect(navigator.currentRoute.children.length, 1); - expect(navigator.currentRoute.children.first, isA<_ComponentB>()); - expect(navigator.stack.length, 2); + expect(router.currentRoute.name, 'B'); + expect(router.currentRoute.children.length, 1); + expect(router.currentRoute.children.first, isA<_ComponentB>()); + expect(router.stack.length, 2); - navigator.pop(); + router.pop(); await game.ready(); - expect(navigator.currentRoute.name, 'A'); - expect(navigator.stack.length, 1); + expect(router.currentRoute.name, 'A'); + expect(router.stack.length, 1); - navigator.pushRoute(Route(builder: _ComponentD.new), name: 'Dee'); + router.pushRoute(Route(builder: _ComponentD.new), name: 'Dee'); await game.ready(); - expect(navigator.routes.length, 4); - expect(navigator.currentRoute.name, 'Dee'); - expect(navigator.currentRoute.children.length, 1); - expect(navigator.currentRoute.children.first, isA<_ComponentD>()); - expect(navigator.stack.length, 2); + expect(router.routes.length, 4); + expect(router.currentRoute.name, 'Dee'); + expect(router.currentRoute.children.length, 1); + expect(router.currentRoute.children.first, isA<_ComponentD>()); + expect(router.stack.length, 2); }); testWithFlameGame('Route factories', (game) async { - final navigator = RouterComponent( + final router = RouterComponent( initialRoute: 'initial', routes: {'initial': Route(builder: _ComponentD.new)}, routeFactories: { @@ -52,24 +52,24 @@ void main() { 'b': (arg) => Route(builder: _ComponentB.new), }, ); - game.add(navigator); + game.add(router); await game.ready(); - expect(navigator.currentRoute.name, 'initial'); + expect(router.currentRoute.name, 'initial'); - navigator.pushNamed('a/101'); + router.pushNamed('a/101'); await game.ready(); - expect(navigator.currentRoute.name, 'a/101'); - expect(navigator.currentRoute.children.first, isA<_ComponentA>()); + expect(router.currentRoute.name, 'a/101'); + expect(router.currentRoute.children.first, isA<_ComponentA>()); - navigator.pushNamed('b/something'); + router.pushNamed('b/something'); await game.ready(); - expect(navigator.currentRoute.name, 'b/something'); - expect(navigator.currentRoute.children.first, isA<_ComponentB>()); + expect(router.currentRoute.name, 'b/something'); + expect(router.currentRoute.children.first, isA<_ComponentB>()); }); testWithFlameGame('push an existing route', (game) async { - final navigator = RouterComponent( + final router = RouterComponent( routes: { 'A': Route(builder: _ComponentA.new), 'B': Route(builder: _ComponentB.new), @@ -79,49 +79,49 @@ void main() { )..addToParent(game); await game.ready(); - navigator.pushNamed('A'); + router.pushNamed('A'); await game.ready(); - expect(navigator.stack.length, 1); + expect(router.stack.length, 1); - navigator.pushNamed('B'); - navigator.pushNamed('C'); - expect(navigator.stack.length, 3); + router.pushNamed('B'); + router.pushNamed('C'); + expect(router.stack.length, 3); - navigator.pushNamed('B'); - expect(navigator.stack.length, 3); - navigator.pushNamed('A'); - expect(navigator.stack.length, 3); + router.pushNamed('B'); + expect(router.stack.length, 3); + router.pushNamed('A'); + expect(router.stack.length, 3); await game.ready(); - expect(navigator.children.length, 3); - expect((navigator.children.elementAt(0) as Route).name, 'C'); - expect((navigator.children.elementAt(1) as Route).name, 'B'); - expect((navigator.children.elementAt(2) as Route).name, 'A'); + expect(router.children.length, 3); + expect((router.children.elementAt(0) as Route).name, 'C'); + expect((router.children.elementAt(1) as Route).name, 'B'); + expect((router.children.elementAt(2) as Route).name, 'A'); }); testWithFlameGame('onUnknownRoute', (game) async { - final navigator = RouterComponent( + final router = RouterComponent( initialRoute: 'home', routes: {'home': Route(builder: _ComponentA.new)}, onUnknownRoute: (name) => Route(builder: _ComponentD.new), )..addToParent(game); await game.ready(); - navigator.pushNamed('hello'); + router.pushNamed('hello'); await game.ready(); - expect(navigator.currentRoute.name, 'hello'); - expect(navigator.currentRoute.children.first, isA<_ComponentD>()); + expect(router.currentRoute.name, 'hello'); + expect(router.currentRoute.children.first, isA<_ComponentD>()); }); testWithFlameGame('default unknown route handling', (game) async { - final navigator = RouterComponent( + final router = RouterComponent( initialRoute: 'home', routes: {'home': Route(builder: _ComponentA.new)}, )..addToParent(game); await game.ready(); expect( - () => navigator.pushNamed('hello'), + () => router.pushNamed('hello'), throwsA( predicate( (e) => @@ -134,20 +134,20 @@ void main() { }); testWithFlameGame('cannot pop last remaining route', (game) async { - final navigator = RouterComponent( + final router = RouterComponent( initialRoute: 'home', routes: {'home': Route(builder: _ComponentA.new)}, )..addToParent(game); await game.ready(); expect( - navigator.pop, + router.pop, failsAssert('Cannot pop the last route from the Navigator'), ); }); testWithFlameGame('popUntilNamed', (game) async { - final navigator = RouterComponent( + final router = RouterComponent( routes: { 'A': Route(builder: _ComponentA.new), 'B': Route(builder: _ComponentB.new), @@ -155,20 +155,20 @@ void main() { }, initialRoute: 'A', ); - game.add(navigator); + game.add(router); await game.ready(); - navigator.pushNamed('B'); - navigator.pushNamed('C'); + router.pushNamed('B'); + router.pushNamed('C'); await game.ready(); - expect(navigator.stack.length, 3); - expect(navigator.children.length, 3); + expect(router.stack.length, 3); + expect(router.children.length, 3); - navigator.popUntilNamed('A'); + router.popUntilNamed('A'); await game.ready(); - expect(navigator.stack.length, 1); - expect(navigator.children.length, 1); - expect(navigator.currentRoute.name, 'A'); + expect(router.stack.length, 1); + expect(router.children.length, 1); + expect(router.currentRoute.name, 'A'); }); }); } From 35027343a877b772edcd9eeae23b2c12457189cb Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Tue, 9 Aug 2022 20:42:44 -0700 Subject: [PATCH 53/54] some more renames --- doc/flame/examples/lib/router.dart | 2 +- doc/flame/router.md | 4 +- .../lib/src/components/router_component.dart | 10 +-- .../flame/test/components/route_test.dart | 66 +++++++++---------- .../components/router_component_test.dart | 4 +- 5 files changed, 43 insertions(+), 43 deletions(-) diff --git a/doc/flame/examples/lib/router.dart b/doc/flame/examples/lib/router.dart index 24f79cb71f9..de8664d182c 100644 --- a/doc/flame/examples/lib/router.dart +++ b/doc/flame/examples/lib/router.dart @@ -32,7 +32,7 @@ class SplashScreenPage extends Component addAll([ Background(const Color(0xff282828)), TextBoxComponent( - text: '[Navigator demo]', + text: '[Router demo]', textRenderer: TextPaint( style: const TextStyle( color: Color(0x66ffffff), diff --git a/doc/flame/router.md b/doc/flame/router.md index 5b287c1cfd5..ae046eb2e8f 100644 --- a/doc/flame/router.md +++ b/doc/flame/router.md @@ -13,7 +13,7 @@ the page below it. # RouterComponent The **RouterComponent**'s job is to manage navigation across multiple screens within the game. It is -similar in spirit to Flutter's [Navigator][Flutter Navigator] class, except that it works with Flame +similar in spirit to Flutter's [Navigator][Flutter Navigator] class, except that it works with Flame components instead of Flutter widgets. A typical game will usually consists of multiple pages: the splash screen, the starting menu page, @@ -33,7 +33,7 @@ dialogue UIs, etc. Usage example: ```dart class MyGame extends FlameGame { - late final RouterComponent navigator; + late final RouterComponent router; @override Future onLoad() async { diff --git a/packages/flame/lib/src/components/router_component.dart b/packages/flame/lib/src/components/router_component.dart index bfd1d2c3893..c53e1870fbd 100644 --- a/packages/flame/lib/src/components/router_component.dart +++ b/packages/flame/lib/src/components/router_component.dart @@ -42,7 +42,7 @@ class RouterComponent extends Component { /// The stack of all currently active routes. This stack must not be empty /// (it will be populated with the [initialRoute] in the beginning). /// - /// The routes in this list are also added to the Navigator as child + /// The routes in this list are also added to the Router as child /// components. However, due to the fact that children are usually added or /// removed with a delay, there could be temporary discrepancies between this /// list and the list of children. @@ -50,7 +50,7 @@ class RouterComponent extends Component { @visibleForTesting List get stack => _routeStack; - /// The map of all routes known to the Navigator, each route will have a + /// The map of all routes known to the Router, each route will have a /// unique name. This map is initialized in the constructor; in addition, any /// routes produced by the [_routeFactories] will also be cached here. Map get routes => _routes; @@ -122,7 +122,7 @@ class RouterComponent extends Component { } /// Removes the topmost route from the stack, and also removes it as a child - /// of the Navigator. + /// of the Router. /// /// The method calls [Route.didPop] for the route that was removed. /// @@ -130,7 +130,7 @@ class RouterComponent extends Component { void pop() { assert( _routeStack.length > 1, - 'Cannot pop the last route from the Navigator', + 'Cannot pop the last route from the Router', ); final route = _routeStack.removeLast(); _adjustRoutesOrder(); @@ -174,7 +174,7 @@ class RouterComponent extends Component { if (onUnknownRoute != null) { return onUnknownRoute!(name)..name = name; } - throw ArgumentError('Route "$name" could not be resolved by the Navigator'); + throw ArgumentError('Route "$name" could not be resolved by the Router'); } void _adjustRoutesOrder() { diff --git a/packages/flame/test/components/route_test.dart b/packages/flame/test/components/route_test.dart index cb595a32037..3724c679a08 100644 --- a/packages/flame/test/components/route_test.dart +++ b/packages/flame/test/components/route_test.dart @@ -9,7 +9,7 @@ import 'package:flutter_test/flutter_test.dart'; void main() { group('Route', () { testWithFlameGame('Route without a builder', (game) async { - final navigator = RouterComponent( + final router = RouterComponent( initialRoute: 'start', routes: { 'start': Route(builder: Component.new), @@ -18,7 +18,7 @@ void main() { )..addToParent(game); await game.ready(); expect( - () => navigator.pushNamed('new'), + () => router.pushNamed('new'), failsAssert( 'Either provide `builder` in the constructor, or override the ' 'build() method', @@ -31,7 +31,7 @@ void main() { var onPopCalled = 0; var buildCalled = 0; Route? previousRoute; - final navigator = RouterComponent( + final router = RouterComponent( initialRoute: 'start', routes: { 'start': Route(builder: Component.new), @@ -53,26 +53,26 @@ void main() { )..addToParent(game); await game.ready(); - navigator.pushNamed('new'); + router.pushNamed('new'); expect(buildCalled, 1); await game.ready(); - expect(navigator.currentRoute.name, 'new'); - expect(navigator.currentRoute.children.first, isA()); + expect(router.currentRoute.name, 'new'); + expect(router.currentRoute.children.first, isA()); expect(onPushCalled, 1); expect(onPopCalled, 0); expect(previousRoute!.name, 'start'); previousRoute = null; - navigator.pop(); + router.pop(); await game.ready(); expect(onPushCalled, 1); expect(onPopCalled, 1); expect(previousRoute!.name, 'start'); - expect(navigator.currentRoute.name, 'start'); + expect(router.currentRoute.name, 'start'); }); testWithFlameGame('Stop and resume time', (game) async { - final navigator = RouterComponent( + final router = RouterComponent( initialRoute: 'start', routes: { 'start': Route(builder: _TimerComponent.new), @@ -85,28 +85,28 @@ void main() { )..addToParent(game); await game.ready(); - final timer = navigator.currentRoute.children.first as _TimerComponent; + final timer = router.currentRoute.children.first as _TimerComponent; expect(timer.elapsedTime, 0); game.update(1); expect(timer.elapsedTime, 1); - navigator.pushNamed('pause'); + router.pushNamed('pause'); await game.ready(); - expect(navigator.currentRoute.name, 'pause'); - expect(navigator.previousRoute!.timeSpeed, 0); + expect(router.currentRoute.name, 'pause'); + expect(router.previousRoute!.timeSpeed, 0); game.update(10); expect(timer.elapsedTime, 1); - navigator.previousRoute!.timeSpeed = 0.1; + router.previousRoute!.timeSpeed = 0.1; game.update(10); expect(timer.elapsedTime, 2); - navigator.pop(); + router.pop(); await game.ready(); - expect(navigator.currentRoute.name, 'start'); - expect(navigator.currentRoute.timeSpeed, 1); + expect(router.currentRoute.name, 'start'); + expect(router.currentRoute.timeSpeed, 1); game.update(10); expect(timer.elapsedTime, 12); @@ -115,7 +115,7 @@ void main() { testGolden( 'Rendering of opaque routes', (game) async { - final navigator = RouterComponent( + final router = RouterComponent( initialRoute: 'initial', routes: { 'initial': Route( @@ -134,7 +134,7 @@ void main() { }, )..addToParent(game); await game.ready(); - navigator.pushNamed('green'); + router.pushNamed('green'); }, size: Vector2(100, 100), goldenFile: '../_goldens/route_opaque.png', @@ -143,7 +143,7 @@ void main() { testGolden( 'Rendering of transparent routes', (game) async { - final navigator = RouterComponent( + final router = RouterComponent( initialRoute: 'initial', routes: { 'initial': Route( @@ -163,7 +163,7 @@ void main() { }, )..addToParent(game); await game.ready(); - navigator.pushNamed('green'); + router.pushNamed('green'); }, size: Vector2(100, 100), goldenFile: '../_goldens/route_transparent.png', @@ -172,7 +172,7 @@ void main() { testGolden( 'Rendering of transparent routes with decorators', (game) async { - final navigator = RouterComponent( + final router = RouterComponent( initialRoute: 'initial', routes: { 'initial': Route( @@ -195,7 +195,7 @@ void main() { }, )..addToParent(game); await game.ready(); - navigator.pushNamed('green'); + router.pushNamed('green'); }, size: Vector2(100, 100), goldenFile: '../_goldens/route_with_decorators.png', @@ -204,7 +204,7 @@ void main() { testGolden( 'Rendering effect can be removed', (game) async { - final navigator = RouterComponent( + final router = RouterComponent( initialRoute: 'initial', routes: { 'initial': Route( @@ -227,16 +227,16 @@ void main() { }, )..addToParent(game); await game.ready(); - navigator.pushNamed('green'); + router.pushNamed('green'); await game.ready(); - navigator.pop(); + router.pop(); }, size: Vector2(100, 100), goldenFile: '../_goldens/route_decorator_removed.png', ); testWithFlameGame('componentsAtPoint for opaque route', (game) async { - final navigator = RouterComponent( + final router = RouterComponent( initialRoute: 'initial', routes: { 'initial': Route( @@ -249,16 +249,16 @@ void main() { )..addToParent(game); await game.ready(); - navigator.pushNamed('new'); + router.pushNamed('new'); await game.ready(); expect( game.componentsAtPoint(Vector2(50, 50)).toList(), - [navigator.currentRoute.children.first, game], + [router.currentRoute.children.first, game], ); }); testWithFlameGame('componentsAtPoint for transparent route', (game) async { - final navigator = RouterComponent( + final router = RouterComponent( initialRoute: 'initial', routes: { 'initial': Route( @@ -272,13 +272,13 @@ void main() { )..addToParent(game); await game.ready(); - navigator.pushNamed('new'); + router.pushNamed('new'); await game.ready(); expect( game.componentsAtPoint(Vector2(50, 50)).toList(), [ - navigator.currentRoute.children.first, - navigator.previousRoute!.children.first, + router.currentRoute.children.first, + router.previousRoute!.children.first, game, ], ); diff --git a/packages/flame/test/components/router_component_test.dart b/packages/flame/test/components/router_component_test.dart index ec1a75c1a70..880348f717b 100644 --- a/packages/flame/test/components/router_component_test.dart +++ b/packages/flame/test/components/router_component_test.dart @@ -127,7 +127,7 @@ void main() { (e) => e is ArgumentError && e.message == - 'Route "hello" could not be resolved by the Navigator', + 'Route "hello" could not be resolved by the Router', ), ), ); @@ -142,7 +142,7 @@ void main() { expect( router.pop, - failsAssert('Cannot pop the last route from the Navigator'), + failsAssert('Cannot pop the last route from the Router'), ); }); From 0cca29b60cd5c82c7a165c029e26170478c91835 Mon Sep 17 00:00:00 2001 From: Pasha Stetsenko Date: Tue, 9 Aug 2022 20:53:07 -0700 Subject: [PATCH 54/54] Route's builder parameter is now unnamed --- doc/flame/examples/lib/router.dart | 10 +++--- doc/flame/router.md | 6 ++-- packages/flame/lib/src/components/route.dart | 4 +-- .../flame/test/components/route_test.dart | 33 +++++++++--------- .../components/router_component_test.dart | 34 +++++++++---------- 5 files changed, 44 insertions(+), 43 deletions(-) diff --git a/doc/flame/examples/lib/router.dart b/doc/flame/examples/lib/router.dart index de8664d182c..f1c7d953fd7 100644 --- a/doc/flame/examples/lib/router.dart +++ b/doc/flame/examples/lib/router.dart @@ -13,10 +13,10 @@ class RouterGame extends FlameGame with HasTappableComponents { add( router = RouterComponent( routes: { - 'splash': Route(builder: SplashScreenPage.new), - 'home': Route(builder: StartPage.new), - 'level1': Route(builder: Level1Page.new), - 'level2': Route(builder: Level2Page.new), + 'splash': Route(SplashScreenPage.new), + 'home': Route(StartPage.new), + 'level1': Route(Level1Page.new), + 'level2': Route(Level2Page.new), 'pause': PauseRoute(), }, initialRoute: 'splash', @@ -361,7 +361,7 @@ class Orbit extends PositionComponent { } class PauseRoute extends Route { - PauseRoute() : super(builder: PausePage.new, transparent: true); + PauseRoute() : super(PausePage.new, transparent: true); @override void onPush(Route? previousRoute) { diff --git a/doc/flame/router.md b/doc/flame/router.md index ae046eb2e8f..bc600029889 100644 --- a/doc/flame/router.md +++ b/doc/flame/router.md @@ -40,9 +40,9 @@ class MyGame extends FlameGame { add( router = RouterComponent( routes: { - 'home': Route(builder: HomePage()), - 'level-selector': Route(builder: LevelSelectorPage()), - 'settings': Route(builder: SettingsPage(), transparent: true), + 'home': Route(HomePage.new), + 'level-selector': Route(LevelSelectorPage.new), + 'settings': Route(SettingsPage.new, transparent: true), 'pause': PauseRoute(), }, initialRoute: 'home', diff --git a/packages/flame/lib/src/components/route.dart b/packages/flame/lib/src/components/route.dart index fd5d721e035..9cb00a8b6a8 100644 --- a/packages/flame/lib/src/components/route.dart +++ b/packages/flame/lib/src/components/route.dart @@ -24,8 +24,8 @@ import 'package:vector_math/vector_math_64.dart'; /// /// Routes are managed by the [RouterComponent] component. class Route extends PositionComponent with ParentIsA { - Route({ - Component Function()? builder, + Route( + Component Function()? builder, { this.transparent = false, }) : _builder = builder; diff --git a/packages/flame/test/components/route_test.dart b/packages/flame/test/components/route_test.dart index 3724c679a08..f3903110f1c 100644 --- a/packages/flame/test/components/route_test.dart +++ b/packages/flame/test/components/route_test.dart @@ -12,8 +12,8 @@ void main() { final router = RouterComponent( initialRoute: 'start', routes: { - 'start': Route(builder: Component.new), - 'new': Route(), + 'start': Route(Component.new), + 'new': Route(null), }, )..addToParent(game); await game.ready(); @@ -34,7 +34,7 @@ void main() { final router = RouterComponent( initialRoute: 'start', routes: { - 'start': Route(builder: Component.new), + 'start': Route(Component.new), 'new': CustomRoute( onPush: (self, prevRoute) { onPushCalled++; @@ -75,7 +75,7 @@ void main() { final router = RouterComponent( initialRoute: 'start', routes: { - 'start': Route(builder: _TimerComponent.new), + 'start': Route(_TimerComponent.new), 'pause': CustomRoute( builder: Component.new, onPush: (self, route) => route?.stopTime(), @@ -119,13 +119,13 @@ void main() { initialRoute: 'initial', routes: { 'initial': Route( - builder: () => _ColoredComponent( + () => _ColoredComponent( color: const Color(0xFFFF0000), size: Vector2.all(100), ), ), 'green': Route( - builder: () => _ColoredComponent( + () => _ColoredComponent( color: const Color(0x8800FF00), position: Vector2.all(10), size: Vector2.all(80), @@ -147,13 +147,13 @@ void main() { initialRoute: 'initial', routes: { 'initial': Route( - builder: () => _ColoredComponent( + () => _ColoredComponent( color: const Color(0xFFFF0000), size: Vector2.all(100), ), ), 'green': Route( - builder: () => _ColoredComponent( + () => _ColoredComponent( color: const Color(0x8800FF00), position: Vector2.all(10), size: Vector2.all(80), @@ -176,7 +176,7 @@ void main() { initialRoute: 'initial', routes: { 'initial': Route( - builder: () => _ColoredComponent( + () => _ColoredComponent( color: const Color(0xFFFF0000), size: Vector2.all(100), ), @@ -208,7 +208,7 @@ void main() { initialRoute: 'initial', routes: { 'initial': Route( - builder: () => _ColoredComponent( + () => _ColoredComponent( color: const Color(0xFFFF0000), size: Vector2.all(100), ), @@ -240,10 +240,10 @@ void main() { initialRoute: 'initial', routes: { 'initial': Route( - builder: () => PositionComponent(size: Vector2.all(100)), + () => PositionComponent(size: Vector2.all(100)), ), 'new': Route( - builder: () => PositionComponent(size: Vector2.all(100)), + () => PositionComponent(size: Vector2.all(100)), ), }, )..addToParent(game); @@ -262,10 +262,10 @@ void main() { initialRoute: 'initial', routes: { 'initial': Route( - builder: () => PositionComponent(size: Vector2.all(100)), + () => PositionComponent(size: Vector2.all(100)), ), 'new': Route( - builder: () => PositionComponent(size: Vector2.all(100)), + () => PositionComponent(size: Vector2.all(100)), transparent: true, ), }, @@ -288,14 +288,15 @@ void main() { class CustomRoute extends Route { CustomRoute({ - super.builder, + Component Function()? builder, super.transparent, void Function(Route, Route?)? onPush, void Function(Route, Route)? onPop, Component Function(Route)? build, }) : _onPush = onPush, _onPop = onPop, - _build = build; + _build = build, + super(builder); final void Function(Route, Route?)? _onPush; final void Function(Route, Route)? _onPop; diff --git a/packages/flame/test/components/router_component_test.dart b/packages/flame/test/components/router_component_test.dart index 880348f717b..dd8a895dad3 100644 --- a/packages/flame/test/components/router_component_test.dart +++ b/packages/flame/test/components/router_component_test.dart @@ -8,9 +8,9 @@ void main() { testWithFlameGame('normal route pushing/popping', (game) async { final router = RouterComponent( routes: { - 'A': Route(builder: _ComponentA.new), - 'B': Route(builder: _ComponentB.new), - 'C': Route(builder: _ComponentC.new), + 'A': Route(_ComponentA.new), + 'B': Route(_ComponentB.new), + 'C': Route(_ComponentC.new), }, initialRoute: 'A', ); @@ -34,7 +34,7 @@ void main() { expect(router.currentRoute.name, 'A'); expect(router.stack.length, 1); - router.pushRoute(Route(builder: _ComponentD.new), name: 'Dee'); + router.pushRoute(Route(_ComponentD.new), name: 'Dee'); await game.ready(); expect(router.routes.length, 4); expect(router.currentRoute.name, 'Dee'); @@ -46,10 +46,10 @@ void main() { testWithFlameGame('Route factories', (game) async { final router = RouterComponent( initialRoute: 'initial', - routes: {'initial': Route(builder: _ComponentD.new)}, + routes: {'initial': Route(_ComponentD.new)}, routeFactories: { - 'a': (arg) => Route(builder: _ComponentA.new), - 'b': (arg) => Route(builder: _ComponentB.new), + 'a': (arg) => Route(_ComponentA.new), + 'b': (arg) => Route(_ComponentB.new), }, ); game.add(router); @@ -71,9 +71,9 @@ void main() { testWithFlameGame('push an existing route', (game) async { final router = RouterComponent( routes: { - 'A': Route(builder: _ComponentA.new), - 'B': Route(builder: _ComponentB.new), - 'C': Route(builder: _ComponentC.new), + 'A': Route(_ComponentA.new), + 'B': Route(_ComponentB.new), + 'C': Route(_ComponentC.new), }, initialRoute: 'A', )..addToParent(game); @@ -102,8 +102,8 @@ void main() { testWithFlameGame('onUnknownRoute', (game) async { final router = RouterComponent( initialRoute: 'home', - routes: {'home': Route(builder: _ComponentA.new)}, - onUnknownRoute: (name) => Route(builder: _ComponentD.new), + routes: {'home': Route(_ComponentA.new)}, + onUnknownRoute: (name) => Route(_ComponentD.new), )..addToParent(game); await game.ready(); @@ -116,7 +116,7 @@ void main() { testWithFlameGame('default unknown route handling', (game) async { final router = RouterComponent( initialRoute: 'home', - routes: {'home': Route(builder: _ComponentA.new)}, + routes: {'home': Route(_ComponentA.new)}, )..addToParent(game); await game.ready(); @@ -136,7 +136,7 @@ void main() { testWithFlameGame('cannot pop last remaining route', (game) async { final router = RouterComponent( initialRoute: 'home', - routes: {'home': Route(builder: _ComponentA.new)}, + routes: {'home': Route(_ComponentA.new)}, )..addToParent(game); await game.ready(); @@ -149,9 +149,9 @@ void main() { testWithFlameGame('popUntilNamed', (game) async { final router = RouterComponent( routes: { - 'A': Route(builder: _ComponentA.new), - 'B': Route(builder: _ComponentB.new), - 'C': Route(builder: _ComponentC.new), + 'A': Route(_ComponentA.new), + 'B': Route(_ComponentB.new), + 'C': Route(_ComponentC.new), }, initialRoute: 'A', );