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]&nbsp;[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