matrix_gesture_detector.dart 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. import 'dart:math';
  2. import 'package:flutter/widgets.dart';
  3. import 'package:trackoffical_app/logger.dart';
  4. typedef MatrixGestureDetectorCallback = void Function(
  5. Matrix4 matrix,
  6. Matrix4 translationDeltaMatrix,
  7. Matrix4 scaleDeltaMatrix,
  8. Matrix4 rotationDeltaMatrix,
  9. );
  10. /// [MatrixGestureDetector] detects translation, scale and rotation gestures
  11. /// and combines them into [Matrix4] object that can be used by [Transform] widget
  12. /// or by low level [CustomPainter] code. You can customize types of reported
  13. /// gestures by passing [shouldTranslate], [shouldScale] and [shouldRotate]
  14. /// parameters.
  15. ///
  16. class MatrixGestureDetector extends StatefulWidget {
  17. /// [Matrix4] change notification callback
  18. ///
  19. final MatrixGestureDetectorCallback onMatrixUpdate;
  20. /// The [child] contained by this detector.
  21. ///
  22. /// {@macro flutter.widgets.child}
  23. ///
  24. final Widget child;
  25. /// Whether to detect translation gestures during the event processing.
  26. ///
  27. /// Defaults to true.
  28. ///
  29. final bool shouldTranslate;
  30. /// Whether to detect scale gestures during the event processing.
  31. ///
  32. /// Defaults to true.
  33. ///
  34. final bool shouldScale;
  35. /// Whether to detect rotation gestures during the event processing.
  36. ///
  37. /// Defaults to true.
  38. ///
  39. final bool shouldRotate;
  40. /// Whether [ClipRect] widget should clip [child] widget.
  41. ///
  42. /// Defaults to true.
  43. ///
  44. final bool clipChild;
  45. final GlobalKey? childKey;
  46. /// When set, it will be used for computing a "fixed" focal point
  47. /// aligned relative to the size of this widget.
  48. final Alignment? focalPointAlignment;
  49. const MatrixGestureDetector({
  50. Key? key,
  51. required this.onMatrixUpdate,
  52. required this.child,
  53. this.shouldTranslate = true,
  54. this.shouldScale = true,
  55. this.shouldRotate = true,
  56. this.clipChild = true,
  57. this.focalPointAlignment,
  58. this.childKey,
  59. }) : super(key: key);
  60. @override
  61. _MatrixGestureDetectorState createState() => _MatrixGestureDetectorState(childKey);
  62. ///
  63. /// Compose the matrix from translation, scale and rotation matrices - you can
  64. /// pass a null to skip any matrix from composition.
  65. ///
  66. /// If [matrix] is not null the result of the composing will be concatenated
  67. /// to that [matrix], otherwise the identity matrix will be used.
  68. ///
  69. static Matrix4 compose(Matrix4 matrix, Matrix4? translationMatrix,
  70. Matrix4? scaleMatrix, Matrix4? rotationMatrix) {
  71. if (translationMatrix != null) matrix = translationMatrix * matrix;
  72. if (scaleMatrix != null) matrix = scaleMatrix * matrix;
  73. if (rotationMatrix != null) matrix = rotationMatrix * matrix;
  74. return matrix;
  75. }
  76. ///
  77. /// Decomposes [matrix] into [MatrixDecomposedValues.translation],
  78. /// [MatrixDecomposedValues.scale] and [MatrixDecomposedValues.rotation] components.
  79. ///
  80. static MatrixDecomposedValues decomposeToValues(Matrix4 matrix) {
  81. var array = matrix.applyToVector3Array([0, 0, 0, 1, 0, 0]);
  82. Offset translation = Offset(array[0], array[1]);
  83. Offset delta = Offset(array[3] - array[0], array[4] - array[1]);
  84. double scale = delta.distance;
  85. double rotation = delta.direction;
  86. return MatrixDecomposedValues(translation, scale, rotation);
  87. }
  88. }
  89. class _MatrixGestureDetectorState extends State<MatrixGestureDetector> {
  90. Matrix4 translationDeltaMatrix = Matrix4.identity();
  91. Matrix4 scaleDeltaMatrix = Matrix4.identity();
  92. Matrix4 rotationDeltaMatrix = Matrix4.identity();
  93. Matrix4 matrix = Matrix4.identity();
  94. final GlobalKey? childKey;
  95. _MatrixGestureDetectorState(this.childKey);
  96. @override
  97. Widget build(BuildContext context) {
  98. Widget child =
  99. widget.clipChild ? ClipRect(child: widget.child) : widget.child;
  100. return GestureDetector(
  101. onScaleStart: onScaleStart,
  102. onScaleUpdate: onScaleUpdate,
  103. child: child,
  104. );
  105. }
  106. _ValueUpdater<Offset> translationUpdater = _ValueUpdater(
  107. onUpdate: (oldVal, newVal) => newVal - (oldVal ?? Offset.zero),
  108. );
  109. _ValueUpdater<double> rotationUpdater = _ValueUpdater(
  110. onUpdate: (oldVal, newVal) => newVal - (oldVal ?? 0),
  111. );
  112. _ValueUpdater<double> scaleUpdater = _ValueUpdater(
  113. onUpdate: (oldVal, newVal) => newVal / (oldVal ?? 1),
  114. );
  115. void onScaleStart(ScaleStartDetails details) {
  116. translationUpdater.value = details.focalPoint;
  117. rotationUpdater.value = double.nan;
  118. scaleUpdater.value = 1.0;
  119. }
  120. void onScaleUpdate(ScaleUpdateDetails details) {
  121. translationDeltaMatrix = Matrix4.identity();
  122. scaleDeltaMatrix = Matrix4.identity();
  123. rotationDeltaMatrix = Matrix4.identity();
  124. // handle matrix translating
  125. if (widget.shouldTranslate) {
  126. Offset translationDelta = translationUpdater.update(details.focalPoint);
  127. translationDeltaMatrix = _translate(translationDelta);
  128. matrix = translationDeltaMatrix * matrix;
  129. }
  130. Offset? focalPoint;
  131. if (widget.focalPointAlignment != null && context.size != null) {
  132. focalPoint = widget.focalPointAlignment!.alongSize(context.size!);
  133. } else {
  134. RenderObject? renderObject;
  135. if (childKey != null ){
  136. renderObject = childKey?.currentContext?.findRenderObject();
  137. }else{
  138. renderObject = context.findRenderObject();
  139. }
  140. if (renderObject != null) {
  141. RenderBox renderBox = renderObject as RenderBox;
  142. focalPoint = renderBox.globalToLocal(details.focalPoint);
  143. }
  144. }
  145. // handle matrix scaling
  146. if (widget.shouldScale && details.scale != 1.0 && focalPoint != null) {
  147. double scaleDelta = scaleUpdater.update(details.scale);
  148. scaleDeltaMatrix = _scale(scaleDelta, focalPoint);
  149. matrix = scaleDeltaMatrix * matrix;
  150. }
  151. // handle matrix rotating
  152. if (widget.shouldRotate && details.rotation != 0.0) {
  153. if (rotationUpdater.value == null || rotationUpdater.value!.isNaN) {
  154. rotationUpdater.value = details.rotation;
  155. } else {
  156. if (focalPoint != null) {
  157. double rotationDelta = rotationUpdater.update(details.rotation);
  158. rotationDeltaMatrix = _rotate(rotationDelta, focalPoint);
  159. matrix = rotationDeltaMatrix * matrix;
  160. }
  161. }
  162. }
  163. widget.onMatrixUpdate(
  164. matrix, translationDeltaMatrix, scaleDeltaMatrix, rotationDeltaMatrix);
  165. }
  166. Matrix4 _translate(Offset translation) {
  167. var dx = translation.dx;
  168. var dy = translation.dy;
  169. // ..[0] = 1 # x scale
  170. // ..[5] = 1 # y scale
  171. // ..[10] = 1 # diagonal "one"
  172. // ..[12] = dx # x translation
  173. // ..[13] = dy # y translation
  174. // ..[15] = 1 # diagonal "one"
  175. return Matrix4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1);
  176. }
  177. Matrix4 _scale(double scale, Offset focalPoint) {
  178. var dx = (1 - scale) * focalPoint.dx;
  179. var dy = (1 - scale) * focalPoint.dy;
  180. // ..[0] = scale # x scale
  181. // ..[5] = scale # y scale
  182. // ..[10] = 1 # diagonal "one"
  183. // ..[12] = dx # x translation
  184. // ..[13] = dy # y translation
  185. // ..[15] = 1 # diagonal "one"
  186. return Matrix4(
  187. scale, 0, 0, 0,
  188. 0, scale, 0, 0,
  189. 0, 0, 1, 0,
  190. 0, 0, 0, 1);
  191. // dx, dy, 0, 1);
  192. }
  193. Matrix4 _rotate(double angle, Offset focalPoint) {
  194. var c = cos(angle);
  195. var s = sin(angle);
  196. var dx = (1 - c) * focalPoint.dx + s * focalPoint.dy;
  197. var dy = (1 - c) * focalPoint.dy - s * focalPoint.dx;
  198. // ..[0] = c # x scale
  199. // ..[1] = s # y skew
  200. // ..[4] = -s # x skew
  201. // ..[5] = c # y scale
  202. // ..[10] = 1 # diagonal "one"
  203. // ..[12] = dx # x translation
  204. // ..[13] = dy # y translation
  205. // ..[15] = 1 # diagonal "one"
  206. return Matrix4(
  207. c, s, 0, 0,
  208. -s, c, 0, 0,
  209. 0, 0, 1, 0,
  210. 0, 0, 0, 1);
  211. // dx, dy, 0, 1);
  212. }
  213. }
  214. typedef _OnUpdate<T> = T Function(T? oldValue, T newValue);
  215. class _ValueUpdater<T> {
  216. final _OnUpdate<T> onUpdate;
  217. T? value;
  218. _ValueUpdater({required this.onUpdate});
  219. T update(T newValue) {
  220. T updated = onUpdate(value, newValue);
  221. value = newValue;
  222. return updated;
  223. }
  224. }
  225. class MatrixDecomposedValues {
  226. /// Translation, in most cases useful only for matrices that are nothing but
  227. /// a translation (no scale and no rotation).
  228. final Offset translation;
  229. /// Scaling factor.
  230. final double scale;
  231. /// Rotation in radians, (-pi..pi) range.
  232. final double rotation;
  233. MatrixDecomposedValues(this.translation, this.scale, this.rotation);
  234. @override
  235. String toString() {
  236. return 'MatrixDecomposedValues(translation: $translation, scale: ${scale.toStringAsFixed(3)}, rotation: ${rotation.toStringAsFixed(3)})';
  237. }
  238. }