| /* |
| * Copyright 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.compose.ui.platform |
| |
| import android.graphics.Matrix as AndroidMatrix |
| import androidx.compose.ui.graphics.Matrix |
| import androidx.compose.ui.graphics.setFrom |
| |
| /** |
| * Helper class to cache a [Matrix] and inverse [Matrix], allowing the instance to be reused until |
| * the Layer's properties have changed, causing it to call [invalidate]. |
| * |
| * This allows us to avoid repeated calls to [AndroidMatrix.getValues], which calls |
| * an expensive native method (nGetValues). If we know the matrix hasn't changed, we can just |
| * re-use it without needing to read and update values. |
| */ |
| internal class LayerMatrixCache<T>( |
| private val getMatrix: (target: T, matrix: AndroidMatrix) -> Unit |
| ) { |
| private var androidMatrixCache: AndroidMatrix? = null |
| private var previousAndroidMatrix: AndroidMatrix? = null |
| private var matrixCache: Matrix? = null |
| private var inverseMatrixCache: Matrix? = null |
| |
| private var isDirty = true |
| private var isInverseDirty = true |
| private var isInverseValid = true |
| |
| /** |
| * Ensures that the internal matrix will be updated next time [calculateMatrix] or |
| * [calculateInverseMatrix] is called - this should be called when something that will |
| * change the matrix calculation has happened. |
| */ |
| fun invalidate() { |
| isDirty = true |
| isInverseDirty = true |
| } |
| |
| /** |
| * Returns the cached [Matrix], updating it if required (if [invalidate] was previously called). |
| */ |
| fun calculateMatrix(target: T): Matrix { |
| val matrix = matrixCache ?: Matrix().also { |
| matrixCache = it |
| } |
| if (!isDirty) { |
| return matrix |
| } |
| |
| val cachedMatrix = androidMatrixCache ?: AndroidMatrix().also { |
| androidMatrixCache = it |
| } |
| |
| getMatrix(target, cachedMatrix) |
| |
| val prevMatrix = previousAndroidMatrix |
| if (prevMatrix == null || cachedMatrix != prevMatrix) { |
| matrix.setFrom(cachedMatrix) |
| androidMatrixCache = prevMatrix |
| previousAndroidMatrix = cachedMatrix |
| } |
| |
| isDirty = false |
| return matrix |
| } |
| |
| /** |
| * Returns the cached inverse [Matrix], updating it if required (if [invalidate] was previously |
| * called). This returns `null` if the inverse matrix isn't valid. This can happen, for example, |
| * when scaling is 0. |
| */ |
| fun calculateInverseMatrix(target: T): Matrix? { |
| val matrix = inverseMatrixCache ?: Matrix().also { |
| inverseMatrixCache = it |
| } |
| if (isInverseDirty) { |
| val normalMatrix = calculateMatrix(target) |
| isInverseValid = normalMatrix.invertTo(matrix) |
| isInverseDirty = false |
| } |
| return if (isInverseValid) matrix else null |
| } |
| } |