Skip to content

Commit d27e7cf

Browse files
authored
feat: backport HeliBoard QoL features — HCESAR, edge-scroll, swipe-to-hide, HW-keyboard toolbar (#74) (#79)
* feat(layouts): add HCESAR layout (#74) * feat(touchpad): edge-scroll cursor acceleration (#74) * feat(toolbar): swipe down on toolbar to hide keyboard (#74) * feat(toolbar): option to show only toolbar with a hardware keyboard (#74)
1 parent c166d95 commit d27e7cf

13 files changed

Lines changed: 243 additions & 32 deletions

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
[
2+
[
3+
{ "label": "h" },
4+
{ "label": "c" },
5+
{ "label": "e" },
6+
{ "label": "s" },
7+
{ "label": "a" },
8+
{ "label": "r" },
9+
{ "label": "o" },
10+
{ "label": "p" },
11+
{ "label": "z" },
12+
{ "$": "shift_state_selector",
13+
"shifted": { "label": "«" },
14+
"default": {
15+
"label": ",",
16+
"popup": {
17+
"relevant": [
18+
{ "label": "}"},
19+
{ "label": "0" },
20+
{ "label": ";" },
21+
{ "label": "!" },
22+
{ "label": "!fixedColumnOrder!4" }
23+
]
24+
}
25+
}
26+
}
27+
],
28+
[
29+
{ "label": "q" },
30+
{ "label": "t" },
31+
{ "label": "d" },
32+
{ "label": "i" },
33+
{ "label": "n" },
34+
{ "label": "u" },
35+
{ "label": "l" },
36+
{ "label": "m" },
37+
{ "label": "x" },
38+
{ "$": "shift_state_selector",
39+
"shifted": { "label": "»" },
40+
"default": {
41+
"label": ".",
42+
"popup": {
43+
"relevant": [
44+
{ "label": "/" },
45+
{ "label": ":" },
46+
{"label": "?" },
47+
{ "label": "!fixedColumnOrder!3" }
48+
]
49+
}
50+
}
51+
}
52+
],
53+
[
54+
{ "label": "ç" },
55+
{ "label": "j" },
56+
{ "label": "b" },
57+
{ "label": "f" },
58+
{ "label": "v" },
59+
{ "label": "g" },
60+
{ "label": "k" }
61+
],
62+
[
63+
{ "label": "y" },
64+
{ "label": "w" }
65+
]
66+
]

app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,8 +331,9 @@ public boolean isImeSuppressedByHardwareKeyboard(
331331
private void setMainKeyboardFrame(
332332
@NonNull final SettingsValues settingsValues,
333333
@NonNull final KeyboardSwitchState toggleState) {
334-
final int visibility = isImeSuppressedByHardwareKeyboard(settingsValues, toggleState) ? View.GONE
335-
: View.VISIBLE;
334+
final boolean suppressKeyboard = isImeSuppressedByHardwareKeyboard(settingsValues, toggleState)
335+
|| (settingsValues.mShowOnlyToolbarWithHardwareKeyboard && settingsValues.mHasHardwareKeyboard);
336+
final int visibility = suppressKeyboard ? View.GONE : View.VISIBLE;
336337
final int stripVisibility = settingsValues.mToolbarMode == ToolbarMode.HIDDEN ? View.GONE : View.VISIBLE;
337338
mStripContainer.setVisibility(stripVisibility);
338339
PointerTracker.switchTo(mKeyboardView);

app/src/main/java/helium314/keyboard/keyboard/TouchpadView.java

Lines changed: 103 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import android.view.MotionEvent;
1010
import android.view.LayoutInflater;
1111
import android.view.View;
12+
import android.os.Handler;
13+
import android.os.Looper;
1214
import android.widget.LinearLayout;
1315

1416
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode;
@@ -59,6 +61,29 @@ public interface TouchpadListener {
5961

6062
private static final int SCROLL_THRESHOLD = 40;
6163

64+
// Edge-scroll acceleration constants (mirrors HeliBoard's TouchpadHandler)
65+
private static final float EDGE_THRESHOLD_PERCENTAGE = 0.1f;
66+
private static final float EDGE_ACCELERATION_FACTOR = 0.95f;
67+
private static final int MIN_EDGE_ACCELERATION_DELAY = 20;
68+
69+
// Edge-scroll state
70+
private final Handler mEdgeScrollHandler = new Handler(Looper.getMainLooper());
71+
private boolean mIsEdgeScrolling = false;
72+
private int mEdgeScrollDirection = KeyCode.UNSPECIFIED;
73+
private long mEdgeScrollDelay = 200;
74+
75+
private final Runnable mEdgeScrollRunnable = new Runnable() {
76+
@Override
77+
public void run() {
78+
if (mIsEdgeScrolling && mListener != null) {
79+
mListener.onCursorMove(mEdgeScrollDirection, mSelectionMode);
80+
mEdgeScrollDelay = Math.max(MIN_EDGE_ACCELERATION_DELAY,
81+
(long) (mEdgeScrollDelay * EDGE_ACCELERATION_FACTOR));
82+
mEdgeScrollHandler.postDelayed(this, mEdgeScrollDelay);
83+
}
84+
}
85+
};
86+
6287
public TouchpadView(Context context) {
6388
super(context);
6489
init(context);
@@ -187,42 +212,54 @@ private void setupTouchSurface() {
187212
mScrollAccY += SCROLL_THRESHOLD;
188213
}
189214
} else if (mIsDragging && pointerCount == 1) {
190-
float deltaX = event.getX() - mLastTouchX;
191-
float deltaY = event.getY() - mLastTouchY;
192-
mLastTouchX = event.getX();
193-
mLastTouchY = event.getY();
194-
195-
mAccX += deltaX;
196-
mAccY += deltaY;
197-
198-
int sensitivity = Settings.getValues().mTouchpadSensitivity;
199-
// Base threshold is 110 for normal slow cursor, but 70 for fast selection
200-
int baseThreshold = mSelectionMode ? 70 : 110;
201-
int threshold = baseThreshold - (int) (sensitivity * 0.6f);
202-
if (threshold < 10) threshold = 10;
203-
204-
while (mAccX >= threshold) {
205-
if (mListener != null) mListener.onCursorMove(KeyCode.ARROW_RIGHT, mSelectionMode);
206-
mAccX -= threshold;
207-
}
208-
while (mAccX <= -threshold) {
209-
if (mListener != null) mListener.onCursorMove(KeyCode.ARROW_LEFT, mSelectionMode);
210-
mAccX += threshold;
211-
}
212-
while (mAccY >= threshold) {
213-
if (mListener != null) mListener.onCursorMove(KeyCode.ARROW_DOWN, mSelectionMode);
214-
mAccY -= threshold;
215-
}
216-
while (mAccY <= -threshold) {
217-
if (mListener != null) mListener.onCursorMove(KeyCode.ARROW_UP, mSelectionMode);
218-
mAccY += threshold;
215+
float x = event.getX();
216+
float y = event.getY();
217+
float deltaX = x - mLastTouchX;
218+
float deltaY = y - mLastTouchY;
219+
mLastTouchX = x;
220+
mLastTouchY = y;
221+
222+
// Edge-scroll acceleration: when pref enabled and touch is near an edge,
223+
// start a repeating accelerating cursor-move instead of normal tracking.
224+
if (Settings.getValues().mTouchpadEdgeScroll && handleEdgeScrolling(x, y)) {
225+
mAccX = 0;
226+
mAccY = 0;
227+
} else {
228+
stopEdgeScrolling();
229+
230+
mAccX += deltaX;
231+
mAccY += deltaY;
232+
233+
int sensitivity = Settings.getValues().mTouchpadSensitivity;
234+
// Base threshold is 110 for normal slow cursor, but 70 for fast selection
235+
int baseThreshold = mSelectionMode ? 70 : 110;
236+
int threshold = baseThreshold - (int) (sensitivity * 0.6f);
237+
if (threshold < 10) threshold = 10;
238+
239+
while (mAccX >= threshold) {
240+
if (mListener != null) mListener.onCursorMove(KeyCode.ARROW_RIGHT, mSelectionMode);
241+
mAccX -= threshold;
242+
}
243+
while (mAccX <= -threshold) {
244+
if (mListener != null) mListener.onCursorMove(KeyCode.ARROW_LEFT, mSelectionMode);
245+
mAccX += threshold;
246+
}
247+
while (mAccY >= threshold) {
248+
if (mListener != null) mListener.onCursorMove(KeyCode.ARROW_DOWN, mSelectionMode);
249+
mAccY -= threshold;
250+
}
251+
while (mAccY <= -threshold) {
252+
if (mListener != null) mListener.onCursorMove(KeyCode.ARROW_UP, mSelectionMode);
253+
mAccY += threshold;
254+
}
219255
}
220256
}
221257
return true;
222258

223259
case MotionEvent.ACTION_UP:
224260
case MotionEvent.ACTION_CANCEL:
225261
mIsDragging = false;
262+
stopEdgeScrolling();
226263
mIsTwoFingerScroll = false;
227264
mIsTwoFingerTap = false;
228265
if (mSelectionMode) {
@@ -244,4 +281,41 @@ private void setupTouchSurface() {
244281
return true;
245282
});
246283
}
284+
285+
/** Returns true and starts/continues edge scrolling if (x, y) is within the edge threshold. */
286+
private boolean handleEdgeScrolling(float x, float y) {
287+
int w = mTouchpadSurface.getWidth();
288+
int h = mTouchpadSurface.getHeight();
289+
int thresholdX = (int) (w * EDGE_THRESHOLD_PERCENTAGE);
290+
int thresholdY = (int) (h * EDGE_THRESHOLD_PERCENTAGE);
291+
292+
int direction;
293+
if (y <= thresholdY) {
294+
direction = KeyCode.ARROW_UP;
295+
} else if (y >= h - thresholdY) {
296+
direction = KeyCode.ARROW_DOWN;
297+
} else if (x <= thresholdX) {
298+
direction = KeyCode.ARROW_LEFT;
299+
} else if (x >= w - thresholdX) {
300+
direction = KeyCode.ARROW_RIGHT;
301+
} else {
302+
return false;
303+
}
304+
305+
if (mIsEdgeScrolling && mEdgeScrollDirection == direction) {
306+
return true; // already running in this direction
307+
}
308+
stopEdgeScrolling();
309+
mEdgeScrollDirection = direction;
310+
mIsEdgeScrolling = true;
311+
int sensitivity = Settings.getValues().mTouchpadSensitivity;
312+
mEdgeScrollDelay = 300L - (long) (sensitivity * 2.5f);
313+
mEdgeScrollHandler.post(mEdgeScrollRunnable);
314+
return true;
315+
}
316+
317+
private void stopEdgeScrolling() {
318+
mIsEdgeScrolling = false;
319+
mEdgeScrollHandler.removeCallbacks(mEdgeScrollRunnable);
320+
}
247321
}

app/src/main/java/helium314/keyboard/latin/LatinIME.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1799,6 +1799,11 @@ public void removeExternalSuggestions() {
17991799
mHandler.postResumeSuggestions(false);
18001800
}
18011801

1802+
@Override
1803+
public void onSwipeDownOnToolbar() {
1804+
requestHideSelf(0);
1805+
}
1806+
18021807
private void loadKeyboard() {
18031808
// Since we are switching languages, the most urgent thing is to let the
18041809
// keyboard graphics

app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ object Defaults {
180180
const val PREF_SPACE_TO_CHANGE_LANG = true
181181
const val PREF_LANGUAGE_SWIPE_DISTANCE = 5
182182
const val PREF_TOUCHPAD_SENSITIVITY = 50
183+
const val PREF_TOUCHPAD_EDGE_SCROLL = false
183184
const val PREF_FORCE_AUTO_CAPS = false
184185
const val PREF_OFFLINE_TEMP = 0.1f // Lower for faster, more deterministic proofreading
185186
const val PREF_OFFLINE_TOP_P = 0.5f // Lower for faster token sampling
@@ -214,6 +215,8 @@ object Defaults {
214215
const val PREF_AUTO_HIDE_TOOLBAR = true
215216
const val PREF_AUTO_HIDE_PINNED_KEYS = true
216217
const val PREF_REMEMBER_TOOLBAR_STATE = false
218+
const val PREF_TOOLBAR_SWIPE_DOWN_TO_HIDE = false
219+
const val PREF_SHOW_ONLY_TOOLBAR_WITH_HARDWARE_KEYBOARD = false
217220
const val PREF_TOOLBAR_EXPANDED = false
218221
val PREF_CLIPBOARD_TOOLBAR_KEYS = defaultClipboardToolbarPref
219222
const val PREF_ABC_AFTER_EMOJI = false

app/src/main/java/helium314/keyboard/latin/settings/Settings.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
209209
public static final String PREF_SPACE_TO_CHANGE_LANG = "prefs_long_press_keyboard_to_change_lang";
210210
public static final String PREF_LANGUAGE_SWIPE_DISTANCE = "language_swipe_distance";
211211
public static final String PREF_TOUCHPAD_SENSITIVITY = "touchpad_sensitivity";
212+
public static final String PREF_TOUCHPAD_EDGE_SCROLL = "touchpad_edge_scroll";
212213
public static final String PREF_PERSIST_FLOATING_KEYBOARD = "persist_floating_keyboard";
213214
public static final String PREF_FORCE_AUTO_CAPS = "force_auto_caps";
214215
public static final String PREF_OFFLINE_TEMP = "offline_temp";
@@ -257,6 +258,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
257258
public static final String PREF_TOOLBAR_MODE = "toolbar_mode";
258259
public static final String PREF_TOOLBAR_HIDING_GLOBAL = "toolbar_hiding_global";
259260
public static final String PREF_SPLIT_TOOLBAR = "split_toolbar";
261+
public static final String PREF_TOOLBAR_SWIPE_DOWN_TO_HIDE = "toolbar_swipe_down_to_hide";
262+
public static final String PREF_SHOW_ONLY_TOOLBAR_WITH_HARDWARE_KEYBOARD = "only_toolbar_with_hw_keyboard";
260263

261264
// Emoji
262265
public static final String PREF_EMOJI_MAX_SDK = "emoji_max_sdk";

app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public class SettingsValues {
8282
public final int mSpaceSwipeVertical;
8383
public final int mLanguageSwipeDistance;
8484
public final int mTouchpadSensitivity;
85+
public final boolean mTouchpadEdgeScroll;
8586
public final boolean mForceAutoCaps;
8687
public final boolean mDeleteSwipeEnabled;
8788
public final boolean mShortcutRowsEnabled;
@@ -171,6 +172,8 @@ public class SettingsValues {
171172
public final boolean mAutoHideToolbar;
172173
public final boolean mAutoHidePinnedKeys;
173174
public final boolean mRememberToolbarState;
175+
public final boolean mToolbarSwipeDownToHide;
176+
public final boolean mShowOnlyToolbarWithHardwareKeyboard;
174177
public final boolean mAlphaAfterEmojiInEmojiView;
175178
public final boolean mAlphaAfterClipHistoryEntry;
176179
public final boolean mAlphaAfterSymbolAndSpace;
@@ -426,6 +429,8 @@ public SettingsValues(final Context context, final SharedPreferences prefs, fina
426429
Defaults.PREF_LANGUAGE_SWIPE_DISTANCE);
427430
mTouchpadSensitivity = prefs.getInt(Settings.PREF_TOUCHPAD_SENSITIVITY,
428431
Defaults.PREF_TOUCHPAD_SENSITIVITY);
432+
mTouchpadEdgeScroll = prefs.getBoolean(Settings.PREF_TOUCHPAD_EDGE_SCROLL,
433+
Defaults.PREF_TOUCHPAD_EDGE_SCROLL);
429434
mForceAutoCaps = prefs.getBoolean(Settings.PREF_FORCE_AUTO_CAPS, Defaults.PREF_FORCE_AUTO_CAPS);
430435
mDeleteSwipeEnabled = prefs.getBoolean(Settings.PREF_DELETE_SWIPE, Defaults.PREF_DELETE_SWIPE);
431436
mShortcutRowsEnabled = prefs.getBoolean(Settings.PREF_SHORTCUT_ROWS, Defaults.PREF_SHORTCUT_ROWS);
@@ -505,6 +510,11 @@ public SettingsValues(final Context context, final SharedPreferences prefs, fina
505510
&& prefs.getBoolean(Settings.PREF_AUTO_HIDE_PINNED_KEYS, Defaults.PREF_AUTO_HIDE_PINNED_KEYS);
506511
mRememberToolbarState = prefs.getBoolean(Settings.PREF_REMEMBER_TOOLBAR_STATE,
507512
Defaults.PREF_REMEMBER_TOOLBAR_STATE);
513+
mToolbarSwipeDownToHide = prefs.getBoolean(Settings.PREF_TOOLBAR_SWIPE_DOWN_TO_HIDE,
514+
Defaults.PREF_TOOLBAR_SWIPE_DOWN_TO_HIDE);
515+
mShowOnlyToolbarWithHardwareKeyboard = prefs.getBoolean(
516+
Settings.PREF_SHOW_ONLY_TOOLBAR_WITH_HARDWARE_KEYBOARD,
517+
Defaults.PREF_SHOW_ONLY_TOOLBAR_WITH_HARDWARE_KEYBOARD);
508518
// Migration: clear any old saved value and reset to default
509519
if (!prefs.contains(Settings.PREF_AUTO_HIDE_PINNED_KEYS)) {
510520
prefs.edit().putBoolean(Settings.PREF_AUTO_HIDE_PINNED_KEYS, Defaults.PREF_AUTO_HIDE_PINNED_KEYS).apply();

app/src/main/java/helium314/keyboard/latin/suggestions/SuggestionStripView.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import helium314.keyboard.latin.utils.setToolbarButtonsActivatedState
6666
import helium314.keyboard.latin.utils.setToolbarButtonsActivatedStateOnPrefChange
6767
import helium314.keyboard.settings.SettingsWithoutKey
6868
import java.util.concurrent.atomic.AtomicBoolean
69+
import kotlin.math.abs
6970
import kotlin.math.min
7071

7172
@SuppressLint("InflateParams")
@@ -82,6 +83,7 @@ class SuggestionStripView(context: Context, attrs: AttributeSet?, defStyle: Int)
8283
fun removeExternalSuggestions()
8384
fun addToDictionary(word: String)
8485
fun blockWord(word: String)
86+
fun onSwipeDownOnToolbar()
8587
}
8688

8789
private val moreSuggestionsContainer: View
@@ -293,6 +295,11 @@ class SuggestionStripView(context: Context, attrs: AttributeSet?, defStyle: Int)
293295
override fun onScroll(down: MotionEvent?, me: MotionEvent, deltaX: Float, deltaY: Float): Boolean {
294296
if (down == null) return false
295297
val dy = me.y - down.y
298+
val dx = me.x - down.x
299+
if (Settings.getValues().mToolbarSwipeDownToHide && dy > 50.dpToPx(resources) && abs(dy) > abs(dx)) {
300+
listener.onSwipeDownOnToolbar()
301+
return true
302+
}
296303
return if (toolbarContainer.visibility != VISIBLE && deltaY > 0 && dy < (-10).dpToPx(resources)) showMoreSuggestions()
297304
else false
298305
}

app/src/main/java/helium314/keyboard/settings/screens/GestureTypingScreen.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ fun GestureTypingScreen(
7272
add(Settings.PREF_SPACE_HORIZONTAL_SWIPE)
7373
add(Settings.PREF_SPACE_VERTICAL_SWIPE)
7474
add(Settings.PREF_TOUCHPAD_SENSITIVITY)
75+
add(Settings.PREF_TOUCHPAD_EDGE_SCROLL)
7576
add(Settings.PREF_DELETE_SWIPE)
7677
add(Settings.PREF_SHORTCUT_ROWS)
7778
if (prefs.getBoolean(Settings.PREF_SHORTCUT_ROWS, Defaults.PREF_SHORTCUT_ROWS)) {
@@ -169,6 +170,9 @@ fun createGestureTypingSettings(context: Context) = listOf(
169170
description = { value -> value.toInt().toString() }
170171
)
171172
},
173+
Setting(context, Settings.PREF_TOUCHPAD_EDGE_SCROLL, R.string.touchpad_edge_scroll, R.string.touchpad_edge_scroll_summary) {
174+
SwitchPreference(it, Defaults.PREF_TOUCHPAD_EDGE_SCROLL)
175+
},
172176
Setting(context, Settings.PREF_DELETE_SWIPE, R.string.delete_swipe, R.string.delete_swipe_summary) {
173177
SwitchPreference(it, Defaults.PREF_DELETE_SWIPE)
174178
},

0 commit comments

Comments
 (0)