/
Ti2DMatrix.java
463 lines (405 loc) Β· 13.4 KB
/
Ti2DMatrix.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
/**
* Titanium SDK
* Copyright TiDev, Inc. 04/07/2022-Present
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/
package org.appcelerator.titanium.view;
import java.util.ArrayList;
import java.util.List;
import org.appcelerator.kroll.KrollDict;
import org.appcelerator.kroll.KrollProxy;
import org.appcelerator.kroll.annotations.Kroll;
import org.appcelerator.titanium.TiC;
import org.appcelerator.titanium.TiDimension;
import org.appcelerator.titanium.util.TiConvert;
import android.graphics.Matrix;
import android.util.Pair;
@Kroll.proxy
public class Ti2DMatrix extends KrollProxy
{
public static final float DEFAULT_ANCHOR_VALUE = -1f;
public static final float VALUE_UNSPECIFIED = Float.MIN_VALUE;
protected Ti2DMatrix next, prev;
public static class Operation
{
public static final int TYPE_SCALE = 0;
public static final int TYPE_TRANSLATE = 1;
public static final int TYPE_ROTATE = 2;
public static final int TYPE_MULTIPLY = 3;
public static final int TYPE_INVERT = 4;
public float scaleFromX, scaleFromY, scaleToX, scaleToY;
public float translateX, translateY;
public float rotateFrom, rotateTo;
public float anchorX = 0.5f, anchorY = 0.5f;
public Ti2DMatrix multiplyWith;
public int type;
public boolean scaleFromValuesSpecified = false;
public boolean rotationFromValueSpecified = false;
public Operation(int type)
{
this.type = type;
}
public void apply(float interpolatedTime, Matrix matrix, int childWidth, int childHeight, float anchorX,
float anchorY)
{
anchorX = anchorX == DEFAULT_ANCHOR_VALUE ? this.anchorX : anchorX;
anchorY = anchorY == DEFAULT_ANCHOR_VALUE ? this.anchorY : anchorY;
switch (type) {
case TYPE_SCALE:
matrix.preScale((interpolatedTime * (scaleToX - scaleFromX)) + scaleFromX,
(interpolatedTime * (scaleToY - scaleFromY)) + scaleFromY, anchorX * childWidth,
anchorY * childHeight);
break;
case TYPE_TRANSLATE:
matrix.preTranslate(interpolatedTime * translateX, interpolatedTime * translateY);
break;
case TYPE_ROTATE:
matrix.preRotate((interpolatedTime * (rotateTo - rotateFrom)) + rotateFrom, anchorX * childWidth,
anchorY * childHeight);
break;
case TYPE_MULTIPLY:
matrix.preConcat(
multiplyWith.interpolate(interpolatedTime, childWidth, childHeight, anchorX, anchorY));
break;
case TYPE_INVERT:
matrix.invert(matrix);
break;
}
}
}
protected Operation op;
public Ti2DMatrix()
{
}
protected Ti2DMatrix(Ti2DMatrix prev, int opType)
{
if (prev != null) {
// this.prev represents the previous matrix. This value does not change.
this.prev = prev;
// prev.next is not constant. Subsequent calls to Ti2DMatrix() will alter the value of prev.next.
prev.next = this;
}
this.op = new Operation(opType);
}
@Override
public void handleCreationDict(KrollDict dict)
{
super.handleCreationDict(dict);
if (dict.containsKey(TiC.PROPERTY_ROTATE)) {
op = new Operation(Operation.TYPE_ROTATE);
op.rotateFrom = 0;
op.rotateTo = TiConvert.toFloat(dict, TiC.PROPERTY_ROTATE);
handleAnchorPoint(dict);
// If scale also specified in creation dict,
// then we need to link a scaling matrix separately.
if (dict.containsKey(TiC.PROPERTY_SCALE)) {
KrollDict newDict = new KrollDict();
newDict.put(TiC.PROPERTY_SCALE, dict.get(TiC.PROPERTY_SCALE));
if (dict.containsKey(TiC.PROPERTY_ANCHOR_POINT)) {
newDict.put(TiC.PROPERTY_ANCHOR_POINT, dict.get(TiC.PROPERTY_ANCHOR_POINT));
}
prev = new Ti2DMatrix();
prev.handleCreationDict(newDict);
prev.next = this;
}
} else if (dict.containsKey(TiC.PROPERTY_SCALE)) {
op = new Operation(Operation.TYPE_SCALE);
op.scaleFromX = op.scaleFromY = 1.0f;
op.scaleToX = op.scaleToY = TiConvert.toFloat(dict, TiC.PROPERTY_SCALE);
handleAnchorPoint(dict);
}
}
protected void handleAnchorPoint(KrollDict dict)
{
if (dict.containsKey(TiC.PROPERTY_ANCHOR_POINT)) {
KrollDict anchorPoint = dict.getKrollDict(TiC.PROPERTY_ANCHOR_POINT);
if (anchorPoint != null) {
op.anchorX = TiConvert.toFloat(anchorPoint, TiC.PROPERTY_X);
op.anchorY = TiConvert.toFloat(anchorPoint, TiC.PROPERTY_Y);
}
}
}
@Kroll.method
public Ti2DMatrix translate(double x, double y)
{
TiDimension xDimension = new TiDimension(TiConvert.toString(x), TiDimension.TYPE_LEFT);
TiDimension yDimension = new TiDimension(TiConvert.toString(y), TiDimension.TYPE_TOP);
Ti2DMatrix newMatrix = new Ti2DMatrix(this, Operation.TYPE_TRANSLATE);
newMatrix.op.translateX = (float) xDimension.getPixels(null);
newMatrix.op.translateY = (float) yDimension.getPixels(null);
return newMatrix;
}
@Kroll.method
public Ti2DMatrix scale(Object[] args)
{
Ti2DMatrix newMatrix = new Ti2DMatrix(this, Operation.TYPE_SCALE);
newMatrix.op.scaleFromX = newMatrix.op.scaleFromY = VALUE_UNSPECIFIED;
newMatrix.op.scaleToX = newMatrix.op.scaleToY = 1.0f;
// varargs for API backwards compatibility
if (args.length == 4) {
// scale(fromX, fromY, toX, toY)
newMatrix.op.scaleFromValuesSpecified = true;
newMatrix.op.scaleFromX = TiConvert.toFloat(args[0]);
newMatrix.op.scaleFromY = TiConvert.toFloat(args[1]);
newMatrix.op.scaleToX = TiConvert.toFloat(args[2]);
newMatrix.op.scaleToY = TiConvert.toFloat(args[3]);
}
if (args.length == 2) {
// scale(toX, toY)
newMatrix.op.scaleFromValuesSpecified = false;
newMatrix.op.scaleToX = TiConvert.toFloat(args[0]);
newMatrix.op.scaleToY = TiConvert.toFloat(args[1]);
} else if (args.length == 1) {
// scale(scaleFactor)
newMatrix.op.scaleFromValuesSpecified = false;
newMatrix.op.scaleToX = newMatrix.op.scaleToY = TiConvert.toFloat(args[0]);
}
// TODO newMatrix.handleAnchorPoint(newMatrix.getProperties());
return newMatrix;
}
@Kroll.method
public Ti2DMatrix rotate(Object[] args)
{
Ti2DMatrix newMatrix = new Ti2DMatrix(this, Operation.TYPE_ROTATE);
if (args.length == 1) {
newMatrix.op.rotationFromValueSpecified = false;
newMatrix.op.rotateFrom = VALUE_UNSPECIFIED;
newMatrix.op.rotateTo = TiConvert.toFloat(args[0]);
} else if (args.length == 2) {
newMatrix.op.rotationFromValueSpecified = true;
newMatrix.op.rotateFrom = TiConvert.toFloat(args[0]);
newMatrix.op.rotateTo = TiConvert.toFloat(args[1]);
}
// TODO newMatrix.handleAnchorPoint(newMatrix.getProperties());
return newMatrix;
}
@Kroll.method
public Ti2DMatrix invert()
{
return new Ti2DMatrix(this, Operation.TYPE_INVERT);
}
@Kroll.method
public Ti2DMatrix multiply(Ti2DMatrix other)
{
Ti2DMatrix newMatrix = new Ti2DMatrix(other, Operation.TYPE_MULTIPLY);
newMatrix.op.multiplyWith = this;
return newMatrix;
}
@Kroll.method
public float[] finalValuesAfterInterpolation(int width, int height)
{
Matrix m = interpolate(1f, width, height, 0.5f, 0.5f);
float[] result = new float[9];
m.getValues(result);
return result;
}
public Matrix interpolate(float interpolatedTime, int childWidth, int childHeight, float anchorX, float anchorY)
{
Ti2DMatrix first = this;
ArrayList<Ti2DMatrix> preMatrixList = new ArrayList<>();
while (first.prev != null) {
first = first.prev;
// It is safe to use prev matrix to trace back the transformation matrix list,
// since prev matrix is constant.
preMatrixList.add(0, first);
}
Matrix matrix = new Matrix();
for (Ti2DMatrix current : preMatrixList) {
if (current.op != null) {
current.op.apply(interpolatedTime, matrix, childWidth, childHeight, anchorX, anchorY);
}
}
if (op != null) {
op.apply(interpolatedTime, matrix, childWidth, childHeight, anchorX, anchorY);
}
return matrix;
}
/**
* Check if this matrix has an operation of a particular type, or if any
* in the chain of operations preceding it does.
* @param operationType Operation.TYPE_SCALE, etc.
* @return true if this matrix or any of the "prev" matrices is of the given type, false otherwise
*/
private boolean containsOperationOfType(int operationType)
{
Ti2DMatrix check = this;
while (check != null) {
if (check.op != null && check.op.type == operationType) {
return true;
}
check = check.prev;
}
return false;
}
public boolean hasScaleOperation()
{
return containsOperationOfType(Operation.TYPE_SCALE);
}
public boolean hasRotateOperation()
{
return containsOperationOfType(Operation.TYPE_ROTATE);
}
/**
* Checks all of the scale operations in the sequence and sets the appropriate
* scale "from" values for them all (in case they aren't specified), then gives
* back the final scale values that will be in effect when the animation has completed.
* @param view The view to be checked.
* @param autoreverse Set true if animation is to be reversed when the end is reached.
* @return Final scale values after the animation has finished.
*/
public Pair<Float, Float> verifyScaleValues(TiUIView view, boolean autoreverse)
{
ArrayList<Operation> scaleOps = new ArrayList<Operation>();
Ti2DMatrix check = this;
while (check != null) {
if (check.op != null && check.op.type == Operation.TYPE_SCALE) {
scaleOps.add(0, check.op);
}
check = check.prev;
}
Pair<Float, Float> viewCurrentScale =
(view == null ? Pair.create(1.0f, 1.0f) : view.getAnimatedScaleValues());
if (scaleOps.size() == 0) {
return viewCurrentScale;
}
float lastToX = viewCurrentScale.first;
float lastToY = viewCurrentScale.second;
for (Operation op : scaleOps) {
if (!op.scaleFromValuesSpecified) {
// The "from" values were not specified,
// so they should be whatever the last "to" values were.
op.scaleFromX = lastToX;
op.scaleFromY = lastToY;
}
lastToX = op.scaleToX;
lastToY = op.scaleToY;
}
// If autoreversing, then the final scale values for the view will be
// whatever they are at the start. Else they are whatever the last "to" scale
// values are in the sequence.
if (autoreverse) {
return viewCurrentScale;
} else {
return Pair.create(Float.valueOf(lastToX), Float.valueOf(lastToY));
}
}
/**
* Checks all of the rotate operations in the sequence and sets the appropriate
* "from" value for them all (in case they aren't specified), then gives
* back the final value that will be in effect when the animation has completed.
* @param view The view to be checked.
* @param autoreverse Set true to reverse the animation when it reaches the end.
* @return Final rotation value after the animation has finished.
*/
public float verifyRotationValues(TiUIView view, boolean autoreverse)
{
ArrayList<Operation> rotationOps = new ArrayList<>();
Ti2DMatrix check = this;
while (check != null) {
if (check.op != null && check.op.type == Operation.TYPE_ROTATE) {
rotationOps.add(0, check.op);
}
check = check.prev;
}
float viewCurrentRotation = (view == null ? 0f : view.getAnimatedRotationDegrees());
if (rotationOps.size() == 0) {
return viewCurrentRotation;
}
float lastRotation = viewCurrentRotation;
for (Operation op : rotationOps) {
if (!op.rotationFromValueSpecified) {
// The "from" value was not specified,
// so it should be whatever the last "to" value was.
op.rotateFrom = lastRotation;
}
lastRotation = op.rotateTo;
}
// If autoreversing, then the final rotation value for the view will be
// whatever it was at the start. Else it's whatever the last "to" rotation
// value is in the sequence.
if (autoreverse) {
return viewCurrentRotation;
} else {
return lastRotation;
}
}
public float[] getRotateOperationParameters()
{
if (this.op == null) {
return new float[4];
}
return new float[] { this.op.rotateFrom, this.op.rotateTo, this.op.anchorX, this.op.anchorY };
}
public void setRotationFromDegrees(float degrees)
{
if (this.op != null) {
this.op.rotateFrom = degrees;
}
}
/**
* Determines whether we can use property Animator instances.
* We can do that if the matrix is not "complicated".
* See the class documentation for
* {@link org.appcelerator.titanium.util.TiAnimationBuilder TiAnimationBuilder}
* for a detailed description of what makes a matrix too
* complicated for property Animators.
* @return
* Returns true if property animators can be used
* Returns false if we need to stick with the old-style view animations.
*/
public boolean canUsePropertyAnimators()
{
boolean hasScale = false, hasRotate = false, hasTranslate = false;
List<Operation> ops = getAllOperations();
for (Operation op : ops) {
if (op == null) {
continue;
}
switch (op.type) {
case Operation.TYPE_SCALE:
if (hasScale) {
return false;
}
hasScale = true;
break;
case Operation.TYPE_TRANSLATE:
if (hasTranslate) {
return false;
}
hasTranslate = true;
break;
case Operation.TYPE_ROTATE:
if (hasRotate) {
return false;
}
hasRotate = true;
break;
case Operation.TYPE_MULTIPLY:
case Operation.TYPE_INVERT:
return false;
}
}
return true;
}
/**
* Collect this matrix's operation and
* those of its predecessors into one
* list. This way we can assess
* what the resulting transform is
* going to do.
* @return List of operations.
*/
public List<Operation> getAllOperations()
{
List<Operation> ops = new ArrayList<>();
Ti2DMatrix toCheck = this;
while (toCheck != null) {
if (toCheck.op != null) {
ops.add(toCheck.op);
}
toCheck = toCheck.prev;
}
return ops;
}
}