[go: nahoru, domu]

blob: fd6a4f352715cd8e5621221b4a7b249dbc4ec301 [file] [log] [blame]
/*
* 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.
*/
@file:Suppress("NOTHING_TO_INLINE")
package androidx.ui.unit
import androidx.compose.Immutable
import androidx.ui.geometry.Offset
import androidx.ui.geometry.Rect
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 represented in pixels (px). Component APIs specify their
* dimensions such as line thickness in DP with Dp objects, while drawing and layout are done
* in pixel dimensions. When specific pixel dimensions are required, create a Px and convert
* it to Dp using [Density.toDp]. Px are normally defined using [px], which can be applied to
* [Int], [Double], and [Float].
* val leftMargin = 10.px
* val rightMargin = 10f.px
* val topMargin = 20.0.px
* val bottomMargin = 10.px
*/
@Suppress("EXPERIMENTAL_FEATURE_WARNING")
@Immutable
data /*inline*/ class Px(val value: Float) : Comparable<Px> {
/**
* Add two [Px]s together.
*/
inline operator fun plus(other: Px) =
Px(value = this.value + other.value)
/**
* Subtract a Px from another one.
*/
inline operator fun minus(other: Px) =
Px(value = this.value - other.value)
/**
* This is the same as multiplying the Px by -1.0.
*/
inline operator fun unaryMinus() = Px(-value)
/**
* Divide a Px by a scalar.
*/
inline operator fun div(other: Float): Px =
Px(value = value / other)
inline operator fun div(other: Int): Px =
Px(value = value / other)
/**
* Divide by another Px to get a scalar.
*/
inline operator fun div(other: Px): Float = value / other.value
/**
* Divide by [PxSquared] to get a [PxInverse].
*/
inline operator fun div(other: PxSquared): PxInverse =
PxInverse(value = value / other.value)
/**
* Multiply a Px by a scalar.
*/
inline operator fun times(other: Float): Px =
Px(value = value * other)
inline operator fun times(other: Int): Px =
Px(value = value * other)
/**
* Multiply by a Px to get a [PxSquared] result.
*/
inline operator fun times(other: Px): PxSquared =
PxSquared(value = value * other.value)
/**
* Multiply by a Px to get a [PxSquared] result.
*/
inline operator fun times(other: PxSquared): PxCubed =
PxCubed(value = value * other.value)
/**
* Compare [Px] with another [Px].
*/
override /* TODO: inline */ operator fun compareTo(other: Px) = value.compareTo(other.value)
/**
* Compares this [Px] to another [IntPx]
*/
inline operator fun compareTo(other: IntPx): Int = value.compareTo(other.value)
/**
* Add an [IntPx] to this [Px].
*/
inline operator fun plus(other: IntPx) =
Px(value = this.value + other.value)
/**
* Subtract an [IntPx] from this [Px].
*/
inline operator fun minus(other: IntPx) =
Px(value = this.value - other.value)
override fun toString() = "$value.px"
companion object {
/**
* Infinite px dimension.
*/
val Infinity = Px(value = Float.POSITIVE_INFINITY)
/**
* Zero px dimension
*/
val Zero = Px(0.0f)
}
}
/**
* Create a [Px] using an [Int]:
* val left = 10
* val x = left.px
* // -- or --
* val y = 10.px
*/
inline val Int.px: Px get() = Px(value = this.toFloat())
/**
* Create a [Px] using a [Double]:
* val left = 10.0
* val x = left.px
* // -- or --
* val y = 10.0.px
*/
inline val Double.px: Px get() = Px(value = this.toFloat())
/**
* Create a [Px] using a [Float]:
* val left = 10f
* val x = left.px
* // -- or --
* val y = 10f.px
*/
inline val Float.px: Px get() = Px(value = this)
inline operator fun Float.div(other: Px) =
PxInverse(this / other.value)
inline operator fun Double.div(other: Px) =
PxInverse(this.toFloat() / other.value)
inline operator fun Int.div(other: Px) =
PxInverse(this / other.value)
inline operator fun Float.times(other: Px) =
Px(this * other.value)
inline operator fun Double.times(other: Px) =
Px(this.toFloat() * other.value)
inline operator fun Int.times(other: Px) =
Px(this * other.value)
inline fun min(a: Px, b: Px): Px = Px(value = min(a.value, b.value))
inline fun max(a: Px, b: Px): Px = Px(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].
*/
inline fun Px.coerceIn(minimumValue: Px, maximumValue: Px): Px =
Px(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.
*/
inline fun Px.coerceAtLeast(minimumValue: Px): Px =
Px(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.
*/
inline fun Px.coerceAtMost(maximumValue: Px): Px =
Px(value = value.coerceAtMost(maximumValue.value))
/**
* Linearly interpolate between two [Px]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.
*/
fun lerp(start: Px, stop: Px, fraction: Float): Px {
return Px(lerp(start.value, stop.value, fraction))
}
/**
* Holds a unit of squared dimensions, such as `1.value * 2.px`. [PxSquared], [PxCubed],
* and [PxInverse] are used primarily for [Px] calculations to ensure resulting
* units are as expected. Many times, [Px] 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 PxSquared(val value: Float) : Comparable<PxSquared> {
/**
* Add two DimensionSquares together.
*/
inline operator fun plus(other: PxSquared) =
PxSquared(value = value + other.value)
/**
* Subtract a DimensionSquare from another one.
*/
inline operator fun minus(other: PxSquared) =
PxSquared(value = value - other.value)
/**
* Divide a DimensionSquare by a scalar.
*/
inline operator fun div(other: Float): PxSquared =
PxSquared(value = value / other)
/**
* Divide by a [Px] to get a [Px] result.
*/
inline operator fun div(other: Px): Px =
Px(value = value / other.value)
/**
* Divide by a PxSquared to get a scalar result.
*/
inline operator fun div(other: PxSquared): Float = value / other.value
/**
* Divide by a [PxCubed] to get a [PxInverse] result.
*/
inline operator fun div(other: PxCubed): PxInverse =
PxInverse(value / other.value)
/**
* Multiply by a scalar to get a PxSquared result.
*/
inline operator fun times(other: Float): PxSquared =
PxSquared(value = value * other)
/**
* Multiply by a scalar to get a PxSquared result.
*/
inline operator fun times(other: Px): PxCubed =
PxCubed(value = value * other.value)
/**
* Support comparing PxSquared with comparison operators.
*/
override /* TODO: inline */ operator fun compareTo(other: PxSquared) =
value.compareTo(other.value)
override fun toString(): String = "$value.px^2"
}
/**
* Holds a unit of cubed dimensions, such as `1.value * 2.value * 3.px`. [PxSquared],
* [PxCubed], and [PxInverse] are used primarily for [Px] calculations to
* ensure resulting units are as expected. Many times, [Px] 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 PxCubed(val value: Float) : Comparable<PxCubed> {
/**
* Add two PxCubed together.
*/
inline operator fun plus(dimension: PxCubed) =
PxCubed(value = value + dimension.value)
/**
* Subtract a PxCubed from another one.
*/
inline operator fun minus(dimension: PxCubed) =
PxCubed(value = value - dimension.value)
/**
* Divide a PxCubed by a scalar.
*/
inline operator fun div(other: Float): PxCubed =
PxCubed(value = value / other)
/**
* Divide by a [Px] to get a [PxSquared] result.
*/
inline operator fun div(other: Px): PxSquared =
PxSquared(value = value / other.value)
/**
* Divide by a [PxSquared] to get a [Px] result.
*/
inline operator fun div(other: PxSquared): Px =
Px(value = value / other.value)
/**
* Divide by a PxCubed to get a scalar result.
*/
inline operator fun div(other: PxCubed): Float = value / other.value
/**
* Multiply by a scalar to get a PxCubed result.
*/
inline operator fun times(other: Float): PxCubed =
PxCubed(value = value * other)
/**
* Support comparing PxCubed with comparison operators.
*/
override /* TODO: inline */ operator fun compareTo(other: PxCubed) =
value.compareTo(other.value)
override fun toString(): String = "$value.px^3"
}
/**
* Holds a unit of an inverse dimensions, such as `1.px / (2.value * 3.px)`. [PxSquared],
* [PxCubed], and [PxInverse] are used primarily for [Px] calculations to
* ensure resulting units are as expected. Many times, [Px] 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 PxInverse(val value: Float) : Comparable<PxInverse> {
/**
* Add two PxInverse together.
*/
inline operator fun plus(dimension: PxInverse) =
PxInverse(value = value + dimension.value)
/**
* Subtract a PxInverse from another one.
*/
inline operator fun minus(dimension: PxInverse) =
PxInverse(value = value - dimension.value)
/**
* Divide a PxInverse by a scalar.
*/
inline operator fun div(other: Float): PxInverse =
PxInverse(value = value / other)
/**
* Multiply by a scalar to get a PxInverse result.
*/
inline operator fun times(other: Float): PxInverse =
PxInverse(value = value * other)
/**
* Multiply by a [Px] to get a scalar result.
*/
inline operator fun times(other: Px): Float = value * other.value
/**
* Multiply by a [PxSquared] to get a [Px] result.
*/
inline operator fun times(other: PxSquared): Px =
Px(value = value * other.value)
/**
* Multiply by a [PxCubed] to get a [PxSquared] result.
*/
inline operator fun times(other: PxCubed): PxSquared =
PxSquared(value = value * other.value)
/**
* Support comparing PxInverse with comparison operators.
*/
override /* TODO: inline */ operator fun compareTo(other: PxInverse) =
value.compareTo(other.value)
override fun toString(): String = "$value.px^-1"
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Structures using Px
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
/**
* A two dimensional size using [Px] for units
*/
@UseExperimental(ExperimentalUnsignedTypes::class)
@Immutable
data class PxSize @PublishedApi internal constructor(@PublishedApi internal val value: Long) {
/**
* The horizontal aspect of the size in [Px].
*/
inline val width: Px
get() = unpackFloat1(value).px
/**
* The vertical aspect of the size in [Px].
*/
inline val height: Px
get() = unpackFloat2(value).px
/**
* Returns a PxSize scaled by multiplying [width] and [height] by [other]
*/
inline operator fun times(other: Int): PxSize =
PxSize(width = width * other, height = height * other)
/**
* Returns a PxSize scaled by multiplying [width] and [height] by [other]
*/
inline operator fun times(other: Float): PxSize =
PxSize(width = width * other, height = height * other)
/**
* Returns a PxSize scaled by multiplying [width] and [height] by [other]
*/
inline operator fun times(other: Double): PxSize = times(other.toFloat())
/**
* Returns a PxSize scaled by dividing [width] and [height] by [other]
*/
inline operator fun div(other: Int): PxSize =
PxSize(width = width / other, height = height / other)
/**
* Returns a PxSize scaled by dividing [width] and [height] by [other]
*/
inline operator fun div(other: Float): PxSize =
PxSize(width = width / other, height = height / other)
/**
* Returns a PxSize scaled by dividing [width] and [height] by [other]
*/
inline operator fun div(other: Double): PxSize = div(other.toFloat())
override fun toString(): String = "$width x $height"
companion object {
/**
* [PxSize] with zero values.
*/
val Zero = PxSize(0.px, 0.px)
}
}
/**
* Constructs a [PxSize] from width and height [Px] values.
*/
@UseExperimental(ExperimentalUnsignedTypes::class)
inline fun PxSize(width: Px, height: Px): PxSize = PxSize(packFloats(width.value, height.value))
/**
* Returns a [PxSize] with [size]'s [PxSize.width] and [PxSize.height] multiplied by [this]
*/
inline operator fun Int.times(size: PxSize) = size * this
/**
* Returns a [PxSize] with [size]'s [PxSize.width] and [PxSize.height] multiplied by [this]
*/
inline operator fun Float.times(size: PxSize) = size * this
/**
* Returns a [PxSize] with [size]'s [PxSize.width] and [PxSize.height] multiplied by [this]
*/
inline operator fun Double.times(size: PxSize) = size * this
/**
* Returns the [PxPosition] of the center of the rect from the point of [0, 0]
* with this [PxSize].
*/
fun PxSize.center(): PxPosition {
return PxPosition(width / 2f, height / 2f)
}
/**
* Returns the smallest dimension size.
*/
val PxSize.minDimension get() = min(width, height)
/**
* A two-dimensional position using [Px] for units
*/
@UseExperimental(ExperimentalUnsignedTypes::class)
@Immutable
data class PxPosition @PublishedApi internal constructor(@PublishedApi internal val value: Long) {
/**
* The horizontal aspect of the position in [Px]
*/
inline val x: Px
get() = unpackFloat1(value).px
/**
* The vertical aspect of the position in [Px]
*/
inline val y: Px
get() = unpackFloat2(value).px
/**
* Subtract a [PxPosition] from another one.
*/
inline operator fun minus(other: PxPosition) =
PxPosition(x - other.x, y - other.y)
/**
* Add a [PxPosition] to another one.
*/
inline operator fun plus(other: PxPosition) =
PxPosition(x + other.x, y + other.y)
/**
* Subtract a [IntPxPosition] from this [PxPosition].
*/
inline operator fun minus(other: IntPxPosition) =
PxPosition(x - other.x, y - other.y)
/**
* Add a [IntPxPosition] to this [PxPosition].
*/
inline operator fun plus(other: IntPxPosition) =
PxPosition(x + other.x, y + other.y)
/**
* Returns a new PxPosition representing the negation of this point.
*/
inline operator fun unaryMinus() = PxPosition(-x, -y)
override fun toString(): String = "($x, $y)"
companion object {
val Origin = PxPosition(0.px, 0.px)
}
}
/**
* Constructs a [PxPosition] from [x] and [y] position [Px] values.
*/
@UseExperimental(ExperimentalUnsignedTypes::class)
inline fun PxPosition(x: Px, y: Px): PxPosition = PxPosition(packFloats(x.value, y.value))
/**
* The magnitude of the offset represented by this [PxPosition].
*/
fun PxPosition.getDistance(): Px = Px(sqrt(x.value * x.value + y.value * y.value))
/**
* Convert a [PxPosition] to a [Offset].
*/
inline fun PxPosition.toOffset(): Offset = Offset(x.value, y.value)
/**
* Round a [PxPosition] down to the nearest [Int] coordinates.
*/
inline fun PxPosition.round(): IntPxPosition = IntPxPosition(x.round(), y.round())
/**
* Linearly interpolate between two [PxPosition]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.
*/
fun lerp(start: PxPosition, stop: PxPosition, fraction: Float): PxPosition =
PxPosition(lerp(start.x, stop.x, fraction), lerp(start.y, stop.y, fraction))
/**
* A four dimensional bounds using [Px] for units
*/
@Immutable
data class PxBounds(
val left: Px,
val top: Px,
val right: Px,
val bottom: Px
)
inline fun PxBounds(topLeft: PxPosition, size: PxSize) =
PxBounds(
left = topLeft.x,
top = topLeft.y,
right = topLeft.x + size.width,
bottom = topLeft.y + size.height
)
/**
* A width of this PxBounds in [Px].
*/
inline val PxBounds.width: Px get() = right - left
/**
* A height of this PxBounds in [Px].
*/
inline val PxBounds.height: Px get() = bottom - top
/**
* Convert a [PxBounds] to a [PxSize].
*/
fun PxBounds.toSize(): PxSize {
return PxSize(width, height)
}
/**
* Convert a [PxSize] to a [PxBounds]. The left and top are 0.px and the right and bottom
* are the width and height, respectively.
*/
fun PxSize.toBounds(): PxBounds {
return PxBounds(0.px, 0.px, width, height)
}
/**
* Convert a [PxBounds] to a [Rect].
*/
fun PxBounds.toRect(): Rect {
return Rect(
left.value,
top.value,
right.value,
bottom.value
)
}
/**
* Convert a [PxSize] to a [Rect].
*/
fun PxSize.toRect(): Rect {
return Rect(0f, 0f, width.value, height.value)
}