diff --git a/packages/flame/lib/src/components/core/component.dart b/packages/flame/lib/src/components/core/component.dart index f7dfc327185..b78b93670f2 100644 --- a/packages/flame/lib/src/components/core/component.dart +++ b/packages/flame/lib/src/components/core/component.dart @@ -5,6 +5,7 @@ import 'package:collection/collection.dart'; import 'package:flame/components.dart'; import 'package:flame/game.dart'; import 'package:flame/src/cache/value_cache.dart'; +import 'package:flame/src/components/core/component_context.dart'; import 'package:flame/src/components/core/component_tree_root.dart'; import 'package:flame/src/effects/provider_interfaces.dart'; import 'package:flutter/painting.dart'; @@ -534,13 +535,31 @@ class Component { void render(Canvas canvas) {} void renderTree(Canvas canvas) { + final context = createContext(); + if (context != null) { + _contexts.add(context); + } + render(canvas); - _children?.forEach((c) => c.renderTree(canvas)); + _children?.forEach((c) { + final hasContext = _contexts.isNotEmpty; + if (hasContext) { + c._contexts.addAll(_contexts); + } + c.renderTree(canvas); + if (hasContext) { + c._contexts.removeRange(_contexts.length, c._contexts.length); + } + }); // Any debug rendering should be rendered on top of everything if (debugMode) { renderDebugMode(canvas); } + + if (context != null) { + _contexts.removeLast(); + } } //#endregion @@ -1007,6 +1026,18 @@ class Component { //#endregion + //#region Context + + final QueueList _contexts = QueueList(); + + ComponentContext? createContext() => null; + + T? findContext() { + return _contexts.whereType().lastOrNull; + } + + //#endregion + //#region Debugging assistance /// Returns whether this [Component] is in debug mode or not. diff --git a/packages/flame/lib/src/components/core/component_context.dart b/packages/flame/lib/src/components/core/component_context.dart new file mode 100644 index 00000000000..bd8bb2933af --- /dev/null +++ b/packages/flame/lib/src/components/core/component_context.dart @@ -0,0 +1 @@ +abstract class ComponentContext {} diff --git a/packages/flame/test/components/component_context_test.dart b/packages/flame/test/components/component_context_test.dart new file mode 100644 index 00000000000..d304cdf34ff --- /dev/null +++ b/packages/flame/test/components/component_context_test.dart @@ -0,0 +1,112 @@ +import 'dart:ui'; + +import 'package:canvas_test/canvas_test.dart'; +import 'package:flame/components.dart'; +import 'package:flame/src/components/core/component_context.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Component Context', () { + testWithFlameGame('simple parent and child', (game) async { + final child = _ChildReadsContext(); + final parent = _ParentWithContext( + startingValue: 42, + children: [ + child, + ], + ); + game.add(parent); + + await game.ready(); + game.render(MockCanvas()); + + expect(child.myContext, 42); + }); + + testWithFlameGame('complex parent and child', (game) async { + final child1 = _ChildReadsContext(); + final child2 = _ChildReadsContext(); + final child3 = _ChildReadsContext(); + + final parent = Component( + children: [ + child1, + _ParentWithContext( + startingValue: 1, + children: [ + child2, + _ParentWithContext( + startingValue: 2, + children: [ + child3, + ], + ), + ], + ), + ], + ); + game.add(parent); + + await game.ready(); + game.render(MockCanvas()); + + expect(child1.myContext, null); + expect(child2.myContext, 1); + expect(child3.myContext, 2); + }); + + testWithFlameGame('mutating context', (game) async { + final child = _ChildReadsContext(); + + final parent = _ParentWithContext( + startingValue: 10, + children: [ + child, + ], + ); + game.add(parent); + + await game.ready(); + final canvas = MockCanvas(); + + game.render(canvas); + expect(child.myContext, 10); + + parent._myContext.value = 20; + game.render(canvas); + expect(child.myContext, 20); + }); + }); +} + +class _IntContext extends ComponentContext { + int value; + + _IntContext(this.value); +} + +class _ParentWithContext extends Component { + final int startingValue; + late final _IntContext _myContext = _IntContext(startingValue); + + _ParentWithContext({ + required this.startingValue, + super.children, + }); + + @override + _IntContext? createContext() => _myContext; +} + +class _ChildReadsContext extends Component { + int? myContext; + + @override + void render(Canvas canvas) { + final context = findContext<_IntContext>(); + myContext = context?.value; + + super.render(canvas); + } +}