diff --git a/app/src/main/cpp/androidfrontend/androidfrontend.cpp b/app/src/main/cpp/androidfrontend/androidfrontend.cpp index e6b7951a6..81fa44063 100644 --- a/app/src/main/cpp/androidfrontend/androidfrontend.cpp +++ b/app/src/main/cpp/androidfrontend/androidfrontend.cpp @@ -131,7 +131,7 @@ class AndroidInputContext : public InputContextV2 { frontend_->updatePagedCandidate(paged); } - bool selectCandidate(int idx) { + bool selectCandidateBulk(int idx) { const auto &list = inputPanel().candidateList(); if (!list) { return false; @@ -150,6 +150,20 @@ class AndroidInputContext : public InputContextV2 { return true; } + bool selectCandidatePaged(int idx) { + const auto &list = inputPanel().candidateList(); + if (!list) { + return false; + } + try { + list->candidate(idx).select(this); + } catch (const std::invalid_argument &e) { + FCITX_WARN() << "selectCandidate index out of range"; + return false; + } + return true; + } + std::vector getCandidates(const int offset, const int limit) { std::vector candidates; const auto &list = inputPanel().candidateList(); @@ -237,6 +251,27 @@ class AndroidInputContext : public InputContextV2 { } } + void offsetCandidatePage(int delta) { + if (delta == 0) { + return; + } + const auto &list = inputPanel().candidateList(); + if (!list) { + return; + } + const auto &pageable = list->toPageable(); + if (!pageable) { + return; + } + if (delta > 0 && pageable->hasNext()) { + pageable->next(); + updateUserInterface(UserInterfaceComponent::InputPanel); + } else if (delta < 0 && pageable->hasPrev()) { + pageable->prev(); + updateUserInterface(UserInterfaceComponent::InputPanel); + } + } + private: AndroidFrontend *frontend_; int uid_; @@ -328,7 +363,11 @@ void AndroidFrontend::releaseInputContext(const int uid) { bool AndroidFrontend::selectCandidate(int idx) { if (!activeIC_) return false; - return activeIC_->selectCandidate(idx); + if (pagingMode_) { + return activeIC_->selectCandidatePaged(idx); + } else { + return activeIC_->selectCandidateBulk(idx); + } } std::vector AndroidFrontend::getCandidateActions(const int idx) { @@ -419,6 +458,11 @@ void AndroidFrontend::updatePagedCandidate(const PagedCandidateEntity &paged) { pagedCandidateCallback(paged); } +void AndroidFrontend::offsetCandidatePage(int delta) { + if (!activeIC_) return; + activeIC_->offsetCandidatePage(delta); +} + void AndroidFrontend::setCommitStringCallback(const CommitStringCallback &callback) { commitStringCallback = callback; } diff --git a/app/src/main/cpp/androidfrontend/androidfrontend.h b/app/src/main/cpp/androidfrontend/androidfrontend.h index 9934ef8e7..9b9db800b 100644 --- a/app/src/main/cpp/androidfrontend/androidfrontend.h +++ b/app/src/main/cpp/androidfrontend/androidfrontend.h @@ -47,6 +47,7 @@ class AndroidFrontend : public AddonInstance { void deleteSurrounding(const int before, const int after); void showToast(const std::string &s); void setCandidatePagingMode(const int mode); + void offsetCandidatePage(int delta); void setCandidateListCallback(const CandidateListCallback &callback); void setCommitStringCallback(const CommitStringCallback &callback); void setPreeditCallback(const ClientPreeditCallback &callback); @@ -74,6 +75,7 @@ class AndroidFrontend : public AddonInstance { FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, triggerCandidateAction); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, showToast); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setCandidatePagingMode); + FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, offsetCandidatePage); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setCandidateListCallback); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setCommitStringCallback); FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setPreeditCallback); diff --git a/app/src/main/cpp/androidfrontend/androidfrontend_public.h b/app/src/main/cpp/androidfrontend/androidfrontend_public.h index d144c9873..69bcbbdd5 100644 --- a/app/src/main/cpp/androidfrontend/androidfrontend_public.h +++ b/app/src/main/cpp/androidfrontend/androidfrontend_public.h @@ -68,6 +68,9 @@ FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, showToast, FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setCandidatePagingMode, void(const int)) +FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, offsetCandidatePage, + void(int)) + FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setCandidateListCallback, void(const CandidateListCallback &)) diff --git a/app/src/main/cpp/native-lib.cpp b/app/src/main/cpp/native-lib.cpp index decf4ccf9..fb66925d2 100644 --- a/app/src/main/cpp/native-lib.cpp +++ b/app/src/main/cpp/native-lib.cpp @@ -429,6 +429,10 @@ class Fcitx { return p_frontend->call(mode); } + void offsetCandidatePage(int delta) { + return p_frontend->call(delta); + } + void save() { p_instance->save(); } @@ -1068,6 +1072,13 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_setFcitxCandidatePagingMode(JNIEnv *env Fcitx::Instance().setCandidatePagingMode(mode); } +extern "C" +JNIEXPORT void JNICALL +Java_org_fcitx_fcitx5_android_core_Fcitx_offsetFcitxCandidatePage(JNIEnv *env, jclass clazz, jint delta) { + RETURN_IF_NOT_RUNNING + Fcitx::Instance().offsetCandidatePage(delta); +} + extern "C" JNIEXPORT void JNICALL Java_org_fcitx_fcitx5_android_core_Fcitx_loopOnce(JNIEnv *env, jclass clazz) { diff --git a/app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt b/app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt index c92b96de7..54aa7a084 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt @@ -73,7 +73,13 @@ class Fcitx(private val context: Context) : FcitxAPI, FcitxLifecycleOwner { override suspend fun save() = withFcitxContext { saveFcitxState() } override suspend fun reloadConfig() = withFcitxContext { reloadFcitxConfig() } - override suspend fun sendKey(key: String, states: UInt, code: Int, up: Boolean, timestamp: Int) = + override suspend fun sendKey( + key: String, + states: UInt, + code: Int, + up: Boolean, + timestamp: Int + ) = withFcitxContext { sendKeyToFcitxString(key, states.toInt(), code, up, timestamp) } override suspend fun sendKey(c: Char, states: UInt, code: Int, up: Boolean, timestamp: Int) = @@ -82,7 +88,13 @@ class Fcitx(private val context: Context) : FcitxAPI, FcitxLifecycleOwner { override suspend fun sendKey(sym: Int, states: UInt, code: Int, up: Boolean, timestamp: Int) = withFcitxContext { sendKeySymToFcitx(sym, states.toInt(), code, up, timestamp) } - override suspend fun sendKey(sym: KeySym, states: KeyStates, code: Int, up: Boolean, timestamp: Int) = + override suspend fun sendKey( + sym: KeySym, + states: KeyStates, + code: Int, + up: Boolean, + timestamp: Int + ) = withFcitxContext { sendKeySymToFcitx(sym.sym, states.toInt(), code, up, timestamp) } override suspend fun select(idx: Int): Boolean = withFcitxContext { selectCandidate(idx) } @@ -172,6 +184,9 @@ class Fcitx(private val context: Context) : FcitxAPI, FcitxLifecycleOwner { override suspend fun setCandidatePagingMode(mode: Int) = withFcitxContext { setFcitxCandidatePagingMode(mode) } + override suspend fun offsetCandidatePage(delta: Int) = + withFcitxContext { offsetFcitxCandidatePage(delta) } + init { if (lifecycle.currentState != FcitxLifecycle.State.STOPPED) throw IllegalAccessException("Fcitx5 has already been created!") @@ -233,7 +248,13 @@ class Fcitx(private val context: Context) : FcitxAPI, FcitxLifecycleOwner { external fun reloadFcitxConfig() @JvmStatic - external fun sendKeyToFcitxString(key: String, state: Int, code: Int, up: Boolean, timestamp: Int) + external fun sendKeyToFcitxString( + key: String, + state: Int, + code: Int, + up: Boolean, + timestamp: Int + ) @JvmStatic external fun sendKeyToFcitxChar(c: Char, state: Int, code: Int, up: Boolean, timestamp: Int) @@ -343,6 +364,9 @@ class Fcitx(private val context: Context) : FcitxAPI, FcitxLifecycleOwner { @JvmStatic external fun setFcitxCandidatePagingMode(mode: Int) + @JvmStatic + external fun offsetFcitxCandidatePage(delta: Int) + @JvmStatic external fun loopOnce() diff --git a/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt b/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt index 4c3e55c8f..b6fd8ce8e 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt @@ -103,5 +103,6 @@ interface FcitxAPI { suspend fun triggerCandidateAction(idx: Int, actionIdx: Int) suspend fun setCandidatePagingMode(mode: Int) + suspend fun offsetCandidatePage(delta: Int) } \ No newline at end of file diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt index 77b1475cc..9a47af029 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt @@ -16,6 +16,7 @@ import androidx.annotation.Size import org.fcitx.fcitx5.android.R import org.fcitx.fcitx5.android.core.FcitxEvent import org.fcitx.fcitx5.android.daemon.FcitxConnection +import org.fcitx.fcitx5.android.daemon.launchOnReady import org.fcitx.fcitx5.android.data.prefs.AppPrefs import org.fcitx.fcitx5.android.data.theme.Theme import org.fcitx.fcitx5.android.input.candidates.floating.PagedCandidatesUi @@ -73,6 +74,8 @@ class CandidatesView( true } + private val touchEventReceiverWindow = TouchEventReceiverWindow(this) + private val setupTextView: TextView.() -> Unit = { textSize = fontSize.toFloat() val v = dp(itemPaddingVertical) @@ -82,7 +85,11 @@ class CandidatesView( private val preeditUi = PreeditUi(ctx, theme, setupTextView) - private val candidatesUi = PagedCandidatesUi(ctx, theme, setupTextView).apply { + private val candidatesUi = PagedCandidatesUi(ctx, theme, setupTextView, + onCandidateClick = { index -> fcitx.launchOnReady { it.select(index) } }, + onPrevPage = { fcitx.launchOnReady { it.offsetCandidatePage(-1) } }, + onNextPage = { fcitx.launchOnReady { it.offsetCandidatePage(1) } } + ).apply { root.viewTreeObserver.addOnGlobalLayoutListener(layoutListener) } @@ -116,6 +123,7 @@ class CandidatesView( candidatesUi.update(paged, orientation) visibility = VISIBLE } else { + touchEventReceiverWindow.dismiss() visibility = GONE } } @@ -141,6 +149,8 @@ class CandidatesView( } translationY = if (bottom + selfHeight > parentHeight - bottomInsets) top - selfHeight else bottom + // update touchEventReceiverWindow's position after CandidatesView's + touchEventReceiverWindow.showup() shouldUpdatePosition = false } @@ -193,6 +203,7 @@ class CandidatesView( override fun onDetachedFromWindow() { viewTreeObserver.removeOnPreDrawListener(preDrawListener) candidatesUi.root.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener) + touchEventReceiverWindow.dismiss() super.onDetachedFromWindow() } } diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/TouchEventReceiverWindow.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/TouchEventReceiverWindow.kt new file mode 100644 index 000000000..8a5e30d7c --- /dev/null +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/TouchEventReceiverWindow.kt @@ -0,0 +1,53 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * SPDX-FileCopyrightText: Copyright 2025 Fcitx5 for Android Contributors + */ + +package org.fcitx.fcitx5.android.input + +import android.annotation.SuppressLint +import android.view.Gravity +import android.view.MotionEvent +import android.view.View +import android.widget.PopupWindow + +class TouchEventReceiverWindow( + private val contentView: View +) { + private val ctx = contentView.context + + private val window = PopupWindow(object : View(ctx) { + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent?): Boolean { + return contentView.dispatchTouchEvent(event) + } + }).apply { + // disable animation + animationStyle = 0 + } + + private var isWindowShowing = false + + private val cachedLocation = intArrayOf(0, 0) + + fun showup() { + isWindowShowing = true + val (x, y) = cachedLocation.also { contentView.getLocationInWindow(it) } + val width = contentView.width + val height = contentView.height + if (window.isShowing) { + window.update(x, y, width, height) + } else { + window.width = width + window.height = height + window.showAtLocation(contentView, Gravity.NO_GRAVITY, x, y) + } + } + + fun dismiss() { + if (isWindowShowing) { + isWindowShowing = false + window.dismiss() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/floating/PagedCandidatesUi.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/floating/PagedCandidatesUi.kt index 56b2c9115..6fe623387 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/floating/PagedCandidatesUi.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/floating/PagedCandidatesUi.kt @@ -7,6 +7,7 @@ package org.fcitx.fcitx5.android.input.candidates.floating import android.annotation.SuppressLint import android.content.Context +import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.core.view.updateLayoutParams @@ -24,7 +25,10 @@ import splitties.views.dsl.recyclerview.recyclerView class PagedCandidatesUi( override val ctx: Context, val theme: Theme, - private val setupTextView: TextView.() -> Unit + private val setupTextView: TextView.() -> Unit, + private val onCandidateClick: (Int) -> Unit, + private val onPrevPage: () -> Unit, + private val onNextPage: () -> Unit ) : Ui { private var data = FcitxEvent.PagedCandidateEvent.Data.Empty @@ -50,6 +54,12 @@ class PagedCandidatesUi( ui.root.layoutParams = FlexboxLayoutManager.LayoutParams(wrap, wrap).apply { flexGrow = 1f } + ui.prevIcon.setOnClickListener { + onPrevPage.invoke() + } + ui.nextIcon.setOnClickListener { + onNextPage.invoke() + } } } } @@ -59,6 +69,9 @@ class PagedCandidatesUi( is UiHolder.Candidate -> { val candidate = data.candidates[position] holder.ui.update(candidate, active = position == data.cursorIndex) + holder.ui.root.setOnClickListener { + onCandidateClick.invoke(position) + } } is UiHolder.Pagination -> { holder.ui.update(data) @@ -79,6 +92,7 @@ class PagedCandidatesUi( isFocusable = false adapter = candidatesAdapter layoutManager = candidatesLayoutManager + overScrollMode = View.OVER_SCROLL_NEVER } @SuppressLint("NotifyDataSetChanged") diff --git a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/floating/PaginationUi.kt b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/floating/PaginationUi.kt index 665552c56..44bc9b7ce 100644 --- a/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/floating/PaginationUi.kt +++ b/app/src/main/java/org/fcitx/fcitx5/android/input/candidates/floating/PaginationUi.kt @@ -31,10 +31,11 @@ class PaginationUi(override val ctx: Context, val theme: Theme) : Ui { imageTintList = ColorStateList.valueOf(theme.keyTextColor) imageDrawable = drawable(icon) scaleType = ImageView.ScaleType.CENTER_CROP + isClickable = true } - private val prevIcon = createIcon(R.drawable.ic_baseline_arrow_prev_24) - private val nextIcon = createIcon(R.drawable.ic_baseline_arrow_next_24) + val prevIcon = createIcon(R.drawable.ic_baseline_arrow_prev_24) + val nextIcon = createIcon(R.drawable.ic_baseline_arrow_next_24) private val disabledAlpha = styledFloat(android.R.attr.disabledAlpha) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy