1 /*
2  * Copyright 2020 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.unit
20 
21 import androidx.compose.runtime.Immutable
22 import androidx.compose.runtime.Stable
23 import androidx.compose.ui.geometry.Offset
24 import androidx.compose.ui.util.fastRoundToInt
25 import androidx.compose.ui.util.lerp
26 import androidx.compose.ui.util.packInts
27 import androidx.compose.ui.util.unpackInt1
28 import androidx.compose.ui.util.unpackInt2
29 import kotlin.jvm.JvmInline
30 
31 /** Constructs a [IntOffset] from [x] and [y] position [Int] values. */
IntOffsetnull32 @Stable inline fun IntOffset(x: Int, y: Int): IntOffset = IntOffset(packInts(x, y))
33 
34 /**
35  * A two-dimensional position using [Int] pixels for units.
36  *
37  * To create an [IntOffset], call the top-level function that accepts an x/y pair of coordinates:
38  * ```
39  * val offset = IntOffset(x, y)
40  * ```
41  *
42  * The primary constructor of [IntOffset] is intended to be used with the [packedValue] property to
43  * allow storing offsets in arrays or collections of primitives without boxing.
44  *
45  * @param packedValue [Long] value encoding the [x] and [y] components of the [IntOffset]. Encoded
46  *   values can be obtained by using the [packedValue] property of existing [IntOffset] instances.
47  */
48 @Immutable
49 @JvmInline
50 value class IntOffset(val packedValue: Long) {
51     /** The horizontal aspect of the position in [Int] pixels. */
52     @Stable
53     val x: Int
54         get() = unpackInt1(packedValue)
55 
56     /** The vertical aspect of the position in [Int] pixels. */
57     @Stable
58     val y: Int
59         get() = unpackInt2(packedValue)
60 
61     @Stable inline operator fun component1(): Int = x
62 
63     @Stable inline operator fun component2(): Int = y
64 
65     /** Returns a copy of this IntOffset instance optionally overriding the x or y parameter */
66     fun copy(x: Int = unpackInt1(packedValue), y: Int = unpackInt2(packedValue)) =
67         IntOffset(packInts(x, y))
68 
69     /** Subtract a [IntOffset] from another one. */
70     @Stable
71     operator fun minus(other: IntOffset) =
72         IntOffset(
73             packInts(
74                 unpackInt1(packedValue) - unpackInt1(other.packedValue),
75                 unpackInt2(packedValue) - unpackInt2(other.packedValue)
76             )
77         )
78 
79     /** Add a [IntOffset] to another one. */
80     @Stable
81     operator fun plus(other: IntOffset) =
82         IntOffset(
83             packInts(
84                 unpackInt1(packedValue) + unpackInt1(other.packedValue),
85                 unpackInt2(packedValue) + unpackInt2(other.packedValue)
86             )
87         )
88 
89     /** Returns a new [IntOffset] representing the negation of this point. */
90     @Stable
91     operator fun unaryMinus() =
92         IntOffset(packInts(-unpackInt1(packedValue), -unpackInt2(packedValue)))
93 
94     /**
95      * Multiplication operator.
96      *
97      * Returns an IntOffset whose coordinates are the coordinates of the left-hand-side operand (an
98      * IntOffset) multiplied by the scalar right-hand-side operand (a Float). The result is rounded
99      * to the nearest integer.
100      */
101     @Stable
102     operator fun times(operand: Float): IntOffset =
103         IntOffset(
104             packInts(
105                 (unpackInt1(packedValue) * operand).fastRoundToInt(),
106                 (unpackInt2(packedValue) * operand).fastRoundToInt()
107             )
108         )
109 
110     /**
111      * Division operator.
112      *
113      * Returns an IntOffset whose coordinates are the coordinates of the left-hand-side operand (an
114      * IntOffset) divided by the scalar right-hand-side operand (a Float). The result is rounded to
115      * the nearest integer.
116      */
117     @Stable
118     operator fun div(operand: Float): IntOffset =
119         IntOffset(
120             packInts(
121                 (unpackInt1(packedValue) / operand).fastRoundToInt(),
122                 (unpackInt2(packedValue) / operand).fastRoundToInt()
123             )
124         )
125 
126     /**
127      * Modulo (remainder) operator.
128      *
129      * Returns an IntOffset whose coordinates are the remainder of dividing the coordinates of the
130      * left-hand-side operand (an IntOffset) by the scalar right-hand-side operand (an Int).
131      */
132     @Stable
133     operator fun rem(operand: Int) =
134         IntOffset(packInts(unpackInt1(packedValue) % operand, unpackInt2(packedValue) % operand))
135 
136     @Stable override fun toString(): String = "($x, $y)"
137 
138     companion object {
139         val Zero = IntOffset(0x0L)
140         val Max = IntOffset(0x7FFF_FFFF_7FFF_FFFF)
141     }
142 }
143 
144 /**
145  * Linearly interpolate between two [IntOffset]s.
146  *
147  * The [fraction] argument represents position on the timeline, with 0.0 meaning that the
148  * interpolation has not started, returning [start] (or something equivalent to [start]), 1.0
149  * meaning that the interpolation has finished, returning [stop] (or something equivalent to
150  * [stop]), and values in between meaning that the interpolation is at the relevant point on the
151  * timeline between [start] and [stop]. The interpolation can be extrapolated beyond 0.0 and 1.0, so
152  * negative values and values greater than 1.0 are valid.
153  */
154 @Stable
lerpnull155 fun lerp(start: IntOffset, stop: IntOffset, fraction: Float): IntOffset =
156     IntOffset(packInts(lerp(start.x, stop.x, fraction), lerp(start.y, stop.y, fraction)))
157 
158 /** Converts the [IntOffset] to an [Offset]. */
159 @Stable inline fun IntOffset.toOffset() = Offset(x.toFloat(), y.toFloat())
160 
161 @Stable operator fun Offset.plus(offset: IntOffset): Offset = Offset(x + offset.x, y + offset.y)
162 
163 @Stable operator fun Offset.minus(offset: IntOffset): Offset = Offset(x - offset.x, y - offset.y)
164 
165 @Stable operator fun IntOffset.plus(offset: Offset): Offset = Offset(x + offset.x, y + offset.y)
166 
167 @Stable operator fun IntOffset.minus(offset: Offset): Offset = Offset(x - offset.x, y - offset.y)
168 
169 /** Round a [Offset] down to the nearest [Int] coordinates. */
170 @Stable fun Offset.round(): IntOffset = IntOffset(packInts(x.fastRoundToInt(), y.fastRoundToInt()))
171