[go: nahoru, domu]

Selection extend by word shrink by character

- Changed SelectionAdjustment so that more advanced selection adjustment
  is possible.
- Introduced SelectionAdjustment.CharacterWithWordAccelerate which
  implemented the Android TextView's selection movement behavior.
- Use SelectionAdjustment.CharacterWithWordAccelerate in
  SelectionContainer.

Bug: 137322559
Test: ./gradlew test
Test: ./gradlew compose:foundation:foundation:connectedAndroidTest
RelNote: N/A
Change-Id: Ia4bccabcde17ce235880f4f18d00083304ee77cb
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
index e669132..dd8f4ec 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/SelectionManager.kt
@@ -242,6 +242,7 @@
 
         selectionRegistrar.>
             showSelectionToolbar()
+            finishSelectionUpdate()
         }
 
         selectionRegistrar. selectableKey ->
@@ -353,27 +354,27 @@
     internal fun mergeSelections(
         startPosition: Offset,
         endPosition: Offset,
-        adjustment: SelectionAdjustment = SelectionAdjustment.NONE,
+        adjustment: SelectionAdjustment = SelectionAdjustment.None,
         previousSelection: Selection? = null,
         isStartHandle: Boolean = true
     ): Pair<Selection?, Map<Long, Selection>> {
         val subselections = mutableMapOf<Long, Selection>()
         val newSelection = selectionRegistrar.sort(requireContainerCoordinates())
             .fastFold(null) { mergedSelection: Selection?, selectable: Selectable ->
+                val subselection =
+                    selectionRegistrar.subselections[selectable.selectableId]
                 val selection = selectable.getSelection(
                     startPosition = startPosition,
                     endPosition = endPosition,
                     containerLayoutCoordinates = requireContainerCoordinates(),
-                    previousSelection = previousSelection,
+                    adjustment = adjustment,
+                    previousSelection = subselection,
                     isStartHandle = isStartHandle,
-                    adjustment = adjustment
                 )
                 selection?.let { subselections[selectable.selectableId] = it }
                 merge(mergedSelection, selection)
             }
-        if (previousSelection != newSelection) hapticFeedBack?.performHapticFeedback(
-            HapticFeedbackType.TextHandleMove
-        )
+        performHapticFeedbackIfNeeded(newSelection, previousSelection, hapticFeedBack)
         return Pair(newSelection, subselections)
     }
 
@@ -389,9 +390,7 @@
                 selection?.let { subselections[selectable.selectableId] = it }
                 merge(mergedSelection, selection)
             }
-        if (previousSelection != newSelection) hapticFeedBack?.performHapticFeedback(
-            HapticFeedbackType.TextHandleMove
-        )
+        performHapticFeedbackIfNeeded(previousSelection, newSelection, hapticFeedBack)
         return Pair(newSelection, subselections)
     }
 
@@ -617,13 +616,14 @@
                     startPosition = currentStart,
                     endPosition = currentEnd,
                     isStartHandle = isStartHandle,
-                    adjustment = SelectionAdjustment.CHARACTER
+                    adjustment = SelectionAdjustment.CharacterWithWordAccelerate
                 )
                 return
             }
 
             override fun onStop() {
                 showSelectionToolbar()
+                finishSelectionUpdate()
             }
 
             override fun onCancel() {
@@ -663,7 +663,7 @@
     private fun updateSelection(
         startPosition: Offset?,
         endPosition: Offset?,
-        adjustment: SelectionAdjustment = SelectionAdjustment.NONE,
+        adjustment: SelectionAdjustment = SelectionAdjustment.None,
         isStartHandle: Boolean = true
     ) {
         if (startPosition == null || endPosition == null) return
@@ -671,14 +671,25 @@
             startPosition = startPosition,
             endPosition = endPosition,
             adjustment = adjustment,
-            isStartHandle = isStartHandle,
             previousSelection = selection,
+            isStartHandle = isStartHandle
         )
         if (newSelection != selection) {
             selectionRegistrar.subselections = newSubselection
             onSelectionChange(newSelection)
         }
     }
+
+    private fun finishSelectionUpdate() {
+        selection?.let {
+            val newSelection = Selection(
+                start = it.start.copy(rawOffset = it.start.offset),
+                end = it.end.copy(rawOffset = it.end.offset),
+                handlesCrossed = it.handlesCrossed
+            )
+            onSelectionChange(newSelection)
+        }
+    }
 }
 
 internal fun merge(lhs: Selection?, rhs: Selection?): Selection? {
@@ -744,3 +755,33 @@
 
 internal fun Rect.containsInclusive(offset: Offset): Boolean =
     offset.x in left..right && offset.y in top..bottom
+
+private fun performHapticFeedbackIfNeeded(
+    previousSelection: Selection?,
+    newSelection: Selection?,
+    hapticFeedback: HapticFeedback?,
+) {
+    if (hapticFeedback == null) {
+        return
+    }
+    if (previousSelection == null) {
+        if (newSelection != null) {
+            hapticFeedback.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+        }
+        return
+    }
+    if (newSelection == null) {
+        // We know previousSelection is not null, so selection is updated.
+        hapticFeedback.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+        return
+    }
+    // We don't care if rawOffset of Selection.AnchorInfo is different, so we only compare the
+    // other fields. If any of them changes, we need to perform hapticFeedback.
+    if (previousSelection.start.offset != newSelection.start.offset ||
+        previousSelection.start.selectableId != newSelection.start.selectableId ||
+        previousSelection.end.offset != newSelection.end.offset ||
+        previousSelection.end.selectableId != previousSelection.end.selectableId
+    ) {
+        hapticFeedback.performHapticFeedback(HapticFeedbackType.TextHandleMove)
+    }
+}