Merge "Account for core/layout offset in CursorAnchorInfo" into androidx-main
diff --git a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/TextInputServiceAndroidCursorAnchorInfoTest.kt b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/TextInputServiceAndroidCursorAnchorInfoTest.kt
index 5b2a4c6..9457bb7 100644
--- a/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/TextInputServiceAndroidCursorAnchorInfoTest.kt
+++ b/compose/foundation/foundation/src/androidInstrumentedTest/kotlin/androidx/compose/foundation/text/input/internal/TextInputServiceAndroidCursorAnchorInfoTest.kt
@@ -43,6 +43,7 @@
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.toSize
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
@@ -109,11 +110,25 @@
}
}
- private val windowOffset = Offset(12f, 34f)
+ private val layoutOffset = Offset(98f, 47f)
+ private val coreNodeOffset = Offset(73f, 50f)
+ private val coreNodeSize = IntSize(25, 51)
+ private val decoratorNodeOffset = Offset(84f, 59f)
+ private val decoratorNodeSize = IntSize(19, 66)
private val layoutState = TextLayoutState().apply {
- coreNodeCoordinates = TestLayoutCoordinates(windowOffset = windowOffset, isAttached = true)
+ textLayoutNodeCoordinates =
+ TestLayoutCoordinates(windowOffset = layoutOffset, isAttached = true)
+ coreNodeCoordinates = TestLayoutCoordinates(
+ windowOffset = coreNodeOffset,
+ size = coreNodeSize,
+ isAttached = true
+ )
decoratorNodeCoordinates =
- TestLayoutCoordinates(windowOffset = windowOffset, isAttached = true)
+ TestLayoutCoordinates(
+ windowOffset = decoratorNodeOffset,
+ size = decoratorNodeSize,
+ isAttached = true
+ )
}
@Test
@@ -130,9 +145,12 @@
selection = textFieldState.selection,
composition = textFieldState.composition,
textLayoutResult = layoutState.layoutResult!!,
- matrix = getAndroidMatrix(windowOffset),
- innerTextFieldBounds = Rect.Zero,
- decorationBoxBounds = Rect.Zero,
+ matrix = getAndroidMatrix(layoutOffset),
+ innerTextFieldBounds = Rect(coreNodeOffset - layoutOffset, coreNodeSize.toSize()),
+ decorationBoxBounds = Rect(
+ decoratorNodeOffset - layoutOffset,
+ decoratorNodeSize.toSize()
+ ),
)
Truth.assertThat(reportedCursorAnchorInfos).containsExactly(expectedInfo)
reportedCursorAnchorInfos.clear()
@@ -182,8 +200,9 @@
Truth.assertThat(reportedCursorAnchorInfos).isEmpty()
// Trigger new layout.
- layoutState.coreNodeCoordinates =
- TestLayoutCoordinates(windowOffset = Offset(67f, 89f), isAttached = true)
+ val newLayoutOffset = Offset(67f, 89f)
+ layoutState.textLayoutNodeCoordinates =
+ TestLayoutCoordinates(windowOffset = newLayoutOffset, isAttached = true)
// Monitoring update.
val expectedInfo = builder.build(
@@ -191,9 +210,12 @@
selection = textFieldState.selection,
composition = textFieldState.composition,
textLayoutResult = layoutState.layoutResult!!,
- matrix = getAndroidMatrix(Offset(67f, 89f)),
- innerTextFieldBounds = Rect.Zero,
- decorationBoxBounds = Rect.Zero,
+ matrix = getAndroidMatrix(newLayoutOffset),
+ innerTextFieldBounds = Rect(coreNodeOffset - newLayoutOffset, coreNodeSize.toSize()),
+ decorationBoxBounds = Rect(
+ decoratorNodeOffset - newLayoutOffset,
+ decoratorNodeSize.toSize()
+ ),
)
Truth.assertThat(reportedCursorAnchorInfos).containsExactly(expectedInfo)
}
@@ -213,16 +235,20 @@
selection = textFieldState.selection,
composition = textFieldState.composition,
textLayoutResult = layoutState.layoutResult!!,
- matrix = getAndroidMatrix(windowOffset),
- innerTextFieldBounds = Rect.Zero,
- decorationBoxBounds = Rect.Zero,
+ matrix = getAndroidMatrix(layoutOffset),
+ innerTextFieldBounds = Rect(coreNodeOffset - layoutOffset, coreNodeSize.toSize()),
+ decorationBoxBounds = Rect(
+ decoratorNodeOffset - layoutOffset,
+ decoratorNodeSize.toSize()
+ ),
)
Truth.assertThat(reportedCursorAnchorInfos).containsExactly(expectedInfo)
reportedCursorAnchorInfos.clear()
// Trigger new layout.
- layoutState.coreNodeCoordinates =
- TestLayoutCoordinates(windowOffset = Offset(67f, 89f), isAttached = true)
+ val newLayoutOffset = Offset(67f, 89f)
+ layoutState.textLayoutNodeCoordinates =
+ TestLayoutCoordinates(windowOffset = newLayoutOffset, isAttached = true)
// Monitoring update.
val expectedInfo2 = builder.build(
@@ -230,9 +256,12 @@
selection = textFieldState.selection,
composition = textFieldState.composition,
textLayoutResult = layoutState.layoutResult!!,
- matrix = getAndroidMatrix(Offset(67f, 89f)),
- innerTextFieldBounds = Rect.Zero,
- decorationBoxBounds = Rect.Zero,
+ matrix = getAndroidMatrix(newLayoutOffset),
+ innerTextFieldBounds = Rect(coreNodeOffset - newLayoutOffset, coreNodeSize.toSize()),
+ decorationBoxBounds = Rect(
+ decoratorNodeOffset - newLayoutOffset,
+ decoratorNodeSize.toSize()
+ ),
)
Truth.assertThat(reportedCursorAnchorInfos).containsExactly(expectedInfo2)
}
@@ -252,9 +281,12 @@
selection = textFieldState.selection,
composition = textFieldState.composition,
textLayoutResult = layoutState.layoutResult!!,
- matrix = getAndroidMatrix(windowOffset),
- innerTextFieldBounds = Rect.Zero,
- decorationBoxBounds = Rect.Zero,
+ matrix = getAndroidMatrix(layoutOffset),
+ innerTextFieldBounds = Rect(coreNodeOffset - layoutOffset, coreNodeSize.toSize()),
+ decorationBoxBounds = Rect(
+ decoratorNodeOffset - layoutOffset,
+ decoratorNodeSize.toSize()
+ ),
)
Truth.assertThat(reportedCursorAnchorInfos).containsExactly(expectedInfo)
reportedCursorAnchorInfos.clear()
@@ -335,12 +367,13 @@
override fun localPositionOf(
sourceCoordinates: LayoutCoordinates,
relativeToSource: Offset
- ): Offset = relativeToSource
+ ): Offset = sourceCoordinates.localToWindow(relativeToSource) - windowOffset
override fun localBoundingBoxOf(
sourceCoordinates: LayoutCoordinates,
clipBounds: Boolean
- ): Rect = Rect.Zero
+ ): Rect =
+ Rect(localPositionOf(sourceCoordinates, Offset.Zero), sourceCoordinates.size.toSize())
override fun transformToScreen(matrix: Matrix) {
matrix.translate(windowOffset.x, windowOffset.y, 0f)
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/CursorAnchorInfoBuilder.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/CursorAnchorInfoBuilder.android.kt
index 99d3944..e4168d9 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/CursorAnchorInfoBuilder.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/CursorAnchorInfoBuilder.android.kt
@@ -28,11 +28,11 @@
* Helper function to build
* [CursorAnchorInfo](https://developer.android.com/reference/android/view/inputmethod/CursorAnchorInfo).
*
- * @param matrix matrix that transforms local coordinates into screen coordinates
- * @param innerTextFieldBounds visible bounds of the text field in local coordinates, or an empty
- * rectangle if the text field is not visible
- * @param decorationBoxBounds visible bounds of the decoration box in local coordinates, or an empty
- * rectangle if the decoration box is not visible
+ * @param matrix matrix that transforms text layout coordinates into screen coordinates
+ * @param innerTextFieldBounds visible bounds of the text field in text layout coordinates, or an
+ * empty rectangle if the text field is not visible
+ * @param decorationBoxBounds visible bounds of the decoration box in text layout coordinates, or an
+ * empty rectangle if the decoration box is not visible
*/
internal fun CursorAnchorInfo.Builder.build(
text: CharSequence,
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/CursorAnchorInfoController.android.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/CursorAnchorInfoController.android.kt
index 81b76da..a45685a 100644
--- a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/CursorAnchorInfoController.android.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text/input/internal/CursorAnchorInfoController.android.kt
@@ -23,10 +23,9 @@
import android.view.inputmethod.InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS
import android.view.inputmethod.InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER
import android.view.inputmethod.InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS
-import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.text.selection.visibleBounds
import androidx.compose.runtime.snapshotFlow
-import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Matrix
import androidx.compose.ui.graphics.setFrom
import kotlinx.coroutines.CoroutineScope
@@ -36,7 +35,6 @@
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.launch
-@OptIn(ExperimentalFoundationApi::class)
internal class CursorAnchorInfoController(
private val textFieldState: TransformedTextFieldState,
private val textLayoutState: TextLayoutState,
@@ -165,6 +163,9 @@
private fun calculateCursorAnchorInfo(): CursorAnchorInfo? {
// State reads
+ val textLayoutCoordinates = textLayoutState.textLayoutNodeCoordinates
+ ?.takeIf { it.isAttached }
+ ?: return null
val coreCoordinates = textLayoutState.coreNodeCoordinates
?.takeIf { it.isAttached }
?: return null
@@ -175,16 +176,15 @@
?: return null
val text = textFieldState.visualText
- // Updates matrix to transform text field local coordinates to screen coordinates.
+ // Updates matrix to transform text layout coordinates to screen coordinates.
matrix.reset()
- coreCoordinates.transformToScreen(matrix)
+ textLayoutCoordinates.transformToScreen(matrix)
androidMatrix.setFrom(matrix)
- val innerTextFieldBounds: Rect = coreCoordinates.visibleBounds()
- val decorationBoxBounds: Rect = coreCoordinates.localBoundingBoxOf(
- decorationBoxCoordinates,
- clipBounds = false
- )
+ val innerTextFieldBounds = coreCoordinates.visibleBounds()
+ .translate(textLayoutCoordinates.localPositionOf(coreCoordinates, Offset.Zero))
+ val decorationBoxBounds = decorationBoxCoordinates.visibleBounds()
+ .translate(textLayoutCoordinates.localPositionOf(decorationBoxCoordinates, Offset.Zero))
return builder.build(
text,
text.selection,