From 47c41b5aa69c0b0808d5027cc56eba453ad4dfc0 Mon Sep 17 00:00:00 2001 From: "Evgeny @ SimpleX Chat" <259188159+evgeny-simplex@users.noreply.github.com> Date: Wed, 1 Apr 2026 20:41:51 +0000 Subject: [PATCH] moare better --- .../simplex/common/views/chat/ChatView.kt | 33 ++++++++++++------- .../common/views/chat/TextSelection.kt | 7 +++- .../common/views/chat/item/FramedItemView.kt | 16 +++++++-- 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 758c0f73f9..d07e8d42e3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -14,6 +14,7 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.* import androidx.compose.ui.draw.* import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.layer.GraphicsLayer import androidx.compose.ui.layout.layoutId @@ -1142,18 +1143,26 @@ fun ChatLayout( val draggingDown = range.startIndex > range.endIndex val gap = with(LocalDensity.current) { 4.dp.toPx() } var buttonSize by remember { mutableStateOf(IntSize.Zero) } - val x = if (draggingDown) manager.focusWindowX - else (manager.focusWindowX - buttonSize.width).coerceAtLeast(0f) - val y = if (draggingDown) manager.focusWindowY + gap - else (manager.focusWindowY - buttonSize.height - gap).coerceAtLeast(0f) - val clampedX = x.coerceIn(0f, (manager.viewportWidth - buttonSize.width).coerceAtLeast(0f)) - val clampedY = y.coerceIn(0f, (manager.viewportHeight - buttonSize.height).coerceAtLeast(0f)) - SelectionCopyButton( - modifier = Modifier - .offset { IntOffset(clampedX.toInt(), clampedY.toInt()) } - .onSizeChanged { buttonSize = it }, - onCopy = { manager.onCopySelection?.invoke() } - ) + val ls = manager.listState?.value + val itemInfo = ls?.layoutInfo?.visibleItemsInfo?.find { it.index == range.endIndex } + if (ls != null && itemInfo != null && manager.focusCharRect != Rect.Zero) { + val itemWindowY = (ls.layoutInfo.viewportEndOffset - itemInfo.offset - itemInfo.size).toFloat() + val cr = manager.focusCharRect + val charX = if (draggingDown) cr.right else cr.left + val charY = itemWindowY + if (draggingDown) cr.bottom else cr.top + val x = if (draggingDown) charX + else (charX - buttonSize.width).coerceAtLeast(0f) + val y = if (draggingDown) charY + gap + else (charY - buttonSize.height - gap).coerceAtLeast(0f) + val clampedX = x.coerceIn(0f, (manager.viewportWidth - buttonSize.width).coerceAtLeast(0f)) + val clampedY = y.coerceIn(0f, (manager.viewportHeight - buttonSize.height).coerceAtLeast(0f)) + SelectionCopyButton( + modifier = Modifier + .offset { IntOffset(clampedX.toInt(), clampedY.toInt()) } + .onSizeChanged { buttonSize = it }, + onCopy = { manager.onCopySelection?.invoke() } + ) + } } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/TextSelection.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/TextSelection.kt index 4e36d30890..57739a2679 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/TextSelection.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/TextSelection.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.key.* import androidx.compose.ui.input.pointer.* @@ -66,6 +67,8 @@ class SelectionManager { var focusWindowX by mutableStateOf(0f) var viewportWidth by mutableStateOf(0f) var viewportHeight by mutableStateOf(0f) + var focusCharRect by mutableStateOf(Rect.Zero) // X: absolute window, Y: relative to item + var listState: State? = null var onCopySelection: (() -> Unit)? = null fun startSelection(startIndex: Int, anchorY: Float, anchorX: Float) { @@ -85,9 +88,10 @@ class SelectionManager { range = r.copy(endIndex = index) } - fun updateFocusOffset(offset: Int) { + fun updateFocusOffset(offset: Int, charRect: Rect = Rect.Zero) { val r = range ?: return range = r.copy(endOffset = offset) + focusCharRect = charRect } fun endSelection() { @@ -171,6 +175,7 @@ fun BoxScope.SelectionHandler( } } + manager.listState = listState manager.onCopySelection = { clipboard.setText(AnnotatedString(manager.getSelectedText(mergedItems.value.items, linkMode))) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt index ab72624a3a..fec797dfa6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt @@ -405,8 +405,20 @@ fun CIMarkdownText( val bounds = boundsState.value ?: return@collect val layout = layoutResultState.value ?: return@collect val offset = layout.getOffsetForPosition(Offset(px - bounds.left, py - bounds.top)) - Log.e(TAG, "focusOffset idx=$selectionIndex offset=$offset bounds=$bounds pointer=($px,$py)") - selectionManager.updateFocusOffset(offset) + val charBox = layout.getBoundingBox(offset.coerceIn(0, layout.layoutInput.text.length - 1)) + val ls = selectionManager.listState?.value + val itemInfo = ls?.layoutInfo?.visibleItemsInfo?.find { it.index == selectionIndex } + val charRect = if (ls != null && itemInfo != null) { + val itemWindowY = (ls.layoutInfo.viewportEndOffset - itemInfo.offset - itemInfo.size).toFloat() + Rect( + left = bounds.left + charBox.left, // absolute window X + top = bounds.top + charBox.top - itemWindowY, // relative to item Y + right = bounds.left + charBox.right, // absolute window X + bottom = bounds.top + charBox.bottom - itemWindowY // relative to item Y + ) + } else Rect.Zero + Log.e(TAG, "focusOffset idx=$selectionIndex offset=$offset bounds=$bounds pointer=($px,$py) charRect=$charRect") + selectionManager.updateFocusOffset(offset, charRect) } } }