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.unpackFloat1
26 import androidx.compose.ui.util.unpackFloat2
27 import kotlin.math.sqrt
28
29 /** Constructs an Offset from the given relative [x] and [y] offsets */
Offsetnull30 @Stable inline fun Offset(x: Float, y: Float) = Offset(packFloats(x, y))
31
32 /**
33 * An immutable 2D floating-point offset.
34 *
35 * Generally speaking, Offsets can be interpreted in two ways:
36 * 1. As representing a point in Cartesian space a specified distance from a separately-maintained
37 * origin. For example, the top-left position of children in the [RenderBox] protocol is
38 * typically represented as an [Offset] from the top left of the parent box.
39 * 2. As a vector that can be applied to coordinates. For example, when painting a widget, the
40 * parent is passed an [Offset] from the screen's origin which it can add to the offsets of its
41 * children to find the [Offset] from the screen's origin to each of the children.
42 *
43 * Because a particular [Offset] can be interpreted as one sense at one time then as the other sense
44 * at a later time, the same class is used for both senses.
45 *
46 * See also:
47 * * [Size], which represents a vector describing the size of a rectangle.
48 *
49 * To create an [Offset], call the top-level function that accepts an x/y pair of coordinates:
50 * ```
51 * val offset = Offset(x, y)
52 * ```
53 *
54 * The primary constructor of [Offset] is intended to be used with the [packedValue] property to
55 * allow storing offsets in arrays or collections of primitives without boxing.
56 *
57 * @param packedValue [Long] value encoding the [x] and [y] components of the [Offset]. Encoded
58 * values can be obtained by using the [packedValue] property of existing [Offset] instances.
59 */
60 @Immutable
61 @kotlin.jvm.JvmInline
62 value class Offset(val packedValue: Long) {
63 @Stable
64 inline val x: Float
65 get() = unpackFloat1(packedValue)
66
67 @Stable
68 inline val y: Float
69 get() = unpackFloat2(packedValue)
70
71 @Stable inline operator fun component1(): Float = x
72
73 @Stable inline operator fun component2(): Float = y
74
75 /** Returns a copy of this Offset instance optionally overriding the x or y parameter */
76 fun copy(x: Float = unpackFloat1(packedValue), y: Float = unpackFloat2(packedValue)) =
77 Offset(packFloats(x, y))
78
79 companion object {
80 /**
81 * An offset with zero magnitude.
82 *
83 * This can be used to represent the origin of a coordinate space.
84 */
85 @Stable val Zero = Offset(0x0L)
86
87 /**
88 * An offset with infinite x and y components.
89 *
90 * See also [isFinite] to check whether both components are finite.
91 */
92 // This is included for completeness, because [Size.infinite] exists.
93 @Stable val Infinite = Offset(DualFloatInfinityBase)
94
95 /**
96 * Represents an unspecified [Offset] value, usually a replacement for `null` when a
97 * primitive value is desired.
98 */
99 @Stable val Unspecified = Offset(UnspecifiedPackedFloats)
100 }
101
102 /**
103 * Returns:
104 * - False if [x] or [y] is a NaN
105 * - True if [x] or [y] is infinite
106 * - True otherwise
107 */
108 @Stable
109 inline fun isValid(): Boolean {
110 // Take the unsigned packed floats and see if they are > InfinityBase (any NaN)
111 val v = packedValue and DualUnsignedFloatMask
112 return (v + DualLoadedSignificand) and Uint64High32 == 0L
113 }
114
115 /**
116 * The magnitude of the offset.
117 *
118 * If you need this value to compare it to another [Offset]'s distance, consider using
119 * [getDistanceSquared] instead, since it is cheaper to compute.
120 */
121 @Stable
122 fun getDistance(): Float {
123 val x = unpackFloat1(packedValue)
124 val y = unpackFloat2(packedValue)
125 return sqrt(x * x + y * y)
126 }
127
128 /**
129 * The square of the magnitude of the offset.
130 *
131 * This is cheaper than computing the [getDistance] itself.
132 */
133 @Stable
134 fun getDistanceSquared(): Float {
135 val x = unpackFloat1(packedValue)
136 val y = unpackFloat2(packedValue)
137 return x * x + y * y
138 }
139
140 /**
141 * Unary negation operator.
142 *
143 * Returns an offset with the coordinates negated.
144 *
145 * If the [Offset] represents an arrow on a plane, this operator returns the same arrow but
146 * pointing in the reverse direction.
147 */
148 @Stable
149 inline operator fun unaryMinus(): Offset {
150 return Offset(packedValue xor DualFloatSignBit)
151 }
152
153 /**
154 * Binary subtraction operator.
155 *
156 * Returns an offset whose [x] value is the left-hand-side operand's [x] minus the
157 * right-hand-side operand's [x] and whose [y] value is the left-hand-side operand's [y] minus
158 * the right-hand-side operand's [y].
159 */
160 @Stable
161 operator fun minus(other: Offset): Offset {
162 return Offset(
163 packFloats(
164 unpackFloat1(packedValue) - unpackFloat1(other.packedValue),
165 unpackFloat2(packedValue) - unpackFloat2(other.packedValue)
166 )
167 )
168 }
169
170 /**
171 * Binary addition operator.
172 *
173 * Returns an offset whose [x] value is the sum of the [x] values of the two operands, and whose
174 * [y] value is the sum of the [y] values of the two operands.
175 */
176 @Stable
177 operator fun plus(other: Offset): Offset {
178 return Offset(
179 packFloats(
180 unpackFloat1(packedValue) + unpackFloat1(other.packedValue),
181 unpackFloat2(packedValue) + unpackFloat2(other.packedValue)
182 )
183 )
184 }
185
186 /**
187 * Multiplication operator.
188 *
189 * Returns an offset whose coordinates are the coordinates of the left-hand-side operand (an
190 * Offset) multiplied by the scalar right-hand-side operand (a Float).
191 */
192 @Stable
193 operator fun times(operand: Float): Offset {
194 return Offset(
195 packFloats(unpackFloat1(packedValue) * operand, unpackFloat2(packedValue) * operand)
196 )
197 }
198
199 /**
200 * Division operator.
201 *
202 * Returns an offset whose coordinates are the coordinates of the left-hand-side operand (an
203 * Offset) divided by the scalar right-hand-side operand (a Float).
204 */
205 @Stable
206 operator fun div(operand: Float): Offset {
207 return Offset(
208 packFloats(unpackFloat1(packedValue) / operand, unpackFloat2(packedValue) / operand)
209 )
210 }
211
212 /**
213 * Modulo (remainder) operator.
214 *
215 * Returns an offset whose coordinates are the remainder of dividing the coordinates of the
216 * left-hand-side operand (an Offset) by the scalar right-hand-side operand (a Float).
217 */
218 @Stable
219 operator fun rem(operand: Float): Offset {
220 return Offset(
221 packFloats(unpackFloat1(packedValue) % operand, unpackFloat2(packedValue) % operand)
222 )
223 }
224
225 override fun toString() =
226 if (isSpecified) {
227 "Offset(${x.toStringAsFixed(1)}, ${y.toStringAsFixed(1)})"
228 } else {
229 // In this case reading the x or y properties will throw, and they don't contain
230 // meaningful
231 // values as strings anyway.
232 "Offset.Unspecified"
233 }
234 }
235
236 /**
237 * Linearly interpolate between two offsets.
238 *
239 * The [fraction] argument represents position on the timeline, with 0.0 meaning that the
240 * interpolation has not started, returning [start] (or something equivalent to [start]), 1.0
241 * meaning that the interpolation has finished, returning [stop] (or something equivalent to
242 * [stop]), and values in between meaning that the interpolation is at the relevant point on the
243 * timeline between [start] and [stop]. The interpolation can be extrapolated beyond 0.0 and 1.0, so
244 * negative values and values greater than 1.0 are valid (and can easily be generated by curves).
245 *
246 * Values for [fraction] are usually obtained from an [Animation<Float>], such as an
247 * `AnimationController`.
248 */
249 @Stable
lerpnull250 fun lerp(start: Offset, stop: Offset, fraction: Float): Offset {
251 return Offset(
252 packFloats(
253 lerp(unpackFloat1(start.packedValue), unpackFloat1(stop.packedValue), fraction),
254 lerp(unpackFloat2(start.packedValue), unpackFloat2(stop.packedValue), fraction)
255 )
256 )
257 }
258
259 /** True if both x and y values of the [Offset] are finite. NaN values are not considered finite. */
260 @Stable
261 inline val Offset.isFinite: Boolean
262 get() {
263 // Mask out the sign bit and do an equality check in each 32-bit lane
264 // against the "infinity base" mask (to check whether each packed float
265 // is infinite or not).
266 val v = (packedValue and DualFloatInfinityBase) xor DualFloatInfinityBase
267 return (v - Uint64Low32) and Uint64High32 == 0L
268 }
269
270 /** `false` when this is [Offset.Unspecified]. */
271 @Stable
272 inline val Offset.isSpecified: Boolean
273 get() = packedValue and DualUnsignedFloatMask != UnspecifiedPackedFloats
274
275 /** `true` when this is [Offset.Unspecified]. */
276 @Stable
277 inline val Offset.isUnspecified: Boolean
278 get() = packedValue and DualUnsignedFloatMask == UnspecifiedPackedFloats
279
280 /**
281 * If this [Offset] [isSpecified] then this is returned, otherwise [block] is executed and its
282 * result is returned.
283 */
takeOrElsenull284 inline fun Offset.takeOrElse(block: () -> Offset): Offset = if (isSpecified) this else block()
285