fix: правильные координаты text selection — window→overlay-local конвертация
Root cause: overlay Canvas рисует в локальных координатах, но LayoutInfo возвращает позицию в window coordinates. Разница = position status bar, toolbar, и parent padding → highlight смещался вниз. Фикс: - onGloballyPositioned на overlay Box → знаем overlayWindowX/Y - Canvas: offsetX/Y = info.windowX - overlayWindowX (window→local) - getCharOffsetFromCoords: overlay-local → text-local через ту же delta - Handle positions теперь в overlay-local координатах → drag работает Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -34,6 +35,8 @@ import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.layout.positionInWindow
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@@ -77,6 +80,10 @@ class TextSelectionHelper {
|
||||
var endHandleX by mutableFloatStateOf(0f)
|
||||
var endHandleY by mutableFloatStateOf(0f)
|
||||
|
||||
// Overlay position in window — set by TextSelectionOverlay
|
||||
var overlayWindowX = 0f
|
||||
var overlayWindowY = 0f
|
||||
|
||||
val isInSelectionMode: Boolean get() = isActive && selectionStart >= 0 && selectionEnd > selectionStart
|
||||
|
||||
fun startSelection(
|
||||
@@ -199,13 +206,14 @@ class TextSelectionHelper {
|
||||
magnifier = null
|
||||
}
|
||||
|
||||
fun getCharOffsetFromCoords(x: Int, y: Int): Int {
|
||||
fun getCharOffsetFromCoords(overlayLocalX: Int, overlayLocalY: Int): Int {
|
||||
val info = layoutInfo ?: return -1
|
||||
val localX = x - info.windowX
|
||||
val localY = y - info.windowY
|
||||
// overlay-local → text-local: subtract text position relative to overlay
|
||||
val textLocalX = overlayLocalX - (info.windowX - overlayWindowX)
|
||||
val textLocalY = overlayLocalY - (info.windowY - overlayWindowY)
|
||||
val layout = info.layout
|
||||
val line = layout.getLineForVertical(localY.coerceIn(0, layout.height))
|
||||
val hx = localX.toFloat().coerceIn(layout.getLineLeft(line), layout.getLineRight(line))
|
||||
val line = layout.getLineForVertical(textLocalY.toInt().coerceIn(0, layout.height))
|
||||
val hx = textLocalX.toFloat().coerceIn(layout.getLineLeft(line), layout.getLineRight(line))
|
||||
return layout.getOffsetForHorizontal(line, hx)
|
||||
}
|
||||
|
||||
@@ -324,7 +332,15 @@ fun TextSelectionOverlay(
|
||||
val handleInsetPx = with(density) { HandleInset.toPx() }
|
||||
val highlightCornerPx = with(density) { HighlightCorner.toPx() }
|
||||
|
||||
Box(modifier = modifier.fillMaxSize()) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.onGloballyPositioned { coords ->
|
||||
val pos = coords.positionInWindow()
|
||||
helper.overlayWindowX = pos.x
|
||||
helper.overlayWindowY = pos.y
|
||||
}
|
||||
) {
|
||||
FloatingToolbarPopup(helper = helper)
|
||||
Canvas(
|
||||
modifier = Modifier
|
||||
@@ -387,6 +403,10 @@ fun TextSelectionOverlay(
|
||||
val layout = info.layout
|
||||
val text = info.text
|
||||
|
||||
// Convert window coords to overlay-local coords
|
||||
val offsetX = info.windowX - helper.overlayWindowX
|
||||
val offsetY = info.windowY - helper.overlayWindowY
|
||||
|
||||
val startOffset = helper.selectionStart.coerceIn(0, text.length)
|
||||
val endOffset = helper.selectionEnd.coerceIn(0, text.length)
|
||||
if (startOffset >= endOffset) return@Canvas
|
||||
@@ -395,17 +415,17 @@ fun TextSelectionOverlay(
|
||||
val endLine = layout.getLineForOffset(endOffset)
|
||||
|
||||
for (line in startLine..endLine) {
|
||||
val lineTop = layout.getLineTop(line).toFloat() + info.windowY
|
||||
val lineBottom = layout.getLineBottom(line).toFloat() + info.windowY
|
||||
val lineTop = layout.getLineTop(line).toFloat() + offsetY
|
||||
val lineBottom = layout.getLineBottom(line).toFloat() + offsetY
|
||||
val left = if (line == startLine) {
|
||||
layout.getPrimaryHorizontal(startOffset) + info.windowX
|
||||
layout.getPrimaryHorizontal(startOffset) + offsetX
|
||||
} else {
|
||||
layout.getLineLeft(line) + info.windowX
|
||||
layout.getLineLeft(line) + offsetX
|
||||
}
|
||||
val right = if (line == endLine) {
|
||||
layout.getPrimaryHorizontal(endOffset) + info.windowX
|
||||
layout.getPrimaryHorizontal(endOffset) + offsetX
|
||||
} else {
|
||||
layout.getLineRight(line) + info.windowX
|
||||
layout.getLineRight(line) + offsetX
|
||||
}
|
||||
drawRoundRect(
|
||||
color = HighlightColor,
|
||||
@@ -415,10 +435,10 @@ fun TextSelectionOverlay(
|
||||
)
|
||||
}
|
||||
|
||||
val startHx = layout.getPrimaryHorizontal(startOffset) + info.windowX
|
||||
val startHy = layout.getLineBottom(startLine).toFloat() + info.windowY
|
||||
val endHx = layout.getPrimaryHorizontal(endOffset) + info.windowX
|
||||
val endHy = layout.getLineBottom(endLine).toFloat() + info.windowY
|
||||
val startHx = layout.getPrimaryHorizontal(startOffset) + offsetX
|
||||
val startHy = layout.getLineBottom(startLine).toFloat() + offsetY
|
||||
val endHx = layout.getPrimaryHorizontal(endOffset) + offsetX
|
||||
val endHy = layout.getLineBottom(endLine).toFloat() + offsetY
|
||||
|
||||
helper.startHandleX = startHx
|
||||
helper.startHandleY = startHy
|
||||
|
||||
Reference in New Issue
Block a user