Text Selection] Update FloatingToolbar.
-- Add copy callback to copy text, thus remove the selected text parameter.
-- Hide FloatingToolbar when selection becomes null, for example, click on the unselected region to cancel the selection, then the FloatingToolbar should disappear.
-- Hide FloatingToolbar when existing the activity.
-- Update FloatingToolbar's position when scrolling the screen.
Bug: 139311164
Test: Manually Tested.
Test: ./gradlew test
Test: ./gradlew buildOnServer
Test: ./gradlew :ui:ui-framework:connectedAndroidTest
Test: ./gradlew :ui:ui-platform:connectedAndroidTest
Change-Id: I83bb102017b2bfbc549c31e94e0efb910e252f7f
diff --git a/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionManager.kt b/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionManager.kt
index d95c3e5..3acc691 100644
--- a/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionManager.kt
+++ b/ui/ui-framework/src/main/java/androidx/ui/core/selection/SelectionManager.kt
@@ -28,11 +28,15 @@
import androidx.ui.core.hapticfeedback.HapticFeedback
import androidx.ui.core.hapticfeedback.HapticFeedbackType
import androidx.ui.core.texttoolbar.TextToolbar
+import androidx.ui.core.texttoolbar.TextToolbarStatus
import androidx.ui.geometry.Rect
import androidx.ui.text.AnnotatedString
import androidx.ui.text.length
import androidx.ui.text.subSequence
+import androidx.ui.unit.Px
import androidx.ui.unit.PxPosition
+import androidx.ui.unit.max
+import androidx.ui.unit.min
import androidx.ui.unit.px
/**
@@ -46,6 +50,7 @@
set(value) {
field = value
updateHandleOffsets()
+ hideSelectionToolbar()
}
/**
@@ -77,6 +82,7 @@
set(value) {
field = value
updateHandleOffsets()
+ updateSelectionToolbarPosition()
}
/**
@@ -120,6 +126,7 @@
init {
selectionRegistrar.>
updateHandleOffsets()
+ hideSelectionToolbar()
}
}
@@ -230,26 +237,108 @@
return selectedText
}
- /**
- * This function collects the selected text and the selected region as a Rectangle region,
- * and pass these information to [TextToolbar] to make the FloatingToolbar show up in the
- * proper place, and copy the text to the [ClipboardManager].
- *
- * @param selectedRegion selected region as [Rect]. The top is the top of the first selected
- * line, and the bottom is the bottom of the last selected line. The left is the leftmost
- * handle's horizontal coordinates, and the right is the rightmost handle's coordinates.
- */
- internal fun showSelectionToolbar(selectedRegion: Rect) {
+ internal fun copy() {
val selectedText = getSelectedText()
- selectedText?.let {
+ selectedText?.let { clipboardManager?.setText(it) }
+ }
+
+ /**
+ * This function get the selected region as a Rectangle region, and pass it to [TextToolbar]
+ * to make the FloatingToolbar show up in the proper place. In addition, this function passes
+ * the copy method as a callback when "copy" is clicked.
+ */
+ internal fun showSelectionToolbar() {
+ selection?.let {
textToolbar?.showCopyMenu(
- rect = selectedRegion,
- text = it,
+ getContentRect(),
+ copy() },
onRelease() }
)
}
}
+ private fun hideSelectionToolbar() {
+ if (textToolbar?.status == TextToolbarStatus.Shown) {
+ val selection = selection
+ if (selection == null) {
+ textToolbar?.hide()
+ }
+ }
+ }
+
+ private fun updateSelectionToolbarPosition() {
+ if (textToolbar?.status == TextToolbarStatus.Shown) {
+ showSelectionToolbar()
+ }
+ }
+
+ /**
+ * Calculate selected region as [Rect]. The top is the top of the first selected
+ * line, and the bottom is the bottom of the last selected line. The left is the leftmost
+ * handle's horizontal coordinates, and the right is the rightmost handle's coordinates.
+ */
+ private fun getContentRect(): Rect {
+ val selection = selection ?: return Rect.zero
+ val startLayoutCoordinates =
+ selection.start.selectable.getLayoutCoordinates() ?: return Rect.zero
+ val endLayoutCoordinates =
+ selection.end.selectable.getLayoutCoordinates() ?: return Rect.zero
+
+ val localLayoutCoordinates = containerLayoutCoordinates
+ if (localLayoutCoordinates != null && localLayoutCoordinates.isAttached) {
+ var startOffset = localLayoutCoordinates.childToLocal(
+ startLayoutCoordinates,
+ selection.start.selectable.getHandlePosition(
+ selection = selection,
+ isStartHandle = true
+ )
+ )
+ var endOffset = localLayoutCoordinates.childToLocal(
+ endLayoutCoordinates,
+ selection.end.selectable.getHandlePosition(
+ selection = selection,
+ isStartHandle = false
+ )
+ )
+
+ startOffset = localLayoutCoordinates.localToRoot(startOffset)
+ endOffset = localLayoutCoordinates.localToRoot(endOffset)
+
+ val left = min(startOffset.x, endOffset.x)
+ val right = max(startOffset.x, endOffset.x)
+
+ var startTop = localLayoutCoordinates.childToLocal(
+ startLayoutCoordinates,
+ PxPosition(
+ Px.Zero,
+ selection.start.selectable.getBoundingBox(selection.start.offset).top.px
+ )
+ )
+
+ var endTop = localLayoutCoordinates.childToLocal(
+ endLayoutCoordinates,
+ PxPosition(
+ Px.Zero,
+ selection.end.selectable.getBoundingBox(selection.end.offset).top.px
+ )
+ )
+
+ startTop = localLayoutCoordinates.localToRoot(startTop)
+ endTop = localLayoutCoordinates.localToRoot(endTop)
+
+ val top = min(startTop.y, endTop.y)
+ val bottom = max(startOffset.y, endOffset.y) + (HANDLE_HEIGHT.value * 4.0).px
+
+ return Rect(
+ left.value,
+ top.value,
+ right.value,
+ bottom.value
+ )
+ }
+ return Rect.zero
+ }
+
// This is for PressGestureDetector to cancel the selection.
fun onRelease() {
// Call mergeSelections with an out of boundary input to inform all text widgets to