Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit ad08268

Browse filesBrowse files
WhiredPlanckrocka
andauthored
Make CandidatesView touchable (#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 583b562 commit ad08268
Copy full SHA for ad08268

File tree

Expand file treeCollapse file tree

10 files changed

+173
-9
lines changed
Filter options
Expand file treeCollapse file tree

10 files changed

+173
-9
lines changed

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

Copy file name to clipboardExpand all lines: app/src/main/cpp/androidfrontend/androidfrontend.cpp
+46-2Lines 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
std::vector<CandidateAction> AndroidFrontend::getCandidateActions(const int idx) {
@@ -419,6 +458,11 @@ void AndroidFrontend::updatePagedCandidate(const PagedCandidateEntity &paged) {
419458
pagedCandidateCallback(paged);
420459
}
421460

461+
void AndroidFrontend::offsetCandidatePage(int delta) {
462+
if (!activeIC_) return;
463+
activeIC_->offsetCandidatePage(delta);
464+
}
465+
422466
void AndroidFrontend::setCommitStringCallback(const CommitStringCallback &callback) {
423467
commitStringCallback = callback;
424468
}

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

Copy file name to clipboardExpand all lines: app/src/main/cpp/androidfrontend/androidfrontend.h
+2Lines 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);
@@ -74,6 +75,7 @@ class AndroidFrontend : public AddonInstance {
7475
FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, triggerCandidateAction);
7576
FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, showToast);
7677
FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setCandidatePagingMode);
78+
FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, offsetCandidatePage);
7779
FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setCandidateListCallback);
7880
FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setCommitStringCallback);
7981
FCITX_ADDON_EXPORT_FUNCTION(AndroidFrontend, setPreeditCallback);

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

Copy file name to clipboardExpand all lines: app/src/main/cpp/androidfrontend/androidfrontend_public.h
+3Lines 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

Copy file name to clipboardExpand all lines: app/src/main/cpp/native-lib.cpp
+11Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,10 @@ class Fcitx {
429429
return p_frontend->call<fcitx::IAndroidFrontend::setCandidatePagingMode>(mode);
430430
}
431431

432+
void offsetCandidatePage(int delta) {
433+
return p_frontend->call<fcitx::IAndroidFrontend::offsetCandidatePage>(delta);
434+
}
435+
432436
void save() {
433437
p_instance->save();
434438
}
@@ -1068,6 +1072,13 @@ Java_org_fcitx_fcitx5_android_core_Fcitx_setFcitxCandidatePagingMode(JNIEnv *env
10681072
Fcitx::Instance().setCandidatePagingMode(mode);
10691073
}
10701074

1075+
extern "C"
1076+
JNIEXPORT void JNICALL
1077+
Java_org_fcitx_fcitx5_android_core_Fcitx_offsetFcitxCandidatePage(JNIEnv *env, jclass clazz, jint delta) {
1078+
RETURN_IF_NOT_RUNNING
1079+
Fcitx::Instance().offsetCandidatePage(delta);
1080+
}
1081+
10711082
extern "C"
10721083
JNIEXPORT void JNICALL
10731084
Java_org_fcitx_fcitx5_android_core_Fcitx_loopOnce(JNIEnv *env, jclass clazz) {

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

Copy file name to clipboardExpand all lines: app/src/main/java/org/fcitx/fcitx5/android/core/Fcitx.kt
+27-3Lines 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) }
@@ -172,6 +184,9 @@ class Fcitx(private val context: Context) : FcitxAPI, FcitxLifecycleOwner {
172184
override suspend fun setCandidatePagingMode(mode: Int) =
173185
withFcitxContext { setFcitxCandidatePagingMode(mode) }
174186

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

235250
@JvmStatic
236-
external fun sendKeyToFcitxString(key: String, state: Int, code: Int, up: Boolean, timestamp: Int)
251+
external fun sendKeyToFcitxString(
252+
key: String,
253+
state: Int,
254+
code: Int,
255+
up: Boolean,
256+
timestamp: Int
257+
)
237258

238259
@JvmStatic
239260
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 {
343364
@JvmStatic
344365
external fun setFcitxCandidatePagingMode(mode: Int)
345366

367+
@JvmStatic
368+
external fun offsetFcitxCandidatePage(delta: Int)
369+
346370
@JvmStatic
347371
external fun loopOnce()
348372

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

Copy file name to clipboardExpand all lines: app/src/main/java/org/fcitx/fcitx5/android/core/FcitxAPI.kt
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,6 @@ interface FcitxAPI {
103103
suspend fun triggerCandidateAction(idx: Int, actionIdx: Int)
104104

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

Copy file name to clipboardExpand all lines: app/src/main/java/org/fcitx/fcitx5/android/input/CandidatesView.kt
+12-1Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import androidx.annotation.Size
1616
import org.fcitx.fcitx5.android.R
1717
import org.fcitx.fcitx5.android.core.FcitxEvent
1818
import org.fcitx.fcitx5.android.daemon.FcitxConnection
19+
import org.fcitx.fcitx5.android.daemon.launchOnReady
1920
import org.fcitx.fcitx5.android.data.prefs.AppPrefs
2021
import org.fcitx.fcitx5.android.data.theme.Theme
2122
import org.fcitx.fcitx5.android.input.candidates.floating.PagedCandidatesUi
@@ -73,6 +74,8 @@ class CandidatesView(
7374
true
7475
}
7576

77+
private val touchEventReceiverWindow = TouchEventReceiverWindow(this)
78+
7679
private val setupTextView: TextView.() -> Unit = {
7780
textSize = fontSize.toFloat()
7881
val v = dp(itemPaddingVertical)
@@ -82,7 +85,11 @@ class CandidatesView(
8285

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

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

@@ -116,6 +123,7 @@ class CandidatesView(
116123
candidatesUi.update(paged, orientation)
117124
visibility = VISIBLE
118125
} else {
126+
touchEventReceiverWindow.dismiss()
119127
visibility = GONE
120128
}
121129
}
@@ -141,6 +149,8 @@ class CandidatesView(
141149
}
142150
translationY =
143151
if (bottom + selfHeight > parentHeight - bottomInsets) top - selfHeight else bottom
152+
// update touchEventReceiverWindow's position after CandidatesView's
153+
touchEventReceiverWindow.showup()
144154
shouldUpdatePosition = false
145155
}
146156

@@ -193,6 +203,7 @@ class CandidatesView(
193203
override fun onDetachedFromWindow() {
194204
viewTreeObserver.removeOnPreDrawListener(preDrawListener)
195205
candidatesUi.root.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
206+
touchEventReceiverWindow.dismiss()
196207
super.onDetachedFromWindow()
197208
}
198209
}
+53Lines 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

Copy file name to clipboardExpand all lines: app/src/main/java/org/fcitx/fcitx5/android/input/candidates/floating/PagedCandidatesUi.kt
+15-1Lines 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

Copy file name to clipboardExpand all lines: app/src/main/java/org/fcitx/fcitx5/android/input/candidates/floating/PaginationUi.kt
+3-2Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.