Skip to content
Closed
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
74 changes: 74 additions & 0 deletions app/src/main/java/helium314/keyboard/keyboard/PointerTracker.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import helium314.keyboard.keyboard.internal.PointerTrackerQueue;
import helium314.keyboard.keyboard.internal.TimerProxy;
import helium314.keyboard.keyboard.internal.TypingTimeRecorder;
import helium314.keyboard.keyboard.internal.PopupKeySpec;
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode;
import helium314.keyboard.latin.R;
import helium314.keyboard.latin.common.Constants;
Expand Down Expand Up @@ -215,6 +216,14 @@ public static int consumeGestureSeedCodepoint() {
private boolean mShortcutBottomRowSwipeAllowed = false;
private boolean mInShortcutRowSwipe = false;
private static boolean sInShortcutRowSwipe = false;
// Swipe-up + hold to open a key's symbol popup (#32). mSwipeUpStart* captured at finger-down so a
// later up-flick + hold can target the original key. mSwipeUpHoldArmed: an up-flick is in progress
// and the hold timer is running (it fires once the finger stills; a continuous glide never lets it).
private Key mSwipeUpStartKey = null;
private int mSwipeUpStartX;
private int mSwipeUpStartY;
private boolean mSwipeUpHoldArmed = false;
private static final int SWIPE_UP_HOLD_MS = 120;

// Touchpad mode for cursor control
public static boolean sPersistentTouchpadModeActive = false;
Expand Down Expand Up @@ -990,6 +999,8 @@ private void onDownEventInternal(final int x, final int y, final long eventTime)
mShortcutTopRowSwipeAllowed = isShortcutRowSource(key, true);
mShortcutBottomRowSwipeAllowed = isShortcutRowSource(key, false);
mInShortcutRowSwipe = false;
mSwipeUpStartKey = null;
mSwipeUpHoldArmed = false;
mKeyboardLayoutHasBeenChanged = false;
mIsTrackingForActionDisabled = false;
resetKeySelectionByDraggingFinger();
Expand All @@ -1013,6 +1024,9 @@ private void onDownEventInternal(final int x, final int y, final long eventTime)
mStartX = x;
mStartY = y;
mStartTime = System.currentTimeMillis();
mSwipeUpStartKey = key;
mSwipeUpStartX = x;
mSwipeUpStartY = y;
}
}

Expand Down Expand Up @@ -1100,6 +1114,9 @@ private void onMoveEvent(final int x, final int y, final long eventTime, final M
if (tryStartShortcutRowSwipe(x, y, eventTime)) {
return;
}
// Swipe-up + hold (#32): arm/refresh the hold timer. Does not consume the event, so gesture
// detection continues — a real glide just keeps the timer from ever firing.
maybeArmOrRefreshSwipeUpHold(x, y);

if (sGestureEnabler.shouldHandleGesture() && me != null) {
// Add historical points to gesture path.
Expand Down Expand Up @@ -1598,6 +1615,59 @@ eventTime, getActivePointerTrackerCount(), graceMs, this,
}
}

/**
* Swipe-up + hold (#32, opt-in): while a swipe-up is armed, called on every move to (re)start the
* hold timer, so it only fires after the finger has been still for SWIPE_UP_HOLD_MS. A continuous
* glide keeps moving and never lets it fire; a deliberate flick-up-then-pause does. Arms on a
* clear upward flick from the start key. Does NOT consume the event — gesture detection proceeds
* in parallel, so gliding (even an upward word like "buy") is unaffected.
*/
private void maybeArmOrRefreshSwipeUpHold(final int x, final int y) {
if (!Settings.getValues().mSwipeUpSymbol || mShortcutTopRowSwipeAllowed
|| mSwipeUpStartKey == null || isShowingPopupKeysPanel()) {
return;
}
final PopupKeySpec[] popupKeys = mSwipeUpStartKey.getPopupKeys();
if (popupKeys == null || popupKeys.length == 0 || popupKeys[0] == null) return;
if (mSwipeUpHoldArmed) {
// Finger still moving — restart the timer so it only fires once the finger stills.
sTimerProxy.cancelLongPressTimersOf(this);
sTimerProxy.startLongPressTimerOf(this, SWIPE_UP_HOLD_MS);
return;
}
// Arm on a clear upward flick from the start key (more vertical than horizontal, little drift).
final int dX = x - mSwipeUpStartX;
final int dY = y - mSwipeUpStartY;
if (dY <= -2 * sPointerStep && abs(dY) > abs(dX) && abs(dX) <= mSwipeUpStartKey.getWidth()) {
mSwipeUpHoldArmed = true;
sTimerProxy.cancelLongPressTimersOf(this);
sTimerProxy.startLongPressTimerOf(this, SWIPE_UP_HOLD_MS);
}
}

/**
* Fired by the hold timer (via onLongPressed) once a swipe-up has been held still: open the START
* key's popup-keys panel (like long-press), cancelling any in-progress glide so it isn't committed.
*/
private void showSwipeUpSymbolPopup() {
mSwipeUpHoldArmed = false;
if (isShowingPopupKeysPanel()) return;
final Key key = mSwipeUpStartKey;
if (key == null || key.getPopupKeys() == null) return;
if (sInGesture) cancelBatchInput();
setReleasedKeyGraphics(key, false);
final PopupKeysPanel panel = sDrawingProxy.showPopupKeysKeyboard(key, this);
if (panel == null) return;
final int translatedX = panel.translateX(mLastX);
final int translatedY = panel.translateY(mLastY);
panel.onDownEvent(translatedX, translatedY, mPointerId, SystemClock.uptimeMillis());
mPopupKeysPanel = panel;
if (mKeySwipeAllowed) {
mKeySwipeAllowed = false;
sInKeySwipe = false;
}
}

@Override
public void cancelTrackingForAction() {
if (isShowingPopupKeysPanel()) {
Expand All @@ -1612,6 +1682,10 @@ public boolean isInOperation() {

public void onLongPressed() {
sTimerProxy.cancelLongPressTimersOf(this);
if (mSwipeUpHoldArmed) {
showSwipeUpSymbolPopup();
return;
}
if (isShowingPopupKeysPanel()) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ object Defaults {
const val PREF_ADD_TO_PERSONAL_DICTIONARY = true
const val PREF_FLAG_UNKNOWN_WORDS = true
const val PREF_GRADUATED_TRUST = true
const val PREF_SWIPE_UP_SYMBOL = false
@JvmField
val PREF_NAVBAR_COLOR = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
const val PREF_NARROW_KEY_GAPS = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_ADD_TO_PERSONAL_DICTIONARY = "add_to_personal_dictionary";
public static final String PREF_FLAG_UNKNOWN_WORDS = "flag_unknown_words";
public static final String PREF_GRADUATED_TRUST = "graduated_trust";
public static final String PREF_SWIPE_UP_SYMBOL = "swipe_up_symbol";
public static final String PREF_NAVBAR_COLOR = "navbar_color";
public static final String PREF_NARROW_KEY_GAPS = "narrow_key_gaps";
public static final String PREF_NARROW_KEY_GAPS_LEVEL = "narrow_key_gaps_level";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ public class SettingsValues {
public final boolean mAddToPersonalDictionary;
public final boolean mFlagUnknownWords;
public final boolean mGraduatedTrust;
public final boolean mSwipeUpSymbol;
public final boolean mUseContactsDictionary;
public final boolean mUseAppsDictionary;
public final boolean mCustomNavBarColor;
Expand Down Expand Up @@ -477,6 +478,8 @@ public SettingsValues(final Context context, final SharedPreferences prefs, fina
Defaults.PREF_FLAG_UNKNOWN_WORDS);
mGraduatedTrust = prefs.getBoolean(Settings.PREF_GRADUATED_TRUST,
Defaults.PREF_GRADUATED_TRUST);
mSwipeUpSymbol = prefs.getBoolean(Settings.PREF_SWIPE_UP_SYMBOL,
Defaults.PREF_SWIPE_UP_SYMBOL);
mUseContactsDictionary = SettingsValues.readUseContactsEnabled(prefs, context);
mUseAppsDictionary = prefs.getBoolean(Settings.PREF_USE_APPS, Defaults.PREF_USE_APPS);
mCustomNavBarColor = prefs.getBoolean(Settings.PREF_NAVBAR_COLOR, Defaults.PREF_NAVBAR_COLOR);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ fun TwoThumbTypingScreen(
if (dualThumbHinting) {
add(Settings.PREF_GESTURE_DUAL_THUMB_MIDLINE_PCT)
}
add(Settings.PREF_SWIPE_UP_SYMBOL)

add(R.string.settings_category_two_thumb_typing_troubleshooting)
add(Settings.PREF_GESTURE_DEBUG_DRAW_POINTS)
Expand Down Expand Up @@ -206,6 +207,10 @@ fun createTwoThumbTypingSettings(context: Context) = listOf(
R.string.gesture_debug_accumulate_fragments, R.string.gesture_debug_accumulate_fragments_summary) {
SwitchPreference(it, Defaults.PREF_GESTURE_DEBUG_ACCUMULATE_FRAGMENTS)
},
Setting(context, Settings.PREF_SWIPE_UP_SYMBOL,
R.string.swipe_up_symbol, R.string.swipe_up_symbol_summary) {
SwitchPreference(it, Defaults.PREF_SWIPE_UP_SYMBOL)
},
)

private const val SPACING_MODE_NORMAL = "normal"
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,8 @@
<string name="two_thumb_backspace_word">Delete whole word</string>
<string name="two_thumb_point_hinting">Improve two-thumb recognition (experimental)</string>
<string name="two_thumb_point_hinting_summary">Adds synthetic hints for the recognizer when both thumbs are used. It may help two-thumb gestures, but can hurt accuracy if the hand split is wrong.</string>
<string name="swipe_up_symbol">Swipe up + hold for symbols (experimental)</string>
<string name="swipe_up_symbol_summary">Swipe up on a key and pause briefly to open its symbol popup (drag to pick, release to take the highlighted one). A continuous glide never triggers it, so swipe typing is unaffected.</string>
<string name="gesture_debug_accumulate_fragments">Keep insight across word parts</string>
<string name="gesture_debug_accumulate_fragments_summary">Keep the trail visible across the parts of one word, clearing when the next word starts. Turn off to clear at each new swipe.</string>

Expand Down
Loading