Skip to content

Commit

Permalink
added decomposeToValues static method
Browse files Browse the repository at this point in the history
  • Loading branch information
pskink committed Mar 8, 2019
1 parent e54e4e3 commit d07013f
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 51 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# matrix_gesture_detector

A gesture detector which detects translation, scale and rotation gestures
`MatrixGestureDetector` detects translation, scale and rotation gestures
and combines them into `Matrix4` object that can be used by `Transform` widget
or by low level `CustomPainter` code. You can customize types of reported
gestures by passing `shouldTranslate`, `shouldScale` and `shouldRotate`
Expand Down
5 changes: 1 addition & 4 deletions example/lib/transform_demo3.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ class _TransformDemo3State extends State<TransformDemo3> {
static const double radius0 = 0.0;

Matrix4 matrix = Matrix4.identity();
double angle = 0.0;
double radius = radius0;
Color color = color0;
ValueNotifier<int> notifier = ValueNotifier(0);
Expand All @@ -42,9 +41,7 @@ class _TransformDemo3State extends State<TransformDemo3> {
onMatrixUpdate: (m, tm, sm, rm) {
matrix = MatrixGestureDetector.compose(matrix, tm, sm, null);

var array = rm.applyToVector3Array([0, 0, 0, 1, 0, 0]);
Offset rotated = Offset(array[3] - array[0], array[4] - array[1]);
angle += rotated.direction;
var angle = MatrixGestureDetector.decomposeToValues(m).rotation;
double t = (1 - cos(2 * angle)) / 2;

radius = radiusTween.transform(t);
Expand Down
99 changes: 66 additions & 33 deletions example/lib/transform_demo4.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,21 @@ class TransformDemo4State extends State<TransformDemo4>
title: Text('Transform Demo 4'),
),
body: Column(
children: makeControls() + makeMainWidget(getLabel()),
children: makeControls() + makeMainWidget(getBody()),
),
);
}

String getLabel() {
String prefix = 'use your fingers to ';
if (shouldRotate && shouldScale) return prefix + 'rotate / scale';
if (shouldRotate) return prefix + 'rotate';
if (shouldScale) return prefix + 'scale';
return 'you have to select at least one checkbox above';
Body getBody() {
String lbl = 'use your fingers to ';
if (shouldRotate && shouldScale)
return Body(lbl + 'rotate / scale', Icons.crop_rotate, Color(0x6600aa00));
if (shouldRotate)
return Body(lbl + 'rotate', Icons.crop_rotate, Color(0x6600aa00));
if (shouldScale)
return Body(lbl + 'scale', Icons.transform, Color(0x660000aa));
return Body('you have to select at least one checkbox above', Icons.warning,
Color(0x66aa0000));
}

Animation<Alignment> makeFocalPointAnimation(Alignment begin, Alignment end) {
Expand Down Expand Up @@ -106,7 +110,7 @@ class TransformDemo4State extends State<TransformDemo4>
),
];

List<Widget> makeMainWidget(String label) => [
List<Widget> makeMainWidget(Body body) => [
Expanded(
child: Padding(
padding: const EdgeInsets.all(32.0),
Expand All @@ -123,37 +127,66 @@ class TransformDemo4State extends State<TransformDemo4>
foregroundPainter: FocalPointPainter(focalPointAnimation),
child: AnimatedBuilder(
animation: notifier,
builder: (ctx, child) {
return Transform(
transform: notifier.value,
child: GridPaper(
color: Color(0xaa0000ff),
child: Container(
decoration: BoxDecoration(
border: Border.all(width: 4.0, color: Colors.blue),
borderRadius:
BorderRadius.all(Radius.circular(32.0)),
),
child: Container(
decoration: FlutterLogoDecoration(),
padding: EdgeInsets.all(32),
alignment: Alignment(0, -0.5),
child: Text(
label,
style: Theme.of(context).textTheme.display2,
textAlign: TextAlign.center,
),
),
),
),
);
},
builder: (ctx, child) => makeTransform(ctx, child, body),
),
),
),
),
)
];

Widget makeTransform(BuildContext context, Widget child, Body body) {
return Transform(
transform: notifier.value,
child: GridPaper(
color: Color(0xaa0000ff),
child: Container(
decoration: BoxDecoration(
border: Border.all(width: 4.0, color: Color(0xaa00cc00)),
borderRadius: BorderRadius.all(Radius.circular(32.0)),
),
child: AnimatedSwitcher(
duration: Duration(milliseconds: 400),
transitionBuilder: (child, animation) => ScaleTransition(
scale: animation,
child: child,
alignment: focalPoint,
),
switchInCurve: Curves.ease,
switchOutCurve: Curves.ease,
child: Stack(
key: ValueKey('$shouldRotate-$shouldScale'),
fit: StackFit.expand,
children: <Widget>[
FittedBox(
child: Icon(
body.icon,
color: body.color,
),
),
Container(
alignment: Alignment(0, -0.5),
child: Text(
body.label,
style: Theme.of(context).textTheme.display2,
textAlign: TextAlign.center,
),
),
],
),
),
),
),
);
}
}

class Body {
String label;
IconData icon;
Color color;

Body(this.label, this.icon, this.color);
}

class FocalPointPainter extends CustomPainter {
Expand Down
77 changes: 65 additions & 12 deletions lib/matrix_gesture_detector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ typedef MatrixGestureDetectorCallback = void Function(
Matrix4 scaleDeltaMatrix,
Matrix4 rotationDeltaMatrix);

/// A gesture detector which detects translation, scale and rotation gestures
/// [MatrixGestureDetector] detects translation, scale and rotation gestures
/// and combines them into [Matrix4] object that can be used by [Transform] widget
/// or by low level [CustomPainter] code. You can customize types of reported
/// gestures by passing [shouldTranslate], [shouldScale] and [shouldRotate]
Expand Down Expand Up @@ -87,6 +87,19 @@ class MatrixGestureDetector extends StatefulWidget {
if (rotationMatrix != null) matrix = rotationMatrix * matrix;
return matrix;
}

///
/// Decomposes [matrix] into [MatrixDecomposedValues.translation],
/// [MatrixDecomposedValues.scale] and [MatrixDecomposedValues.rotation] components.
///
static MatrixDecomposedValues decomposeToValues(Matrix4 matrix) {
var array = matrix.applyToVector3Array([0, 0, 0, 1, 0, 0]);
Offset translation = Offset(array[0], array[1]);
Offset delta = Offset(array[3] - array[0], array[4] - array[1]);
double scale = delta.distance;
double rotation = delta.direction;
return MatrixDecomposedValues(translation, scale, rotation);
}
}

class _MatrixGestureDetectorState extends State<MatrixGestureDetector> {
Expand Down Expand Up @@ -165,25 +178,46 @@ class _MatrixGestureDetectorState extends State<MatrixGestureDetector> {
}

Matrix4 _translate(Offset translation) {
return Matrix4.translationValues(translation.dx, translation.dy, 0.0);
var dx = translation.dx;
var dy = translation.dy;

// ..[0] = 1 # x scale
// ..[5] = 1 # y scale
// ..[10] = 1 # diagonal "one"
// ..[12] = dx # x translation
// ..[13] = dy # y translation
// ..[15] = 1 # diagonal "one"
return Matrix4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1);
}

Matrix4 _scale(double scale, Offset focalPoint) {
Matrix4 matrix = Matrix4.diagonal3Values(scale, scale, 1);
var dx = (1 - scale) * focalPoint.dx;
var dy = (1 - scale) * focalPoint.dy;
matrix.setTranslationRaw(dx, dy, 0.0);
return matrix;

// ..[0] = scale # x scale
// ..[5] = scale # y scale
// ..[10] = 1 # diagonal "one"
// ..[12] = dx # x translation
// ..[13] = dy # y translation
// ..[15] = 1 # diagonal "one"
return Matrix4(scale, 0, 0, 0, 0, scale, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1);
}

Matrix4 _rotate(double angle, Offset focalPoint) {
Matrix4 matrix = Matrix4.rotationZ(angle);
var cosa = cos(angle);
var sina = sin(angle);
var dx = (1 - cosa) * focalPoint.dx + sina * focalPoint.dy;
var dy = (1 - cosa) * focalPoint.dy - sina * focalPoint.dx;
matrix.setTranslationRaw(dx, dy, 0.0);
return matrix;
var c = cos(angle);
var s = sin(angle);
var dx = (1 - c) * focalPoint.dx + s * focalPoint.dy;
var dy = (1 - c) * focalPoint.dy - s * focalPoint.dx;

// ..[0] = c # x scale
// ..[1] = s # y skew
// ..[4] = -s # x skew
// ..[5] = c # y scale
// ..[10] = 1 # diagonal "one"
// ..[12] = dx # x translation
// ..[13] = dy # y translation
// ..[15] = 1 # diagonal "one"
return Matrix4(c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, dx, dy, 0, 1);
}
}

Expand All @@ -201,3 +235,22 @@ class _ValueUpdater<T> {
return updated;
}
}

class MatrixDecomposedValues {
/// Translation, in most cases useful only for matrices that are nothing but
/// a translation (no scale and no rotation).
final Offset translation;

/// Scaling factor.
final double scale;

/// Rotation in radians, (-pi..pi) range.
final double rotation;

MatrixDecomposedValues(this.translation, this.scale, this.rotation);

@override
String toString() {
return 'MatrixDecomposedValues(translation: $translation, scale: ${scale.toStringAsFixed(3)}, rotation: ${rotation.toStringAsFixed(3)})';
}
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: matrix_gesture_detector
description: A gesture detector mapping translation/rotation/scale gestures to a Matrix4 object.
version: 0.0.6
version: 0.1.0
author: pskink <[email protected]>
homepage: https://github.com/pskink/matrix_gesture_detector

Expand Down

0 comments on commit d07013f

Please sign in to comment.