@@ -27,6 +27,8 @@ import androidx.compose.foundation.text.selection.hasSelection
2727import androidx.compose.foundation.text.selection.isMouseOrTouchPad
2828import androidx.compose.ui.Modifier
2929import androidx.compose.ui.geometry.Offset
30+ import androidx.compose.ui.geometry.Rect
31+ import androidx.compose.ui.geometry.Size
3032import androidx.compose.ui.hapticfeedback.HapticFeedback
3133import androidx.compose.ui.hapticfeedback.HapticFeedbackType
3234import androidx.compose.ui.input.pointer.AwaitPointerEventScope
@@ -44,9 +46,12 @@ import androidx.compose.ui.node.ModifierNodeElement
4446import androidx.compose.ui.node.currentValueOf
4547import androidx.compose.ui.platform.InspectorInfo
4648import androidx.compose.ui.platform.LocalHapticFeedback
49+ import androidx.compose.ui.relocation.bringIntoView
4750import androidx.compose.ui.util.fastAll
4851import androidx.compose.ui.util.fastForEach
4952import kotlin.coroutines.cancellation.CancellationException
53+ import kotlinx.coroutines.CoroutineStart
54+ import kotlinx.coroutines.launch
5055
5156private interface CupertinoTextDragObserver {
5257 fun onStart (startPoint : Offset , selectionAdjustment : SelectionAdjustment )
@@ -59,24 +64,101 @@ internal actual fun SelectionRegistrar.makeSelectionModifier(
5964 selectableId : Long ,
6065 layoutCoordinates : () -> LayoutCoordinates ?
6166): Modifier {
62- val longPressDragObserver = object : CupertinoTextDragObserver {
67+ return CupertinoSelectionModifierElement (
68+ selectionRegistrar = this ,
69+ selectableId = selectableId,
70+ layoutCoordinates = layoutCoordinates,
71+ )
72+ }
73+
74+ internal class CupertinoSelectionModifierElement (
75+ private val selectionRegistrar : SelectionRegistrar ,
76+ private val selectableId : Long ,
77+ private val layoutCoordinates : () -> LayoutCoordinates ? ,
78+ ) : ModifierNodeElement<CupertinoSelectionModifierNode>() {
79+
80+ override fun create () =
81+ CupertinoSelectionModifierNode (
82+ selectionRegistrar = selectionRegistrar,
83+ selectableId = selectableId,
84+ layoutCoordinates = layoutCoordinates,
85+ )
86+
87+ override fun update (node : CupertinoSelectionModifierNode ) {
88+ node.update(
89+ selectionRegistrar = selectionRegistrar,
90+ selectableId = selectableId,
91+ layoutCoordinates = layoutCoordinates,
92+ )
93+ }
94+
95+ override fun InspectorInfo.inspectableProperties () {
96+ name = " selection"
97+ properties[" selectableId" ] = selectableId
98+ }
99+
100+ override fun equals (other : Any? ): Boolean {
101+ if (this == = other) return true
102+ if (other !is CupertinoSelectionModifierElement ) return false
103+
104+ return selectionRegistrar == other.selectionRegistrar &&
105+ selectableId == other.selectableId &&
106+ layoutCoordinates == = other.layoutCoordinates
107+ }
108+
109+ override fun hashCode (): Int {
110+ var result = selectableId.hashCode()
111+ result = 31 * result + selectionRegistrar.hashCode()
112+ result = 31 * result + layoutCoordinates.hashCode()
113+ return result
114+ }
115+ }
116+
117+ internal class CupertinoSelectionModifierNode (
118+ private var selectionRegistrar : SelectionRegistrar ,
119+ private var selectableId : Long ,
120+ private var layoutCoordinates : () -> LayoutCoordinates ? ,
121+ ) : DelegatingNode(), CompositionLocalConsumerModifierNode {
122+
123+ private val pointerInputNode = delegate(
124+ SuspendingPointerInputModifierNode {
125+ val hapticFeedback = currentValueOf(LocalHapticFeedback )
126+ val clicksCounter = ClicksCounter (viewConfiguration)
127+
128+ awaitEachGesture {
129+ val down = awaitDown()
130+
131+ if (
132+ down.isMouseOrTouchPad() &&
133+ down.buttons.isPrimaryPressed &&
134+ down.changes.fastAll { ! it.isConsumed }
135+ ) {
136+ mouseSelection(mouseSelectionObserver, clicksCounter, down)
137+ } else if (! down.isMouseOrTouchPad()) {
138+ touchSelection(longPressDragObserver, clicksCounter, down, hapticFeedback)
139+ }
140+ }
141+ }
142+ )
143+
144+ private val longPressDragObserver = object : CupertinoTextDragObserver {
63145 /* *
64- * The beginning position of the drag gesture. Every time a new drag gesture starts, it wil be
65- * recalculated.
146+ * The beginning position of the drag gesture. Every time a new drag gesture starts, it will
147+ * be recalculated.
66148 */
67149 var lastPosition = Offset .Zero
68150
69151 /* *
70- * The total distance being dragged of the drag gesture. Every time a new drag gesture starts,
71- * it will be zeroed out.
152+ * The total distance being dragged of the drag gesture. Every time a new drag gesture
153+ * starts, it will be zeroed out.
72154 */
73155 var dragTotalDistance = Offset .Zero
74156
75157 override fun onStart (startPoint : Offset , selectionAdjustment : SelectionAdjustment ) {
76158 layoutCoordinates()?.let {
77159 if (! it.isAttached) return
78160
79- notifySelectionUpdateStart(
161+ selectionRegistrar. notifySelectionUpdateStart(
80162 layoutCoordinates = it,
81163 startPosition = startPoint,
82164 adjustment = selectionAdjustment,
@@ -86,7 +168,7 @@ internal actual fun SelectionRegistrar.makeSelectionModifier(
86168 lastPosition = startPoint
87169 }
88170 // selection never started
89- if (! hasSelection(selectableId)) return
171+ if (! selectionRegistrar. hasSelection(selectableId)) return
90172 // Zero out the total distance that being dragged.
91173 dragTotalDistance = Offset .Zero
92174 }
@@ -95,7 +177,7 @@ internal actual fun SelectionRegistrar.makeSelectionModifier(
95177 layoutCoordinates()?.let {
96178 if (! it.isAttached) return
97179 // selection never started, did not consume any drag
98- if (! hasSelection(selectableId)) return
180+ if (! selectionRegistrar. hasSelection(selectableId)) return
99181
100182 dragTotalDistance + = delta
101183 val newPosition = lastPosition + dragTotalDistance
@@ -105,7 +187,7 @@ internal actual fun SelectionRegistrar.makeSelectionModifier(
105187 // long-press is using SelectionAdjustment.WORD or
106188 // SelectionAdjustment.PARAGRAPH that updates the start handle position from
107189 // the dragBeginPosition.
108- val consumed = notifySelectionUpdate(
190+ val consumed = selectionRegistrar. notifySelectionUpdate(
109191 layoutCoordinates = it,
110192 previousPosition = lastPosition,
111193 newPosition = delta,
@@ -121,193 +203,45 @@ internal actual fun SelectionRegistrar.makeSelectionModifier(
121203 }
122204
123205 override fun onStop () {
124- if (hasSelection(selectableId)) {
125- notifySelectionUpdateEnd()
206+ if (selectionRegistrar. hasSelection(selectableId)) {
207+ selectionRegistrar. notifySelectionUpdateEnd()
126208 }
127209 }
128210
129211 override fun onCancel () {
130- if (hasSelection(selectableId)) {
131- notifySelectionUpdateEnd()
212+ if (selectionRegistrar. hasSelection(selectableId)) {
213+ selectionRegistrar. notifySelectionUpdateEnd()
132214 }
133215 }
134216 }
135217
136- // The rest of that method copied from SelectionController.kt
137- val mouseSelectionObserver = object : MouseSelectionObserver {
138- var lastPosition = Offset .Zero
139-
140- override fun onExtend (downPosition : Offset ): Boolean {
141- layoutCoordinates()?.let { layoutCoordinates ->
142- if (! layoutCoordinates.isAttached) return false
143- val consumed = notifySelectionUpdate(
144- layoutCoordinates = layoutCoordinates,
145- newPosition = downPosition,
146- previousPosition = lastPosition,
147- isStartHandle = false ,
148- adjustment = SelectionAdjustment .None ,
149- isInTouchMode = false
150- )
151- if (consumed) {
152- lastPosition = downPosition
153- }
154- return hasSelection(selectableId)
155- }
156- return false
157- }
158-
159- override fun onExtendDrag (dragPosition : Offset ): Boolean {
160- layoutCoordinates()?.let { layoutCoordinates ->
161- if (! layoutCoordinates.isAttached) return false
162- if (! hasSelection(selectableId)) return false
163-
164- val consumed = notifySelectionUpdate(
165- layoutCoordinates = layoutCoordinates,
166- newPosition = dragPosition,
167- previousPosition = lastPosition,
168- isStartHandle = false ,
169- adjustment = SelectionAdjustment .None ,
170- isInTouchMode = false
171- )
172-
173- if (consumed) {
174- lastPosition = dragPosition
175- }
176- }
177- return true
178- }
179-
180- override fun onStart (
181- downPosition : Offset ,
182- adjustment : SelectionAdjustment ,
183- clickCount : Int ,
184- ): Boolean {
185- layoutCoordinates()?.let {
186- if (! it.isAttached) return false
218+ private fun createMouseSelectionObserver () = selectionRegistrar.skikoMouseSelectionObserver(
219+ selectableId = selectableId,
220+ layoutCoordinates = layoutCoordinates,
221+ bringIntoView = ::bringIntoView
222+ )
187223
188- notifySelectionUpdateStart(
189- layoutCoordinates = it,
190- startPosition = downPosition,
191- adjustment = adjustment,
192- isInTouchMode = false
193- )
224+ private var mouseSelectionObserver = createMouseSelectionObserver()
194225
195- lastPosition = downPosition
196- return hasSelection(selectableId)
226+ private fun bringIntoView (offset : Offset ) {
227+ coroutineScope.launch(start = CoroutineStart .UNDISPATCHED ) {
228+ bringIntoView {
229+ Rect (offset = offset, size = Size .Zero )
197230 }
198-
199- return false
200- }
201-
202- override fun onDrag (
203- dragPosition : Offset ,
204- adjustment : SelectionAdjustment
205- ): Boolean {
206- layoutCoordinates()?.let {
207- if (! it.isAttached) return false
208- if (! hasSelection(selectableId)) return false
209-
210- val consumed = notifySelectionUpdate(
211- layoutCoordinates = it,
212- previousPosition = lastPosition,
213- newPosition = dragPosition,
214- isStartHandle = false ,
215- adjustment = adjustment,
216- isInTouchMode = false
217- )
218- if (consumed) {
219- lastPosition = dragPosition
220- }
221- }
222- return true
223- }
224-
225- override fun onDragDone () {
226- notifySelectionUpdateEnd()
227231 }
228232 }
229233
230- return Modifier .selectionGestureInput(mouseSelectionObserver, longPressDragObserver)
231- }
232-
233- private fun Modifier.selectionGestureInput (
234- mouseSelectionObserver : MouseSelectionObserver ,
235- textDragObserver : CupertinoTextDragObserver ,
236- ): Modifier = this .then(
237- SelectionGestureInputElement (
238- mouseSelectionObserver = mouseSelectionObserver,
239- textDragObserver = textDragObserver,
240- )
241- )
242-
243- private class SelectionGestureInputElement (
244- private val mouseSelectionObserver : MouseSelectionObserver ,
245- private val textDragObserver : CupertinoTextDragObserver ,
246- ) : ModifierNodeElement<SelectionGestureInputNode>() {
247-
248- override fun create (): SelectionGestureInputNode = SelectionGestureInputNode (
249- mouseSelectionObserver = mouseSelectionObserver,
250- textDragObserver = textDragObserver,
251- )
252-
253- override fun update (node : SelectionGestureInputNode ) = node.update(
254- mouseSelectionObserver = mouseSelectionObserver,
255- textDragObserver = textDragObserver,
256- )
257-
258- override fun InspectorInfo.inspectableProperties () {
259- name = " selectionGestureInput"
260- properties[" mouseSelectionObserver" ] = mouseSelectionObserver
261- properties[" textDragObserver" ] = textDragObserver
262- }
263-
264- override fun equals (other : Any? ): Boolean =
265- other is SelectionGestureInputElement &&
266- other != = this &&
267- mouseSelectionObserver == other.mouseSelectionObserver &&
268- textDragObserver == other.textDragObserver
269-
270- override fun hashCode (): Int {
271- var result = mouseSelectionObserver.hashCode()
272- result = 31 * result + textDragObserver.hashCode()
273- return result
274- }
275- }
276-
277- private class SelectionGestureInputNode (
278- private var mouseSelectionObserver : MouseSelectionObserver ,
279- private var textDragObserver : CupertinoTextDragObserver ,
280- ): DelegatingNode(), CompositionLocalConsumerModifierNode {
281-
282- private val pointerInputNode = delegate(
283- SuspendingPointerInputModifierNode (
284- pointerInputEventHandler = {
285- val hapticFeedback = currentValueOf(LocalHapticFeedback )
286- val clicksCounter = ClicksCounter (viewConfiguration)
287-
288- awaitEachGesture {
289- val down = awaitDown()
290-
291- if (
292- down.isMouseOrTouchPad() &&
293- down.buttons.isPrimaryPressed &&
294- down.changes.fastAll { ! it.isConsumed }
295- ) {
296- mouseSelection(mouseSelectionObserver, clicksCounter, down)
297- } else if (! down.isMouseOrTouchPad()) {
298- touchSelection(textDragObserver, clicksCounter, down, hapticFeedback)
299- }
300- }
301- }
302- )
303- )
304-
305234 fun update (
306- mouseSelectionObserver : MouseSelectionObserver ,
307- textDragObserver : CupertinoTextDragObserver ,
235+ selectionRegistrar : SelectionRegistrar ,
236+ selectableId : Long ,
237+ layoutCoordinates : () -> LayoutCoordinates ? ,
308238 ) {
309- this .mouseSelectionObserver = mouseSelectionObserver
310- this .textDragObserver = textDragObserver
239+ this .selectionRegistrar = selectionRegistrar
240+ this .selectableId = selectableId
241+ this .layoutCoordinates = layoutCoordinates
242+
243+ mouseSelectionObserver = createMouseSelectionObserver()
244+ pointerInputNode.resetPointerInputHandler()
311245 }
312246}
313247
0 commit comments