1 /*
2  * Copyright 2023 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.input.pointer.util
20 
21 import androidx.compose.ui.input.pointer.PointerId
22 
23 /**
24  * This collection is specifically for dealing with [PointerId] values. We know that they contain
25  * [Long] values, so we store them in an underlying LongArray. We want to be able to resize the
26  * array if there are many ids to be stored, so we recreate the internal LongArray as necessary
27  * (since LongArray is not itself resizable).
28  */
29 internal class PointerIdArray {
30     /**
31      * The size of this [PointerIdArray], which is equal to the number of ids stored in the array.
32      */
33     // Note that this is different than the size of the backing LongArray, which may allocate more
34     // entries to avoid resizing for every additional id that is added.
35     var size = 0
36         private set
37 
38     /** Returns index of last item in array */
39     inline val lastIndex: Int
40         get() = size - 1
41 
42     /**
43      * The ids are stored as Long values in a LongArray. LongArray is not resizable, and we may need
44      * to expand this array if there are many pointer ids in use at any given time, so we keep the
45      * LongArray private and resize the PointerIdArray by allocating a larger LongArray (and copying
46      * existing values to it) as necessary.
47      *
48      * By default, we allocate the underlying array with 2 elements, since it is uncommon (though
49      * possible) to have more than two ids at a time.
50      */
51     private var internalArray = LongArray(2)
52 
53     /**
54      * Returns the PointerId at the given index. This getter allows use of [] syntax to retrieve
55      * values.
56      */
getnull57     operator fun get(index: Int): PointerId {
58         return PointerId(internalArray[index])
59     }
60 
61     /**
62      * Removes the given [PointerId] from this array, if it exists.
63      *
64      * @return true if [pointerId] was in the array, false otherwise
65      */
removenull66     inline fun remove(pointerId: PointerId): Boolean {
67         return remove(pointerId.value)
68     }
69 
70     /**
71      * Removes a [PointerId] with the given value from this array, if it exists.
72      *
73      * @return true if a [PointerId] with the value [pointerIdValue] was in the array, false
74      *   otherwise
75      */
removenull76     fun remove(pointerIdValue: Long): Boolean {
77         for (i in 0 until size) {
78             if (pointerIdValue == internalArray[i]) {
79                 for (j in i until size - 1) {
80                     internalArray[j] = internalArray[j + 1]
81                 }
82                 size--
83                 return true
84             }
85         }
86         return false
87     }
88 
89     /**
90      * Removes the [PointerId] at the given index value, if the index is less than the size of the
91      * array.
92      *
93      * @return true if a [PointerId] at that index was removed, false otherwise
94      */
removeAtnull95     fun removeAt(index: Int): Boolean {
96         if (index < size) {
97             for (i in index until size - 1) {
98                 internalArray[i] = internalArray[i + 1]
99             }
100             size--
101             return true
102         }
103         return false
104     }
105 
106     /** Returns the current size of the array */
isEmptynull107     fun isEmpty() = size == 0
108 
109     /**
110      * Adds the given pointerId value to this array unless it is already there.
111      *
112      * @return true if id was added, false otherwise
113      */
114     fun add(value: Long): Boolean {
115         if (!contains(value)) {
116             set(size, value)
117             return true
118         }
119         return false
120     }
121 
122     /**
123      * Adds the given pointerId value to this array unless it is already there.
124      *
125      * @return true if id was added, false otherwise
126      */
addnull127     inline fun add(pointerId: PointerId): Boolean {
128         return add(pointerId.value)
129     }
130 
131     /**
132      * Sets the value at the given index to a [PointerId] with the value [value]. The index must be
133      * less than or equal to the current size of the array. If it is equal to the size of the array,
134      * the storage in the array will be expanded to ensure that the item can be added to the end of
135      * it.
136      */
setnull137     operator fun set(index: Int, value: Long) {
138         var internalArray = internalArray
139         if (index >= internalArray.size) {
140             // Increase the size of the backing array
141             internalArray = resizeStorage(index + 1)
142         }
143         internalArray[index] = value
144         if (index >= size) size = index + 1
145     }
146 
resizeStoragenull147     private fun resizeStorage(minSize: Int): LongArray {
148         return internalArray.copyOf(maxOf(minSize, internalArray.size * 2)).apply {
149             internalArray = this
150         }
151     }
152 
153     /**
154      * Sets the value at the given index to [pointerId]. The index must be less than or equal to the
155      * current size of the array. If it is equal to the size of the array, the storage in the array
156      * will be expanded to ensure that the item can be added to the end of it.
157      */
setnull158     inline operator fun set(index: Int, pointerId: PointerId) {
159         set(index, pointerId.value)
160     }
161 
162     /** Clears the array. The new [size] of the array will be 0. */
clearnull163     fun clear() {
164         // No need to clear, just reset the size. Elements beyond the current size are ignored.
165         size = 0
166     }
167 
168     /** Returns true if [pointerId] is in the array, false otherwise */
containsnull169     inline fun contains(pointerId: PointerId): Boolean {
170         return contains(pointerId.value)
171     }
172 
173     /** Returns true if a [PointerId] with the given value is in the array, false otherwise */
containsnull174     fun contains(pointerIdValue: Long): Boolean {
175         for (i in 0 until size) {
176             if (internalArray[i] == pointerIdValue) return true
177         }
178         return false
179     }
180 }
181