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