Skip to content

Commit 9d05290

Browse files
committed
refactor: improve cursor following of candidate window
1 parent 4559365 commit 9d05290

File tree

1 file changed

+81
-65
lines changed

1 file changed

+81
-65
lines changed

app/src/main/java/com/osfans/trime/ime/composition/CompositionPopupWindow.kt

Lines changed: 81 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,17 @@ import com.osfans.trime.data.theme.ColorManager
2323
import com.osfans.trime.data.theme.Theme
2424
import com.osfans.trime.ime.bar.QuickBar
2525
import com.osfans.trime.ime.broadcast.InputBroadcastReceiver
26+
import com.osfans.trime.ime.core.TrimeInputMethodService
2627
import com.osfans.trime.ime.dependency.InputScope
2728
import com.osfans.trime.ime.enums.PopupPosition
2829
import me.tatarka.inject.annotations.Inject
2930
import splitties.dimensions.dp
30-
import timber.log.Timber
3131

3232
@InputScope
3333
@Inject
3434
class CompositionPopupWindow(
3535
private val ctx: Context,
36+
private val service: TrimeInputMethodService,
3637
private val rime: RimeSession,
3738
private val theme: Theme,
3839
private val bar: QuickBar,
@@ -83,15 +84,15 @@ class CompositionPopupWindow(
8384

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

86-
private val mPopupRectF = RectF()
87+
private val anchorPosition = RectF()
8788
private val mPopupHandler = Handler(Looper.getMainLooper())
8889

8990
private val mPopupTimer =
9091
Runnable {
9192
if (bar.view.windowToken == null) return@Runnable
9293
bar.view.let { anchor ->
93-
var x = 0
94-
var y = 0
94+
var x: Int
95+
var y: Int
9596
val (_, anchorY) =
9697
intArrayOf(0, 0).also {
9798
anchor.getLocationInWindow(it)
@@ -105,49 +106,50 @@ class CompositionPopupWindow(
105106
val minY = anchor.dp(popupMargin)
106107
val maxX = anchor.width - selfWidth - minX
107108
val maxY = anchorY - selfHeight - minY
108-
if (isWinFixed() || !isCursorUpdated) {
109-
// setCandidatesViewShown(true);
110-
when (popupWindowPos) {
111-
PopupPosition.TOP_RIGHT -> {
112-
x = maxX
113-
y = minY
114-
}
115-
PopupPosition.TOP_LEFT -> {
116-
x = minX
117-
y = minY
118-
}
119-
PopupPosition.BOTTOM_RIGHT -> {
120-
x = maxX
121-
y = maxY
122-
}
123-
PopupPosition.DRAG -> {
124-
x = popupWindowX
125-
y = popupWindowY
126-
}
127-
PopupPosition.FIXED, PopupPosition.BOTTOM_LEFT -> {
128-
x = minX
129-
y = maxY
130-
}
131-
else -> {
132-
x = minX
133-
y = maxY
134-
}
109+
when (popupWindowPos) {
110+
PopupPosition.TOP_RIGHT -> {
111+
x = maxX
112+
y = minY
135113
}
136-
} else {
137-
// setCandidatesViewShown(false);
138-
when (popupWindowPos) {
139-
PopupPosition.LEFT, PopupPosition.LEFT_UP -> x = mPopupRectF.left.toInt()
140-
PopupPosition.RIGHT, PopupPosition.RIGHT_UP -> x = mPopupRectF.right.toInt()
141-
else -> Timber.wtf("UNREACHABLE BRANCH")
114+
PopupPosition.TOP_LEFT -> {
115+
x = minX
116+
y = minY
142117
}
143-
x = MathUtils.clamp(x, minX, maxX)
144-
when (popupWindowPos) {
145-
PopupPosition.LEFT, PopupPosition.RIGHT ->
146-
y = mPopupRectF.bottom.toInt() + popupMargin
147-
PopupPosition.LEFT_UP, PopupPosition.RIGHT_UP ->
148-
y = mPopupRectF.top.toInt() - selfHeight - popupMargin
149-
else -> Timber.wtf("UNREACHABLE BRANCH")
118+
PopupPosition.BOTTOM_RIGHT -> {
119+
x = maxX
120+
y = maxY
121+
}
122+
PopupPosition.DRAG -> {
123+
x = popupWindowX
124+
y = popupWindowY
125+
}
126+
PopupPosition.FIXED, PopupPosition.BOTTOM_LEFT -> {
127+
x = minX
128+
y = maxY
129+
}
130+
PopupPosition.LEFT -> {
131+
x = anchorPosition.left.toInt()
132+
y = anchorPosition.bottom.toInt() + popupMargin
133+
}
134+
PopupPosition.LEFT_UP -> {
135+
x = anchorPosition.left.toInt()
136+
y = anchorPosition.top.toInt() - selfHeight - popupMargin
137+
}
138+
PopupPosition.RIGHT -> {
139+
x = anchorPosition.right.toInt()
140+
y = anchorPosition.bottom.toInt() + popupMargin
141+
}
142+
PopupPosition.RIGHT_UP -> {
143+
x = anchorPosition.right.toInt()
144+
y = anchorPosition.top.toInt() - selfHeight - popupMargin
145+
}
146+
else -> {
147+
x = minX
148+
y = maxY
150149
}
150+
}
151+
if (!isWinFixed() || isCursorUpdated) {
152+
x = MathUtils.clamp(x, minX, maxX)
151153
y = MathUtils.clamp(y, minY, maxY)
152154
}
153155
if (!mPopupWindow.isShowing) {
@@ -179,6 +181,7 @@ class CompositionPopupWindow(
179181
fun hideCompositionView() {
180182
mPopupWindow.dismiss()
181183
mPopupHandler.removeCallbacks(mPopupTimer)
184+
decorLocationUpdated = false
182185
}
183186

184187
private fun updateCompositionView() {
@@ -188,35 +191,48 @@ class CompositionPopupWindow(
188191
mPopupHandler.post(mPopupTimer)
189192
}
190193

191-
fun updateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo) {
194+
private val decorLocation = floatArrayOf(0f, 0f)
195+
private var decorLocationUpdated = false
196+
197+
private fun updateDecorLocation() {
198+
val (dX, dY) =
199+
intArrayOf(0, 0).also {
200+
service.window.window!!
201+
.decorView
202+
.getLocationOnScreen(it)
203+
}
204+
decorLocation[0] = dX.toFloat()
205+
decorLocation[1] = dY.toFloat()
206+
decorLocationUpdated = true
207+
}
208+
209+
fun updateCursorAnchorInfo(info: CursorAnchorInfo) {
192210
if (!isWinFixed()) {
193-
val composingText = cursorAnchorInfo.composingText
211+
val bounds = info.getCharacterBounds(0)
194212
// update mPopupRectF
195-
if (composingText == null) {
213+
if (bounds == null) {
196214
// composing is disabled in target app or trime settings
197215
// use the position of the insertion marker instead
198-
mPopupRectF.top = cursorAnchorInfo.insertionMarkerTop
199-
mPopupRectF.left = cursorAnchorInfo.insertionMarkerHorizontal
200-
mPopupRectF.bottom = cursorAnchorInfo.insertionMarkerBottom
201-
mPopupRectF.right = mPopupRectF.left
216+
anchorPosition.top = info.insertionMarkerTop
217+
anchorPosition.left = info.insertionMarkerHorizontal
218+
anchorPosition.bottom = info.insertionMarkerBottom
219+
anchorPosition.right = info.insertionMarkerHorizontal
202220
} else {
203-
val startPos: Int = cursorAnchorInfo.composingTextStart
204-
val endPos = startPos + composingText.length - 1
205-
val startCharRectF = cursorAnchorInfo.getCharacterBounds(startPos)
206-
val endCharRectF = cursorAnchorInfo.getCharacterBounds(endPos)
207-
if (startCharRectF == null || endCharRectF == null) {
208-
// composing text has been changed, the next onUpdateCursorAnchorInfo is on the road
209-
// ignore this outdated update
210-
return
211-
}
212221
// for different writing system (e.g. right to left languages),
213222
// we have to calculate the correct RectF
214-
mPopupRectF.top = startCharRectF.top.coerceAtMost(endCharRectF.top)
215-
mPopupRectF.left = startCharRectF.left.coerceAtMost(endCharRectF.left)
216-
mPopupRectF.bottom = startCharRectF.bottom.coerceAtLeast(endCharRectF.bottom)
217-
mPopupRectF.right = startCharRectF.right.coerceAtLeast(endCharRectF.right)
223+
val horizontal = if (root.layoutDirection == View.LAYOUT_DIRECTION_RTL) bounds.right else bounds.left
224+
anchorPosition.top = bounds.top
225+
anchorPosition.left = horizontal
226+
anchorPosition.bottom = bounds.bottom
227+
anchorPosition.right = horizontal
228+
}
229+
info.matrix.mapRect(anchorPosition)
230+
// avoid calling `decorView.getLocationOnScreen` repeatedly
231+
if (!decorLocationUpdated) {
232+
updateDecorLocation()
218233
}
219-
cursorAnchorInfo.matrix.mapRect(mPopupRectF)
234+
val (dX, dY) = decorLocation
235+
anchorPosition.offset(-dX, -dY)
220236
}
221237
}
222238
}

0 commit comments

Comments
 (0)