From 4e78fba1191022701d380556a563aa9d4fbe2005 Mon Sep 17 00:00:00 2001 From: WhiredPlanck Date: Mon, 2 Dec 2024 01:37:42 +0800 Subject: [PATCH 1/3] feat: restore horizontal padding for candidate item in candidate window --- .../trime/ime/candidates/popup/LabeledCandidateItemUi.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/osfans/trime/ime/candidates/popup/LabeledCandidateItemUi.kt b/app/src/main/java/com/osfans/trime/ime/candidates/popup/LabeledCandidateItemUi.kt index 019ccd8dea..54587029de 100644 --- a/app/src/main/java/com/osfans/trime/ime/candidates/popup/LabeledCandidateItemUi.kt +++ b/app/src/main/java/com/osfans/trime/ime/candidates/popup/LabeledCandidateItemUi.kt @@ -18,9 +18,11 @@ import com.osfans.trime.data.theme.ColorManager import com.osfans.trime.data.theme.FontManager import com.osfans.trime.data.theme.Theme import com.osfans.trime.util.sp +import splitties.dimensions.dp import splitties.views.backgroundColor import splitties.views.dsl.core.Ui import splitties.views.dsl.core.textView +import splitties.views.horizontalPadding class LabeledCandidateItemUi( override val ctx: Context, @@ -53,7 +55,10 @@ class LabeledCandidateItemUi( private val highlightCandidateTextColor = ColorManager.getColor("hilited_candidate_text_color")!! private val highlightBackColor = ColorManager.getColor("hilited_back_color")!! - override val root = textView() + override val root = + textView { + horizontalPadding = dp(theme.generalStyle.candidatePadding) + } fun update( candidate: RimeProto.Candidate, From ecbb20b37149dd1e994491ae2915ddacd4bdc86f Mon Sep 17 00:00:00 2001 From: WhiredPlanck Date: Wed, 4 Dec 2024 01:07:06 +0800 Subject: [PATCH 2/3] feat: restore vertical layout in candidate window refactor: update rime options on start input view --- .../ime/candidates/popup/PagedCandidatesUi.kt | 21 +++++++++++++++---- .../trime/ime/composition/CandidatesView.kt | 7 +++++-- .../trime/ime/core/TrimeInputMethodService.kt | 2 +- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/osfans/trime/ime/candidates/popup/PagedCandidatesUi.kt b/app/src/main/java/com/osfans/trime/ime/candidates/popup/PagedCandidatesUi.kt index b378bfe7cd..fd105f29f1 100644 --- a/app/src/main/java/com/osfans/trime/ime/candidates/popup/PagedCandidatesUi.kt +++ b/app/src/main/java/com/osfans/trime/ime/candidates/popup/PagedCandidatesUi.kt @@ -26,6 +26,8 @@ class PagedCandidatesUi( ) : Ui { private var menu = RimeProto.Context.Menu() + private var isHorizontal = true + sealed class UiHolder( open val ui: Ui, ) : RecyclerView.ViewHolder(ui.root) { @@ -78,7 +80,7 @@ class PagedCandidatesUi( is UiHolder.Pagination -> { holder.ui.update(menu) holder.ui.root.updateLayoutParams { - alignSelf = AlignItems.CENTER + alignSelf = if (isHorizontal) AlignItems.CENTER else AlignItems.STRETCH } } } @@ -92,8 +94,6 @@ class PagedCandidatesUi( private val candidatesLayoutManager = FlexboxLayoutManager(ctx).apply { flexWrap = FlexWrap.WRAP - flexDirection = FlexDirection.ROW - alignItems = AlignItems.BASELINE } override val root = @@ -106,8 +106,21 @@ class PagedCandidatesUi( layoutManager = candidatesLayoutManager } - fun update(menu: RimeProto.Context.Menu) { + fun update( + menu: RimeProto.Context.Menu, + isHorizontal: Boolean, + ) { this.menu = menu + this.isHorizontal = isHorizontal + candidatesLayoutManager.apply { + if (isHorizontal) { + flexDirection = FlexDirection.ROW + alignItems = AlignItems.BASELINE + } else { + flexDirection = FlexDirection.COLUMN + alignItems = AlignItems.STRETCH + } + } candidatesAdapter.submitList(menu.candidates.toList()) } } diff --git a/app/src/main/java/com/osfans/trime/ime/composition/CandidatesView.kt b/app/src/main/java/com/osfans/trime/ime/composition/CandidatesView.kt index 4b583347f7..42a6de84bc 100644 --- a/app/src/main/java/com/osfans/trime/ime/composition/CandidatesView.kt +++ b/app/src/main/java/com/osfans/trime/ime/composition/CandidatesView.kt @@ -66,6 +66,9 @@ class CandidatesView( private fun updateUi() { if (evaluateVisibility()) { preeditUi.update(inputComposition) + // if CandidatesView can be shown, rime engine is ready most of the time, + // so it should be safety to get option immediately + val isHorizontalLayout = rime.run { getRuntimeOption("_horizontal") } when (candidatesMode) { PopupCandidatesMode.CURRENT_PAGE -> { candidatesUi.root.let { @@ -73,14 +76,14 @@ class CandidatesView( it.visibility = View.VISIBLE } } - candidatesUi.update(menu) + candidatesUi.update(menu, isHorizontalLayout) } PopupCandidatesMode.PREEDIT_ONLY -> { candidatesUi.root.let { if (it.visibility != View.GONE) { it.visibility = View.GONE - candidatesUi.update(RimeProto.Context.Menu()) + candidatesUi.update(RimeProto.Context.Menu(), isHorizontalLayout) } } } diff --git a/app/src/main/java/com/osfans/trime/ime/core/TrimeInputMethodService.kt b/app/src/main/java/com/osfans/trime/ime/core/TrimeInputMethodService.kt index ccf1ee4aec..97462316c3 100644 --- a/app/src/main/java/com/osfans/trime/ime/core/TrimeInputMethodService.kt +++ b/app/src/main/java/com/osfans/trime/ime/core/TrimeInputMethodService.kt @@ -202,7 +202,6 @@ open class TrimeInputMethodService : LifecycleInputMethodService() { 2 -> Locale(latinLocale[0], latinLocale[1]) else -> Locale.US } - updateRimeOption(this) Timber.d("Trime.onCreate completed") } } catch (e: Exception) { @@ -428,6 +427,7 @@ open class TrimeInputMethodService : LifecycleInputMethodService() { ) { Timber.d("onStartInputView: restarting=%s", restarting) postRimeJob { + updateRimeOption(this) InputFeedbackManager.loadSoundEffects(this@TrimeInputMethodService) InputFeedbackManager.resetPlayProgress() isComposable = From 7c09acd7bc5a5b5c84c37d2db7ef6dfb432f11ee Mon Sep 17 00:00:00 2001 From: WhiredPlanck Date: Mon, 2 Dec 2024 03:19:25 +0800 Subject: [PATCH 3/3] refactor: improve cursor following of candidate window --- .../ime/composition/CompositionPopupWindow.kt | 146 ++++++++++-------- 1 file changed, 81 insertions(+), 65 deletions(-) diff --git a/app/src/main/java/com/osfans/trime/ime/composition/CompositionPopupWindow.kt b/app/src/main/java/com/osfans/trime/ime/composition/CompositionPopupWindow.kt index a25bdbcc09..e2f25a3706 100644 --- a/app/src/main/java/com/osfans/trime/ime/composition/CompositionPopupWindow.kt +++ b/app/src/main/java/com/osfans/trime/ime/composition/CompositionPopupWindow.kt @@ -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, @@ -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) @@ -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) { @@ -179,6 +181,7 @@ class CompositionPopupWindow( fun hideCompositionView() { mPopupWindow.dismiss() mPopupHandler.removeCallbacks(mPopupTimer) + decorLocationUpdated = false } private fun updateCompositionView() { @@ -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) } } }