1 /*
2  * Copyright 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.compose.ui.platform
18 
19 import android.graphics.Matrix as AndroidMatrix
20 import androidx.compose.ui.geometry.MutableRect
21 import androidx.compose.ui.geometry.Offset
22 import androidx.compose.ui.graphics.Matrix
23 import androidx.compose.ui.graphics.isIdentity
24 import androidx.compose.ui.graphics.setFrom
25 
26 /**
27  * Helper class to cache a [Matrix] and inverse [Matrix], allowing the instance to be reused until
28  * the Layer's properties have changed, causing it to call [invalidate].
29  *
30  * This allows us to avoid repeated calls to [AndroidMatrix.getValues], which calls an expensive
31  * native method (nGetValues). If we know the matrix hasn't changed, we can just re-use it without
32  * needing to read and update values.
33  */
34 internal class LayerMatrixCache<T>(
35     private val getMatrix: (target: T, matrix: AndroidMatrix) -> Unit
36 ) {
37     private var androidMatrixCache: AndroidMatrix? = null
38     private var matrixCache: Matrix = Matrix()
39     private var inverseMatrixCache: Matrix = Matrix()
40 
41     private var isDirty = false
42     private var isInverseDirty = false
43     private var isInverseValid = true
44     private var isIdentity = true
45 
46     /** Reset the cache to the identity matrix. */
resetnull47     fun reset() {
48         isDirty = false
49         isInverseDirty = false
50         isIdentity = true
51         isInverseValid = true
52         matrixCache.reset()
53         inverseMatrixCache.reset()
54     }
55 
56     /**
57      * Ensures that the internal matrix will be updated next time [calculateMatrix] or
58      * [calculateInverseMatrix] is called - this should be called when something that will change
59      * the matrix calculation has happened.
60      */
invalidatenull61     fun invalidate() {
62         isDirty = true
63         isInverseDirty = true
64     }
65 
66     /**
67      * Returns the cached [Matrix], updating it if required (if [invalidate] was previously called).
68      */
calculateMatrixnull69     fun calculateMatrix(target: T): Matrix {
70         val matrix = matrixCache
71         if (!isDirty) {
72             return matrix
73         }
74 
75         val cachedMatrix = androidMatrixCache ?: AndroidMatrix().also { androidMatrixCache = it }
76         getMatrix(target, cachedMatrix)
77         matrix.setFrom(cachedMatrix)
78         isDirty = false
79         isIdentity = matrix.isIdentity()
80         return matrix
81     }
82 
83     /**
84      * Returns the cached inverse [Matrix], updating it if required (if [invalidate] was previously
85      * called). This returns `null` if the inverse matrix isn't valid. This can happen, for example,
86      * when scaling is 0.
87      */
calculateInverseMatrixnull88     fun calculateInverseMatrix(target: T): Matrix? {
89         val matrix = inverseMatrixCache
90         if (isInverseDirty) {
91             val normalMatrix = calculateMatrix(target)
92             isInverseValid = normalMatrix.invertTo(matrix)
93             isInverseDirty = false
94         }
95         return if (isInverseValid) matrix else null
96     }
97 
mapnull98     fun map(target: T, rect: MutableRect) {
99         val matrix = calculateMatrix(target)
100         if (!isIdentity) {
101             matrix.map(rect)
102         }
103     }
104 
mapInversenull105     fun mapInverse(target: T, rect: MutableRect) {
106         val matrix = calculateInverseMatrix(target)
107         if (matrix == null) {
108             rect.set(0f, 0f, 0f, 0f)
109         } else if (!isIdentity) {
110             matrix.map(rect)
111         }
112     }
113 
mapnull114     fun map(target: T, offset: Offset): Offset {
115         val matrix = calculateMatrix(target)
116         return if (!isIdentity) {
117             matrix.map(offset)
118         } else {
119             offset
120         }
121     }
122 
mapInversenull123     fun mapInverse(target: T, offset: Offset): Offset {
124         val matrix = calculateInverseMatrix(target)
125         return if (matrix == null) {
126             Offset.Infinite
127         } else if (!isIdentity) {
128             matrix.map(offset)
129         } else {
130             offset
131         }
132     }
133 }
134