Skip to content

Commit

Permalink
refactor: improve cursor following of candidate window
Browse files Browse the repository at this point in the history
  • Loading branch information
WhiredPlanck committed Dec 5, 2024
1 parent 4559365 commit 9d05290
Showing 1 changed file with 81 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,17 @@ import com.osfans.trime.data.theme.ColorManager
import com.osfans.trime.data.theme.Theme
import com.osfans.trime.ime.bar.QuickBar
import com.osfans.trime.ime.broadcast.InputBroadcastReceiver
import com.osfans.trime.ime.core.TrimeInputMethodService
import com.osfans.trime.ime.dependency.InputScope
import com.osfans.trime.ime.enums.PopupPosition
import me.tatarka.inject.annotations.Inject
import splitties.dimensions.dp
import timber.log.Timber

@InputScope
@Inject
class CompositionPopupWindow(
private val ctx: Context,
private val service: TrimeInputMethodService,
private val rime: RimeSession,
private val theme: Theme,
private val bar: QuickBar,
Expand Down Expand Up @@ -83,15 +84,15 @@ class CompositionPopupWindow(

var isCursorUpdated = false // 光標是否移動

private val mPopupRectF = RectF()
private val anchorPosition = RectF()
private val mPopupHandler = Handler(Looper.getMainLooper())

private val mPopupTimer =
Runnable {
if (bar.view.windowToken == null) return@Runnable
bar.view.let { anchor ->
var x = 0
var y = 0
var x: Int
var y: Int
val (_, anchorY) =
intArrayOf(0, 0).also {
anchor.getLocationInWindow(it)
Expand All @@ -105,49 +106,50 @@ class CompositionPopupWindow(
val minY = anchor.dp(popupMargin)
val maxX = anchor.width - selfWidth - minX
val maxY = anchorY - selfHeight - minY
if (isWinFixed() || !isCursorUpdated) {
// setCandidatesViewShown(true);
when (popupWindowPos) {
PopupPosition.TOP_RIGHT -> {
x = maxX
y = minY
}
PopupPosition.TOP_LEFT -> {
x = minX
y = minY
}
PopupPosition.BOTTOM_RIGHT -> {
x = maxX
y = maxY
}
PopupPosition.DRAG -> {
x = popupWindowX
y = popupWindowY
}
PopupPosition.FIXED, PopupPosition.BOTTOM_LEFT -> {
x = minX
y = maxY
}
else -> {
x = minX
y = maxY
}
when (popupWindowPos) {
PopupPosition.TOP_RIGHT -> {
x = maxX
y = minY
}
} else {
// setCandidatesViewShown(false);
when (popupWindowPos) {
PopupPosition.LEFT, PopupPosition.LEFT_UP -> x = mPopupRectF.left.toInt()
PopupPosition.RIGHT, PopupPosition.RIGHT_UP -> x = mPopupRectF.right.toInt()
else -> Timber.wtf("UNREACHABLE BRANCH")
PopupPosition.TOP_LEFT -> {
x = minX
y = minY
}
x = MathUtils.clamp(x, minX, maxX)
when (popupWindowPos) {
PopupPosition.LEFT, PopupPosition.RIGHT ->
y = mPopupRectF.bottom.toInt() + popupMargin
PopupPosition.LEFT_UP, PopupPosition.RIGHT_UP ->
y = mPopupRectF.top.toInt() - selfHeight - popupMargin
else -> Timber.wtf("UNREACHABLE BRANCH")
PopupPosition.BOTTOM_RIGHT -> {
x = maxX
y = maxY
}
PopupPosition.DRAG -> {
x = popupWindowX
y = popupWindowY
}
PopupPosition.FIXED, PopupPosition.BOTTOM_LEFT -> {
x = minX
y = maxY
}
PopupPosition.LEFT -> {
x = anchorPosition.left.toInt()
y = anchorPosition.bottom.toInt() + popupMargin
}
PopupPosition.LEFT_UP -> {
x = anchorPosition.left.toInt()
y = anchorPosition.top.toInt() - selfHeight - popupMargin
}
PopupPosition.RIGHT -> {
x = anchorPosition.right.toInt()
y = anchorPosition.bottom.toInt() + popupMargin
}
PopupPosition.RIGHT_UP -> {
x = anchorPosition.right.toInt()
y = anchorPosition.top.toInt() - selfHeight - popupMargin
}
else -> {
x = minX
y = maxY
}
}
if (!isWinFixed() || isCursorUpdated) {
x = MathUtils.clamp(x, minX, maxX)
y = MathUtils.clamp(y, minY, maxY)
}
if (!mPopupWindow.isShowing) {
Expand Down Expand Up @@ -179,6 +181,7 @@ class CompositionPopupWindow(
fun hideCompositionView() {
mPopupWindow.dismiss()
mPopupHandler.removeCallbacks(mPopupTimer)
decorLocationUpdated = false
}

private fun updateCompositionView() {
Expand All @@ -188,35 +191,48 @@ class CompositionPopupWindow(
mPopupHandler.post(mPopupTimer)
}

fun updateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo) {
private val decorLocation = floatArrayOf(0f, 0f)
private var decorLocationUpdated = false

private fun updateDecorLocation() {
val (dX, dY) =
intArrayOf(0, 0).also {
service.window.window!!
.decorView
.getLocationOnScreen(it)
}
decorLocation[0] = dX.toFloat()
decorLocation[1] = dY.toFloat()
decorLocationUpdated = true
}

fun updateCursorAnchorInfo(info: CursorAnchorInfo) {
if (!isWinFixed()) {
val composingText = cursorAnchorInfo.composingText
val bounds = info.getCharacterBounds(0)
// update mPopupRectF
if (composingText == null) {
if (bounds == null) {
// composing is disabled in target app or trime settings
// use the position of the insertion marker instead
mPopupRectF.top = cursorAnchorInfo.insertionMarkerTop
mPopupRectF.left = cursorAnchorInfo.insertionMarkerHorizontal
mPopupRectF.bottom = cursorAnchorInfo.insertionMarkerBottom
mPopupRectF.right = mPopupRectF.left
anchorPosition.top = info.insertionMarkerTop
anchorPosition.left = info.insertionMarkerHorizontal
anchorPosition.bottom = info.insertionMarkerBottom
anchorPosition.right = info.insertionMarkerHorizontal
} else {
val startPos: Int = cursorAnchorInfo.composingTextStart
val endPos = startPos + composingText.length - 1
val startCharRectF = cursorAnchorInfo.getCharacterBounds(startPos)
val endCharRectF = cursorAnchorInfo.getCharacterBounds(endPos)
if (startCharRectF == null || endCharRectF == null) {
// composing text has been changed, the next onUpdateCursorAnchorInfo is on the road
// ignore this outdated update
return
}
// for different writing system (e.g. right to left languages),
// we have to calculate the correct RectF
mPopupRectF.top = startCharRectF.top.coerceAtMost(endCharRectF.top)
mPopupRectF.left = startCharRectF.left.coerceAtMost(endCharRectF.left)
mPopupRectF.bottom = startCharRectF.bottom.coerceAtLeast(endCharRectF.bottom)
mPopupRectF.right = startCharRectF.right.coerceAtLeast(endCharRectF.right)
val horizontal = if (root.layoutDirection == View.LAYOUT_DIRECTION_RTL) bounds.right else bounds.left
anchorPosition.top = bounds.top
anchorPosition.left = horizontal
anchorPosition.bottom = bounds.bottom
anchorPosition.right = horizontal
}
info.matrix.mapRect(anchorPosition)
// avoid calling `decorView.getLocationOnScreen` repeatedly
if (!decorLocationUpdated) {
updateDecorLocation()
}
cursorAnchorInfo.matrix.mapRect(mPopupRectF)
val (dX, dY) = decorLocation
anchorPosition.offset(-dX, -dY)
}
}
}

2 comments on commit 9d05290

@cabins
Copy link
Contributor

@cabins cabins commented on 9d05290 Dec 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

对于这几个场景的y,建议做一些修正。因为这个y可能会大于maxY或者小于minY。

建议:
① 对于LEFT, RIGHT这两个,当y>maxY的时候,把位置临时改成UP的y值,这样候选框在屏幕的底部的时候,就不会超出屏幕了。
② 对于UP的两个,当y<minY的时候,建议把位置改为LEFT/RIGHT的y值,这样当候选框在屏幕的顶部的时候,就不会超出屏幕了。

望采纳。

@WhiredPlanck
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cabins 你仔细看的话会发现这个提交的 151 - 153 行已经做了边界情况限制,所以无需担心。

Please sign in to comment.