Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adding x,y,width and height inputs to position components on Dev Tools #3263

Merged
merged 4 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,24 @@ class ComponentTreeNode {
final int id;
final String name;
final String toStringText;
final bool isPositionComponent;
final List<ComponentTreeNode> children;

ComponentTreeNode(this.id, this.name, this.toStringText, this.children);
ComponentTreeNode(
this.id,
this.name,
this.toStringText,
// ignore: avoid_positional_boolean_parameters
this.isPositionComponent,
this.children,
);

ComponentTreeNode.fromComponent(Component component)
: this(
component.hashCode,
component.runtimeType.toString(),
component.toString(),
component is PositionComponent,
component.children.map(ComponentTreeNode.fromComponent).toList(),
);

Expand All @@ -47,6 +56,7 @@ class ComponentTreeNode {
json['id'] as int,
json['name'] as String,
json['toString'] as String,
json['isPositionComponent'] as bool,
(json['children'] as List)
.map((e) => ComponentTreeNode.fromJson(e as Map<String, dynamic>))
.toList(),
Expand All @@ -57,6 +67,7 @@ class ComponentTreeNode {
'id': id,
'name': name,
'toString': toStringText,
'isPositionComponent': isPositionComponent,
'children': children.map((e) => e.toJson()).toList(),
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'dart:convert';
import 'dart:developer';

import 'package:flame/components.dart';
import 'package:flame/src/devtools/dev_tools_connector.dart';

class PositionAttributesComponentConnector extends DevToolsConnector {
erickzanardo marked this conversation as resolved.
Show resolved Hide resolved
@override
void init() {
registerExtension(
'ext.flame_devtools.getPositionComponentAttributes',
(method, parameters) async {
final id = int.tryParse(parameters['id'] ?? '');

final positionComponent = findGameComponent<PositionComponent>(id);

if (positionComponent != null) {
return ServiceExtensionResponse.result(
json.encode({
'id': id,
'x': positionComponent.x,
'y': positionComponent.y,
'width': positionComponent.width,
'height': positionComponent.height,
}),
);
} else {
return ServiceExtensionResponse.error(
ServiceExtensionResponse.extensionError,
'No PositionComponent found with id: $id',
);
}
},
);

registerExtension(
'ext.flame_devtools.setPositionComponentAttributes',
(method, parameters) async {
final id = int.tryParse(parameters['id'] ?? '');
final attribute = parameters['attribute'];

final positionComponent = findGameComponent<PositionComponent>(id);

if (positionComponent != null) {
if (attribute == 'x') {
positionComponent.x = double.parse(parameters['value']!);
} else if (attribute == 'y') {
positionComponent.y = double.parse(parameters['value']!);
} else if (attribute == 'width') {
positionComponent.width = double.parse(parameters['value']!);
} else if (attribute == 'height') {
positionComponent.height = double.parse(parameters['value']!);
} else {
return ServiceExtensionResponse.error(
ServiceExtensionResponse.extensionError,
'Invalid attribute: $attribute',
);
}
return ServiceExtensionResponse.result('Success');
} else {
return ServiceExtensionResponse.error(
ServiceExtensionResponse.extensionError,
'No PositionComponent found with id: $id',
);
}
},
);
}
}
20 changes: 20 additions & 0 deletions packages/flame/lib/src/devtools/dev_tools_connector.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:developer';

import 'package:flame/components.dart';
import 'package:flame/debug.dart';
import 'package:flame/game.dart';
import 'package:flutter/foundation.dart';
Expand Down Expand Up @@ -29,4 +30,23 @@ abstract class DevToolsConnector {

/// Here you can do clean-up before a new game is set in the connector.
void disposeGame() {}

/// Finds a component in the game tree by its id.
///
/// Returns the component if found, otherwise null.
T? findGameComponent<T extends Component>(int? id) {
erickzanardo marked this conversation as resolved.
Show resolved Hide resolved
T? component;
game.propagateToChildren<T>(
(c) {
if (c.hashCode == id) {
component = c;
return false;
}
return true;
},
includeSelf: true,
);

return component;
}
}
2 changes: 2 additions & 0 deletions packages/flame/lib/src/devtools/dev_tools_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flame/src/devtools/connectors/component_snapshot_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/connectors/position_attributes_component_connector.dart';
import 'package:flame/src/devtools/dev_tools_connector.dart';

/// When [DevToolsService] is initialized by the [FlameGame] it will call
Expand Down Expand Up @@ -37,6 +38,7 @@ class DevToolsService {
ComponentTreeConnector(),
GameLoopConnector(),
ComponentSnapshotConnector(),
PositionAttributesComponentConnector(),
];

/// This method is called every time a new game is set in the service and it
Expand Down
52 changes: 52 additions & 0 deletions packages/flame_devtools/lib/repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,56 @@ sealed class Repository {
);
return snapshotResponse.json!['snapshot'] as String?;
}

static Future<PositionComponentAttributes> getPositionComponentAttributes({
int? id,
}) async {
final potentialPositionComponentResponse =
await serviceManager.callServiceExtensionOnMainIsolate(
'ext.flame_devtools.getPositionComponentAttributes',
args: {'id': id},
);

return PositionComponentAttributes.fromJson(
potentialPositionComponentResponse.json!,
);
}
erickzanardo marked this conversation as resolved.
Show resolved Hide resolved

static Future<void> setPositionComponentAttribute({
required String attribute,
required dynamic value,
int? id,
}) async {
await serviceManager.callServiceExtensionOnMainIsolate(
'ext.flame_devtools.setPositionComponentAttributes',
args: {
'id': id,
'attribute': attribute,
'value': value,
},
);
}
}

class PositionComponentAttributes {
final double x;
final double y;
final double width;
final double height;

PositionComponentAttributes({
required this.x,
required this.y,
required this.width,
required this.height,
});

factory PositionComponentAttributes.fromJson(Map<String, dynamic> json) {
return PositionComponentAttributes(
x: json['x'] as double,
y: json['y'] as double,
width: json['width'] as double,
height: json['height'] as double,
);
}
}
8 changes: 8 additions & 0 deletions packages/flame_devtools/lib/widgets/component_tree.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:devtools_app_shared/ui.dart' as devtools_ui;
import 'package:flame_devtools/widgets/component_snapshot.dart';
import 'package:flame_devtools/widgets/component_tree_model.dart';
import 'package:flame_devtools/widgets/debug_mode_button.dart';
import 'package:flame_devtools/widgets/position_component_attributes_form.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

Expand Down Expand Up @@ -152,6 +153,13 @@ class ComponentSection extends ConsumerWidget {
'Children: ${node.children.length}',
style: textStyle,
),
if (node.isPositionComponent)
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: PositionComponentAttributesForm(
componentId: node.id,
),
),
Text(
'toString:\n${node.toStringText}',
style: textStyle,
Expand Down
100 changes: 100 additions & 0 deletions packages/flame_devtools/lib/widgets/incremental_number_form_field.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import 'package:flutter/material.dart';

class IncrementalNumberFormField<T extends num> extends StatefulWidget {
const IncrementalNumberFormField({
required this.initialValue,
required this.label,
this.onChanged,
super.key,
});

final String label;
final T initialValue;
final void Function(T)? onChanged;

@override
State<IncrementalNumberFormField<T>> createState() =>
_IncrementalNumberFormFieldState<T>();
}

class _IncrementalNumberFormFieldState<T extends num>
extends State<IncrementalNumberFormField<T>> {
late final _controller = TextEditingController()
..text = widget.initialValue.toString();

String? errorText;

@override
void dispose() {
_controller.dispose();
super.dispose();
}

T _parse() {
if (T == double) {
return double.parse(_controller.text) as T;
} else {
return int.parse(_controller.text) as T;
}
}

void _tryUpdate(String value) {
try {
final value = _parse();
_update(value);
} on Exception catch (_) {
setState(() {
errorText = 'Invalid number';
});
}
}

void _update(T v) {
setState(() {
errorText = null;
});

widget.onChanged?.call(v);
}

void _increment() {
final value = _parse() + 1 as T;
_update(value);
_controller.text = value.toString();
}

void _decrement() {
final value = _parse() - 1 as T;
_update(value);
_controller.text = value.toString();
}

@override
Widget build(BuildContext context) {
return Row(
children: [
IconButton(
onPressed: _decrement,
icon: const Icon(Icons.remove),
),
const SizedBox(width: 8),
SizedBox(
width: 100,
child: TextField(
decoration: InputDecoration(
labelText: widget.label,
errorText: errorText,
),
controller: _controller,
onChanged: _tryUpdate,
),
),
const SizedBox(width: 8),
IconButton(
onPressed: _increment,
icon: const Icon(Icons.add),
),
],
);
}
}
Loading
Loading