Skip to content

Commit bbfaf5f

Browse files
WhiredPlanckrocka
authored andcommitted
Make CandidatesView touchable (fcitx5-android#680)
* Make CandidatesView touchable * Update touchEventReceiverWindow's position after CandidatesView's * Disable animations * Make pagination icons clickable * Fix selecting paged candidate --------- Co-authored-by: Rocka <i@rocka.me>
1 parent ada02ab commit bbfaf5f

File tree

10 files changed

+173
-9
lines changed

10 files changed

+173
-9
lines changed

app/src/main/cpp/androidfrontend/androidfrontend.cpp

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ class AndroidInputContext : public InputContextV2 {
131131
frontend_->updatePagedCandidate(paged);
132132
}
133133

134-
bool selectCandidate(int idx) {
134+
bool selectCandidateBulk(int idx) {
135135
const auto &list = inputPanel().candidateList();
136136
if (!list) {
137137
return false;
@@ -150,6 +150,20 @@ class AndroidInputContext : public InputContextV2 {
150150
return true;
151151
}
152152

153+
bool selectCandidatePaged(int idx) {
154+
const auto &list = inputPanel().candidateList();
155+
if (!list) {
156+
return false;
157+
}
158+
try {
159+
list->candidate(idx).select(this);
160+
} catch (const std::invalid_argument &e) {
161+
FCITX_WARN() << "selectCandidate index out of range";
162+
return false;
163+
}
164+
return true;
165+
}
166+
153167
std::vector<std::string> getCandidates(const int offset, const int limit) {
154168
std::vector<std::string> candidates;
155169
const auto &list = inputPanel().candidateList();
@@ -237,6 +251,27 @@ class AndroidInputContext : public InputContextV2 {
237251
}
238252
}
239253

254+
void offsetCandidatePage(int delta) {
255+
if (delta == 0) {
256+
return;
257+
}
258+
const auto &list = inputPanel().candidateList();
259+
if (!list) {
260+
return;
261+
}
262+
const auto &pageable = list->toPageable();
263+
if (!pageable) {
264+
return;
265+
}
266+
if (delta > 0 && pageable->hasNext()) {
267+
pageable->next();
268+
updateUserInterface(UserInterfaceComponent::InputPanel);
269+
} else if (delta < 0 && pageable->hasPrev()) {
270+
pageable->prev();
271+
updateUserInterface(UserInterfaceComponent::InputPanel);
272+
}
273+
}
274+
240275
private:
241276
AndroidFrontend *frontend_;
242277
int uid_;
@@ -328,7 +363,11 @@ void AndroidFrontend::releaseInputContext(const int uid) {
328363

329364
bool AndroidFrontend::selectCandidate(int idx) {
330365
if (!activeIC_) return false;
331-
return activeIC_->selectCandidate(idx);
366+
if (pagingMode_) {
367+
return activeIC_->selectCandidatePaged(idx);
368+
} else {
369+
return activeIC_->selectCandidateBulk(idx);
370+
}
332371
}
333372

334373
bool AndroidFrontend::sendHardShift() {
@@ -431,6 +470,11 @@ void AndroidFrontend::updatePagedCandidate(const PagedCandidateEntity &paged) {
431470
pagedCandidateCallback(paged);
432471
}
433472

473+
void AndroidFrontend::offsetCandidatePage(int delta) {
474+
if (!activeIC_) return;
475+
activeIC_->offsetCandidatePage(delta);
476+
}
477+
434478
void AndroidFrontend::setCommitStringCallback(const CommitStringCallback &callback) {
435479
commitStringCallback = callback;
436480
}

app/src/main/cpp/androidfrontend/androidfrontend.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class AndroidFrontend : public AddonInstance {
4747
void deleteSurrounding(const int before, const int after);
4848
void showToast(const std::string &s);
4949
void setCandidatePagingMode(const int mode);
50+
void offsetCandidatePage(int delta);
5051
void setCandidateListCallback(const CandidateListCallback &callback);
5152
void setCommitStringCallback(const CommitStringCallback &callback);
5253
void setPreeditCallback(const ClientPreeditCallback &callback);
@@ -75,6 +76,7 @@ class AndroidFrontend : public AddonInstance {
7576
FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, triggerCandidateAction);
7677
FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, showToast);
7778
FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setCandidatePagingMode);
79+
FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, offsetCandidatePage);
7880
FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setCandidateListCallback);
7981
FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setCommitStringCallback);
8082
FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setPreeditCallback);

app/src/main/cpp/androidfrontend/androidfrontend_public.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, showToast,
6868
FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setCandidatePagingMode,
6969
void(const int))
7070

71+
FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, offsetCandidatePage,
72+
void(int))
73+
7174
FCITX_ADDON_DECLARE_FUNCTION(AndroidFrontend, setCandidateListCallback,
7275
void(const CandidateListCallback &))
7376

app/src/main/cpp/native-lib.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,10 @@ class Fcitx {
433433
return p_frontend->call<fcitx::IAndroidFrontend::setCandidatePagingMode>(mode);
434434
}
435435

436+
void offsetCandidatePage(int delta) {
437+
return p_frontend->call<fcitx::IAndroidFrontend::offsetCandidatePage>(delta);
438+
}
439+
436440
void save() {
437441
p_instance->save();
438442
}
@@ -1080,6 +1084,13 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_setFcitxCandidatePagingMode(JNIEnv *env
10801084
Fcitx::Instance().setCandidatePagingMode(mode);
10811085
}
10821086

1087+
extern "C"
1088+
JNIEXPORT void JNICALL
1089+
Java_org_fcitx_fcitx5_android_core_Fcitx_offsetFcitxCandidatePage(JNIEnv *env, jclass clazz, jint delta) {
1090+
RETURN_IF_NOT_RUNNING
1091+
Fcitx::Instance().offsetCandidatePage(delta);
1092+
}
1093+
10831094
extern "C"
10841095
JNIEXPORT void JNICALL
10851096
Java_org_fcitx_fcitx5_android_core_Fcitx_loopOnce(JNIEnv *env, jclass clazz) {

app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,13 @@ class Fcitx(private val context: Context) : FcitxAPI, FcitxLifecycleOwner {
7373
override suspend fun save() = withFcitxContext { saveFcitxState() }
7474
override suspend fun reloadConfig() = withFcitxContext { reloadFcitxConfig() }
7575

76-
override suspend fun sendKey(key: String, states: UInt, code: Int, up: Boolean, timestamp: Int) =
76+
override suspend fun sendKey(
77+
key: String,
78+
states: UInt,
79+
code: Int,
80+
up: Boolean,
81+
timestamp: Int
82+
) =
7783
withFcitxContext { sendKeyToFcitxString(key, states.toInt(), code, up, timestamp) }
7884

7985
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 {
8288
override suspend fun sendKey(sym: Int, states: UInt, code: Int, up: Boolean, timestamp: Int) =
8389
withFcitxContext { sendKeySymToFcitx(sym, states.toInt(), code, up, timestamp) }
8490

85-
override suspend fun sendKey(sym: KeySym, states: KeyStates, code: Int, up: Boolean, timestamp: Int) =
91+
override suspend fun sendKey(
92+
sym: KeySym,
93+
states: KeyStates,
94+
code: Int,
95+
up: Boolean,
96+
timestamp: Int
97+
) =
8698
withFcitxContext { sendKeySymToFcitx(sym.sym, states.toInt(), code, up, timestamp) }
8799

88100
override suspend fun select(idx: Int): Boolean = withFcitxContext { selectCandidate(idx) }
@@ -173,6 +185,9 @@ class Fcitx(private val context: Context) : FcitxAPI, FcitxLifecycleOwner {
173185
override suspend fun setCandidatePagingMode(mode: Int) =
174186
withFcitxContext { setFcitxCandidatePagingMode(mode) }
175187

188+
override suspend fun offsetCandidatePage(delta: Int) =
189+
withFcitxContext { offsetFcitxCandidatePage(delta) }
190+
176191
init {
177192
if (lifecycle.currentState != FcitxLifecycle.State.STOPPED)
178193
throw IllegalAccessException("Fcitx5 has already been created!")
@@ -234,7 +249,13 @@ class Fcitx(private val context: Context) : FcitxAPI, FcitxLifecycleOwner {
234249
external fun reloadFcitxConfig()
235250

236251
@JvmStatic
237-
external fun sendKeyToFcitxString(key: String, state: Int, code: Int, up: Boolean, timestamp: Int)
252+
external fun sendKeyToFcitxString(
253+
key: String,
254+
state: Int,
255+
code: Int,
256+
up: Boolean,
257+
timestamp: Int
258+
)
238259

239260
@JvmStatic
240261
external fun sendKeyToFcitxChar(c: Char, state: Int, code: Int, up: Boolean, timestamp: Int)
@@ -347,6 +368,9 @@ class Fcitx(private val context: Context) : FcitxAPI, FcitxLifecycleOwner {
347368
@JvmStatic
348369
external fun setFcitxCandidatePagingMode(mode: Int)
349370

371+
@JvmStatic
372+
external fun offsetFcitxCandidatePage(delta: Int)
373+
350374
@JvmStatic
351375
external fun loopOnce()
352376

app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,6 @@ interface FcitxAPI {
103103
suspend fun getCandidateActions(idx: Int): Array<CandidateAction>
104104
suspend fun triggerCandidateAction(idx: Int, actionIdx: Int)
105105
suspend fun setCandidatePagingMode(mode: Int)
106+
suspend fun offsetCandidatePage(delta: Int)
106107

107108
}

app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import androidx.annotation.Size
1717
import org.fcitx.fcitx5.android.R
1818
import org.fcitx.fcitx5.android.core.FcitxEvent
1919
import org.fcitx.fcitx5.android.daemon.FcitxConnection
20+
import org.fcitx.fcitx5.android.daemon.launchOnReady
2021
import org.fcitx.fcitx5.android.data.prefs.AppPrefs
2122
import org.fcitx.fcitx5.android.data.theme.Theme
2223
import org.fcitx.fcitx5.android.input.candidates.floating.PagedCandidatesUi
@@ -74,6 +75,8 @@ class CandidatesView(
7475
true
7576
}
7677

78+
private val touchEventReceiverWindow = TouchEventReceiverWindow(this)
79+
7780
private val setupTextView: TextView.() -> Unit = {
7881
textSize = fontSize.toFloat()
7982
val v = dp(itemPaddingVertical)
@@ -83,7 +86,11 @@ class CandidatesView(
8386

8487
private val preeditUi = PreeditUi(ctx, theme, setupTextView)
8588

86-
private val candidatesUi = PagedCandidatesUi(ctx, theme, setupTextView).apply {
89+
private val candidatesUi = PagedCandidatesUi(ctx, theme, setupTextView,
90+
onCandidateClick = { index -> fcitx.launchOnReady { it.select(index) } },
91+
onPrevPage = { fcitx.launchOnReady { it.offsetCandidatePage(-1) } },
92+
onNextPage = { fcitx.launchOnReady { it.offsetCandidatePage(1) } }
93+
).apply {
8794
root.viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
8895
}
8996

@@ -117,6 +124,7 @@ class CandidatesView(
117124
candidatesUi.update(paged, orientation)
118125
visibility = VISIBLE
119126
} else {
127+
touchEventReceiverWindow.dismiss()
120128
visibility = GONE
121129
}
122130
}
@@ -142,6 +150,8 @@ class CandidatesView(
142150
}
143151
translationY =
144152
if (bottom + selfHeight > parentHeight - bottomInsets) top - selfHeight else bottom
153+
// update touchEventReceiverWindow's position after CandidatesView's
154+
touchEventReceiverWindow.showup()
145155
shouldUpdatePosition = false
146156
}
147157

@@ -200,6 +210,7 @@ class CandidatesView(
200210
override fun onDetachedFromWindow() {
201211
viewTreeObserver.removeOnPreDrawListener(preDrawListener)
202212
candidatesUi.root.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
213+
touchEventReceiverWindow.dismiss()
203214
super.onDetachedFromWindow()
204215
}
205216
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* SPDX-FileCopyrightText: Copyright 2025 Fcitx5 for Android Contributors
4+
*/
5+
6+
package org.fcitx.fcitx5.android.input
7+
8+
import android.annotation.SuppressLint
9+
import android.view.Gravity
10+
import android.view.MotionEvent
11+
import android.view.View
12+
import android.widget.PopupWindow
13+
14+
class TouchEventReceiverWindow(
15+
private val contentView: View
16+
) {
17+
private val ctx = contentView.context
18+
19+
private val window = PopupWindow(object : View(ctx) {
20+
@SuppressLint("ClickableViewAccessibility")
21+
override fun onTouchEvent(event: MotionEvent?): Boolean {
22+
return contentView.dispatchTouchEvent(event)
23+
}
24+
}).apply {
25+
// disable animation
26+
animationStyle = 0
27+
}
28+
29+
private var isWindowShowing = false
30+
31+
private val cachedLocation = intArrayOf(0, 0)
32+
33+
fun showup() {
34+
isWindowShowing = true
35+
val (x, y) = cachedLocation.also { contentView.getLocationInWindow(it) }
36+
val width = contentView.width
37+
val height = contentView.height
38+
if (window.isShowing) {
39+
window.update(x, y, width, height)
40+
} else {
41+
window.width = width
42+
window.height = height
43+
window.showAtLocation(contentView, Gravity.NO_GRAVITY, x, y)
44+
}
45+
}
46+
47+
fun dismiss() {
48+
if (isWindowShowing) {
49+
isWindowShowing = false
50+
window.dismiss()
51+
}
52+
}
53+
}

app/src/main/java/org/fcitx/fcitx5/android/input/candidates/floating/PagedCandidatesUi.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package org.fcitx.fcitx5.android.input.candidates.floating
77

88
import android.annotation.SuppressLint
99
import android.content.Context
10+
import android.view.View
1011
import android.view.ViewGroup
1112
import android.widget.TextView
1213
import androidx.core.view.updateLayoutParams
@@ -24,7 +25,10 @@ import splitties.views.dsl.recyclerview.recyclerView
2425
class PagedCandidatesUi(
2526
override val ctx: Context,
2627
val theme: Theme,
27-
private val setupTextView: TextView.() -> Unit
28+
private val setupTextView: TextView.() -> Unit,
29+
private val onCandidateClick: (Int) -> Unit,
30+
private val onPrevPage: () -> Unit,
31+
private val onNextPage: () -> Unit
2832
) : Ui {
2933

3034
private var data = FcitxEvent.PagedCandidateEvent.Data.Empty
@@ -50,6 +54,12 @@ class PagedCandidatesUi(
5054
ui.root.layoutParams = FlexboxLayoutManager.LayoutParams(wrap, wrap).apply {
5155
flexGrow = 1f
5256
}
57+
ui.prevIcon.setOnClickListener {
58+
onPrevPage.invoke()
59+
}
60+
ui.nextIcon.setOnClickListener {
61+
onNextPage.invoke()
62+
}
5363
}
5464
}
5565
}
@@ -59,6 +69,9 @@ class PagedCandidatesUi(
5969
is UiHolder.Candidate -> {
6070
val candidate = data.candidates[position]
6171
holder.ui.update(candidate, active = position == data.cursorIndex)
72+
holder.ui.root.setOnClickListener {
73+
onCandidateClick.invoke(position)
74+
}
6275
}
6376
is UiHolder.Pagination -> {
6477
holder.ui.update(data)
@@ -79,6 +92,7 @@ class PagedCandidatesUi(
7992
isFocusable = false
8093
adapter = candidatesAdapter
8194
layoutManager = candidatesLayoutManager
95+
overScrollMode = View.OVER_SCROLL_NEVER
8296
}
8397

8498
@SuppressLint("NotifyDataSetChanged")

app/src/main/java/org/fcitx/fcitx5/android/input/candidates/floating/PaginationUi.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ class PaginationUi(override val ctx: Context, val theme: Theme) : Ui {
3131
imageTintList = ColorStateList.valueOf(theme.keyTextColor)
3232
imageDrawable = drawable(icon)
3333
scaleType = ImageView.ScaleType.CENTER_CROP
34+
isClickable = true
3435
}
3536

36-
private val prevIcon = createIcon(R.drawable.ic_baseline_arrow_prev_24)
37-
private val nextIcon = createIcon(R.drawable.ic_baseline_arrow_next_24)
37+
val prevIcon = createIcon(R.drawable.ic_baseline_arrow_prev_24)
38+
val nextIcon = createIcon(R.drawable.ic_baseline_arrow_next_24)
3839

3940
private val disabledAlpha = styledFloat(android.R.attr.disabledAlpha)
4041

0 commit comments

Comments
 (0)
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