1 /*
2 * Copyright 2019 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
17 @file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
18
19 package androidx.compose.ui.geometry
20
21 import androidx.compose.runtime.Immutable
22 import androidx.compose.runtime.Stable
23 import androidx.compose.ui.util.lerp
24 import androidx.compose.ui.util.packFloats
25 import androidx.compose.ui.util.unpackAbsFloat1
26 import androidx.compose.ui.util.unpackAbsFloat2
27 import androidx.compose.ui.util.unpackFloat1
28 import androidx.compose.ui.util.unpackFloat2
29 import kotlin.math.max
30 import kotlin.math.min
31
32 /** Constructs a [Size] from the given width and height */
Sizenull33 @Stable inline fun Size(width: Float, height: Float) = Size(packFloats(width, height))
34
35 /**
36 * Holds a 2D floating-point size.
37 *
38 * You can think of this as an [Offset] from the origin.
39 *
40 * To create a [Size], call the top-level function that accepts a width/height pair of dimensions:
41 * ```
42 * val size = Size(width, height)
43 * ```
44 *
45 * The primary constructor of [Size] is intended to be used with the [packedValue] property to allow
46 * storing sizes in arrays or collections of primitives without boxing.
47 *
48 * @param packedValue [Long] value encoding the [width] and [height] components of the [Size].
49 * Encoded values can be obtained by using the [packedValue] property of existing [Size]
50 * instances.
51 */
52 @Immutable
53 @kotlin.jvm.JvmInline
54 value class Size(val packedValue: Long) {
55 @Stable
56 inline val width: Float
57 get() = unpackFloat1(packedValue)
58
59 @Stable
60 inline val height: Float
61 get() = unpackFloat2(packedValue)
62
63 @Stable inline operator fun component1(): Float = width
64
65 @Stable inline operator fun component2(): Float = height
66
67 /** Returns a copy of this Size instance optionally overriding the width or height parameter */
68 fun copy(width: Float = unpackFloat1(packedValue), height: Float = unpackFloat2(packedValue)) =
69 Size(packFloats(width, height))
70
71 companion object {
72 /** An empty size, one with a zero width and a zero height. */
73 @Stable val Zero = Size(0x0L)
74
75 /**
76 * A size whose [width] and [height] are unspecified. This is a sentinel value used to
77 * initialize a non-null parameter. Access to width or height on an unspecified size is not
78 * allowed.
79 */
80 @Stable val Unspecified = Size(UnspecifiedPackedFloats)
81 }
82
83 /**
84 * Whether this size encloses a non-zero area.
85 *
86 * Negative areas are considered empty.
87 */
88 @Stable
89 fun isEmpty(): Boolean {
90 // Mask the sign bits, shift them to the right and replicate them by multiplying by -1.
91 // This will give us a mask of 0xffff_ffff for negative packed floats, and 0x0000_0000
92 // for positive packed floats. We invert the mask and do an and operation with the
93 // original value to set any negative float to 0.0f.
94 val v = packedValue and ((packedValue and DualFloatSignBit ushr 31) * -0x1).inv()
95 // At this point any negative float is set to 0, so the sign bit is always 0.
96 // We take the 2 packed floats and "and" them together: if any of the two floats
97 // is 0.0f (either because the original value is 0.0f or because it was negative and
98 // we turned it into 0.0f with the line above), the result of the and operation will
99 // be 0 and we know our Size is empty.
100 val w = (v ushr 32) and (v and 0xffffffffL)
101 // We treat Size.Unspecified as being empty
102 return (w == 0L) or (packedValue == UnspecifiedPackedFloats)
103 }
104
105 /**
106 * Multiplication operator.
107 *
108 * Returns a [Size] whose dimensions are the dimensions of the left-hand-side operand (a [Size])
109 * multiplied by the scalar right-hand-side operand (a [Float]).
110 */
111 @Stable
112 operator fun times(operand: Float): Size =
113 Size(packFloats(unpackFloat1(packedValue) * operand, unpackFloat2(packedValue) * operand))
114
115 /**
116 * Division operator.
117 *
118 * Returns a [Size] whose dimensions are the dimensions of the left-hand-side operand (a [Size])
119 * divided by the scalar right-hand-side operand (a [Float]).
120 */
121 @Stable
122 operator fun div(operand: Float): Size =
123 Size(packFloats(unpackFloat1(packedValue) / operand, unpackFloat2(packedValue) / operand))
124
125 /** The lesser of the magnitudes of the [width] and the [height]. */
126 @Stable
127 val minDimension: Float
128 get() = min(unpackAbsFloat1(packedValue), unpackAbsFloat2(packedValue))
129
130 /** The greater of the magnitudes of the [width] and the [height]. */
131 @Stable
132 val maxDimension: Float
133 get() = max(unpackAbsFloat1(packedValue), unpackAbsFloat2(packedValue))
134
135 override fun toString() =
136 if (isSpecified) {
137 "Size(${width.toStringAsFixed(1)}, ${height.toStringAsFixed(1)})"
138 } else {
139 // In this case reading the width or height properties will throw, and they don't
140 // contain meaningful values as strings anyway.
141 "Size.Unspecified"
142 }
143 }
144
145 /** `false` when this is [Size.Unspecified]. */
146 @Stable
147 inline val Size.isSpecified: Boolean
148 get() = packedValue != 0x7fc00000_7fc00000L // NaN_NaN, see UnspecifiedPackedFloats
149
150 /** `true` when this is [Size.Unspecified]. */
151 @Stable
152 inline val Size.isUnspecified: Boolean
153 get() = packedValue == 0x7fc00000_7fc00000L // NaN_NaN, see UnspecifiedPackedFloats
154
155 /**
156 * If this [Size] [isSpecified] then this is returned, otherwise [block] is executed and its
157 * result is returned.
158 */
takeOrElsenull159 inline fun Size.takeOrElse(block: () -> Size): Size = if (isSpecified) this else block()
160
161 /**
162 * Linearly interpolate between two sizes
163 *
164 * The [fraction] argument represents position on the timeline, with 0.0 meaning that the
165 * interpolation has not started, returning [start] (or something equivalent to [start]), 1.0
166 * meaning that the interpolation has finished, returning [stop] (or something equivalent to
167 * [stop]), and values in between meaning that the interpolation is at the relevant point on the
168 * timeline between [start] and [stop]. The interpolation can be extrapolated beyond 0.0 and 1.0, so
169 * negative values and values greater than 1.0 are valid (and can easily be generated by curves).
170 *
171 * Values for [fraction] are usually obtained from an [Animation<Float>], such as an
172 * `AnimationController`.
173 */
174 @Stable
175 fun lerp(start: Size, stop: Size, fraction: Float): Size =
176 Size(
177 packFloats(
178 lerp(unpackFloat1(start.packedValue), unpackFloat1(stop.packedValue), fraction),
179 lerp(unpackFloat2(start.packedValue), unpackFloat2(stop.packedValue), fraction)
180 )
181 )
182
183 /** Returns a [Size] with [size]'s [Size.width] and [Size.height] multiplied by [this] */
184 @Stable inline operator fun Int.times(size: Size) = size * this.toFloat()
185
186 /** Returns a [Size] with [size]'s [Size.width] and [Size.height] multiplied by [this] */
187 @Stable inline operator fun Double.times(size: Size) = size * this.toFloat()
188
189 /** Returns a [Size] with [size]'s [Size.width] and [Size.height] multiplied by [this] */
190 @Stable inline operator fun Float.times(size: Size) = size * this
191
192 /** Convert a [Size] to a [Rect]. */
193 @Stable fun Size.toRect(): Rect = Rect(Offset.Zero, this)
194
195 /** Returns the [Offset] of the center of the rect from the point of [0, 0] with this [Size]. */
196 @Stable
197 val Size.center: Offset
198 get() = Offset(unpackFloat1(packedValue) / 2f, unpackFloat2(packedValue) / 2f)
199