From 06183fa710b4eed02044ec6b3702845d39524101 Mon Sep 17 00:00:00 2001 From: Terence Tong Date: Thu, 24 Feb 2022 12:05:32 +0800 Subject: [PATCH] Thu Feb 24 12:05:32 PM HKT 2022 --- example/pspad/README.md | 16 ++ example/pspad/lib/main.dart | 180 +++++++++++++++ example/pspad/lib/pad.dart | 448 ++++++++++++++++++++++++++++++++++++ example/pspad/pubspec.yaml | 90 ++++++++ 4 files changed, 734 insertions(+) create mode 100644 example/pspad/README.md create mode 100644 example/pspad/lib/main.dart create mode 100644 example/pspad/lib/pad.dart create mode 100644 example/pspad/pubspec.yaml diff --git a/example/pspad/README.md b/example/pspad/README.md new file mode 100644 index 0000000..80d2e9d --- /dev/null +++ b/example/pspad/README.md @@ -0,0 +1,16 @@ +# pspad + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/example/pspad/lib/main.dart b/example/pspad/lib/main.dart new file mode 100644 index 0000000..b86db13 --- /dev/null +++ b/example/pspad/lib/main.dart @@ -0,0 +1,180 @@ +import 'package:flutter/material.dart'; +import 'pad.dart'; +import 'package:roslibdart/roslibdart.dart'; +import 'dart:async'; +import 'dart:convert'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + String title = 'PS Pad'; + return MaterialApp( + title: title, + home: RoslibPSPad(title: title), + ); + } +} + +class RoslibPSPad extends StatefulWidget { + const RoslibPSPad({Key? key, required this.title}) : super(key: key); + + final String title; + + @override + State createState() => _RoslibPSPadState(); +} + +class _RoslibPSPadState extends State { + String host = 'ws://127.0.0.1:9090'; + late Ros ros; + late Topic cmdVelTopic; + double turnAngularVelocity = 1.5; + double forwardVelocity = 1.5; + + @override + void initState() { + ros = Ros(url: host); + cmdVelTopic = Topic( + ros: ros, + name: '/turtle1/cmd_vel', + type: "geometry_msgs/msg/Twist", + reconnectOnClose: true, + queueLength: 10, + queueSize: 10); + + ros.connect(); + super.initState(); + Timer.periodic(const Duration(milliseconds: 200), directionFired); + Timer.periodic(const Duration(milliseconds: 200), keyFired); + } + + int leftButtonState = 1; + int rightButtonState = 1; + int forwardButtonState = 1; + int backwardButtonState = 1; + int triangleButtonState = 1; + int squareButtonState = 1; + int circleButtonState = 1; + int crossButtonState = 1; + + var lastDirectionTwist = {}; + var lastKeyTwist = {}; + void directionFired(Timer timer) { + var linear = {'x': 0.0, 'y': 0.0, 'z': 0.0}; + var angular = {'x': 0.0, 'y': 0.0, 'z': 0.0}; + if (leftButtonState == -1) { + linear = {'x': 0.0, 'y': 0.0, 'z': 0.0}; + angular = {'x': 0.0, 'y': 0.0, 'z': turnAngularVelocity}; + } else if (rightButtonState == -1) { + linear = {'x': 0.0, 'y': 0.0, 'z': 0.0}; + angular = {'x': 0.0, 'y': 0.0, 'z': -turnAngularVelocity}; + } else if (forwardButtonState == -1) { + linear = {'x': forwardVelocity, 'y': 0.0, 'z': 0.0}; + angular = {'x': 0.0, 'y': 0.0, 'z': 0}; + } else if (backwardButtonState == -1) { + linear = {'x': -forwardVelocity, 'y': 0.0, 'z': 0.0}; + angular = {'x': 0.0, 'y': 0.0, 'z': 0}; + } + var twist = {'linear': linear, 'angular': angular}; + if (lastDirectionTwist == twist && + twist['linear'] == {'x': 0.0, 'y': 0.0, 'z': 0.0} && + twist['angular'] == {'x': 0.0, 'y': 0.0, 'z': 0.0}) { + return; + } + + cmdVelTopic.publish(twist); + lastDirectionTwist = twist; + } + + void keyFired(Timer timer) { + var keys = { + 'triangle': triangleButtonState, + 'square': squareButtonState, + 'circle': circleButtonState, + 'cross': crossButtonState + }; + + if (lastKeyTwist == keys && + keys['triangle'] == 1 && + keys['square'] == 1 && + keys['circle'] == 1 && + keys['cross'] == 1) { + return; + } + print('Key to published ' + jsonEncode(keys)); + lastKeyTwist = keys; + } + + void leftCallback(int event) { + leftButtonState = event; + } + + void rightCallback(int event) { + rightButtonState = event; + } + + void forwardCallback(int event) { + forwardButtonState = event; + } + + void backwardCallback(int event) { + backwardButtonState = event; + } + + void squareCallback(int event) { + squareButtonState = event; + } + + void circleCallback(int event) { + circleButtonState = event; + } + + void triangleCallback(int event) { + triangleButtonState = event; + } + + void crossCallback(int event) { + crossButtonState = event; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: Center( + // Center is a layout widget. It takes a single child and positions it + // in the middle of the parent. + child: Container( + margin: const EdgeInsets.all(50), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + DirectionPad( + diameter: 200, + leftCallback: leftCallback, + rightCallback: rightCallback, + forwardCallback: forwardCallback, + backwardCallback: backwardCallback, + ), + KeyPad( + diameter: 200, + squareCallback: squareCallback, + circleCallback: circleCallback, + triangleCallback: triangleCallback, + crossCallback: crossCallback, + ), + ], + ), + )), + ); + } +} diff --git a/example/pspad/lib/pad.dart b/example/pspad/lib/pad.dart new file mode 100644 index 0000000..cecd258 --- /dev/null +++ b/example/pspad/lib/pad.dart @@ -0,0 +1,448 @@ +import 'package:flutter/material.dart'; +import 'dart:math'; + +class TrianglePainter extends CustomPainter { + double sideSize; + + Color color; + TrianglePainter({required this.sideSize, required this.color}); + + @override + void paint(Canvas canvas, Size size) { + double ySize = sideSize * cos(30 * pi / 180); + double xSize = sideSize; + + double point0x = xSize / 2; + double point0y = ySize / 2; + + double point1x = -xSize / 2; + double point1y = ySize / 2; + + double point2x = 0; + double point2y = -ySize / 2; + + Path path = Path(); + path.moveTo(point0x, point0y); + path.lineTo(point1x, point1y); + path.lineTo(point2x, point2y); + path.lineTo(point0x, point0y); + path.close(); + canvas.drawPath( + path, + Paint() + ..color = color + ..strokeWidth = 4 + ..style = PaintingStyle.stroke); + + canvas.save(); + canvas.restore(); + } + + @override + bool shouldRepaint(TrianglePainter oldDelegate) { + return oldDelegate.color != color || oldDelegate.sideSize != sideSize; + } +} + +Widget triangle(double sideSize, Color color, double angle) { + return Transform.rotate( + child: CustomPaint( + painter: TrianglePainter( + color: color, + sideSize: sideSize, + ), + ), + angle: angle * pi / 180); +} + +class SquarePainter extends CustomPainter { + double xSize, ySize; + + Color color; + SquarePainter( + {required this.xSize, required this.ySize, required this.color}); + + @override + void paint(Canvas canvas, Size size) { + double point0x = xSize / 2; + double point0y = ySize / 2; + + double point1x = -xSize / 2; + double point1y = ySize / 2; + + double point2x = -xSize / 2; + double point2y = -ySize / 2; + + double point3x = xSize / 2; + double point3y = -ySize / 2; + + Path path = Path(); + path.moveTo(point0x, point0y); + path.lineTo(point1x, point1y); + path.lineTo(point2x, point2y); + path.lineTo(point3x, point3y); + path.lineTo(point0x, point0y); + path.close(); + canvas.drawPath( + path, + Paint() + ..color = color + ..strokeWidth = 4 + ..style = PaintingStyle.stroke); + + canvas.save(); + canvas.restore(); + } + + @override + bool shouldRepaint(SquarePainter oldDelegate) { + return oldDelegate.color != color || + oldDelegate.xSize != xSize || + oldDelegate.ySize != ySize; + } +} + +Widget square(double size, Color color) { + return CustomPaint( + painter: SquarePainter( + color: color, + xSize: size, + ySize: size, + )); +} + +Widget circle(double diameter, Color? color) { + return Container( + width: diameter, + height: diameter, + decoration: BoxDecoration( + color: color, + shape: BoxShape.circle, + ), + ); +} + +Widget hollowCircle(double diameter, Color color) { + return Container( + width: diameter, + height: diameter, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + width: 4, + color: color, + ), + ), + ); +} + +class CrossPainter extends CustomPainter { + double xSize, ySize; + + Color color; + CrossPainter({required this.xSize, required this.ySize, required this.color}); + + @override + void paint(Canvas canvas, Size size) { + double point0x = xSize / 2; + double point0y = ySize / 2; + + double point1x = -xSize / 2; + double point1y = ySize / 2; + + double point2x = -xSize / 2; + double point2y = -ySize / 2; + + double point3x = xSize / 2; + double point3y = -ySize / 2; + + final paint = Paint() + ..color = color + ..strokeWidth = 4; + canvas.drawLine(Offset(point0x, point0y), Offset(point2x, point2y), paint); + canvas.drawLine(Offset(point1x, point1y), Offset(point3x, point3y), paint); + } + + @override + bool shouldRepaint(CrossPainter oldDelegate) { + return oldDelegate.color != color || + oldDelegate.xSize != xSize || + oldDelegate.ySize != ySize; + } +} + +Widget cross(double size, Color color) { + return CustomPaint( + painter: CrossPainter( + color: color, + xSize: size, + ySize: size, + )); +} + +class DirectionPad extends StatefulWidget { + final double diameter; + final Function leftCallback, rightCallback, forwardCallback, backwardCallback; + const DirectionPad( + {Key? key, + required this.diameter, + required this.leftCallback, + required this.rightCallback, + required this.forwardCallback, + required this.backwardCallback}) + : super(key: key); + + @override + State createState() => _DirectionPadState(); +} + +class _DirectionPadState extends State { + Widget horizontalBar(double longSize, double shortSize, Color color) { + return Container( + width: longSize, + height: shortSize, + decoration: BoxDecoration( + color: color, + border: Border.all( + color: color, + ), + borderRadius: const BorderRadius.all(Radius.circular(5)))); + } + + @override + Widget build(BuildContext context) { + double diameter = widget.diameter; + double circleDiameter = diameter; + double keyLongSize = diameter * 0.8; + double keyShortSize = diameter * 0.25; + double longMargin = (circleDiameter - keyLongSize) / 2; + double shortMargin = (circleDiameter - keyShortSize) / 2; + double triangleSize = diameter * 0.175; + Color callbackColor = const Color(0x00000000).withOpacity(0.0); + return Stack( + children: [ + circle(circleDiameter, Colors.grey[300]), + Positioned( + left: longMargin, + top: shortMargin, + child: horizontalBar(keyLongSize, keyShortSize, Colors.grey[600]!)), + Positioned( + left: shortMargin, + top: longMargin, + child: horizontalBar(keyShortSize, keyLongSize, Colors.grey[600]!)), + // Left Button + Positioned( + left: longMargin + keyLongSize / 6, + top: shortMargin + keyShortSize / 2, + child: triangle(triangleSize, Colors.grey[800]!, -90.0)), + Positioned( + left: longMargin + keyLongSize / 6 - triangleSize / 2, + top: shortMargin + keyShortSize / 2 - triangleSize / 2, + child: Listener( + onPointerDown: (event) { + widget.leftCallback(-1); + }, + onPointerUp: (event) { + widget.leftCallback(1); + }, + child: Container( + width: triangleSize, + height: triangleSize, + color: callbackColor, + ))), + // Forward Button + Positioned( + left: diameter / 2, + top: longMargin + keyLongSize / 6, + child: triangle(triangleSize, Colors.grey[800]!, 0.0)), + Positioned( + left: diameter / 2 - triangleSize / 2, + top: longMargin + keyLongSize / 6 - triangleSize / 2, + child: Listener( + onPointerDown: (event) { + widget.forwardCallback(-1); + }, + onPointerUp: (event) { + widget.forwardCallback(1); + }, + child: Container( + width: triangleSize, + height: triangleSize, + color: callbackColor))), + // Backward Button + Positioned( + left: diameter / 2, + top: longMargin + keyLongSize * 2 / 3 + keyLongSize / 6, + child: triangle(triangleSize, Colors.grey[800]!, 180.0)), + Positioned( + left: diameter / 2 - triangleSize / 2, + top: longMargin + + keyLongSize * 2 / 3 + + keyLongSize / 6 - + triangleSize / 2, + child: Listener( + onPointerDown: (event) { + widget.backwardCallback(-1); + }, + onPointerUp: (event) { + widget.backwardCallback(1); + }, + child: Container( + width: triangleSize, + height: triangleSize, + color: callbackColor, + ))), + // Right Button + Positioned( + left: longMargin + keyLongSize * 2 / 3 + keyLongSize / 6, + top: shortMargin + keyShortSize / 2, + child: triangle(triangleSize, Colors.grey[800]!, 90.0)), + Positioned( + left: longMargin + + keyLongSize * 2 / 3 + + keyLongSize / 6 - + triangleSize / 2, + top: shortMargin + keyShortSize / 2 - triangleSize / 2, + child: Listener( + onPointerDown: (event) { + widget.rightCallback(-1); + }, + onPointerUp: (event) { + widget.rightCallback(1); + }, + child: Container( + width: triangleSize, + height: triangleSize, + color: callbackColor, + ))), + ], + ); + } +} + +class KeyPad extends StatefulWidget { + final double diameter; + final Function squareCallback, + circleCallback, + triangleCallback, + crossCallback; + const KeyPad( + {Key? key, + required this.diameter, + required this.squareCallback, + required this.circleCallback, + required this.triangleCallback, + required this.crossCallback}) + : super(key: key); + + @override + State createState() => _KeyPadState(); +} + +class _KeyPadState extends State { + @override + Widget build(BuildContext context) { + double diameter = widget.diameter; + double circleDiameter = diameter; + double shortMargin = diameter * 0.225; + double keySize = diameter * 0.275; + double iconSize = diameter * 0.125; + double hollowCircleDiameter = iconSize + 8; + Color callbackColor = const Color(0x00000000).withOpacity(0.0); + return Stack(children: [ + Positioned(child: circle(circleDiameter, Colors.grey[300]!)), + Positioned( + left: shortMargin - keySize / 2, + top: circleDiameter / 2 - keySize / 2, + child: circle(keySize, Colors.grey[700]!)), + Positioned( + left: circleDiameter - shortMargin - keySize / 2, + top: circleDiameter / 2 - keySize / 2, + child: circle(keySize, Colors.grey[700]!)), + Positioned( + left: circleDiameter / 2 - keySize / 2, + top: circleDiameter - shortMargin - keySize / 2, + child: circle(keySize, Colors.grey[700]!)), + Positioned( + left: circleDiameter / 2 - keySize / 2, + top: shortMargin - keySize / 2, + child: circle(keySize, Colors.grey[700]!)), + Positioned( + left: circleDiameter / 2, + top: shortMargin - 4, + child: triangle(iconSize, Colors.green[500]!, 0)), + Positioned( + left: circleDiameter / 2 - keySize / 2, + top: shortMargin - keySize / 2, + child: Listener( + onPointerDown: (event) { + widget.triangleCallback(-1); + }, + onPointerUp: (event) { + widget.triangleCallback(1); + }, + child: Container( + width: keySize, + height: keySize, + color: callbackColor, + ))), + Positioned( + left: circleDiameter / 2, + top: circleDiameter - shortMargin, + child: cross(iconSize, Colors.cyan[500]!)), + Positioned( + left: circleDiameter / 2 - keySize / 2, + top: circleDiameter - shortMargin - keySize / 2, + child: Listener( + onPointerDown: (event) { + widget.triangleCallback(-1); + }, + onPointerUp: (event) { + widget.triangleCallback(1); + }, + child: Container( + width: keySize, + height: keySize, + color: callbackColor, + ))), + Positioned( + left: shortMargin, + top: circleDiameter / 2, + child: square(iconSize, Colors.pink[500]!)), + Positioned( + left: shortMargin - keySize / 2, + top: circleDiameter / 2 - keySize / 2, + child: Listener( + onPointerDown: (event) { + widget.triangleCallback(-1); + }, + onPointerUp: (event) { + widget.triangleCallback(1); + }, + child: Container( + width: keySize, + height: keySize, + color: callbackColor, + ))), + Positioned( + left: -hollowCircleDiameter / 2 + circleDiameter - shortMargin, + top: -hollowCircleDiameter / 2 + circleDiameter / 2, + child: hollowCircle(hollowCircleDiameter, Colors.orange[500]!)), + Positioned( + left: -hollowCircleDiameter / 2 + circleDiameter - keySize, + top: circleDiameter / 2 - keySize / 2, + child: Listener( + onPointerDown: (event) { + widget.triangleCallback(-1); + }, + onPointerUp: (event) { + widget.triangleCallback(1); + }, + child: Container( + width: keySize, + height: keySize, + color: callbackColor, + ))), + ]); + } +} diff --git a/example/pspad/pubspec.yaml b/example/pspad/pubspec.yaml new file mode 100644 index 0000000..962c146 --- /dev/null +++ b/example/pspad/pubspec.yaml @@ -0,0 +1,90 @@ +name: pspad +description: A new Flutter project. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=2.16.0-134.5.beta <3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + roslibdart: ^0.0.1-dev+2 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^1.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages