[go: nahoru, domu]

blob: e170b598d9836bb5e7268d9e0fb3da74e4b66da5 [file] [log] [blame]
/*
* Copyright 2018 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.
*/
@file:Suppress("NOTHING_TO_INLINE")
package androidx.ui.unit
import androidx.compose.Immutable
import androidx.compose.Stable
import androidx.ui.unit.Dp.Companion.Hairline
import androidx.ui.util.lerp
import androidx.ui.util.packFloats
import androidx.ui.util.unpackFloat1
import androidx.ui.util.unpackFloat2
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sqrt
/**
* Dimension value representing device-independent pixels (dp). Component APIs specify their
* dimensions such as line thickness in DP with Dp objects. Hairline (1 pixel) thickness
* may be specified with [Hairline], a dimension that take up no space. Dp are normally
* defined using [dp], which can be applied to [Int], [Double], and [Float].
* val leftMargin = 10.dp
* val rightMargin = 10f.dp
* val topMargin = 20.0.dp
* val bottomMargin = 10.dp
* Drawing is done in pixels. To retrieve the pixel size of a Dp, use [toPx]:
* val lineThicknessPx = lineThickness.toPx(context)
* [toPx] is normally needed only for painting operations.
*/
@Suppress("EXPERIMENTAL_FEATURE_WARNING")
@Immutable
inline class Dp(val value: Float) : Comparable<Dp> {
/**
* Add two [Dp]s together.
*/
@Stable
inline operator fun plus(other: Dp) =
Dp(value = this.value + other.value)
/**
* Subtract a Dp from another one.
*/
@Stable
inline operator fun minus(other: Dp) =
Dp(value = this.value - other.value)
/**
* This is the same as multiplying the Dp by -1.0.
*/
@Stable
inline operator fun unaryMinus() = Dp(-value)
/**
* Divide a Dp by a scalar.
*/
@Stable
inline operator fun div(other: Float): Dp =
Dp(value = value / other)
@Stable
inline operator fun div(other: Int): Dp =
Dp(value = value / other)
/**
* Divide by another Dp to get a scalar.
*/
@Stable
inline operator fun div(other: Dp): Float = value / other.value
/**
* Divide by [DpSquared] to get a [DpInverse].
*/
@Stable
inline operator fun div(other: DpSquared): DpInverse =
DpInverse(value = value / other.value)
/**
* Multiply a Dp by a scalar.
*/
@Stable
inline operator fun times(other: Float): Dp =
Dp(value = value * other)
@Stable
inline operator fun times(other: Int): Dp =
Dp(value = value * other)
/**
* Multiply by a Dp to get a [DpSquared] result.
*/
@Stable
inline operator fun times(other: Dp): DpSquared =
DpSquared(value = value * other.value)
/**
* Multiply by a Dp to get a [DpSquared] result.
*/
@Stable
inline operator fun times(other: DpSquared): DpCubed =
DpCubed(value = value * other.value)
/**
* Support comparing Dimensions with comparison operators.
*/
@Stable
override /* TODO: inline */ operator fun compareTo(other: Dp) = value.compareTo(other.value)
@Stable
override fun toString() = "$value.dp"
companion object {
/**
* A dimension used to represent a hairline drawing element. Hairline elements take up no
* space, but will draw a single pixel, independent of the device's resolution and density.
*/
@Stable
val Hairline = Dp(value = 0f)
/**
* Infinite dp dimension.
*/
@Stable
val Infinity = Dp(value = Float.POSITIVE_INFINITY)
/**
* Constant that means unspecified Dp
*/
@Stable
val Unspecified = Dp(value = Float.NaN)
}
}
/**
* Create a [Dp] using an [Int]:
* val left = 10
* val x = left.dp
* // -- or --
* val y = 10.dp
*/
@Stable
inline val Int.dp: Dp get() = Dp(value = this.toFloat())
/**
* Create a [Dp] using a [Double]:
* val left = 10.0
* val x = left.dp
* // -- or --
* val y = 10.0.dp
*/
@Stable
inline val Double.dp: Dp get() = Dp(value = this.toFloat())
/**
* Create a [Dp] using a [Float]:
* val left = 10f
* val x = left.dp
* // -- or --
* val y = 10f.dp
*/
@Stable
inline val Float.dp: Dp get() = Dp(value = this)
@Stable
inline operator fun Float.div(other: Dp) =
DpInverse(this / other.value)
@Stable
inline operator fun Double.div(other: Dp) =
DpInverse(this.toFloat() / other.value)
@Stable
inline operator fun Int.div(other: Dp) =
DpInverse(this / other.value)
@Stable
inline operator fun Float.times(other: Dp) =
Dp(this * other.value)
@Stable
inline operator fun Double.times(other: Dp) =
Dp(this.toFloat() * other.value)
@Stable
inline operator fun Int.times(other: Dp) =
Dp(this * other.value)
@Stable
inline fun min(a: Dp, b: Dp): Dp = Dp(value = min(a.value, b.value))
@Stable
inline fun max(a: Dp, b: Dp): Dp = Dp(value = max(a.value, b.value))
/**
* Ensures that this value lies in the specified range [minimumValue]..[maximumValue].
*
* @return this value if it's in the range, or [minimumValue] if this value is less than
* [minimumValue], or [maximumValue] if this value is greater than [maximumValue].
*/
@Stable
inline fun Dp.coerceIn(minimumValue: Dp, maximumValue: Dp): Dp =
Dp(value = value.coerceIn(minimumValue.value, maximumValue.value))
/**
* Ensures that this value is not less than the specified [minimumValue].
* @return this value if it's greater than or equal to the [minimumValue] or the
* [minimumValue] otherwise.
*/
@Stable
inline fun Dp.coerceAtLeast(minimumValue: Dp): Dp =
Dp(value = value.coerceAtLeast(minimumValue.value))
/**
* Ensures that this value is not greater than the specified [maximumValue].
*
* @return this value if it's less than or equal to the [maximumValue] or the
* [maximumValue] otherwise.
*/
@Stable
inline fun Dp.coerceAtMost(maximumValue: Dp): Dp =
Dp(value = value.coerceAtMost(maximumValue.value))
/**
*
* Return `true` when it is finite or `false` when it is [Dp.Infinity]
*/
@Stable
inline fun Dp.isFinite(): Boolean = value != Float.POSITIVE_INFINITY
/**
* Linearly interpolate between two [Dp]s.
*
* 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.
*/
@Stable
fun lerp(start: Dp, stop: Dp, fraction: Float): Dp {
return Dp(lerp(start.value, stop.value, fraction))
}
/**
* Holds a unit of squared dimensions, such as `1.value * 2.dp`. [DpSquared], [DpCubed],
* and [DpInverse] are used primarily for [Dp] calculations to ensure resulting
* units are as expected. Many times, [Dp] calculations use scalars to determine the final
* dimension during calculation:
* val width = oldWidth * stretchAmount
* Other times, it is useful to do intermediate calculations with Dimensions directly:
* val width = oldWidth * newTotalWidth / oldTotalWidth
*/
@Suppress("EXPERIMENTAL_FEATURE_WARNING")
@Immutable
inline class DpSquared(val value: Float) : Comparable<DpSquared> {
/**
* Add two DimensionSquares together.
*/
@Stable
inline operator fun plus(other: DpSquared) =
DpSquared(value = value + other.value)
/**
* Subtract a DimensionSquare from another one.
*/
@Stable
inline operator fun minus(other: DpSquared) =
DpSquared(value = value - other.value)
/**
* Divide a DimensionSquare by a scalar.
*/
@Stable
inline operator fun div(other: Float): DpSquared =
DpSquared(value = value / other)
/**
* Divide by a [Dp] to get a [Dp] result.
*/
@Stable
inline operator fun div(other: Dp): Dp =
Dp(value = value / other.value)
/**
* Divide by a DpSquared to get a scalar result.
*/
@Stable
inline operator fun div(other: DpSquared): Float = value / other.value
/**
* Divide by a [DpCubed] to get a [DpInverse] result.
*/
@Stable
inline operator fun div(other: DpCubed): DpInverse =
DpInverse(value / other.value)
/**
* Multiply by a scalar to get a DpSquared result.
*/
@Stable
inline operator fun times(other: Float): DpSquared =
DpSquared(value = value * other)
/**
* Multiply by a scalar to get a DpSquared result.
*/
@Stable
inline operator fun times(other: Dp): DpCubed =
DpCubed(value = value * other.value)
/**
* Support comparing DpSquared with comparison operators.
*/
@Stable
override /* TODO: inline */ operator fun compareTo(other: DpSquared) =
value.compareTo(other.value)
@Stable
override fun toString(): String = "$value.dp^2"
}
/**
* Holds a unit of cubed dimensions, such as `1.value * 2.value * 3.dp`. [DpSquared],
* [DpCubed], and [DpInverse] are used primarily for [Dp] calculations to
* ensure resulting units are as expected. Many times, [Dp] calculations use scalars to
* determine the final dimension during calculation:
* val width = oldWidth * stretchAmount
* Other times, it is useful to do intermediate calculations with Dimensions directly:
* val width = oldWidth * newTotalWidth / oldTotalWidth
*/
@Suppress("EXPERIMENTAL_FEATURE_WARNING")
@Immutable
inline class DpCubed(val value: Float) : Comparable<DpCubed> {
/**
* Add two DpCubed together.
*/
@Stable
inline operator fun plus(dimension: DpCubed) =
DpCubed(value = value + dimension.value)
/**
* Subtract a DpCubed from another one.
*/
@Stable
inline operator fun minus(dimension: DpCubed) =
DpCubed(value = value - dimension.value)
/**
* Divide a DpCubed by a scalar.
*/
@Stable
inline operator fun div(other: Float): DpCubed =
DpCubed(value = value / other)
/**
* Divide by a [Dp] to get a [DpSquared] result.
*/
@Stable
inline operator fun div(other: Dp): DpSquared =
DpSquared(value = value / other.value)
/**
* Divide by a [DpSquared] to get a [Dp] result.
*/
@Stable
inline operator fun div(other: DpSquared): Dp =
Dp(value = value / other.value)
/**
* Divide by a DpCubed to get a scalar result.
*/
@Stable
inline operator fun div(other: DpCubed): Float = value / other.value
/**
* Multiply by a scalar to get a DpCubed result.
*/
@Stable
inline operator fun times(other: Float): DpCubed =
DpCubed(value = value * other)
/**
* Support comparing DpCubed with comparison operators.
*/
@Stable
override /* TODO: inline */ operator fun compareTo(other: DpCubed) =
value.compareTo(other.value)
@Stable
override fun toString(): String = "$value.dp^3"
}
/**
* Holds a unit of an inverse dimensions, such as `1.dp / (2.value * 3.dp)`. [DpSquared],
* [DpCubed], and [DpInverse] are used primarily for [Dp] calculations to
* ensure resulting units are as expected. Many times, [Dp] calculations use scalars to
* determine the final dimension during calculation:
* val width = oldWidth * stretchAmount
* Other times, it is useful to do intermediate calculations with Dimensions directly:
* val width = oldWidth * newTotalWidth / oldTotalWidth
*/
@Suppress("EXPERIMENTAL_FEATURE_WARNING")
@Immutable
inline class DpInverse(val value: Float) : Comparable<DpInverse> {
/**
* Add two DpInverse together.
*/
@Stable
inline operator fun plus(dimension: DpInverse) =
DpInverse(value = value + dimension.value)
/**
* Subtract a DpInverse from another one.
*/
@Stable
inline operator fun minus(dimension: DpInverse) =
DpInverse(value = value - dimension.value)
/**
* Divide a DpInverse by a scalar.
*/
@Stable
inline operator fun div(other: Float): DpInverse =
DpInverse(value = value / other)
/**
* Multiply by a scalar to get a DpInverse result.
*/
@Stable
inline operator fun times(other: Float): DpInverse =
DpInverse(value = value * other)
/**
* Multiply by a [Dp] to get a scalar result.
*/
@Stable
inline operator fun times(other: Dp): Float = value * other.value
/**
* Multiply by a [DpSquared] to get a [Dp] result.
*/
@Stable
inline operator fun times(other: DpSquared): Dp =
Dp(value = value * other.value)
/**
* Multiply by a [DpCubed] to get a [DpSquared] result.
*/
@Stable
inline operator fun times(other: DpCubed): DpSquared =
DpSquared(value = value * other.value)
/**
* Support comparing DpInverse with comparison operators.
*/
@Stable
override /* TODO: inline */ operator fun compareTo(other: DpInverse) =
value.compareTo(other.value)
@Stable
override fun toString(): String = "$value.dp^-1"
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Structures using Dp
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
/**
* A two-dimensional position using [Dp] for units
*/
@OptIn(ExperimentalUnsignedTypes::class)
@Suppress("EXPERIMENTAL_FEATURE_WARNING")
@Immutable
inline class Position(@PublishedApi internal val packedValue: Long) {
/**
* The horizontal aspect of the position in [Dp]
*/
@Stable
/*inline*/ val x: Dp
get() = unpackFloat1(packedValue).dp
/**
* The vertical aspect of the position in [Dp]
*/
@Stable
/*inline*/ val y: Dp
get() = unpackFloat2(packedValue).dp
/**
* Subtract a [Position] from another one.
*/
@Stable
inline operator fun minus(other: Position) =
Position(x - other.x, y - other.y)
/**
* Add a [Position] to another one.
*/
@Stable
inline operator fun plus(other: Position) =
Position(x + other.x, y + other.y)
@Stable
override fun toString(): String = "($x, $y)"
}
/**
* Constructs a [Position] from [x] and [y] position [Dp] values.
*/
@OptIn(ExperimentalUnsignedTypes::class)
@Stable
inline fun Position(x: Dp, y: Dp): Position = Position(packFloats(x.value, y.value))
/**
* The magnitude of the offset represented by this [Position].
*/
@Stable
fun Position.getDistance(): Dp {
return Dp(sqrt(x.value * x.value + y.value * y.value))
}
/**
* Linearly interpolate between two [Position]s.
*
* 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.
*/
@Stable
fun lerp(start: Position, stop: Position, fraction: Float): Position =
Position(lerp(start.x, stop.x, fraction), lerp(start.y, stop.y, fraction))
/**
* A four dimensional bounds using [Dp] for units
*/
@Immutable
data class Bounds(
@Stable
val left: Dp,
@Stable
val top: Dp,
@Stable
val right: Dp,
@Stable
val bottom: Dp
)
/**
* A width of this Bounds in [Dp].
*/
@Stable
inline val Bounds.width: Dp get() = right - left
/**
* A height of this Bounds in [Dp].
*/
@Stable
inline val Bounds.height: Dp get() = bottom - top