From 50c28811fcfce87ac8aa739e00493e8f7cf30b7f Mon Sep 17 00:00:00 2001 From: Gold87 <91761103+Gold872@users.noreply.github.com> Date: Mon, 30 Dec 2024 21:00:13 -0500 Subject: [PATCH] Added Ability to Rotate Field (#166) * Improved math behind field object position calculation (center origin) * Ability to rotate field --- lib/services/field_images.dart | 11 +- lib/services/nt_widget_builder.dart | 4 +- .../nt_widgets/multi-topic/field_widget.dart | 436 ++++++++++-------- lib/widgets/tab_grid.dart | 6 - .../multi-topic/field_widget_test.dart | 43 +- test/widgets/tab_grid_test.dart | 16 +- test_resources/test-layout.json | 1 + 7 files changed, 312 insertions(+), 205 deletions(-) diff --git a/lib/services/field_images.dart b/lib/services/field_images.dart index a0685305..49872c4f 100644 --- a/lib/services/field_images.dart +++ b/lib/services/field_images.dart @@ -58,13 +58,22 @@ class Field { int? fieldImageWidth; int? fieldImageHeight; + Size? get fieldImageSize => + (fieldImageWidth != null && fieldImageHeight != null) + ? Size(fieldImageWidth!.toDouble(), fieldImageHeight!.toDouble()) + : null; + late double fieldWidthMeters; late double fieldHeightMeters; late Offset topLeftCorner; late Offset bottomRightCorner; - late Offset fieldCenter; + Offset get center => (fieldImageLoaded) + ? Offset(bottomRightCorner.dx - topLeftCorner.dx, + bottomRightCorner.dy - topLeftCorner.dy) / + 2 + : const Offset(0, 0); late Image fieldImage; diff --git a/lib/services/nt_widget_builder.dart b/lib/services/nt_widget_builder.dart index 111fc366..8879da94 100644 --- a/lib/services/nt_widget_builder.dart +++ b/lib/services/nt_widget_builder.dart @@ -216,9 +216,9 @@ class NTWidgetBuilder { model: FieldWidgetModel.new, widget: FieldWidget.new, fromJson: FieldWidgetModel.fromJson, - minWidth: _normalSize * 3, + minWidth: _normalSize * 2, minHeight: _normalSize * 2, - defaultWidth: 3, + defaultWidth: 2, defaultHeight: 2); register( diff --git a/lib/widgets/nt_widgets/multi-topic/field_widget.dart b/lib/widgets/nt_widgets/multi-topic/field_widget.dart index 8685e2fc..d013f247 100644 --- a/lib/widgets/nt_widgets/multi-topic/field_widget.dart +++ b/lib/widgets/nt_widgets/multi-topic/field_widget.dart @@ -18,6 +18,15 @@ import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_text_input.dart' import 'package:elastic_dashboard/widgets/dialog_widgets/dialog_toggle_switch.dart'; import 'package:elastic_dashboard/widgets/nt_widgets/nt_widget.dart'; +extension _SizeUtils on Size { + Offset get toOffset => Offset(width, height); + + Size rotateBy(double angle) => Size( + (width * cos(angle) - height * sin(angle)).abs(), + (height * cos(angle) + width * sin(angle)).abs(), + ); +} + class FieldWidgetModel extends MultiTopicNTWidgetModel { @override String type = 'Field'; @@ -48,6 +57,8 @@ class FieldWidgetModel extends MultiTopicNTWidgetModel { bool _showOtherObjects = true; bool _showTrajectories = true; + double _fieldRotation = 0.0; + Color _robotColor = Colors.red; Color _trajectoryColor = Colors.white; @@ -84,6 +95,13 @@ class FieldWidgetModel extends MultiTopicNTWidgetModel { refresh(); } + double get fieldRotation => _fieldRotation; + + set fieldRotation(double value) { + _fieldRotation = value; + refresh(); + } + Color get robotColor => _robotColor; set robotColor(Color value) { @@ -114,11 +132,12 @@ class FieldWidgetModel extends MultiTopicNTWidgetModel { required super.ntConnection, required super.preferences, required super.topic, - String? fieldName, + String? fieldGame, bool showOtherObjects = true, bool showTrajectories = true, double robotWidthMeters = 0.85, double robotLengthMeters = 0.85, + double fieldRotation = 0.0, Color robotColor = Colors.red, Color trajectoryColor = Colors.white, super.dataType, @@ -127,10 +146,11 @@ class FieldWidgetModel extends MultiTopicNTWidgetModel { _showOtherObjects = showOtherObjects, _robotWidthMeters = robotWidthMeters, _robotLengthMeters = robotLengthMeters, + _fieldRotation = fieldRotation, _robotColor = robotColor, _trajectoryColor = trajectoryColor, super() { - _fieldGame = fieldName ?? _fieldGame; + _fieldGame = fieldGame ?? _fieldGame; if (!FieldImages.hasField(_fieldGame)) { _fieldGame = _defaultGame; @@ -154,15 +174,17 @@ class FieldWidgetModel extends MultiTopicNTWidgetModel { _showOtherObjects = tryCast(jsonData['show_other_objects']) ?? true; _showTrajectories = tryCast(jsonData['show_trajectories']) ?? true; + _fieldRotation = tryCast(jsonData['field_rotation']) ?? 0.0; + + _robotColor = Color(tryCast(jsonData['robot_color']) ?? Colors.red.value); + _trajectoryColor = + Color(tryCast(jsonData['trajectory_color']) ?? Colors.white.value); + if (!FieldImages.hasField(_fieldGame)) { _fieldGame = _defaultGame; } _field = FieldImages.getFieldFromGame(_fieldGame)!; - - _robotColor = Color(tryCast(jsonData['robot_color']) ?? Colors.red.value); - _trajectoryColor = - Color(tryCast(jsonData['trajectory_color']) ?? Colors.white.value); } @override @@ -177,6 +199,7 @@ class FieldWidgetModel extends MultiTopicNTWidgetModel { _otherObjectTopics.add(nt4Topic.name); _otherObjectSubscriptions .add(ntConnection.subscribe(nt4Topic.name, super.period)); + refresh(); } }; @@ -223,6 +246,7 @@ class FieldWidgetModel extends MultiTopicNTWidgetModel { 'robot_length': _robotLengthMeters, 'show_other_objects': _showOtherObjects, 'show_trajectories': _showTrajectories, + 'field_rotation': _fieldRotation, 'robot_color': robotColor.value, 'trajectory_color': trajectoryColor.value, }; @@ -365,6 +389,54 @@ class FieldWidgetModel extends MultiTopicNTWidgetModel { ), ], ), + const SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: OutlinedButton.icon( + style: OutlinedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5.0), + ), + ), + label: const Text('Rotate Left'), + icon: const Icon(Icons.rotate_90_degrees_ccw), + onPressed: () { + double newRotation = fieldRotation - 90; + if (newRotation < -180) { + newRotation += 360; + } + fieldRotation = newRotation; + }, + ), + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: OutlinedButton.icon( + style: OutlinedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5.0), + ), + ), + label: const Text('Rotate Right'), + icon: const Icon(Icons.rotate_90_degrees_cw), + onPressed: () { + double newRotation = fieldRotation + 90; + if (newRotation > 180) { + newRotation -= 360; + } + fieldRotation = newRotation; + }, + ), + ), + ), + ], + ), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, @@ -407,24 +479,13 @@ class FieldWidget extends NTWidget { const FieldWidget({super.key}); - double _getBackgroundFitWidth(FieldWidgetModel model, Size size) { - double fitWidth = size.width; - double fitHeight = size.height; - - return min( - fitWidth, - fitHeight / - ((model._field.fieldImageHeight ?? 0) / - (model._field.fieldImageWidth ?? 1))); - } - Widget _getTransformedFieldObject( - FieldWidgetModel model, - List objectPosition, - Offset center, - Offset fieldCenter, - double scaleReduction, - {Size? objectSize}) { + FieldWidgetModel model, { + required List objectPosition, + required Offset fieldCenter, + required double scaleReduction, + Size? objectSize, + }) { for (int i = 0; i < objectPosition.length; i++) { if (!objectPosition[i].isFinite) { objectPosition[i] = 0.0; @@ -432,17 +493,14 @@ class FieldWidget extends NTWidget { } double xFromCenter = - (objectPosition[0]) * model.field.pixelsPerMeterHorizontal - - fieldCenter.dx; - - double yFromCenter = fieldCenter.dy - - (objectPosition[1]) * model.field.pixelsPerMeterVertical; - - Offset positionOffset = center + - (Offset(xFromCenter + model.field.topLeftCorner.dx, - yFromCenter - model.field.topLeftCorner.dy)) * + (objectPosition[0] * model.field.pixelsPerMeterHorizontal - + fieldCenter.dx) * scaleReduction; + double yFromCenter = (fieldCenter.dy - + (objectPosition[1] * model.field.pixelsPerMeterVertical)) * + scaleReduction; + double width = (objectSize?.width ?? model.otherObjectSize) * model.field.pixelsPerMeterHorizontal * scaleReduction; @@ -451,8 +509,7 @@ class FieldWidget extends NTWidget { model.field.pixelsPerMeterVertical * scaleReduction; - Matrix4 transform = Matrix4.translationValues( - positionOffset.dx - length / 2, positionOffset.dy - width / 2, 0.0) + Matrix4 transform = Matrix4.translationValues(xFromCenter, yFromCenter, 0.0) ..rotateZ(-radians(objectPosition[2])); Widget otherObject = Container( @@ -465,15 +522,16 @@ class FieldWidget extends NTWidget { color: Colors.black.withOpacity(0.35), border: Border.all( color: model.robotColor, - width: 4.0, + width: 0.125 * min(width, length), ), ), width: length, height: width, child: CustomPaint( - size: Size(width * 0.25, width * 0.25), - painter: - TrianglePainter(strokeColor: const Color.fromARGB(255, 0, 255, 0)), + size: Size(length * 0.275, width * 0.275), + painter: TrianglePainter( + strokeWidth: 0.08 * min(width, length), + ), ), ); @@ -484,12 +542,12 @@ class FieldWidget extends NTWidget { ); } - Offset _getTransformedTrajectoryPoint( - FieldWidgetModel model, - List objectPosition, - Offset center, - Offset fieldCenter, - double scaleReduction) { + Offset _getTrajectoryPointOffset( + FieldWidgetModel model, { + required List objectPosition, + required Offset fieldCenter, + required double scaleReduction, + }) { for (int i = 0; i < objectPosition.length; i++) { if (!objectPosition[i].isFinite) { objectPosition[i] = 0.0; @@ -497,18 +555,15 @@ class FieldWidget extends NTWidget { } double xFromCenter = - (objectPosition[0]) * model.field.pixelsPerMeterHorizontal - - fieldCenter.dx; - - double yFromCenter = fieldCenter.dy - - (objectPosition[1]) * model.field.pixelsPerMeterVertical; - - Offset positionOffset = center + - (Offset(xFromCenter + model.field.topLeftCorner.dx, - yFromCenter - model.field.topLeftCorner.dy)) * + (objectPosition[0] * model.field.pixelsPerMeterHorizontal - + fieldCenter.dx) * scaleReduction; - return positionOffset; + double yFromCenter = (fieldCenter.dy - + (objectPosition[1] * model.field.pixelsPerMeterVertical)) * + scaleReduction; + + return Offset(xFromCenter, yFromCenter); } @override @@ -521,136 +576,153 @@ class FieldWidget extends NTWidget { listeners.addAll(model._otherObjectSubscriptions); } - return ListenableBuilder( - listenable: Listenable.merge(listeners), - child: model.field.fieldImage, - builder: (context, child) { - List robotPositionRaw = - model.robotSubscription.value?.tryCast>() ?? []; - - List? robotPosition = []; - if (robotPositionRaw.isEmpty) { - robotPosition = null; - } else { - robotPosition = robotPositionRaw.whereType().toList(); - } - - RenderBox? renderBox = - context.findAncestorRenderObjectOfType(); - - Size size = (renderBox == null || !renderBox.hasSize) - ? model.widgetSize ?? const Size(0, 0) - : renderBox.size; - - if (size != const Size(0, 0)) { - model.widgetSize = size; - } - - Offset center = Offset(size.width / 2, size.height / 2); - Offset fieldCenter = Offset( - (model.field.fieldImageWidth?.toDouble() ?? 0.0), - (model.field.fieldImageHeight?.toDouble() ?? 0.0)) / - 2; - - double scaleReduction = (_getBackgroundFitWidth(model, size)) / - (model.field.fieldImageWidth ?? 1); - - if (!model.rendered && - renderBox != null && - model.widgetSize != null && - size != const Size(0, 0) && - size.width > 100.0 && - scaleReduction != 0.0 && - fieldCenter != const Offset(0.0, 0.0) && - model.field.fieldImageLoaded) { - model.rendered = true; - } - - // Try rebuilding again if the image isn't fully rendered - // Can't do it if it's in a unit test cause it causes issues with timers running - if (!model.rendered && - !Platform.environment.containsKey('FLUTTER_TEST')) { - Future.delayed(const Duration(milliseconds: 100), model.refresh); - } - - Widget robot = _getTransformedFieldObject( - model, - robotPosition ?? [0.0, 0.0, 0.0], - center, - fieldCenter, - scaleReduction, - objectSize: Size(model.robotWidthMeters, model.robotLengthMeters)); - - List otherObjects = []; - List> trajectoryPoints = []; - - if (model.showOtherObjects || model.showTrajectories) { - for (NT4Subscription objectSubscription - in model._otherObjectSubscriptions) { - List? objectPositionRaw = - objectSubscription.value?.tryCast>(); - - if (objectPositionRaw == null) { - continue; + return LayoutBuilder( + builder: (context, constraints) { + return ListenableBuilder( + listenable: Listenable.merge(listeners), + child: model.field.fieldImage, + builder: (context, child) { + List robotPositionRaw = + model.robotSubscription.value?.tryCast>() ?? []; + + List? robotPosition = []; + if (robotPositionRaw.isEmpty) { + robotPosition = null; + } else { + robotPosition = robotPositionRaw.whereType().toList(); } - bool isTrajectory = objectPositionRaw.length > 24; + Size size = Size(constraints.maxWidth, constraints.maxHeight); + + model.widgetSize = size; + + FittedSizes fittedSizes = applyBoxFit( + BoxFit.contain, + model.field.fieldImageSize ?? const Size(0, 0), + size, + ); + + FittedSizes rotatedFittedSizes = applyBoxFit( + BoxFit.contain, + model.field.fieldImageSize + ?.rotateBy(-radians(model.fieldRotation)) ?? + const Size(0, 0), + size, + ); + + Offset fittedCenter = fittedSizes.destination.toOffset / 2; + Offset fieldCenter = model.field.center; + + double scaleReduction = + (fittedSizes.destination.width / fittedSizes.source.width); + double rotatedScaleReduction = + (rotatedFittedSizes.destination.width / + rotatedFittedSizes.source.width); + + if (!model.rendered && + model.widgetSize != null && + size != const Size(0, 0) && + size.width > 100.0 && + scaleReduction != 0.0 && + fieldCenter != const Offset(0.0, 0.0) && + model.field.fieldImageLoaded) { + model.rendered = true; + } - if (isTrajectory && !model.showTrajectories) { - continue; - } else if (!model.showOtherObjects && !isTrajectory) { - continue; + // Try rebuilding again if the image isn't fully rendered + // Can't do it if it's in a unit test cause it causes issues with timers running + if (!model.rendered && + !Platform.environment.containsKey('FLUTTER_TEST')) { + Future.delayed(const Duration(milliseconds: 100), model.refresh); } - List objectPosition = - objectPositionRaw.whereType().toList(); + Widget robot = _getTransformedFieldObject( + model, + objectPosition: robotPosition ?? [0.0, 0.0, 0.0], + fieldCenter: fieldCenter, + scaleReduction: scaleReduction, + objectSize: Size(model.robotWidthMeters, model.robotLengthMeters), + ); + + List otherObjects = []; + List> trajectoryPoints = []; + + if (model.showOtherObjects || model.showTrajectories) { + for (NT4Subscription objectSubscription + in model._otherObjectSubscriptions) { + List? objectPositionRaw = + objectSubscription.value?.tryCast>(); + + if (objectPositionRaw == null) { + continue; + } - if (isTrajectory) { - trajectoryPoints.add([]); - } + bool isTrajectory = objectPositionRaw.length > 24; - for (int i = 0; i < objectPosition.length - 2; i += 3) { - if (isTrajectory) { - trajectoryPoints.last.add( - _getTransformedTrajectoryPoint( - model, - objectPosition.sublist(i, i + 2), - center, - fieldCenter, - scaleReduction, - ), - ); - } else { - otherObjects.add( - _getTransformedFieldObject( - model, - objectPosition.sublist(i, i + 3), - center, - fieldCenter, - scaleReduction, - ), - ); + if (isTrajectory && !model.showTrajectories) { + continue; + } else if (!model.showOtherObjects && !isTrajectory) { + continue; + } + + List objectPosition = + objectPositionRaw.whereType().toList(); + + if (isTrajectory) { + trajectoryPoints.add([]); + } + + for (int i = 0; i < objectPosition.length - 2; i += 3) { + if (isTrajectory) { + trajectoryPoints.last.add( + _getTrajectoryPointOffset( + model, + objectPosition: objectPosition.sublist(i, i + 2), + fieldCenter: fieldCenter, + scaleReduction: scaleReduction, + ), + ); + } else { + otherObjects.add( + _getTransformedFieldObject( + model, + objectPosition: objectPosition.sublist(i, i + 3), + fieldCenter: fieldCenter, + scaleReduction: scaleReduction, + ), + ); + } + } } } - } - } - - return Stack( - children: [ - child!, - for (List points in trajectoryPoints) - CustomPaint( - painter: TrajectoryPainter( - color: model.trajectoryColor, - points: points, - strokeWidth: model.trajectoryPointSize * - model.field.pixelsPerMeterHorizontal * - scaleReduction, + + return Transform.scale( + scale: rotatedScaleReduction / scaleReduction, + child: Transform.rotate( + angle: radians(model.fieldRotation), + child: Stack( + alignment: Alignment.center, + children: [ + child!, + for (List points in trajectoryPoints) + CustomPaint( + size: fittedSizes.destination, + painter: TrajectoryPainter( + center: fittedCenter, + color: model.trajectoryColor, + points: points, + strokeWidth: model.trajectoryPointSize * + model.field.pixelsPerMeterHorizontal * + scaleReduction, + ), + ), + robot, + ...otherObjects, + ], ), ), - robot, - ...otherObjects, - ], + ); + }, ); }, ); @@ -662,10 +734,11 @@ class TrianglePainter extends CustomPainter { final PaintingStyle paintingStyle; final double strokeWidth; - TrianglePainter( - {this.strokeColor = Colors.white, - this.strokeWidth = 3, - this.paintingStyle = PaintingStyle.stroke}); + TrianglePainter({ + this.strokeColor = Colors.white, + this.strokeWidth = 3, + this.paintingStyle = PaintingStyle.stroke, + }); @override void paint(Canvas canvas, Size size) { @@ -695,11 +768,13 @@ class TrianglePainter extends CustomPainter { } class TrajectoryPainter extends CustomPainter { + final Offset center; final List points; final double strokeWidth; final Color color; TrajectoryPainter({ + required this.center, required this.points, required this.strokeWidth, this.color = Colors.white, @@ -717,10 +792,10 @@ class TrajectoryPainter extends CustomPainter { ..strokeCap = StrokeCap.round; Path trajectoryPath = Path(); - trajectoryPath.moveTo(points[0].dx, points[0].dy); + trajectoryPath.moveTo(points[0].dx + center.dx, points[0].dy + center.dy); for (Offset point in points) { - trajectoryPath.lineTo(point.dx, point.dy); + trajectoryPath.lineTo(point.dx + center.dx, point.dy + center.dy); } canvas.drawPath(trajectoryPath, trajectoryPaint); } @@ -728,6 +803,7 @@ class TrajectoryPainter extends CustomPainter { @override bool shouldRepaint(TrajectoryPainter oldDelegate) { return oldDelegate.points != points || - oldDelegate.strokeWidth != strokeWidth; + oldDelegate.strokeWidth != strokeWidth || + oldDelegate.color != color; } } diff --git a/lib/widgets/tab_grid.dart b/lib/widgets/tab_grid.dart index cfa23f6f..5fb19912 100644 --- a/lib/widgets/tab_grid.dart +++ b/lib/widgets/tab_grid.dart @@ -14,7 +14,6 @@ import 'package:elastic_dashboard/services/settings.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/draggable_list_layout.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/draggable_nt_widget_container.dart'; import 'package:elastic_dashboard/widgets/draggable_containers/draggable_widget_container.dart'; -import 'package:elastic_dashboard/widgets/nt_widgets/multi-topic/field_widget.dart'; import 'draggable_containers/models/layout_container_model.dart'; import 'draggable_containers/models/list_layout_model.dart'; import 'draggable_containers/models/nt_widget_container_model.dart'; @@ -328,11 +327,6 @@ class TabGridModel extends ChangeNotifier { model.previewVisible = false; model.validLocation = true; - if (model is NTWidgetContainerModel && - model.childModel is FieldWidgetModel) { - model.childModel.refresh(); - } - model.disposeModel(); } diff --git a/test/widgets/nt_widgets/multi-topic/field_widget_test.dart b/test/widgets/nt_widgets/multi-topic/field_widget_test.dart index c38de3fd..3b8a5a01 100644 --- a/test/widgets/nt_widgets/multi-topic/field_widget_test.dart +++ b/test/widgets/nt_widgets/multi-topic/field_widget_test.dart @@ -29,6 +29,7 @@ void main() { 'robot_length': 1.0, 'show_other_objects': true, 'show_trajectories': true, + 'field_rotation': 90.0, 'robot_color': Colors.red.value, 'trajectory_color': Colors.white.value, }; @@ -83,6 +84,7 @@ void main() { expect(fieldWidgetModel.robotLengthMeters, 1.0); expect(fieldWidgetModel.showOtherObjects, isTrue); expect(fieldWidgetModel.showTrajectories, isTrue); + expect(fieldWidgetModel.fieldRotation, 90.0); expect(fieldWidgetModel.robotColor.value, Colors.red.value); expect(fieldWidgetModel.trajectoryColor.value, Colors.white.value); }); @@ -106,6 +108,7 @@ void main() { expect(fieldWidgetModel.robotLengthMeters, 1.0); expect(fieldWidgetModel.showOtherObjects, isTrue); expect(fieldWidgetModel.showTrajectories, isTrue); + expect(fieldWidgetModel.fieldRotation, 90.0); expect(fieldWidgetModel.robotColor.value, Colors.red.value); expect(fieldWidgetModel.trajectoryColor.value, Colors.white.value); }); @@ -116,11 +119,12 @@ void main() { preferences: preferences, period: 0.100, topic: 'Test/Field', - fieldName: 'Crescendo', + fieldGame: 'Crescendo', showOtherObjects: true, showTrajectories: true, robotWidthMeters: 1.0, robotLengthMeters: 1.0, + fieldRotation: 90.0, robotColor: Colors.red, trajectoryColor: Colors.white, ); @@ -227,11 +231,12 @@ void main() { preferences: preferences, period: 0.100, topic: 'Test/Field', - fieldName: 'Crescendo', + fieldGame: 'Crescendo', showOtherObjects: true, showTrajectories: true, robotWidthMeters: 1.0, robotLengthMeters: 1.0, + fieldRotation: 90.0, robotColor: Colors.red, trajectoryColor: Colors.white, ); @@ -272,6 +277,14 @@ void main() { find.widgetWithText(DialogToggleSwitch, 'Show Non-Robot Objects'); final showTrajectories = find.widgetWithText(DialogToggleSwitch, 'Show Trajectories'); + final rotateLeft = find.ancestor( + of: find.text('Rotate Left'), + matching: find.byWidgetPredicate((widget) => widget is OutlinedButton), + ); + final rotateRight = find.ancestor( + of: find.text('Rotate Right'), + matching: find.byWidgetPredicate((widget) => widget is OutlinedButton), + ); final robotColor = find.widgetWithText(DialogColorPicker, 'Robot Color'); final trajectoryColor = find.widgetWithText(DialogColorPicker, 'Trajectory Color'); @@ -281,21 +294,21 @@ void main() { expect(length, findsOneWidget); expect(showNonRobot, findsOneWidget); expect(showTrajectories, findsOneWidget); + expect(rotateLeft, findsOneWidget); + expect(rotateRight, findsOneWidget); expect(robotColor, findsOneWidget); expect(trajectoryColor, findsOneWidget); + await widgetTester.ensureVisible(game); await widgetTester.tap(game); await widgetTester.pumpAndSettle(); - final chargedUpButton = find.widgetWithText( - DropdownMenuItem, - 'Charged Up', - ); + final chargedUpButton = find.text('Charged Up'); expect(chargedUpButton, findsOneWidget); - await widgetTester.ensureVisible(chargedUpButton); await widgetTester.tap(chargedUpButton); await widgetTester.pumpAndSettle(); + expect(fieldWidgetModel.field.game, 'Charged Up'); await widgetTester.enterText(width, '0.50'); @@ -327,5 +340,21 @@ void main() { ); await widgetTester.pumpAndSettle(); expect(fieldWidgetModel.showTrajectories, false); + + await widgetTester.tap(rotateRight); + await widgetTester.pumpAndSettle(); + expect(fieldWidgetModel.fieldRotation, 180.0); + + await widgetTester.tap(rotateRight); + await widgetTester.pumpAndSettle(); + expect(fieldWidgetModel.fieldRotation, -90.0); + + await widgetTester.tap(rotateLeft); + await widgetTester.pumpAndSettle(); + expect(fieldWidgetModel.fieldRotation, -180.0); + + await widgetTester.tap(rotateLeft); + await widgetTester.pumpAndSettle(); + expect(fieldWidgetModel.fieldRotation, 90.0); }); } diff --git a/test/widgets/tab_grid_test.dart b/test/widgets/tab_grid_test.dart index 342d5e73..b63fcc73 100644 --- a/test/widgets/tab_grid_test.dart +++ b/test/widgets/tab_grid_test.dart @@ -195,10 +195,10 @@ void main() async { await widgetTester.pumpAndSettle(); expect(find.text('Test Number'), findsAtLeastNWidgets(2)); + expect(find.byType(TextDisplay), findsOneWidget); expect(find.text('Edit Properties'), findsOneWidget); await widgetTester.tap(find.text('Edit Properties')); - await widgetTester.pumpAndSettle(); expect(find.text('Container Settings'), findsOneWidget); @@ -214,23 +214,21 @@ void main() async { expect(find.text('Editing Title Test'), findsAtLeastNWidgets(2)); - final widgetTypeSelection = - find.widgetWithText(DropdownMenuItem, 'Text Display'); + final widgetTypeSelection = find.text('Text Display'); expect(widgetTypeSelection, findsOneWidget); await widgetTester.tap(widgetTypeSelection); await widgetTester.pumpAndSettle(); - expect(find.widgetWithText(DropdownMenuItem, 'Text Display'), - findsAtLeastNWidgets(2)); - expect( - find.widgetWithText(DropdownMenuItem, 'Graph'), findsNothing); + expect(find.text('Text Display'), findsNWidgets(2)); + expect(find.text('Graph'), findsNothing); - await widgetTester.tap( - find.widgetWithText(DropdownMenuItem, 'Text Display').last); + await widgetTester.tap(find.text('Text Display').last); await widgetTester.pumpAndSettle(); + expect(find.byType(TextDisplay), findsOneWidget); + final closeButton = find.widgetWithText(TextButton, 'Close'); expect(closeButton, findsOneWidget); diff --git a/test_resources/test-layout.json b/test_resources/test-layout.json index c4878b34..537a7259 100644 --- a/test_resources/test-layout.json +++ b/test_resources/test-layout.json @@ -93,6 +93,7 @@ "robot_length": 1.0, "show_other_objects": true, "show_trajectories": true, + "field_rotation": 0.0, "robot_color": 4294198070, "trajectory_color": 4294967295 }