Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Candidate window 20241204 #1523

Merged
merged 3 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -78,7 +80,7 @@ class PagedCandidatesUi(
is UiHolder.Pagination -> {
holder.ui.update(menu)
holder.ui.root.updateLayoutParams<FlexboxLayoutManager.LayoutParams> {
alignSelf = AlignItems.CENTER
alignSelf = if (isHorizontal) AlignItems.CENTER else AlignItems.STRETCH
}
}
}
Expand All @@ -92,8 +94,6 @@ class PagedCandidatesUi(
private val candidatesLayoutManager =
FlexboxLayoutManager(ctx).apply {
flexWrap = FlexWrap.WRAP
flexDirection = FlexDirection.ROW
alignItems = AlignItems.BASELINE
}

override val root =
Expand All @@ -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())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,24 @@ 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 {
if (it.visibility == View.GONE) {
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)
}
}
}
Expand Down
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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -428,6 +427,7 @@ open class TrimeInputMethodService : LifecycleInputMethodService() {
) {
Timber.d("onStartInputView: restarting=%s", restarting)
postRimeJob {
updateRimeOption(this)
InputFeedbackManager.loadSoundEffects(this@TrimeInputMethodService)
InputFeedbackManager.resetPlayProgress()
isComposable =
Expand Down
Loading