From 9eaa8307b0f06763e0d216b98398182eeba612d4 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Mon, 11 Mar 2024 23:35:29 +0100 Subject: [PATCH 01/14] feat: Component tree for the devtools extension --- melos.yaml | 6 ++ packages/flame/lib/devtools.dart | 2 + .../connectors/component_tree_connector.dart | 59 +++++++++++++ .../lib/src/devtools/dev_tools_service.dart | 2 + packages/flame_devtools/README.md | 6 ++ packages/flame_devtools/lib/main.dart | 2 + packages/flame_devtools/lib/repository.dart | 11 +++ .../lib/widgets/component_counter.dart | 2 +- .../lib/widgets/component_tree.dart | 82 +++++++++++++++++++ packages/flame_devtools/pubspec.yaml | 8 +- 10 files changed, 175 insertions(+), 5 deletions(-) create mode 100644 packages/flame/lib/devtools.dart create mode 100644 packages/flame/lib/src/devtools/connectors/component_tree_connector.dart create mode 100644 packages/flame_devtools/lib/widgets/component_tree.dart diff --git a/melos.yaml b/melos.yaml index 2e29ea1b08d..8c804738e9b 100644 --- a/melos.yaml +++ b/melos.yaml @@ -118,3 +118,9 @@ scripts: packageFilters: scope: flame_devtools description: Builds the devtools and copies the build directory to the Flame package. + + devtools-simulator: + run: melos exec -- flutter run -d chrome --dart-define=use_simulated_environment=true + packageFilters: + scope: flame_devtools + description: Runs the devtools in the simulated mode. diff --git a/packages/flame/lib/devtools.dart b/packages/flame/lib/devtools.dart new file mode 100644 index 00000000000..87093bab12b --- /dev/null +++ b/packages/flame/lib/devtools.dart @@ -0,0 +1,2 @@ +export 'src/devtools/connectors/component_tree_connector.dart' + show ComponentTreeNode; diff --git a/packages/flame/lib/src/devtools/connectors/component_tree_connector.dart b/packages/flame/lib/src/devtools/connectors/component_tree_connector.dart new file mode 100644 index 00000000000..3b2b3b20248 --- /dev/null +++ b/packages/flame/lib/src/devtools/connectors/component_tree_connector.dart @@ -0,0 +1,59 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:flame/components.dart'; +import 'package:flame/src/devtools/dev_tools_connector.dart'; + +/// The [ComponentTreeConnector] is responsible for reporting the component +/// tree of the game to the devtools extension. +class ComponentTreeConnector extends DevToolsConnector { + @override + void init() { + // Get the component tree of the game. + registerExtension( + 'ext.flame_devtools.getComponentTree', + (method, parameters) async { + final componentTree = ComponentTreeNode.fromComponent(game); + + return ServiceExtensionResponse.result( + json.encode({ + 'component_tree': componentTree.toJson(), + }), + ); + }, + ); + } +} + +/// This should only be used internally by the devtools extension. +class ComponentTreeNode { + final int id; + final String name; + final List children; + + ComponentTreeNode(this.id, this.name, this.children); + + ComponentTreeNode.fromComponent(Component component) + : this( + component.hashCode, + component.runtimeType.toString(), + component.children.map(ComponentTreeNode.fromComponent).toList(), + ); + + ComponentTreeNode.fromJson(Map json) + : this( + json['id'] as int, + json['name'] as String, + (json['children'] as List) + .map((e) => ComponentTreeNode.fromJson(e as Map)) + .toList(), + ); + + Map toJson() { + return { + 'id': id, + 'name': name, + 'children': children.map((e) => e.toJson()).toList(), + }; + } +} diff --git a/packages/flame/lib/src/devtools/dev_tools_service.dart b/packages/flame/lib/src/devtools/dev_tools_service.dart index d56bd4a56fc..fe11e207401 100644 --- a/packages/flame/lib/src/devtools/dev_tools_service.dart +++ b/packages/flame/lib/src/devtools/dev_tools_service.dart @@ -1,5 +1,6 @@ import 'package:flame/game.dart'; import 'package:flame/src/devtools/connectors/component_count_connector.dart'; +import 'package:flame/src/devtools/connectors/component_tree_connector.dart'; import 'package:flame/src/devtools/connectors/debug_mode_connector.dart'; import 'package:flame/src/devtools/connectors/game_loop_connector.dart'; import 'package:flame/src/devtools/dev_tools_connector.dart'; @@ -32,6 +33,7 @@ class DevToolsService { final connectors = [ DebugModeConnector(), ComponentCountConnector(), + ComponentTreeConnector(), GameLoopConnector(), ]; diff --git a/packages/flame_devtools/README.md b/packages/flame_devtools/README.md index 012490dcaea..76829ecd511 100644 --- a/packages/flame_devtools/README.md +++ b/packages/flame_devtools/README.md @@ -18,3 +18,9 @@ To develop things from the Flame side, create a new `DevToolsConnector` which registers the new extension end points so that you can communicate with Flame from the devtools extension. Don't forget to add the new connector to the list of connectors in the `DevToolsService` class. + +If you want to run with the devtools extension with the simulated mode for +faster development, you can use `melos devtools-simulate` to start the +simulated environment and run the devtools extension in the browser. +Remember that you have to manually enter the Dart VM Service Connection URI +in the simulated devtools environment. diff --git a/packages/flame_devtools/lib/main.dart b/packages/flame_devtools/lib/main.dart index 91a7878898d..e5aff1c5d0b 100644 --- a/packages/flame_devtools/lib/main.dart +++ b/packages/flame_devtools/lib/main.dart @@ -1,5 +1,6 @@ import 'package:devtools_extensions/devtools_extensions.dart'; import 'package:flame_devtools/widgets/component_counter.dart'; +import 'package:flame_devtools/widgets/component_tree.dart'; import 'package:flame_devtools/widgets/debug_mode_button.dart'; import 'package:flame_devtools/widgets/game_loop_controls.dart'; import 'package:flutter/material.dart'; @@ -25,6 +26,7 @@ class FlameDevTools extends StatelessWidget { const DebugModeButton(), ].withSpacing(), ), + const ComponentTree(), ].withSpacing(), ), ); diff --git a/packages/flame_devtools/lib/repository.dart b/packages/flame_devtools/lib/repository.dart index 2f657f9d5ab..c516d95e57a 100644 --- a/packages/flame_devtools/lib/repository.dart +++ b/packages/flame_devtools/lib/repository.dart @@ -1,4 +1,5 @@ import 'package:devtools_extensions/devtools_extensions.dart'; +import 'package:flame/devtools.dart'; sealed class Repository { Repository._(); @@ -11,6 +12,16 @@ sealed class Repository { return componentCountResponse.json!['component_count'] as int; } + static Future getComponentTree() async { + final componentTreeResponse = + await serviceManager.callServiceExtensionOnMainIsolate( + 'ext.flame_devtools.getComponentTree', + ); + return ComponentTreeNode.fromJson( + componentTreeResponse.json!['component_tree'] as Map, + ); + } + static Future swapDebugMode() async { final nextDebugMode = !(await getDebugMode()); await serviceManager.callServiceExtensionOnMainIsolate( diff --git a/packages/flame_devtools/lib/widgets/component_counter.dart b/packages/flame_devtools/lib/widgets/component_counter.dart index 66a9c162f0e..430d8124fc9 100644 --- a/packages/flame_devtools/lib/widgets/component_counter.dart +++ b/packages/flame_devtools/lib/widgets/component_counter.dart @@ -33,7 +33,7 @@ class _ComponentCounterState extends State { onPressed: () => setState(() { _componentCount = Repository.getComponentCount(); }), - child: const Text('Update count'), + child: const Text('Update cunt'), ), ], ); diff --git a/packages/flame_devtools/lib/widgets/component_tree.dart b/packages/flame_devtools/lib/widgets/component_tree.dart new file mode 100644 index 00000000000..acf792754ad --- /dev/null +++ b/packages/flame_devtools/lib/widgets/component_tree.dart @@ -0,0 +1,82 @@ +import 'package:animated_tree_view/tree_view/tree_node.dart'; +import 'package:animated_tree_view/tree_view/tree_view.dart'; +import 'package:flame/devtools.dart'; +import 'package:flame_devtools/repository.dart'; +import 'package:flutter/material.dart'; + +class ComponentTree extends StatefulWidget { + const ComponentTree({super.key}); + + @override + State createState() => _ComponentTreeState(); +} + +class _ComponentTreeState extends State { + Future? _componentTree; + final TreeNode _tree = TreeNode.root(); + TreeNode? _selectedTreeNode; + + @override + void initState() { + _refreshComponentTree(); + super.initState(); + } + + void _refreshComponentTree() { + _tree.clear(); + _componentTree = Repository.getComponentTree(); + _componentTree?.then((value) => _buildTree(value, _tree)); + } + + void _buildTree(ComponentTreeNode node, TreeNode parent) { + final current = TreeNode( + key: node.id.toString(), + parent: parent, + data: node, + ); + parent.add(current); + for (final child in node.children) { + _buildTree(child, current); + } + } + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: _componentTree, + builder: (context, value) { + return Row( + children: [ + Expanded( + child: value.hasData + ? ValueListenableBuilder( + valueListenable: _tree, + builder: (context, _, __) => TreeView.simple( + showRootNode: false, + shrinkWrap: true, + builder: (context, node) { + return ListTile( + title: Text(node.data?.name ?? 'something'), + subtitle: Text(node.data?.id.toString() ?? 'else'), + onTap: () { + return setState(() => _selectedTreeNode = node); + }, + ); + }, + tree: _tree, + ), + ) + : const CircularProgressIndicator(strokeWidth: 20), + ), + Expanded( + child: Text( + _selectedTreeNode?.data?.name ?? 'Select a node', + style: const TextStyle(fontSize: 24), + ), + ) + ], + ); + }, + ); + } +} diff --git a/packages/flame_devtools/pubspec.yaml b/packages/flame_devtools/pubspec.yaml index 8dae46f7a4d..26b5d7c74ad 100644 --- a/packages/flame_devtools/pubspec.yaml +++ b/packages/flame_devtools/pubspec.yaml @@ -7,15 +7,15 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: - devtools_app_shared: ^0.0.9 - devtools_extensions: ^0.0.13 + animated_tree_view: ^2.2.0 + devtools_app_shared: ^0.0.10 + devtools_extensions: ^0.0.14 + flame: ^1.16.0 flutter: sdk: flutter dev_dependencies: flame_lint: ^1.1.2 - flutter_test: - sdk: flutter flutter: uses-material-design: true From a6e192d89a72b1afff3ebf7d5a7ccca7b807283f Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Fri, 15 Mar 2024 09:46:50 +0100 Subject: [PATCH 02/14] feat: Add setter and getter for single component debugMode --- .../connectors/debug_mode_connector.dart | 80 +++++++++++++------ 1 file changed, 57 insertions(+), 23 deletions(-) diff --git a/packages/flame/lib/src/devtools/connectors/debug_mode_connector.dart b/packages/flame/lib/src/devtools/connectors/debug_mode_connector.dart index 8a2a3ca12e6..76052199cd0 100644 --- a/packages/flame/lib/src/devtools/connectors/debug_mode_connector.dart +++ b/packages/flame/lib/src/devtools/connectors/debug_mode_connector.dart @@ -2,15 +2,11 @@ import 'dart:convert'; import 'dart:developer'; import 'package:flame/components.dart'; -import 'package:flame/game.dart'; import 'package:flame/src/devtools/dev_tools_connector.dart'; -import 'package:flutter/foundation.dart'; /// The [DebugModeConnector] is responsible for reporting and setting the /// `debugMode` of the game from the devtools extension. class DebugModeConnector extends DevToolsConnector { - var _debugModeNotifier = ValueNotifier(false); - @override void init() { // Get the current `debugMode`. @@ -19,7 +15,7 @@ class DebugModeConnector extends DevToolsConnector { (method, parameters) async { return ServiceExtensionResponse.result( json.encode({ - 'debug_mode': _debugModeNotifier.value, + 'debug_mode': game.debugMode, }), ); }, @@ -30,34 +26,72 @@ class DebugModeConnector extends DevToolsConnector { 'ext.flame_devtools.setDebugMode', (method, parameters) async { final debugMode = bool.parse(parameters['debug_mode'] ?? 'false'); - _debugModeNotifier.value = debugMode; + _setDebugMode(debugMode); + return ServiceExtensionResponse.result( + json.encode({ + 'debug_mode': debugMode, + }), + ); + }, + ); + + // Set the `debugMode` for one component in the tree. + registerExtension( + 'ext.flame_devtools.setDebugModeSingle', + (method, parameters) async { + final id = int.tryParse(parameters['id'] ?? ''); + final debugMode = bool.parse(parameters['debug_mode'] ?? 'false'); + _setDebugMode(debugMode, id: id); return ServiceExtensionResponse.result( json.encode({ + 'id': id, 'debug_mode': debugMode, }), ); }, ); + + // Get the `debugMode` for one component in the tree. + registerExtension( + 'ext.flame_devtools.getDebugModeSingle', + (method, parameters) async { + final id = int.tryParse(parameters['id'] ?? ''); + return ServiceExtensionResponse.result( + json.encode({ + 'id': id, + 'debug_mode': id != null ? _getDebugMode(id) : null, + }), + ); + }, + ); } - @override - void initGame(FlameGame game) { - super.initGame(game); - _debugModeNotifier = ValueNotifier(game.debugMode); - _debugModeNotifier.addListener(() { - final newDebugMode = _debugModeNotifier.value; - game.propagateToChildren( - (c) { - c.debugMode = newDebugMode; - return true; - }, - includeSelf: true, - ); - }); + bool _getDebugMode(int id) { + var debugMode = false; + game.propagateToChildren( + (c) { + if (c.hashCode != id) { + debugMode = c.debugMode; + return false; + } + return true; + }, + includeSelf: true, + ); + return debugMode; } - @override - void disposeGame() { - _debugModeNotifier.dispose(); + void _setDebugMode(bool debugMode, {int? id}) { + game.propagateToChildren( + (c) { + if (id != null && c.hashCode != id) { + c.debugMode = debugMode; + return false; + } + c.debugMode = debugMode; + return true; + }, + includeSelf: true, + ); } } From a0fd9826eb04024b011ed66c8ccee2add316b9bb Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Mon, 18 Mar 2024 00:05:12 +0100 Subject: [PATCH 03/14] feat: Possibility to swap debugMode for single components --- .../connectors/debug_mode_connector.dart | 47 ++++--------- packages/flame_devtools/lib/repository.dart | 12 ++-- .../lib/widgets/component_tree.dart | 66 +++++++++++++++---- .../lib/widgets/debug_mode_button.dart | 12 +++- 4 files changed, 82 insertions(+), 55 deletions(-) diff --git a/packages/flame/lib/src/devtools/connectors/debug_mode_connector.dart b/packages/flame/lib/src/devtools/connectors/debug_mode_connector.dart index 76052199cd0..f75097a5166 100644 --- a/packages/flame/lib/src/devtools/connectors/debug_mode_connector.dart +++ b/packages/flame/lib/src/devtools/connectors/debug_mode_connector.dart @@ -9,35 +9,26 @@ import 'package:flame/src/devtools/dev_tools_connector.dart'; class DebugModeConnector extends DevToolsConnector { @override void init() { - // Get the current `debugMode`. + // Get the `debugMode` for a component in the tree. + // If no id is provided, the `debugMode` for the entire game will be + // returned. registerExtension( 'ext.flame_devtools.getDebugMode', (method, parameters) async { + final id = int.tryParse(parameters['id'] ?? '') ?? game.hashCode; return ServiceExtensionResponse.result( json.encode({ - 'debug_mode': game.debugMode, + 'id': id, + 'debug_mode': _getDebugMode(id), }), ); }, ); - // Set the `debugMode` for all components in the tree. + // Set the `debugMode` for a component in the tree. + // If no id is provided, the `debugMode` will be set for the entire game. registerExtension( 'ext.flame_devtools.setDebugMode', - (method, parameters) async { - final debugMode = bool.parse(parameters['debug_mode'] ?? 'false'); - _setDebugMode(debugMode); - return ServiceExtensionResponse.result( - json.encode({ - 'debug_mode': debugMode, - }), - ); - }, - ); - - // Set the `debugMode` for one component in the tree. - registerExtension( - 'ext.flame_devtools.setDebugModeSingle', (method, parameters) async { final id = int.tryParse(parameters['id'] ?? ''); final debugMode = bool.parse(parameters['debug_mode'] ?? 'false'); @@ -50,27 +41,13 @@ class DebugModeConnector extends DevToolsConnector { ); }, ); - - // Get the `debugMode` for one component in the tree. - registerExtension( - 'ext.flame_devtools.getDebugModeSingle', - (method, parameters) async { - final id = int.tryParse(parameters['id'] ?? ''); - return ServiceExtensionResponse.result( - json.encode({ - 'id': id, - 'debug_mode': id != null ? _getDebugMode(id) : null, - }), - ); - }, - ); } bool _getDebugMode(int id) { var debugMode = false; game.propagateToChildren( (c) { - if (c.hashCode != id) { + if (c.hashCode == id) { debugMode = c.debugMode; return false; } @@ -84,11 +61,13 @@ class DebugModeConnector extends DevToolsConnector { void _setDebugMode(bool debugMode, {int? id}) { game.propagateToChildren( (c) { - if (id != null && c.hashCode != id) { + if (id == null) { + c.debugMode = debugMode; + return true; + } else if (c.hashCode == id) { c.debugMode = debugMode; return false; } - c.debugMode = debugMode; return true; }, includeSelf: true, diff --git a/packages/flame_devtools/lib/repository.dart b/packages/flame_devtools/lib/repository.dart index c516d95e57a..c9fc22b54c8 100644 --- a/packages/flame_devtools/lib/repository.dart +++ b/packages/flame_devtools/lib/repository.dart @@ -22,19 +22,23 @@ sealed class Repository { ); } - static Future swapDebugMode() async { - final nextDebugMode = !(await getDebugMode()); + static Future swapDebugMode({int? id}) async { + final nextDebugMode = !(await getDebugMode(id: id)); await serviceManager.callServiceExtensionOnMainIsolate( 'ext.flame_devtools.setDebugMode', - args: {'debug_mode': nextDebugMode}, + args: { + 'debug_mode': nextDebugMode, + 'id': id, + }, ); return nextDebugMode; } - static Future getDebugMode() async { + static Future getDebugMode({int? id}) async { final debugModeResponse = await serviceManager.callServiceExtensionOnMainIsolate( 'ext.flame_devtools.getDebugMode', + args: {'id': id}, ); return debugModeResponse.json!['debug_mode'] as bool; } diff --git a/packages/flame_devtools/lib/widgets/component_tree.dart b/packages/flame_devtools/lib/widgets/component_tree.dart index acf792754ad..7a7033e5cfe 100644 --- a/packages/flame_devtools/lib/widgets/component_tree.dart +++ b/packages/flame_devtools/lib/widgets/component_tree.dart @@ -1,7 +1,7 @@ -import 'package:animated_tree_view/tree_view/tree_node.dart'; -import 'package:animated_tree_view/tree_view/tree_view.dart'; +import 'package:animated_tree_view/animated_tree_view.dart'; import 'package:flame/devtools.dart'; import 'package:flame_devtools/repository.dart'; +import 'package:flame_devtools/widgets/debug_mode_button.dart'; import 'package:flutter/material.dart'; class ComponentTree extends StatefulWidget { @@ -54,13 +54,30 @@ class _ComponentTreeState extends State { builder: (context, _, __) => TreeView.simple( showRootNode: false, shrinkWrap: true, + indentation: const Indentation( + color: Colors.blue, + style: IndentStyle.roundJoint, + ), + expansionIndicatorBuilder: (context, node) => + node.isLeaf + ? NoExpansionIndicator(tree: node) + : ChevronIndicator.rightDown( + tree: node, + alignment: Alignment.centerLeft, + ), builder: (context, node) { - return ListTile( - title: Text(node.data?.name ?? 'something'), - subtitle: Text(node.data?.id.toString() ?? 'else'), - onTap: () { - return setState(() => _selectedTreeNode = node); - }, + return Padding( + padding: node.isLeaf + ? EdgeInsets.zero + : const EdgeInsets.only(left: 20), + child: ListTile( + title: Text(node.data?.name ?? 'something'), + subtitle: + Text(node.data?.id.toString() ?? 'else'), + onTap: () { + return setState(() => _selectedTreeNode = node); + }, + ), ); }, tree: _tree, @@ -68,15 +85,36 @@ class _ComponentTreeState extends State { ) : const CircularProgressIndicator(strokeWidth: 20), ), - Expanded( - child: Text( - _selectedTreeNode?.data?.name ?? 'Select a node', - style: const TextStyle(fontSize: 24), - ), - ) + Expanded(child: ComponentView(_selectedTreeNode?.data)), ], ); }, ); } } + +class ComponentView extends StatelessWidget { + const ComponentView(this.componentNode, {super.key}); + + final ComponentTreeNode? componentNode; + + @override + Widget build(BuildContext context) { + final node = componentNode; + const textStyle = TextStyle(fontSize: 18); + return node == null + ? const Text( + 'Select a component in the tree', + style: textStyle, + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Type: ${node.name}', style: textStyle), + Text('Id: ${node.id}', style: textStyle), + Text('Children: ${node.children.length}', style: textStyle), + DebugModeButton(id: node.id), + ], + ); + } +} diff --git a/packages/flame_devtools/lib/widgets/debug_mode_button.dart b/packages/flame_devtools/lib/widgets/debug_mode_button.dart index d79f9424156..9f2c3e1f09d 100644 --- a/packages/flame_devtools/lib/widgets/debug_mode_button.dart +++ b/packages/flame_devtools/lib/widgets/debug_mode_button.dart @@ -2,7 +2,9 @@ import 'package:flame_devtools/repository.dart'; import 'package:flutter/material.dart'; class DebugModeButton extends StatefulWidget { - const DebugModeButton({super.key}); + const DebugModeButton({this.id, super.key}); + + final int? id; @override State createState() => _DebugModeButtonState(); @@ -13,7 +15,7 @@ class _DebugModeButtonState extends State { @override void initState() { - _debugMode = Repository.getDebugMode(); + _debugMode = Repository.getDebugMode(id: widget.id); super.initState(); } @@ -31,7 +33,11 @@ class _DebugModeButtonState extends State { return ElevatedButton( onPressed: value.data == null ? null - : () => setState(() => _debugMode = Repository.swapDebugMode()), + : () { + setState( + () => _debugMode = Repository.swapDebugMode(id: widget.id), + ); + }, child: Text('$buttonPrefix Debug Mode'), ); }, From 5dc775b8f66488ba71f2101255ea967e0b5d67f8 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Mon, 18 Mar 2024 23:59:28 +0100 Subject: [PATCH 04/14] Use shared components for layout design --- packages/flame_devtools/lib/main.dart | 4 +- .../lib/widgets/component_counter.dart | 2 +- .../lib/widgets/component_tree.dart | 201 ++++++++++++------ 3 files changed, 143 insertions(+), 64 deletions(-) diff --git a/packages/flame_devtools/lib/main.dart b/packages/flame_devtools/lib/main.dart index e5aff1c5d0b..37a8d597791 100644 --- a/packages/flame_devtools/lib/main.dart +++ b/packages/flame_devtools/lib/main.dart @@ -17,12 +17,10 @@ class FlameDevTools extends StatelessWidget { return DevToolsExtension( child: Column( children: [ - const GameLoopControls(), Row( mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.end, children: [ - const ComponentCounter(), + const GameLoopControls(), const DebugModeButton(), ].withSpacing(), ), diff --git a/packages/flame_devtools/lib/widgets/component_counter.dart b/packages/flame_devtools/lib/widgets/component_counter.dart index 430d8124fc9..66a9c162f0e 100644 --- a/packages/flame_devtools/lib/widgets/component_counter.dart +++ b/packages/flame_devtools/lib/widgets/component_counter.dart @@ -33,7 +33,7 @@ class _ComponentCounterState extends State { onPressed: () => setState(() { _componentCount = Repository.getComponentCount(); }), - child: const Text('Update cunt'), + child: const Text('Update count'), ), ], ); diff --git a/packages/flame_devtools/lib/widgets/component_tree.dart b/packages/flame_devtools/lib/widgets/component_tree.dart index 7a7033e5cfe..4592a078374 100644 --- a/packages/flame_devtools/lib/widgets/component_tree.dart +++ b/packages/flame_devtools/lib/widgets/component_tree.dart @@ -1,4 +1,5 @@ import 'package:animated_tree_view/animated_tree_view.dart'; +import 'package:devtools_app_shared/ui.dart'; import 'package:flame/devtools.dart'; import 'package:flame_devtools/repository.dart'; import 'package:flame_devtools/widgets/debug_mode_button.dart'; @@ -15,6 +16,7 @@ class _ComponentTreeState extends State { Future? _componentTree; final TreeNode _tree = TreeNode.root(); TreeNode? _selectedTreeNode; + int _componentCount = 0; @override void initState() { @@ -24,11 +26,13 @@ class _ComponentTreeState extends State { void _refreshComponentTree() { _tree.clear(); + _componentCount = 0; _componentTree = Repository.getComponentTree(); - _componentTree?.then((value) => _buildTree(value, _tree)); + _componentTree?.then((value) => setState(() => _buildTree(value, _tree))); } void _buildTree(ComponentTreeNode node, TreeNode parent) { + _componentCount++; final current = TreeNode( key: node.id.toString(), parent: parent, @@ -42,53 +46,118 @@ class _ComponentTreeState extends State { @override Widget build(BuildContext context) { - return FutureBuilder( - future: _componentTree, - builder: (context, value) { - return Row( - children: [ - Expanded( - child: value.hasData - ? ValueListenableBuilder( - valueListenable: _tree, - builder: (context, _, __) => TreeView.simple( - showRootNode: false, - shrinkWrap: true, - indentation: const Indentation( - color: Colors.blue, - style: IndentStyle.roundJoint, + final theme = Theme.of(context); + return Column( + children: [ + FutureBuilder( + future: _componentTree, + builder: (context, value) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: RoundedOutlinedBorder( + child: Column( + children: [ + AreaPaneHeader( + title: Row( + children: [ + Text( + 'Component Tree ($_componentCount components)', + style: theme.textTheme.titleSmall, + ), + IconButton( + icon: const Icon(Icons.refresh), + alignment: Alignment.topCenter, + onPressed: () => + setState(_refreshComponentTree), + ), + ], + ), ), - expansionIndicatorBuilder: (context, node) => - node.isLeaf - ? NoExpansionIndicator(tree: node) - : ChevronIndicator.rightDown( - tree: node, - alignment: Alignment.centerLeft, - ), - builder: (context, node) { - return Padding( - padding: node.isLeaf - ? EdgeInsets.zero - : const EdgeInsets.only(left: 20), - child: ListTile( - title: Text(node.data?.name ?? 'something'), - subtitle: - Text(node.data?.id.toString() ?? 'else'), - onTap: () { - return setState(() => _selectedTreeNode = node); - }, + if (value.hasData) + SingleChildScrollView( + child: ValueListenableBuilder( + valueListenable: _tree, + builder: (context, _, __) => TreeView.simple( + showRootNode: false, + shrinkWrap: true, + indentation: const Indentation( + color: Colors.blue, + style: IndentStyle.roundJoint, + ), + padding: const EdgeInsets.only(left: 20), + expansionIndicatorBuilder: (context, node) => + node.isLeaf + ? NoExpansionIndicator(tree: node) + : ChevronIndicator.rightDown( + tree: node, + alignment: Alignment.centerLeft, + ), + builder: (context, node) { + return Padding( + padding: node.isLeaf + ? EdgeInsets.zero + : const EdgeInsets.only(left: 20), + child: ListTile( + //key: Key( + // node.data?.id.toString() ?? node.key, + //), + title: Text(node.data!.name), + subtitle: Text(node.data!.id.toString()), + onTap: () { + return setState( + () => _selectedTreeNode = node, + ); + }, + ), + ); + }, + tree: _tree, + ), ), - ); - }, - tree: _tree, - ), - ) - : const CircularProgressIndicator(strokeWidth: 20), - ), - Expanded(child: ComponentView(_selectedTreeNode?.data)), - ], - ); - }, + ) + else + const CircularProgressIndicator(strokeWidth: 20), + ], + ), + ), + ), + const SizedBox(width: 20), + Expanded( + child: RoundedOutlinedBorder( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AreaPaneHeader( + title: Row( + children: [ + Text( + 'Selected Component', + style: theme.textTheme.titleSmall, + ), + IconButton( + icon: const Icon(Icons.refresh), + alignment: Alignment.topCenter, + onPressed: () => setState( + () { + // TODO(Lukas): Update the selected component + }, + ), + ), + ], + ), + ), + ComponentView(_selectedTreeNode?.data), + ], + ), + ), + ), + ], + ); + }, + ), + ], ); } } @@ -102,19 +171,31 @@ class ComponentView extends StatelessWidget { Widget build(BuildContext context) { final node = componentNode; const textStyle = TextStyle(fontSize: 18); - return node == null - ? const Text( - 'Select a component in the tree', - style: textStyle, - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Type: ${node.name}', style: textStyle), - Text('Id: ${node.id}', style: textStyle), - Text('Children: ${node.children.length}', style: textStyle), - DebugModeButton(id: node.id), - ], - ); + return Padding( + padding: const EdgeInsets.all(20), + child: node == null + ? const Text( + 'Select a component in the tree', + style: textStyle, + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Type: ${node.name}', style: textStyle), + Text('Id: ${node.id}', style: textStyle), + Text('Children: ${node.children.length}', style: textStyle), + DebugModeButton(id: node.id), + ].withSpacing(), + ), + ); + } +} + +extension on List { + List withSpacing() { + return expand((item) sync* { + yield const SizedBox(width: 10, height: 10); + yield item; + }).skip(1).toList(); } } From 90d81a4d961bbe48a0ca3acd44000e5426f56446 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Tue, 19 Mar 2024 23:35:09 +0100 Subject: [PATCH 05/14] fix: Expand tree automatically --- .../lib/widgets/component_tree.dart | 96 +++++++++---------- 1 file changed, 47 insertions(+), 49 deletions(-) diff --git a/packages/flame_devtools/lib/widgets/component_tree.dart b/packages/flame_devtools/lib/widgets/component_tree.dart index 4592a078374..1b07901464a 100644 --- a/packages/flame_devtools/lib/widgets/component_tree.dart +++ b/packages/flame_devtools/lib/widgets/component_tree.dart @@ -68,7 +68,8 @@ class _ComponentTreeState extends State { ), IconButton( icon: const Icon(Icons.refresh), - alignment: Alignment.topCenter, + iconSize: 18, + alignment: Alignment.center, onPressed: () => setState(_refreshComponentTree), ), @@ -86,6 +87,8 @@ class _ComponentTreeState extends State { color: Colors.blue, style: IndentStyle.roundJoint, ), + onTreeReady: (controller) => controller + .expandAllChildren(controller.tree), padding: const EdgeInsets.only(left: 20), expansionIndicatorBuilder: (context, node) => node.isLeaf @@ -100,9 +103,9 @@ class _ComponentTreeState extends State { ? EdgeInsets.zero : const EdgeInsets.only(left: 20), child: ListTile( - //key: Key( - // node.data?.id.toString() ?? node.key, - //), + key: Key( + node.data?.id.toString() ?? node.key, + ), title: Text(node.data!.name), subtitle: Text(node.data!.id.toString()), onTap: () { @@ -124,35 +127,7 @@ class _ComponentTreeState extends State { ), ), const SizedBox(width: 20), - Expanded( - child: RoundedOutlinedBorder( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AreaPaneHeader( - title: Row( - children: [ - Text( - 'Selected Component', - style: theme.textTheme.titleSmall, - ), - IconButton( - icon: const Icon(Icons.refresh), - alignment: Alignment.topCenter, - onPressed: () => setState( - () { - // TODO(Lukas): Update the selected component - }, - ), - ), - ], - ), - ), - ComponentView(_selectedTreeNode?.data), - ], - ), - ), - ), + ComponentView(_selectedTreeNode?.data), ], ); }, @@ -170,23 +145,46 @@ class ComponentView extends StatelessWidget { @override Widget build(BuildContext context) { final node = componentNode; - const textStyle = TextStyle(fontSize: 18); - return Padding( - padding: const EdgeInsets.all(20), - child: node == null - ? const Text( - 'Select a component in the tree', - style: textStyle, - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Type: ${node.name}', style: textStyle), - Text('Id: ${node.id}', style: textStyle), - Text('Children: ${node.children.length}', style: textStyle), - DebugModeButton(id: node.id), - ].withSpacing(), + final theme = Theme.of(context); + final textStyle = theme.textTheme.bodyLarge; + + return Expanded( + child: RoundedOutlinedBorder( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AreaPaneHeader( + title: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Selected Component', + style: theme.textTheme.titleSmall, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(20), + child: node == null + ? Text( + 'Select a component in the tree', + style: textStyle, + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Type: ${node.name}', style: textStyle), + Text('Id: ${node.id}', style: textStyle), + Text('Children: ${node.children.length}', + style: textStyle), + DebugModeButton(id: node.id), + ].withSpacing(), + ), ), + ], + ), + ), ); } } From bed21e1f37ade18268ee9d569ab2e99d6275418f Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Sun, 24 Mar 2024 21:13:24 +0100 Subject: [PATCH 06/14] Scrolling for the tree --- packages/flame_devtools/lib/main.dart | 3 +- .../lib/widgets/component_tree.dart | 169 +++++++++--------- .../lib/widgets/game_loop_controls.dart | 18 +- 3 files changed, 95 insertions(+), 95 deletions(-) diff --git a/packages/flame_devtools/lib/main.dart b/packages/flame_devtools/lib/main.dart index 37a8d597791..d0c7d58d619 100644 --- a/packages/flame_devtools/lib/main.dart +++ b/packages/flame_devtools/lib/main.dart @@ -1,5 +1,4 @@ import 'package:devtools_extensions/devtools_extensions.dart'; -import 'package:flame_devtools/widgets/component_counter.dart'; import 'package:flame_devtools/widgets/component_tree.dart'; import 'package:flame_devtools/widgets/debug_mode_button.dart'; import 'package:flame_devtools/widgets/game_loop_controls.dart'; @@ -24,7 +23,7 @@ class FlameDevTools extends StatelessWidget { const DebugModeButton(), ].withSpacing(), ), - const ComponentTree(), + const Expanded(child: ComponentTree()), ].withSpacing(), ), ); diff --git a/packages/flame_devtools/lib/widgets/component_tree.dart b/packages/flame_devtools/lib/widgets/component_tree.dart index 1b07901464a..c8220b8cf8f 100644 --- a/packages/flame_devtools/lib/widgets/component_tree.dart +++ b/packages/flame_devtools/lib/widgets/component_tree.dart @@ -47,92 +47,89 @@ class _ComponentTreeState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - return Column( - children: [ - FutureBuilder( - future: _componentTree, - builder: (context, value) { - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: RoundedOutlinedBorder( - child: Column( - children: [ - AreaPaneHeader( - title: Row( - children: [ - Text( - 'Component Tree ($_componentCount components)', - style: theme.textTheme.titleSmall, - ), - IconButton( - icon: const Icon(Icons.refresh), - iconSize: 18, - alignment: Alignment.center, - onPressed: () => - setState(_refreshComponentTree), - ), - ], + + return FutureBuilder( + future: _componentTree, + builder: (context, value) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: RoundedOutlinedBorder( + child: Column( + children: [ + AreaPaneHeader( + title: Row( + children: [ + Text( + 'Component Tree ($_componentCount components)', + style: theme.textTheme.titleSmall, ), - ), - if (value.hasData) - SingleChildScrollView( - child: ValueListenableBuilder( - valueListenable: _tree, - builder: (context, _, __) => TreeView.simple( - showRootNode: false, - shrinkWrap: true, - indentation: const Indentation( - color: Colors.blue, - style: IndentStyle.roundJoint, - ), - onTreeReady: (controller) => controller - .expandAllChildren(controller.tree), - padding: const EdgeInsets.only(left: 20), - expansionIndicatorBuilder: (context, node) => - node.isLeaf - ? NoExpansionIndicator(tree: node) - : ChevronIndicator.rightDown( - tree: node, - alignment: Alignment.centerLeft, - ), - builder: (context, node) { - return Padding( - padding: node.isLeaf - ? EdgeInsets.zero - : const EdgeInsets.only(left: 20), - child: ListTile( - key: Key( - node.data?.id.toString() ?? node.key, - ), - title: Text(node.data!.name), - subtitle: Text(node.data!.id.toString()), - onTap: () { - return setState( - () => _selectedTreeNode = node, - ); - }, - ), - ); - }, - tree: _tree, - ), - ), - ) - else - const CircularProgressIndicator(strokeWidth: 20), - ], + IconButton( + icon: const Icon(Icons.refresh), + iconSize: 18, + alignment: Alignment.center, + onPressed: () => setState(_refreshComponentTree), + ), + ], + ), ), - ), + if (value.hasData) + Expanded( + child: SingleChildScrollView( + child: TreeView.simple( + showRootNode: false, + shrinkWrap: true, + indentation: const Indentation( + color: Colors.blue, + style: IndentStyle.roundJoint, + ), + onTreeReady: (controller) => + controller.expandAllChildren(controller.tree), + padding: const EdgeInsets.only(left: 20), + expansionIndicatorBuilder: (context, node) => + node.isLeaf + ? NoExpansionIndicator(tree: node) + : ChevronIndicator.rightDown( + tree: node, + alignment: Alignment.centerLeft, + ), + builder: (context, node) { + return Padding( + padding: node.isLeaf + ? EdgeInsets.zero + : const EdgeInsets.only(left: 20), + child: ListTile( + key: Key( + node.data?.id.toString() ?? node.key, + ), + selected: node == _selectedTreeNode, + selectedColor: theme.colorScheme.primary, + title: Text(node.data!.name), + subtitle: Text(node.data!.id.toString()), + onTap: () { + return setState( + () => _selectedTreeNode = node, + ); + }, + ), + ); + }, + tree: _tree, + ), + ), + ) + else + const CircularProgressIndicator(strokeWidth: 20), + ], ), - const SizedBox(width: 20), - ComponentView(_selectedTreeNode?.data), - ], - ); - }, - ), - ], + ), + ), + const SizedBox(width: 20), + ComponentView(_selectedTreeNode?.data), + ], + ); + }, ); } } @@ -176,8 +173,10 @@ class ComponentView extends StatelessWidget { children: [ Text('Type: ${node.name}', style: textStyle), Text('Id: ${node.id}', style: textStyle), - Text('Children: ${node.children.length}', - style: textStyle), + Text( + 'Children: ${node.children.length}', + style: textStyle, + ), DebugModeButton(id: node.id), ].withSpacing(), ), diff --git a/packages/flame_devtools/lib/widgets/game_loop_controls.dart b/packages/flame_devtools/lib/widgets/game_loop_controls.dart index 5ecff67c555..565b5c2a27d 100644 --- a/packages/flame_devtools/lib/widgets/game_loop_controls.dart +++ b/packages/flame_devtools/lib/widgets/game_loop_controls.dart @@ -45,29 +45,25 @@ class _GameLoopControlsState extends State { IconButton( onPressed: (isPaused == null || !isPaused) ? null - : () => setState( - () => Repository.step(stepTime: -stepTime), - ), + : () => Repository.step(stepTime: -stepTime), icon: const Icon(Icons.skip_previous), ), IconButton( onPressed: (isPaused == null || isPaused) ? null - : () => setState(() => _paused = Repository.setPaused(true)), + : () => _setPaused(true), icon: const Icon(Icons.pause), ), IconButton( onPressed: (isPaused == null || !isPaused) ? null - : () => setState(() => _paused = Repository.setPaused(false)), + : () => _setPaused(false), icon: const Icon(Icons.play_arrow), ), IconButton( onPressed: (isPaused == null || !isPaused) ? null - : () => setState( - () => Repository.step(stepTime: stepTime), - ), + : () => Repository.step(stepTime: stepTime), icon: const Icon(Icons.skip_next), ), ], @@ -76,6 +72,12 @@ class _GameLoopControlsState extends State { ); } + void _setPaused(bool paused) { + setState(() { + _paused = Repository.setPaused(paused); + }); + } + @override void dispose() { _stepTimeController.dispose(); From 5b9b410fc9a5892322ed8baf80c0cd542a0229fd Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Sun, 24 Mar 2024 21:22:36 +0100 Subject: [PATCH 07/14] Add toString information about components for devtools --- .../connectors/component_tree_connector.dart | 6 +++++- .../flame_devtools/lib/widgets/component_tree.dart | 12 ++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/flame/lib/src/devtools/connectors/component_tree_connector.dart b/packages/flame/lib/src/devtools/connectors/component_tree_connector.dart index 3b2b3b20248..0e1ea1abb6a 100644 --- a/packages/flame/lib/src/devtools/connectors/component_tree_connector.dart +++ b/packages/flame/lib/src/devtools/connectors/component_tree_connector.dart @@ -29,14 +29,16 @@ class ComponentTreeConnector extends DevToolsConnector { class ComponentTreeNode { final int id; final String name; + final String toStringText; final List children; - ComponentTreeNode(this.id, this.name, this.children); + ComponentTreeNode(this.id, this.name, this.toStringText, this.children); ComponentTreeNode.fromComponent(Component component) : this( component.hashCode, component.runtimeType.toString(), + component.toString(), component.children.map(ComponentTreeNode.fromComponent).toList(), ); @@ -44,6 +46,7 @@ class ComponentTreeNode { : this( json['id'] as int, json['name'] as String, + json['toString'] as String, (json['children'] as List) .map((e) => ComponentTreeNode.fromJson(e as Map)) .toList(), @@ -53,6 +56,7 @@ class ComponentTreeNode { return { 'id': id, 'name': name, + 'toString': toStringText, 'children': children.map((e) => e.toJson()).toList(), }; } diff --git a/packages/flame_devtools/lib/widgets/component_tree.dart b/packages/flame_devtools/lib/widgets/component_tree.dart index c8220b8cf8f..53bec93da19 100644 --- a/packages/flame_devtools/lib/widgets/component_tree.dart +++ b/packages/flame_devtools/lib/widgets/component_tree.dart @@ -171,13 +171,21 @@ class ComponentView extends StatelessWidget { : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + Row( + children: [ + Text('Id: ${node.id}', style: textStyle), + DebugModeButton(id: node.id), + ].withSpacing(), + ), Text('Type: ${node.name}', style: textStyle), - Text('Id: ${node.id}', style: textStyle), Text( 'Children: ${node.children.length}', style: textStyle, ), - DebugModeButton(id: node.id), + Text( + 'toString:\n${node.toStringText}', + style: textStyle, + ), ].withSpacing(), ), ), From 99fd695b032e4ddf6537d17365bd895e5b3a1503 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Sun, 24 Mar 2024 21:28:12 +0100 Subject: [PATCH 08/14] Don't return future from setState --- packages/flame_devtools/lib/widgets/debug_mode_button.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/flame_devtools/lib/widgets/debug_mode_button.dart b/packages/flame_devtools/lib/widgets/debug_mode_button.dart index 9f2c3e1f09d..a1669790606 100644 --- a/packages/flame_devtools/lib/widgets/debug_mode_button.dart +++ b/packages/flame_devtools/lib/widgets/debug_mode_button.dart @@ -35,7 +35,9 @@ class _DebugModeButtonState extends State { ? null : () { setState( - () => _debugMode = Repository.swapDebugMode(id: widget.id), + () { + _debugMode = Repository.swapDebugMode(id: widget.id); + }, ); }, child: Text('$buttonPrefix Debug Mode'), From 6f2dfce20b9c7ab79c69b4275a7ce38efafc1d19 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Sun, 24 Mar 2024 21:41:41 +0100 Subject: [PATCH 09/14] Use a split pane for the component tree view --- .../lib/widgets/component_tree.dart | 227 +++++++++--------- 1 file changed, 113 insertions(+), 114 deletions(-) diff --git a/packages/flame_devtools/lib/widgets/component_tree.dart b/packages/flame_devtools/lib/widgets/component_tree.dart index 53bec93da19..0bbf3fc733a 100644 --- a/packages/flame_devtools/lib/widgets/component_tree.dart +++ b/packages/flame_devtools/lib/widgets/component_tree.dart @@ -51,81 +51,82 @@ class _ComponentTreeState extends State { return FutureBuilder( future: _componentTree, builder: (context, value) { - return Row( - crossAxisAlignment: CrossAxisAlignment.start, + return Split( + axis: MediaQuery.of(context).size.width > 1000 + ? Axis.horizontal + : Axis.vertical, + initialFractions: const [0.5, 0.5], + minSizes: const [300, 350], children: [ - Expanded( - child: RoundedOutlinedBorder( - child: Column( - children: [ - AreaPaneHeader( - title: Row( - children: [ - Text( - 'Component Tree ($_componentCount components)', - style: theme.textTheme.titleSmall, - ), - IconButton( - icon: const Icon(Icons.refresh), - iconSize: 18, - alignment: Alignment.center, - onPressed: () => setState(_refreshComponentTree), - ), - ], - ), + RoundedOutlinedBorder( + child: Column( + children: [ + AreaPaneHeader( + title: Row( + children: [ + Text( + 'Component Tree ($_componentCount components)', + style: theme.textTheme.titleSmall, + ), + IconButton( + icon: const Icon(Icons.refresh), + iconSize: 18, + alignment: Alignment.center, + onPressed: () => setState(_refreshComponentTree), + ), + ], ), - if (value.hasData) - Expanded( - child: SingleChildScrollView( - child: TreeView.simple( - showRootNode: false, - shrinkWrap: true, - indentation: const Indentation( - color: Colors.blue, - style: IndentStyle.roundJoint, - ), - onTreeReady: (controller) => - controller.expandAllChildren(controller.tree), - padding: const EdgeInsets.only(left: 20), - expansionIndicatorBuilder: (context, node) => - node.isLeaf - ? NoExpansionIndicator(tree: node) - : ChevronIndicator.rightDown( - tree: node, - alignment: Alignment.centerLeft, - ), - builder: (context, node) { - return Padding( - padding: node.isLeaf - ? EdgeInsets.zero - : const EdgeInsets.only(left: 20), - child: ListTile( - key: Key( - node.data?.id.toString() ?? node.key, - ), - selected: node == _selectedTreeNode, - selectedColor: theme.colorScheme.primary, - title: Text(node.data!.name), - subtitle: Text(node.data!.id.toString()), - onTap: () { - return setState( - () => _selectedTreeNode = node, - ); - }, - ), - ); - }, - tree: _tree, + ), + if (value.hasData) + Expanded( + child: SingleChildScrollView( + child: TreeView.simple( + showRootNode: false, + shrinkWrap: true, + indentation: const Indentation( + color: Colors.blue, + style: IndentStyle.roundJoint, ), + onTreeReady: (controller) => + controller.expandAllChildren(controller.tree), + padding: const EdgeInsets.only(left: 20), + expansionIndicatorBuilder: (context, node) => + node.isLeaf + ? NoExpansionIndicator(tree: node) + : ChevronIndicator.rightDown( + tree: node, + alignment: Alignment.centerLeft, + ), + builder: (context, node) { + return Padding( + padding: node.isLeaf + ? EdgeInsets.zero + : const EdgeInsets.only(left: 20), + child: ListTile( + key: Key( + node.data?.id.toString() ?? node.key, + ), + selected: node == _selectedTreeNode, + selectedColor: theme.colorScheme.primary, + title: Text(node.data!.name), + subtitle: Text(node.data!.id.toString()), + onTap: () { + return setState( + () => _selectedTreeNode = node, + ); + }, + ), + ); + }, + tree: _tree, ), - ) - else - const CircularProgressIndicator(strokeWidth: 20), - ], - ), + ), + ) + else + const CircularProgressIndicator(strokeWidth: 20), + ], ), ), - const SizedBox(width: 20), ComponentView(_selectedTreeNode?.data), ], ); @@ -145,52 +146,50 @@ class ComponentView extends StatelessWidget { final theme = Theme.of(context); final textStyle = theme.textTheme.bodyLarge; - return Expanded( - child: RoundedOutlinedBorder( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AreaPaneHeader( - title: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Selected Component', - style: theme.textTheme.titleSmall, - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.all(20), - child: node == null - ? Text( - 'Select a component in the tree', - style: textStyle, - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text('Id: ${node.id}', style: textStyle), - DebugModeButton(id: node.id), - ].withSpacing(), - ), - Text('Type: ${node.name}', style: textStyle), - Text( - 'Children: ${node.children.length}', - style: textStyle, - ), - Text( - 'toString:\n${node.toStringText}', - style: textStyle, - ), - ].withSpacing(), - ), + return RoundedOutlinedBorder( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AreaPaneHeader( + title: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Selected Component', + style: theme.textTheme.titleSmall, + ), + ], ), - ], - ), + ), + Padding( + padding: const EdgeInsets.all(20), + child: node == null + ? Text( + 'Select a component in the tree', + style: textStyle, + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text('Id: ${node.id}', style: textStyle), + DebugModeButton(id: node.id), + ].withSpacing(), + ), + Text('Type: ${node.name}', style: textStyle), + Text( + 'Children: ${node.children.length}', + style: textStyle, + ), + Text( + 'toString:\n${node.toStringText}', + style: textStyle, + ), + ].withSpacing(), + ), + ), + ], ), ); } From 85624a9d1c4d6765c0a4076a61af8c5f088733dc Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Sun, 24 Mar 2024 21:44:25 +0100 Subject: [PATCH 10/14] Update flame_devtools readme --- packages/flame_devtools/README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/flame_devtools/README.md b/packages/flame_devtools/README.md index 76829ecd511..65d286374ba 100644 --- a/packages/flame_devtools/README.md +++ b/packages/flame_devtools/README.md @@ -1,3 +1,24 @@ + +

+ + flame + +

+ +

+A Flutter-based game engine. +

+ +

+ + + + +

+ +--- + + # flame_devtools A DevTools extension for Flame games. To use it you just have to run your From 2cadf4b6c541ba300b5ab6bf9e9cbfbc98794bd5 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Sun, 24 Mar 2024 21:46:43 +0100 Subject: [PATCH 11/14] Add pre-hook to build devtools before publishing --- melos.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/melos.yaml b/melos.yaml index 8c804738e9b..a16a30fa2ec 100644 --- a/melos.yaml +++ b/melos.yaml @@ -13,6 +13,7 @@ command: branch: main # Generates a link to a prefilled GitHub release creation page. releaseUrl: true + bootstrap: environment: sdk: ">=3.0.0 <4.0.0" @@ -24,6 +25,10 @@ command: dartdoc: ^6.3.0 mocktail: ^1.0.1 test: any + + publish: + hooks: + pre: melos devtools-build scripts: lint:all: From 6ef1e51ec18fe0b0ddde9ca6554eb8821242deb6 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Sun, 24 Mar 2024 22:12:46 +0100 Subject: [PATCH 12/14] Add a key to the debug mode button --- packages/flame_devtools/lib/widgets/debug_mode_button.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/flame_devtools/lib/widgets/debug_mode_button.dart b/packages/flame_devtools/lib/widgets/debug_mode_button.dart index a1669790606..93e2742a72c 100644 --- a/packages/flame_devtools/lib/widgets/debug_mode_button.dart +++ b/packages/flame_devtools/lib/widgets/debug_mode_button.dart @@ -2,10 +2,13 @@ import 'package:flame_devtools/repository.dart'; import 'package:flutter/material.dart'; class DebugModeButton extends StatefulWidget { - const DebugModeButton({this.id, super.key}); + const DebugModeButton({super.key, this.id}); final int? id; + @override + Key? get key => super.key ?? ValueKey(id); + @override State createState() => _DebugModeButtonState(); } From 6e783212ed2b6a923bb49d04bb40c850969c83e8 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Sun, 24 Mar 2024 22:30:31 +0100 Subject: [PATCH 13/14] Add docs about the devtools extension --- doc/flame/other/debug.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/flame/other/debug.md b/doc/flame/other/debug.md index f75607fa3a3..fbd02f3a8ae 100644 --- a/doc/flame/other/debug.md +++ b/doc/flame/other/debug.md @@ -13,6 +13,14 @@ To see a working example of the debugging features of the `FlameGame`, check thi [example](https://github.com/flame-engine/flame/blob/main/examples/lib/stories/components/debug_example.dart). +## Devtools extension + +If you open the [Flutter DevTools](https://docs.flutter.dev/tools/devtools/overview), you will see a +new tab called "Flame". This tab will show you information about the current game, for example a +visualization of the component tree, the ability to play, pause and step the game, information +about the selected component, and more. + + ## FPS The FPS reported from Flame might be a bit lower than what is reported from for example the Flutter From 372487831e3d7fc6b4d8ede14a3ae1a0b3645247 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Mon, 25 Mar 2024 09:14:31 +0100 Subject: [PATCH 14/14] Add .pubignore file so that the extension directory is included in publish --- packages/flame/.pubignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/flame/.pubignore diff --git a/packages/flame/.pubignore b/packages/flame/.pubignore new file mode 100644 index 00000000000..f192e449890 --- /dev/null +++ b/packages/flame/.pubignore @@ -0,0 +1 @@ +!extension/**/*