1 /* <lambda>null2 * Copyright 2021 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 package androidx.compose.foundation.text.selection 18 19 import androidx.collection.LongObjectMap 20 import androidx.collection.emptyLongObjectMap 21 import androidx.collection.mutableLongObjectMapOf 22 import androidx.compose.foundation.AtomicLong 23 import androidx.compose.foundation.internal.requirePrecondition 24 import androidx.compose.runtime.getValue 25 import androidx.compose.runtime.mutableStateOf 26 import androidx.compose.runtime.saveable.Saver 27 import androidx.compose.runtime.setValue 28 import androidx.compose.ui.geometry.Offset 29 import androidx.compose.ui.layout.LayoutCoordinates 30 31 internal class SelectionRegistrarImpl private constructor(initialIncrementId: Long) : 32 SelectionRegistrar { 33 companion object { 34 val Saver = 35 Saver<SelectionRegistrarImpl, Long>( 36 save = { it.incrementId.get() }, 37 restore = { SelectionRegistrarImpl(it) } 38 ) 39 } 40 41 constructor() : this(initialIncrementId = 1L) 42 43 /** A flag to check if the [Selectable]s have already been sorted. */ 44 internal var sorted: Boolean = false 45 46 /** 47 * This is essentially the list of registered components that want to handle text selection that 48 * are below the SelectionContainer. 49 */ 50 private val _selectables = mutableListOf<Selectable>() 51 52 /** Getter for handlers that returns a List. */ 53 internal val selectables: List<Selectable> 54 get() = _selectables 55 56 private val _selectableMap = mutableLongObjectMapOf<Selectable>() 57 58 /** A map from selectable keys to subscribed selectables. */ 59 internal val selectableMap: LongObjectMap<Selectable> 60 get() = _selectableMap 61 62 /** 63 * The incremental id to be assigned to each selectable. It starts from 1 and 0 is used to 64 * denote an invalid id. 65 * 66 * @see SelectionRegistrar.InvalidSelectableId 67 */ 68 private var incrementId = AtomicLong(initialIncrementId) 69 70 /** The callback to be invoked when the position change was triggered. */ 71 internal var onPositionChangeCallback: ((Long) -> Unit)? = null 72 73 /** The callback to be invoked when the selection is initiated. */ 74 internal var onSelectionUpdateStartCallback: 75 ((Boolean, LayoutCoordinates, Offset, SelectionAdjustment) -> Unit)? = 76 null 77 78 /** The callback to be invoked when the selection is initiated with selectAll [Selection]. */ 79 internal var onSelectionUpdateSelectAll: ((Boolean, Long) -> Unit)? = null 80 81 /** 82 * The callback to be invoked when the selection is updated. If the first offset is null it 83 * means that the start of selection is unknown for the caller. 84 */ 85 internal var onSelectionUpdateCallback: 86 ((Boolean, LayoutCoordinates, Offset, Offset, Boolean, SelectionAdjustment) -> Boolean)? = 87 null 88 89 /** The callback to be invoked when selection update finished. */ 90 internal var onSelectionUpdateEndCallback: (() -> Unit)? = null 91 92 /** The callback to be invoked when one of the selectable has changed. */ 93 internal var onSelectableChangeCallback: ((Long) -> Unit)? = null 94 95 /** 96 * The callback to be invoked after a selectable is unsubscribed from this [SelectionRegistrar]. 97 */ 98 internal var afterSelectableUnsubscribe: ((Long) -> Unit)? = null 99 100 override var subselections: LongObjectMap<Selection> by mutableStateOf(emptyLongObjectMap()) 101 102 override fun subscribe(selectable: Selectable): Selectable { 103 requirePrecondition(selectable.selectableId != SelectionRegistrar.InvalidSelectableId) { 104 "The selectable contains an invalid id: ${selectable.selectableId}" 105 } 106 requirePrecondition(!_selectableMap.containsKey(selectable.selectableId)) { 107 "Another selectable with the id: $selectable.selectableId has already subscribed." 108 } 109 _selectableMap[selectable.selectableId] = selectable 110 _selectables.add(selectable) 111 sorted = false 112 return selectable 113 } 114 115 override fun unsubscribe(selectable: Selectable) { 116 if (!_selectableMap.containsKey(selectable.selectableId)) return 117 _selectables.remove(selectable) 118 _selectableMap.remove(selectable.selectableId) 119 afterSelectableUnsubscribe?.invoke(selectable.selectableId) 120 } 121 122 override fun nextSelectableId(): Long { 123 var id = incrementId.getAndIncrement() 124 while (id == SelectionRegistrar.InvalidSelectableId) { 125 id = incrementId.getAndIncrement() 126 } 127 return id 128 } 129 130 /** 131 * Sort the list of registered [Selectable]s in [SelectionRegistrar]. Currently the order of 132 * selectables is geometric-based. 133 */ 134 fun sort(containerLayoutCoordinates: LayoutCoordinates): List<Selectable> { 135 if (!sorted) { 136 // Sort selectables by y-coordinate first, and then x-coordinate, to match English 137 // hand-writing habit. 138 _selectables.sortWith { a: Selectable, b: Selectable -> 139 val layoutCoordinatesA = a.getLayoutCoordinates() 140 val layoutCoordinatesB = b.getLayoutCoordinates() 141 142 val positionA = 143 if (layoutCoordinatesA != null) { 144 containerLayoutCoordinates.localPositionOf(layoutCoordinatesA, Offset.Zero) 145 } else { 146 Offset.Zero 147 } 148 val positionB = 149 if (layoutCoordinatesB != null) { 150 containerLayoutCoordinates.localPositionOf(layoutCoordinatesB, Offset.Zero) 151 } else { 152 Offset.Zero 153 } 154 155 if (positionA.y == positionB.y) { 156 compareValues(positionA.x, positionB.x) 157 } else { 158 compareValues(positionA.y, positionB.y) 159 } 160 } 161 sorted = true 162 } 163 return selectables 164 } 165 166 override fun notifyPositionChange(selectableId: Long) { 167 // Set the variable sorted to be false, when the global position of a registered 168 // selectable changes. 169 sorted = false 170 onPositionChangeCallback?.invoke(selectableId) 171 } 172 173 override fun notifySelectionUpdateStart( 174 layoutCoordinates: LayoutCoordinates, 175 startPosition: Offset, 176 adjustment: SelectionAdjustment, 177 isInTouchMode: Boolean 178 ) { 179 onSelectionUpdateStartCallback?.invoke( 180 isInTouchMode, 181 layoutCoordinates, 182 startPosition, 183 adjustment 184 ) 185 } 186 187 override fun notifySelectionUpdateSelectAll(selectableId: Long, isInTouchMode: Boolean) { 188 onSelectionUpdateSelectAll?.invoke(isInTouchMode, selectableId) 189 } 190 191 override fun notifySelectionUpdate( 192 layoutCoordinates: LayoutCoordinates, 193 newPosition: Offset, 194 previousPosition: Offset, 195 isStartHandle: Boolean, 196 adjustment: SelectionAdjustment, 197 isInTouchMode: Boolean 198 ): Boolean { 199 return onSelectionUpdateCallback?.invoke( 200 isInTouchMode, 201 layoutCoordinates, 202 newPosition, 203 previousPosition, 204 isStartHandle, 205 adjustment 206 ) ?: true 207 } 208 209 override fun notifySelectionUpdateEnd() { 210 onSelectionUpdateEndCallback?.invoke() 211 } 212 213 override fun notifySelectableChange(selectableId: Long) { 214 onSelectableChangeCallback?.invoke(selectableId) 215 } 216 } 217