ScaleUtil.kt
/*
* Copyright 2019 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.gesture
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.positionChange
import kotlin.math.abs
import kotlin.math.hypot
/**
* Gets the difference in direction and magnitude of the distance between the given pointer
* (represented by [i]) along a given dimension (represented by [previous] and [current]). For
* example, if a given pointer's x value was previously 5 pixels greater than the average x values
* and is currently 7 pixels greater than the average X value, this will return a value of 2.
*/
internal fun getVectorToAverageChange(
previous: DimensionData,
current: DimensionData,
i: Int
): Float {
val currentVectorToAverage = current.vectorsToAverage[i]
val previousVectorToAverage = previous.vectorsToAverage[i]
val absDistanceChanged = abs(abs(currentVectorToAverage) - abs(previousVectorToAverage))
return if (currentVectorToAverage - previousVectorToAverage < 0) {
-absDistanceChanged
} else {
absDistanceChanged
}
}
/**
* Calculates the [DimensionData] for a set of values that represent pointer positions on one
* dimension.
*/
internal fun List<Float>.calculateDimensionInformation(): DimensionData {
val average = average().toFloat()
val vectorsToAverage = map {
it - average
}
return DimensionData(average, vectorsToAverage)
}
/**
* Calculates [AllDimensionData] for the given list of [PointerInputChange]s.
*/
internal fun List<PointerInputChange>.calculateAllDimensionInformation() =
AllDimensionData(
map {
it.previous.position!!.x
}.calculateDimensionInformation(),
map {
it.previous.position!!.y
}.calculateDimensionInformation(),
map {
it.previous.position!!.x + it.positionChange().x
}.calculateDimensionInformation(),
map {
it.previous.position!!.y + it.positionChange().y
}.calculateDimensionInformation()
)
/**
* Calculates the scale factor from the [AllDimensionData].
*
* A scale factor of .5 means that the average distance to the center of all pointers has been
* cut in half, while a scale factor of 2 means that average has doubled. A scale factor of 1 means
* no scaling has occurred.
*/
internal fun AllDimensionData.calculateScaleFactor() =
averageDistanceToCenter(currentX, currentY) / averageDistanceToCenter(previousX, previousY)
/**
* Calculates the average distance change of all pointers from the average pointer using
* [AllDimensionData].
*
* If 2 pointers were 10 pixel away from each other, and then move such that they were 20
* pixels away from each other, this function would return 10.
*
* If 2 pointers were 10 pixels away from each other, and then moved such that they were 5 pixels
* away from each other, this function would return 5
*/
internal fun AllDimensionData.calculateScaleDifference(): Float =
(averageDistanceToCenter(currentX, currentY) - averageDistanceToCenter(previousX, previousY))
/**
* Data about a particular dimension (x or y).
*
* @param average The average value for all of the points along the given dimension.
* @param vectorsToAverage The list of vectors from each point on the given dimension to the
* average. Negative means to the left or up, and positive means to the right or down.
*/
internal data class DimensionData(
val average: Float,
val vectorsToAverage: List<Float>
)
/**
* The collection of all [DimensionData] for the previous and current pointer locations.
*/
internal data class AllDimensionData(
val previousX: DimensionData,
val previousY: DimensionData,
val currentX: DimensionData,
val currentY: DimensionData
)
/**
* Calculates the average distance to the center of all pointers represented by [x] and [y].
*/
private fun averageDistanceToCenter(x: DimensionData, y: DimensionData): Float {
var totalDistanceToCenter = 0f
val count = x.vectorsToAverage.size
for (i in 0 until count) {
totalDistanceToCenter += hypot(x.vectorsToAverage[i], y.vectorsToAverage[i])
}
return totalDistanceToCenter / count
}