[go: nahoru, domu]

blob: 9bec925ad6a2ac8b73899837b7c9d82dc2a0fd65 [file] [log] [blame]
/*
* Copyright 2020 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.layout
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.util.packFloats
import androidx.compose.ui.util.unpackFloat1
import androidx.compose.ui.util.unpackFloat2
/**
* Constructs a [ScaleFactor] from the given x and y scale values
*/
@Stable
fun ScaleFactor(scaleX: Float, scaleY: Float) = ScaleFactor(packFloats(scaleX, scaleY))
/**
* Holds 2 dimensional scaling factors for horizontal and vertical axes
*/
@Suppress("INLINE_CLASS_DEPRECATED", "EXPERIMENTAL_FEATURE_WARNING")
@Immutable
inline class ScaleFactor internal constructor(@PublishedApi internal val packedValue: Long) {
/**
* Returns the scale factor to apply along the horizontal axis
*/
@Stable
val scaleX: Float
get() {
// Explicitly compare against packed values to avoid
// auto-boxing of ScaleFactor.Unspecified
check(this.packedValue != ScaleFactor.Unspecified.packedValue) {
"ScaleFactor is unspecified"
}
return unpackFloat1(packedValue)
}
/**
* Returns the scale factor to apply along the vertical axis
*/
@Stable
val scaleY: Float
get() {
// Explicitly compare against packed values to avoid
// auto-boxing of Size.Unspecified
check(this.packedValue != ScaleFactor.Unspecified.packedValue) {
"ScaleFactor is unspecified"
}
return unpackFloat2(packedValue)
}
@Suppress("NOTHING_TO_INLINE")
@Stable
inline operator fun component1(): Float = scaleX
@Suppress("NOTHING_TO_INLINE")
@Stable
inline operator fun component2(): Float = scaleY
/**
* Returns a copy of this ScaleFactor instance optionally overriding the
* scaleX or scaleY parameters
*/
fun copy(scaleX: Float = this.scaleX, scaleY: Float = this.scaleY) = ScaleFactor(scaleX, scaleY)
/**
* Multiplication operator.
*
* Returns a [ScaleFactor] with scale x and y values multiplied by the operand
*/
@Stable
operator fun times(operand: Float) = ScaleFactor(scaleX * operand, scaleY * operand)
/**
* Division operator.
*
* Returns a [ScaleFactor] with scale x and y values divided by the operand
*/
@Stable
operator fun div(operand: Float) = ScaleFactor(scaleX / operand, scaleY / operand)
override fun toString() = "ScaleFactor(${scaleX.roundToTenths()}, ${scaleY.roundToTenths()})"
companion object {
/**
* A ScaleFactor whose [scaleX] and [scaleY] parameters are unspecified. This is a sentinel
* value used to initialize a non-null parameter.
* Access to scaleX or scaleY on an unspecified size is not allowed
*/
@Stable
val Unspecified = ScaleFactor(Float.NaN, Float.NaN)
}
}
private fun Float.roundToTenths(): Float {
val shifted = this * 10
val decimal = shifted - shifted.toInt()
// Kotlin's round operator rounds 0.5f down to 0. Manually compare against
// 0.5f and round up if necessary
val roundedShifted = if (decimal >= 0.5f) {
shifted.toInt() + 1
} else {
shifted.toInt()
}
return roundedShifted.toFloat() / 10
}
/**
* `false` when this is [ScaleFactor.Unspecified].
*/
@Stable
inline val ScaleFactor.isSpecified: Boolean
get() = packedValue != ScaleFactor.Unspecified.packedValue
/**
* `true` when this is [ScaleFactor.Unspecified].
*/
@Stable
inline val ScaleFactor.isUnspecified: Boolean
get() = packedValue == ScaleFactor.Unspecified.packedValue
/**
* If this [ScaleFactor] [isSpecified] then this is returned, otherwise [block] is executed
* and its result is returned.
*/
inline fun ScaleFactor.takeOrElse(block: () -> ScaleFactor): ScaleFactor =
if (isSpecified) this else block()
/**
* Multiplication operator with [Size].
*
* Return a new [Size] with the width and height multiplied by the [ScaleFactor.scaleX] and
* [ScaleFactor.scaleY] respectively
*/
@Stable
operator fun Size.times(scaleFactor: ScaleFactor): Size =
Size(this.width * scaleFactor.scaleX, this.height * scaleFactor.scaleY)
/**
* Multiplication operator with [Size] with reverse parameter types to maintain
* commutative properties of multiplication
*
* Return a new [Size] with the width and height multiplied by the [ScaleFactor.scaleX] and
* [ScaleFactor.scaleY] respectively
*/
@Stable
operator fun ScaleFactor.times(size: Size): Size = size * this
/**
* Division operator with [Size]
*
* Return a new [Size] with the width and height divided by [ScaleFactor.scaleX] and
* [ScaleFactor.scaleY] respectively
*/
@Stable
operator fun Size.div(scaleFactor: ScaleFactor): Size =
Size(width / scaleFactor.scaleX, height / scaleFactor.scaleY)
/**
* Linearly interpolate between two [ScaleFactor] parameters
*
* The [fraction] argument represents position on the timeline, with 0.0 meaning
* that the interpolation has not started, returning [start] (or something
* equivalent to [start]), 1.0 meaning that the interpolation has finished,
* returning [stop] (or something equivalent to [stop]), and values in between
* meaning that the interpolation is at the relevant point on the timeline
* between [start] and [stop]. The interpolation can be extrapolated beyond 0.0 and
* 1.0, so negative values and values greater than 1.0 are valid (and can
* easily be generated by curves).
*
* Values for [fraction] are usually obtained from an [Animation<Float>], such as
* an `AnimationController`.
*/
@Stable
fun lerp(start: ScaleFactor, stop: ScaleFactor, fraction: Float): ScaleFactor {
return ScaleFactor(
androidx.compose.ui.util.lerp(start.scaleX, stop.scaleX, fraction),
androidx.compose.ui.util.lerp(start.scaleY, stop.scaleY, fraction)
)
}