Skip to content

Commit

Permalink
Caret behavior by long tap in cupertino textfield (#913)
Browse files Browse the repository at this point in the history
## Proposed Changes

Added native behavior for long tap and drag in Cupertino text field.
Unlike Android, long tap and drag gesture doesn't enter selection mode,
It just moves the cursor.

## Testing

Test: Open test app, go Components -> Text fields -> Almost fullscreen,
tap on the textfield to focus it, then try to long tap in somewhere else
and drag.

## Issues Fixed

-
https://youtrack.jetbrains.com/issue/COMPOSE-449/iOS-caret-movement-by-long-tap-in-textfields
-
https://youtrack.jetbrains.com/issue/COMPOSE-448/iOS-separate-long-tap-logic-and-drag-selection-logic-in-textfields
(it is related to the issue above)

## Google CLA
You need to sign the Google Contributor’s License Agreement at
https://cla.developers.google.com/.
This is needed since we synchronise most of the code with Google’s AOSP
repository. Signing this agreement allows us to synchronise code from
your Pull Requests as well.

---------

Co-authored-by: Igor Demin <[email protected]>
Co-authored-by: Ivan Matkov <[email protected]>
Co-authored-by: Elijah Semyonov <[email protected]>
Co-authored-by: dima.avdeev <[email protected]>
Co-authored-by: Alexander Maryanovsky <[email protected]>
Co-authored-by: Oleksandr Karpovich <[email protected]>
Co-authored-by: Nikolai Rykunov <[email protected]>
Co-authored-by: Konstantin <[email protected]>
Co-authored-by: Farouk Abichou <[email protected]>
Co-authored-by: Alexander Zhirkevich <[email protected]>
Co-authored-by: Jake Wharton <[email protected]>
Co-authored-by: Sebastian <[email protected]>
Co-authored-by: Igor Demin <[email protected]>
Co-authored-by: Oleksandr Karpovich <[email protected]>
Co-authored-by: sanao <[email protected]>
  • Loading branch information
16 people authored Dec 7, 2023
1 parent 928fa13 commit 072a8c2
Showing 1 changed file with 63 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ internal fun Modifier.cupertinoTextFieldPointer(
offsetMapping: OffsetMapping
): Modifier = if (enabled) {
if (isInTouchMode) {
val selectionModifier = getSelectionModifier(manager)
val longPressHandlerModifier = getLongPressHandlerModifier(state, offsetMapping)
val tapHandlerModifier = getTapHandlerModifier(
interactionSource,
state,
Expand All @@ -59,7 +59,7 @@ internal fun Modifier.cupertinoTextFieldPointer(
)
this
.then(tapHandlerModifier)
.then(selectionModifier)
.then(longPressHandlerModifier)
.pointerHoverIcon(textPointerIcon)
} else {
this
Expand Down Expand Up @@ -142,19 +142,70 @@ private fun getTapHandlerModifier(
}
}

/**
* Returns a modifier which allows to precisely move the caret in the text by drag gesture after long press
*
* @param state The state of the text field.
* @param offsetMapping The offset mapping of the text field.
* @return A modifier that handles long press and drag gestures.
*/
@Composable
private fun getSelectionModifier(manager: TextFieldSelectionManager): Modifier {
val currentManager by rememberUpdatedState(manager)
private fun getLongPressHandlerModifier(
state: TextFieldState,
offsetMapping: OffsetMapping
): Modifier {
val currentState by rememberUpdatedState(state)
val currentOffsetMapping by rememberUpdatedState(offsetMapping)

return Modifier.pointerInput(Unit) {
val longTapActionsObserver =
object : TextDragObserver {
var dragTotalDistance = Offset.Zero
var dragBeginOffset = Offset.Zero

override fun onStart(startPoint: Offset) {
currentState.layoutResult?.let { layoutResult ->
TextFieldDelegate.setCursorOffset(
startPoint,
layoutResult,
currentState.processor,
currentOffsetMapping,
currentState.onValueChange
)
dragBeginOffset = startPoint
}
dragTotalDistance = Offset.Zero
}

override fun onDrag(delta: Offset) {
dragTotalDistance += delta
currentState.layoutResult?.let { layoutResult ->
val currentDragPosition = dragBeginOffset + dragTotalDistance
TextFieldDelegate.setCursorOffset(
currentDragPosition,
layoutResult,
currentState.processor,
currentOffsetMapping,
currentState.onValueChange
)
}
}

// Unnecessary here
override fun onDown(point: Offset) {}

override fun onUp() {}

override fun onStop() {}

override fun onCancel() {}
}

detectDragGesturesAfterLongPress(
onDragStart = {
currentManager.touchSelectionObserver.onStart(
startPoint = it
)
},
onDrag = { _, delta -> currentManager.touchSelectionObserver.onDrag(delta = delta) },
onDragCancel = { currentManager.touchSelectionObserver.onCancel() },
onDragEnd = { currentManager.touchSelectionObserver.onStop() }
onDragStart = { longTapActionsObserver.onStart(it) },
onDrag = { _, delta -> longTapActionsObserver.onDrag(delta = delta) },
onDragCancel = { longTapActionsObserver.onCancel() },
onDragEnd = { longTapActionsObserver.onStop() }
)
}
}
Expand Down

0 comments on commit 072a8c2

Please sign in to comment.