1616
1717package androidx.compose.foundation.text.input.internal.selection
1818
19- import androidx.compose.foundation.gestures.awaitEachGesture
20- import androidx.compose.foundation.gestures.awaitPress
2119import androidx.compose.foundation.gestures.detectTapAndPress
2220import androidx.compose.foundation.interaction.MutableInteractionSource
2321import androidx.compose.foundation.interaction.PressInteraction
@@ -42,22 +40,16 @@ import androidx.compose.foundation.text.input.internal.selection.TextFieldSelect
4240import androidx.compose.foundation.text.input.internal.selection.TextToolbarState.Cursor
4341import androidx.compose.foundation.text.input.internal.selection.TextToolbarState.None
4442import androidx.compose.foundation.text.input.internal.selection.TextToolbarState.Selection
45- import androidx.compose.foundation.text.selection.ClicksCounter
4643import androidx.compose.foundation.text.selection.MouseSelectionObserver
4744import androidx.compose.foundation.text.selection.SelectionAdjustment
48- import androidx.compose.foundation.text.selection.isMouseOrTouchPad
49- import androidx.compose.foundation.text.selection.mouseSelection
50- import androidx.compose.foundation.text.selection.touchSelectionFirstPress
45+ import androidx.compose.foundation.text.selection.awaitSelectionGestures
5146import androidx.compose.ui.Modifier
5247import androidx.compose.ui.geometry.Offset
5348import androidx.compose.ui.geometry.isSpecified
5449import androidx.compose.ui.hapticfeedback.HapticFeedbackType
55- import androidx.compose.ui.input.pointer.PointerInputChange
5650import androidx.compose.ui.input.pointer.PointerInputScope
57- import androidx.compose.ui.input.pointer.isPrimaryPressed
5851import androidx.compose.ui.platform.Clipboard
5952import androidx.compose.ui.text.TextRange
60- import androidx.compose.ui.util.fastAll
6153import kotlinx.coroutines.CoroutineScope
6254import kotlinx.coroutines.CoroutineStart
6355import kotlinx.coroutines.coroutineScope
@@ -212,100 +204,74 @@ internal actual suspend fun TextFieldSelectionState.textFieldSelectionGestures(
212204) {
213205 val selectionState = this
214206 val uiKitTextDragObserver = UIKitTextFieldTextDragObserver (selectionState)
215- val clicksCounter = ClicksCounter (pointerInputScope.viewConfiguration)
216- pointerInputScope.awaitEachGesture {
217- while (true ) {
218- val downEvent = awaitPress({true })
219- clicksCounter.update(downEvent.changes[0 ])
220- val isPrecise = downEvent.isMouseOrTouchPad()
221- if (
222- isPrecise &&
223- downEvent.buttons.isPrimaryPressed &&
224- downEvent.changes.fastAll { ! it.isConsumed }
225- ) {
226- // Use default BTF2 logic for mouse
227- mouseSelection(mouseSelectionObserver, clicksCounter, downEvent)
228- } else if (! isPrecise) {
229- when (clicksCounter.clicks) {
230- 1 -> {
231- // The default BTF2 logic, except
232- // moving text cursor without selection requires custom TextDragObserver
233- touchSelectionFirstPress(
234- observer = uiKitTextDragObserver,
235- downEvent = downEvent
236- )
237- }
238-
239- 2 -> {
240- doRepeatingTapSelection(
241- downEvent.changes.first(),
242- selectionState,
243- SelectionAdjustment .Word
244- )
245- }
207+ pointerInputScope.awaitSelectionGestures(
208+ mouseSelectionObserver = mouseSelectionObserver,
209+ textDragObserver = uiKitTextDragObserver,
210+ )
211+ }
246212
247- else -> {
248- val downChange = downEvent.changes.first()
249- clearSelection(
250- downChange,
251- selectionState
252- ) // Previous selection must be cleared, otherwise this closure won't get third (and further) click
253- doRepeatingTapSelection(
254- downChange,
255- selectionState,
256- SelectionAdjustment . Paragraph
257- )
258- }
259- }
260- }
261- }
213+ private fun TextFieldSelectionState. moveCaretByLongPress (
214+ touchPointOffset : Offset ,
215+ ) {
216+ hapticFeedBack?.performHapticFeedback( HapticFeedbackType . LongPress )
217+ // Long Press at the blank area, the cursor should show up at the end of the line.
218+ if ( ! textLayoutState.isPositionOnText(touchPointOffset)) {
219+ val offset = textLayoutState.getOffsetForPosition(touchPointOffset)
220+ textFieldState.placeCursorBeforeCharAt(offset)
221+ } else {
222+ if (textFieldState.visualText.isEmpty()) return
223+ val coercedOffset =
224+ textLayoutState.coercedInVisibleBoundsOfInputText(touchPointOffset)
225+ placeCursorAtNearestOffset(
226+ textLayoutState.fromDecorationToTextLayout(coercedOffset)
227+ )
262228 }
229+ showCursorHandle = true
230+ updateHandleDragging(Handle .Cursor , touchPointOffset)
263231}
264232
265- private fun doRepeatingTapSelection (
266- touchChange : PointerInputChange ,
267- selectionState : TextFieldSelectionState ,
233+ private fun TextFieldSelectionState.doRepeatingTapSelection (
234+ touchPointOffset : Offset ,
268235 selectionAdjustment : SelectionAdjustment
269236) {
270- val selectionOffset = selectionState.textLayoutState.getOffsetForPosition(
271- position = touchChange.position
237+ clearSelection(touchPointOffset) // otherwise it won't be changed by triple tap
238+
239+ val selectionOffset = textLayoutState.getOffsetForPosition(
240+ position = touchPointOffset
272241 )
273- touchChange.consume()
274242
275- val newSelection = selectionState. updateSelection(
276- selectionState. textFieldState.visualText,
243+ val newSelection = updateSelection(
244+ textFieldState.visualText,
277245 selectionOffset,
278246 selectionOffset,
279247 isStartHandle = false ,
280248 adjustment = selectionAdjustment,
281249 hapticFeedbackType = HapticFeedbackType .TextHandleMove ,
282250 )
283251
284- selectionState. textFieldState.selectCharsIn(newSelection)
285- selectionState. updateTextToolbarState(Selection )
252+ textFieldState.selectCharsIn(newSelection)
253+ updateTextToolbarState(Selection )
286254}
287255
288- private fun clearSelection (
289- touchChange : PointerInputChange ,
290- selectionState : TextFieldSelectionState
256+ private fun TextFieldSelectionState.clearSelection (
257+ touchPointOffset : Offset ,
291258) {
292- val selectionOffset = selectionState. textLayoutState.getOffsetForPosition(
293- position = touchChange.position
259+ val selectionOffset = textLayoutState.getOffsetForPosition(
260+ position = touchPointOffset
294261 )
295- val clearedSelection = selectionState. updateSelection(
296- TextFieldCharSequence (selectionState. textFieldState.visualText, TextRange .Zero ),
262+ val clearedSelection = updateSelection(
263+ TextFieldCharSequence (textFieldState.visualText, TextRange .Zero ),
297264 selectionOffset,
298265 selectionOffset,
299266 isStartHandle = false ,
300267 adjustment = SelectionAdjustment .None ,
301268 hapticFeedbackType = HapticFeedbackType .TextHandleMove ,
302269 )
303- selectionState. textFieldState.selectCharsIn(clearedSelection)
270+ textFieldState.selectCharsIn(clearedSelection)
304271}
305272
306273private class UIKitTextFieldTextDragObserver (
307274 private val textFieldSelectionState : TextFieldSelectionState ,
308- private val requestFocus : () -> Unit = {}
309275) : TextDragObserver {
310276 private var dragBeginPosition: Offset = Offset .Unspecified
311277 private var dragTotalDistance: Offset = Offset .Zero
@@ -317,7 +283,6 @@ private class UIKitTextFieldTextDragObserver(
317283 dragBeginPosition = Offset .Unspecified
318284 dragTotalDistance = Offset .Zero
319285 textFieldSelectionState.directDragGestureInitiator = InputType .None
320- requestFocus()
321286 textFieldSelectionState.clearHandleDragging()
322287 }
323288 }
@@ -338,21 +303,11 @@ private class UIKitTextFieldTextDragObserver(
338303 dragBeginPosition = startPoint
339304 dragTotalDistance = Offset .Zero
340305
341- textFieldSelectionState.hapticFeedBack?.performHapticFeedback(HapticFeedbackType .LongPress )
342- // Long Press at the blank area, the cursor should show up at the end of the line.
343- if (! textFieldSelectionState.textLayoutState.isPositionOnText(startPoint)) {
344- val offset = textFieldSelectionState.textLayoutState.getOffsetForPosition(startPoint)
345- textFieldSelectionState.textFieldState.placeCursorBeforeCharAt(offset)
306+ if (selectionAdjustment != SelectionAdjustment .None ) {
307+ textFieldSelectionState.doRepeatingTapSelection(startPoint, selectionAdjustment)
346308 } else {
347- if (textFieldSelectionState.textFieldState.visualText.isEmpty()) return
348- val coercedOffset =
349- textFieldSelectionState.textLayoutState.coercedInVisibleBoundsOfInputText(startPoint)
350- textFieldSelectionState.placeCursorAtNearestOffset(
351- textFieldSelectionState.textLayoutState.fromDecorationToTextLayout(coercedOffset)
352- )
309+ textFieldSelectionState.moveCaretByLongPress(startPoint)
353310 }
354- textFieldSelectionState.showCursorHandle = true
355- textFieldSelectionState.updateHandleDragging(Handle .Cursor , startPoint)
356311 }
357312
358313 override fun onDrag (delta : Offset ) {
0 commit comments