1 /*
2  * Copyright 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 @file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
17 
18 package androidx.compose.ui.unit
19 
20 import androidx.compose.runtime.Immutable
21 import androidx.compose.runtime.Stable
22 import androidx.compose.ui.geometry.isSpecified
23 import androidx.compose.ui.unit.Dp.Companion.Hairline
24 import androidx.compose.ui.util.fastIsFinite
25 import androidx.compose.ui.util.lerp
26 import androidx.compose.ui.util.packFloats
27 import androidx.compose.ui.util.unpackFloat1
28 import androidx.compose.ui.util.unpackFloat2
29 import kotlin.jvm.JvmInline
30 import kotlin.math.max
31 import kotlin.math.min
32 
33 /**
34  * Dimension value representing device-independent pixels (dp). Component APIs specify their
35  * dimensions such as line thickness in DP with Dp objects. Hairline (1 pixel) thickness may be
36  * specified with [Hairline], a dimension that take up no space. Dp are normally defined using [dp],
37  * which can be applied to [Int], [Double], and [Float].
38  *
39  * @sample androidx.compose.ui.unit.samples.DpSample
40  *
41  * Drawing and Layout are done in pixels. To retrieve the pixel size of a Dp, use [Density.toPx]:
42  *
43  * @sample androidx.compose.ui.unit.samples.ToPxSample
44  */
45 @Immutable
46 @JvmInline
47 value class Dp(val value: Float) : Comparable<Dp> {
48     /** Add two [Dp]s together. */
plusnull49     @Stable inline operator fun plus(other: Dp) = Dp(this.value + other.value)
50 
51     /** Subtract a Dp from another one. */
52     @Stable inline operator fun minus(other: Dp) = Dp(this.value - other.value)
53 
54     /** This is the same as multiplying the Dp by -1.0. */
55     @Stable inline operator fun unaryMinus() = Dp(-value)
56 
57     /** Divide a Dp by a scalar. */
58     @Stable inline operator fun div(other: Float): Dp = Dp(value / other)
59 
60     @Stable inline operator fun div(other: Int): Dp = Dp(value / other)
61 
62     /** Divide by another Dp to get a scalar. */
63     @Stable inline operator fun div(other: Dp): Float = value / other.value
64 
65     /** Multiply a Dp by a scalar. */
66     @Stable inline operator fun times(other: Float): Dp = Dp(value * other)
67 
68     @Stable inline operator fun times(other: Int): Dp = Dp(value * other)
69 
70     /** Support comparing Dimensions with comparison operators. */
71     @Stable
72     override /* TODO: inline */  operator fun compareTo(other: Dp) = value.compareTo(other.value)
73 
74     @Stable override fun toString() = if (isUnspecified) "Dp.Unspecified" else "$value.dp"
75 
76     companion object {
77         /**
78          * A dimension used to represent a hairline drawing element. Hairline elements take up no
79          * space, but will draw a single pixel, independent of the device's resolution and density.
80          */
81         @Stable val Hairline = Dp(0f)
82 
83         /** Infinite dp dimension. */
84         @Stable val Infinity = Dp(Float.POSITIVE_INFINITY)
85 
86         /**
87          * Constant that means unspecified Dp. Instead of comparing a [Dp] value to this constant,
88          * consider using [isSpecified] and [isUnspecified] instead.
89          */
90         @Stable val Unspecified = Dp(Float.NaN)
91     }
92 }
93 
94 /** `false` when this is [Dp.Unspecified]. */
95 @Stable
96 inline val Dp.isSpecified: Boolean
97     get() = !value.isNaN()
98 
99 /** `true` when this is [Dp.Unspecified]. */
100 @Stable
101 inline val Dp.isUnspecified: Boolean
102     get() = value.isNaN()
103 
104 /**
105  * If this [Dp] [isSpecified] then this is returned, otherwise [block] is executed and its result is
106  * returned.
107  */
takeOrElsenull108 inline fun Dp.takeOrElse(block: () -> Dp): Dp = if (isSpecified) this else block()
109 
110 /** Create a [Dp] using an [Int]: val left = 10 val x = left.dp // -- or -- val y = 10.dp */
111 @Stable
112 inline val Int.dp: Dp
113     get() = Dp(this.toFloat())
114 
115 /** Create a [Dp] using a [Double]: val left = 10.0 val x = left.dp // -- or -- val y = 10.0.dp */
116 @Stable
117 inline val Double.dp: Dp
118     get() = Dp(this.toFloat())
119 
120 /** Create a [Dp] using a [Float]: val left = 10f val x = left.dp // -- or -- val y = 10f.dp */
121 @Stable
122 inline val Float.dp: Dp
123     get() = Dp(this)
124 
125 @Stable inline operator fun Float.times(other: Dp) = Dp(this * other.value)
126 
127 @Stable inline operator fun Double.times(other: Dp) = Dp(this.toFloat() * other.value)
128 
129 @Stable inline operator fun Int.times(other: Dp) = Dp(this * other.value)
130 
131 @Stable inline fun min(a: Dp, b: Dp): Dp = Dp(min(a.value, b.value))
132 
133 @Stable inline fun max(a: Dp, b: Dp): Dp = Dp(max(a.value, b.value))
134 
135 /**
136  * Ensures that this value lies in the specified range [minimumValue]..[maximumValue].
137  *
138  * @return this value if it's in the range, or [minimumValue] if this value is less than
139  *   [minimumValue], or [maximumValue] if this value is greater than [maximumValue].
140  */
141 @Stable
142 inline fun Dp.coerceIn(minimumValue: Dp, maximumValue: Dp): Dp =
143     Dp(value.coerceIn(minimumValue.value, maximumValue.value))
144 
145 /**
146  * Ensures that this value is not less than the specified [minimumValue].
147  *
148  * @return this value if it's greater than or equal to the [minimumValue] or the [minimumValue]
149  *   otherwise.
150  */
151 @Stable
152 inline fun Dp.coerceAtLeast(minimumValue: Dp): Dp = Dp(value.coerceAtLeast(minimumValue.value))
153 
154 /**
155  * Ensures that this value is not greater than the specified [maximumValue].
156  *
157  * @return this value if it's less than or equal to the [maximumValue] or the [maximumValue]
158  *   otherwise.
159  */
160 @Stable
161 inline fun Dp.coerceAtMost(maximumValue: Dp): Dp = Dp(value.coerceAtMost(maximumValue.value))
162 
163 /** Return `true` when it is finite or `false` when it is [Dp.Infinity] */
164 @Stable
165 inline val Dp.isFinite: Boolean
166     get() = value.fastIsFinite()
167 
168 /**
169  * Linearly interpolate between two [Dp]s.
170  *
171  * The [fraction] argument represents position on the timeline, with 0.0 meaning that the
172  * interpolation has not started, returning [start] (or something equivalent to [start]), 1.0
173  * meaning that the interpolation has finished, returning [stop] (or something equivalent to
174  * [stop]), and values in between meaning that the interpolation is at the relevant point on the
175  * timeline between [start] and [stop]. The interpolation can be extrapolated beyond 0.0 and 1.0, so
176  * negative values and values greater than 1.0 are valid.
177  */
178 @Stable
179 fun lerp(start: Dp, stop: Dp, fraction: Float): Dp {
180     return Dp(lerp(start.value, stop.value, fraction))
181 }
182 
183 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
184 // Structures using Dp
185 // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
186 
187 /** Constructs a [DpOffset] from [x] and [y] position [Dp] values. */
DpOffsetnull188 @Stable inline fun DpOffset(x: Dp, y: Dp): DpOffset = DpOffset(packFloats(x.value, y.value))
189 
190 /**
191  * A two-dimensional offset using [Dp] for units.
192  *
193  * To create a [DpOffset], call the top-level function that accepts an x/y pair of coordinates:
194  * ```
195  * val offset = DpOffset(x, y)
196  * ```
197  *
198  * The primary constructor of [DpOffset] is intended to be used with the [packedValue] property to
199  * allow storing offsets in arrays or collections of primitives without boxing.
200  *
201  * @param packedValue [Long] value encoding the [x] and [y] components of the [DpOffset]. Encoded
202  *   values can be obtained by using the [packedValue] property of existing [DpOffset] instances.
203  */
204 @Immutable
205 @JvmInline
206 value class DpOffset(val packedValue: Long) {
207     /** The horizontal aspect of the offset in [Dp] */
208     @Stable
209     val x: Dp
210         get() = unpackFloat1(packedValue).dp
211 
212     /** The vertical aspect of the offset in [Dp] */
213     @Stable
214     val y: Dp
215         get() = unpackFloat2(packedValue).dp
216 
217     /** Returns a copy of this [DpOffset] instance optionally overriding the x or y parameter */
218     fun copy(x: Dp = this.x, y: Dp = this.y): DpOffset = DpOffset(packFloats(x.value, y.value))
219 
220     /** Subtract a [DpOffset] from another one. */
221     @Stable
222     operator fun minus(other: DpOffset) =
223         DpOffset(packFloats((x - other.x).value, (y - other.y).value))
224 
225     /** Add a [DpOffset] to another one. */
226     @Stable
227     operator fun plus(other: DpOffset) =
228         DpOffset(packFloats((x + other.x).value, (y + other.y).value))
229 
230     @Stable
231     override fun toString(): String =
232         if (isSpecified) {
233             "($x, $y)"
234         } else {
235             "DpOffset.Unspecified"
236         }
237 
238     companion object {
239         /** A [DpOffset] with 0 DP [x] and 0 DP [y] values. */
240         val Zero = DpOffset(0x0L)
241 
242         /**
243          * Represents an offset whose [x] and [y] are unspecified. This is usually a replacement for
244          * `null` when a primitive value is desired. Access to [x] or [y] on an unspecified offset
245          * is not allowed.
246          */
247         val Unspecified = DpOffset(0x7fc00000_7fc00000L)
248     }
249 }
250 
251 /** `false` when this is [DpOffset.Unspecified]. */
252 @Stable
253 inline val DpOffset.isSpecified: Boolean
254     get() = packedValue != 0x7fc00000_7fc00000L // Keep UnspecifiedPackedFloats internal
255 
256 /** `true` when this is [DpOffset.Unspecified]. */
257 @Stable
258 inline val DpOffset.isUnspecified: Boolean
259     get() = packedValue == 0x7fc00000_7fc00000L // Keep UnspecifiedPackedFloats internal
260 
261 /**
262  * If this [DpOffset]&nbsp;[isSpecified] then this is returned, otherwise [block] is executed and
263  * its result is returned.
264  */
takeOrElsenull265 inline fun DpOffset.takeOrElse(block: () -> DpOffset): DpOffset = if (isSpecified) this else block()
266 
267 /**
268  * Linearly interpolate between two [DpOffset]s.
269  *
270  * The [fraction] argument represents position on the timeline, with 0.0 meaning that the
271  * interpolation has not started, returning [start] (or something equivalent to [start]), 1.0
272  * meaning that the interpolation has finished, returning [stop] (or something equivalent to
273  * [stop]), and values in between meaning that the interpolation is at the relevant point on the
274  * timeline between [start] and [stop]. The interpolation can be extrapolated beyond 0.0 and 1.0, so
275  * negative values and values greater than 1.0 are valid.
276  */
277 @Stable
278 fun lerp(start: DpOffset, stop: DpOffset, fraction: Float): DpOffset =
279     DpOffset(
280         packFloats(
281             lerp(start.x.value, stop.x.value, fraction),
282             lerp(start.y.value, stop.y.value, fraction)
283         )
284     )
285 
286 /** Constructs a [DpSize] from [width] and [height] [Dp] values. */
287 @Stable fun DpSize(width: Dp, height: Dp): DpSize = DpSize(packFloats(width.value, height.value))
288 
289 /** A two-dimensional Size using [Dp] for units */
290 @Immutable
291 @JvmInline
292 value class DpSize internal constructor(@PublishedApi internal val packedValue: Long) {
293     /** The horizontal aspect of the Size in [Dp] */
294     @Stable
295     val width: Dp
296         get() = unpackFloat1(packedValue).dp
297 
298     /** The vertical aspect of the Size in [Dp] */
299     @Stable
300     val height: Dp
301         get() = unpackFloat2(packedValue).dp
302 
303     /**
304      * Returns a copy of this [DpSize] instance optionally overriding the width or height parameter
305      */
306     fun copy(width: Dp = this.width, height: Dp = this.height): DpSize =
307         DpSize(packFloats(width.value, height.value))
308 
309     /** Subtract a [DpSize] from another one. */
310     @Stable
311     operator fun minus(other: DpSize) =
312         DpSize(packFloats((width - other.width).value, (height - other.height).value))
313 
314     /** Add a [DpSize] to another one. */
315     @Stable
316     operator fun plus(other: DpSize) =
317         DpSize(packFloats((width + other.width).value, (height + other.height).value))
318 
319     @Stable inline operator fun component1(): Dp = width
320 
321     @Stable inline operator fun component2(): Dp = height
322 
323     @Stable
324     operator fun times(other: Int): DpSize =
325         DpSize(packFloats((width * other).value, (height * other).value))
326 
327     @Stable
328     operator fun times(other: Float): DpSize =
329         DpSize(packFloats((width * other).value, (height * other).value))
330 
331     @Stable
332     operator fun div(other: Int): DpSize =
333         DpSize(packFloats((width / other).value, (height / other).value))
334 
335     @Stable
336     operator fun div(other: Float): DpSize =
337         DpSize(packFloats((width / other).value, (height / other).value))
338 
339     @Stable
340     override fun toString(): String =
341         if (isSpecified) {
342             "$width x $height"
343         } else {
344             "DpSize.Unspecified"
345         }
346 
347     companion object {
348         /** A [DpSize] with 0 DP [width] and 0 DP [height] values. */
349         val Zero = DpSize(0x0L)
350 
351         /**
352          * A size whose [width] and [height] are unspecified. This is usually a replacement for
353          * `null` when a primitive value is desired. Access to [width] or [height] on an unspecified
354          * size is not allowed.
355          */
356         val Unspecified = DpSize(0x7fc00000_7fc00000L)
357     }
358 }
359 
360 /** `false` when this is [DpSize.Unspecified]. */
361 @Stable
362 inline val DpSize.isSpecified: Boolean
363     get() = packedValue != 0x7fc00000_7fc00000L // Keep UnspecifiedPackedFloats internal
364 
365 /** `true` when this is [DpSize.Unspecified]. */
366 @Stable
367 inline val DpSize.isUnspecified: Boolean
368     get() = packedValue == 0x7fc00000_7fc00000L // Keep UnspecifiedPackedFloats internal
369 
370 /**
371  * If this [DpSize]&nbsp;[isSpecified] then this is returned, otherwise [block] is executed and its
372  * result is returned.
373  */
takeOrElsenull374 inline fun DpSize.takeOrElse(block: () -> DpSize): DpSize = if (isSpecified) this else block()
375 
376 /** Returns the [DpOffset] of the center of the rect from the point of [0, 0] with this [DpSize]. */
377 @Stable
378 val DpSize.center: DpOffset
379     get() = DpOffset(packFloats((width / 2f).value, (height / 2f).value))
380 
381 @Stable inline operator fun Int.times(size: DpSize) = size * this
382 
383 @Stable inline operator fun Float.times(size: DpSize) = size * this
384 
385 /**
386  * Linearly interpolate between two [DpSize]s.
387  *
388  * The [fraction] argument represents position on the timeline, with 0.0 meaning that the
389  * interpolation has not started, returning [start], 1.0 meaning that the interpolation has
390  * finished, returning [stop], and values in between meaning that the interpolation is at the
391  * relevant point on the timeline between [start] and [stop]. The interpolation can be extrapolated
392  * beyond 0.0 and 1.0, so negative values and values greater than 1.0 are valid.
393  */
394 @Stable
395 fun lerp(start: DpSize, stop: DpSize, fraction: Float): DpSize =
396     DpSize(
397         packFloats(
398             lerp(start.width, stop.width, fraction).value,
399             lerp(start.height, stop.height, fraction).value
400         )
401     )
402 
403 /** A four dimensional bounds using [Dp] for units */
404 @Immutable
405 @Suppress("DataClassDefinition")
406 data class DpRect(
407     @Stable val left: Dp,
408     @Stable val top: Dp,
409     @Stable val right: Dp,
410     @Stable val bottom: Dp
411 ) {
412     /** Constructs a [DpRect] from the top-left [origin] and the width and height in [size]. */
413     constructor(
414         origin: DpOffset,
415         size: DpSize
416     ) : this(origin.x, origin.y, origin.x + size.width, origin.y + size.height)
417 
418     companion object
419 }
420 
421 /** A width of this Bounds in [Dp]. */
422 @Stable
423 inline val DpRect.width: Dp
424     get() = right - left
425 
426 /** A height of this Bounds in [Dp]. */
427 @Stable
428 inline val DpRect.height: Dp
429     get() = bottom - top
430 
431 /** Returns the size of the [DpRect]. */
432 @Stable
433 inline val DpRect.size: DpSize
434     get() = DpSize(width, height)
435