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] [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] [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