diff --git a/java/res/values/strings-uix.xml b/java/res/values/strings-uix.xml
index 6627fea9f4..17096aaeaa 100644
--- a/java/res/values/strings-uix.xml
+++ b/java/res/values/strings-uix.xml
@@ -440,6 +440,13 @@
Swipe Typing (alpha)
Allow swiping from key to key to write words.
+ Swipe left to delete word
+ Swipe right for space
+ Swipe down to accept middle prediction
+ Swipe diagonal down to accept side predictions
+ Swipe diagonal down right to accept right-side prediction, diagonal down left to accept left-side prediction
+ Swipe up to undo
+ Swipe Gestures
Emoji Suggestions
Suggest emojis while you\'re typing
Vibration
diff --git a/java/src/org/futo/inputmethod/keyboard/KeyboardActionListener.java b/java/src/org/futo/inputmethod/keyboard/KeyboardActionListener.java
index 948fba78d3..a6be04242e 100644
--- a/java/src/org/futo/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/org/futo/inputmethod/keyboard/KeyboardActionListener.java
@@ -106,8 +106,16 @@ public interface KeyboardActionListener {
public void onUpWithDeletePointerActive();
public void onUpWithPointerActive();
public void onSwipeLanguage(int direction);
+ public void onSwipeAction(int direction);
public void onMovingCursorLockEvent(boolean canMoveCursor);
+ public static final int SWIPE_ACTION_LEFT = -1;
+ public static final int SWIPE_ACTION_RIGHT = 1;
+ public static final int SWIPE_ACTION_UP = 2;
+ public static final int SWIPE_ACTION_DOWN = 3;
+ public static final int SWIPE_ACTION_DOWN_LEFT = 4;
+ public static final int SWIPE_ACTION_DOWN_RIGHT = 5;
+
public static final KeyboardActionListener EMPTY_LISTENER = new Adapter();
public static class Adapter implements KeyboardActionListener {
@@ -144,6 +152,8 @@ public void onUpWithPointerActive() {}
@Override
public void onSwipeLanguage(int direction) {}
@Override
+ public void onSwipeAction(int direction) {}
+ @Override
public void onMovingCursorLockEvent(boolean canMoveCursor) {}
}
}
diff --git a/java/src/org/futo/inputmethod/keyboard/PointerTracker.java b/java/src/org/futo/inputmethod/keyboard/PointerTracker.java
index e09c182dc5..c775a8ffe8 100644
--- a/java/src/org/futo/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/org/futo/inputmethod/keyboard/PointerTracker.java
@@ -150,6 +150,10 @@ public PointerTrackerParams(final TypedArray mainKeyboardViewAttr) {
private boolean mStartedOnFastLongPress;
private boolean mCursorMoved = false;
private boolean mSpacebarLongPressed = false;
+ private boolean mSwipeActionTriggered = false;
+
+ private static final int sSwipeActionStep =
+ (int)(18.0 * Resources.getSystem().getDisplayMetrics().density);
// true if keyboard layout has been changed.
private boolean mKeyboardLayoutHasBeenChanged;
@@ -745,8 +749,12 @@ private void onDownEventInternal(final int x, final int y, final long eventTime)
mStartTime = System.currentTimeMillis();
mStartedOnFastLongPress = key.isFastLongPress();
mSpacebarLongPressed = false;
+ mSwipeActionTriggered = false;
- mIsSlidingCursor = key.getCode() == Constants.CODE_DELETE || key.getCode() == Constants.CODE_SPACE;
+ final boolean swipeActionsMode = !Settings.getInstance().getCurrent().mGestureInputEnabled;
+ mIsSlidingCursor = key.getCode() == Constants.CODE_DELETE
+ || key.getCode() == Constants.CODE_SPACE
+ || swipeActionsMode;
mIsFlickingKey = !mIsSlidingCursor && key.getHasFlick();
mFlickDirection = key.flickDirection(0, 0);
mCurrentKey = key;
@@ -954,6 +962,72 @@ private void onMoveEventInternal(final int x, final int y, final long eventTime)
final SettingsValues settingsValues = Settings.getInstance().getCurrent();
+ // Swipe actions (when gesture typing is off)
+ // Skip spacebar — it has its own swipe gestures (cursor movement, language switch)
+ if (mIsSlidingCursor && oldKey != null && !mSwipeActionTriggered
+ && !settingsValues.mGestureInputEnabled
+ && oldKey.getCode() != Constants.CODE_SPACE
+ && oldKey.getCode() != Constants.CODE_DELETE) {
+ final int dx = x - mStartX;
+ final int dy = y - mStartY;
+ final long swipeDistSq = (long)dx * dx + (long)dy * dy;
+ final long thresholdSq = (long)sSwipeActionStep * sSwipeActionStep;
+ final int swipeIgnoreTime = settingsValues.mKeyLongpressTimeout
+ / MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT;
+
+ if (swipeDistSq >= thresholdSq
+ && mStartTime + swipeIgnoreTime < System.currentTimeMillis()) {
+ final int absDx = Math.abs(dx);
+ final int absDy = Math.abs(dy);
+ int direction = 0;
+ boolean enabled = false;
+
+ if (absDx > 2 * absDy) {
+ // Horizontal swipe
+ if (dx < 0) {
+ direction = KeyboardActionListener.SWIPE_ACTION_LEFT;
+ enabled = settingsValues.mSwipeLeftDelete;
+ } else {
+ direction = KeyboardActionListener.SWIPE_ACTION_RIGHT;
+ enabled = settingsValues.mSwipeRightSpace;
+ }
+ } else if (absDy > 2 * absDx) {
+ // Vertical swipe
+ if (dy < 0) {
+ direction = KeyboardActionListener.SWIPE_ACTION_UP;
+ enabled = settingsValues.mSwipeUpUndo;
+ } else {
+ direction = KeyboardActionListener.SWIPE_ACTION_DOWN;
+ enabled = settingsValues.mSwipeDownPrediction;
+ }
+ } else if (dy > 0) {
+ // Diagonal down
+ if (dx < 0) {
+ direction = KeyboardActionListener.SWIPE_ACTION_DOWN_LEFT;
+ enabled = settingsValues.mSwipeDownLRPrediction;
+ } else {
+ direction = KeyboardActionListener.SWIPE_ACTION_DOWN_RIGHT;
+ enabled = settingsValues.mSwipeDownLRPrediction;
+ }
+ }
+
+ if (enabled && direction != 0) {
+ mSwipeActionTriggered = true;
+ sTimerProxy.cancelKeyTimersOf(this);
+ sListener.onSwipeAction(direction);
+ mLastX = x;
+ mLastY = y;
+ return;
+ }
+ }
+ }
+
+ if (mSwipeActionTriggered) {
+ mLastX = x;
+ mLastY = y;
+ return;
+ }
+
if (mIsSlidingCursor && oldKey != null && oldKey.getCode() == Constants.CODE_SPACE) {
int pointerStep = sPointerStep;
if(settingsValues.mSpacebarMode == Settings.SPACEBAR_MODE_SWIPE_LANGUAGE && !mSpacebarLongPressed) {
@@ -1100,6 +1174,10 @@ private void onUpEventInternal(final int x, final int y, final long eventTime) {
sListener.onUpWithPointerActive();
}
+ if (mSwipeActionTriggered) {
+ return;
+ }
+
if(mIsFlickingKey && currentKey != null) {
final Key flickedKey = currentKey.flick(x - mStartX, y - mStartY);
detectAndSendKey(flickedKey, mKeyX, mKeyY, eventTime);
diff --git a/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java b/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java
index 49bd237883..89a32ab4e7 100644
--- a/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java
+++ b/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java
@@ -47,6 +47,7 @@
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodSubtype;
import androidx.annotation.NonNull;
@@ -648,6 +649,41 @@ public void onSwipeLanguage(int direction) {
Subtypes.INSTANCE.switchToNextLanguage(mInputMethodService, direction);
}
+ @Override
+ public void onSwipeAction(int direction) {
+ if (direction == KeyboardActionListener.SWIPE_ACTION_LEFT) {
+ // Delete word before cursor
+ sendCtrlKeyEvent(KeyEvent.KEYCODE_DEL);
+ } else if (direction == KeyboardActionListener.SWIPE_ACTION_RIGHT) {
+ // Insert space
+ onCodeInput(Constants.CODE_SPACE,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, false);
+ } else if (direction == KeyboardActionListener.SWIPE_ACTION_UP) {
+ // Undo
+ sendCtrlKeyEvent(KeyEvent.KEYCODE_Z);
+ } else if (direction == KeyboardActionListener.SWIPE_ACTION_DOWN) {
+ // Accept center prediction
+ getLatinIME().getUixManager().pickSuggestionAtVisualPosition(1);
+ } else if (direction == KeyboardActionListener.SWIPE_ACTION_DOWN_LEFT) {
+ // Accept left prediction
+ getLatinIME().getUixManager().pickSuggestionAtVisualPosition(0);
+ } else if (direction == KeyboardActionListener.SWIPE_ACTION_DOWN_RIGHT) {
+ // Accept right prediction
+ getLatinIME().getUixManager().pickSuggestionAtVisualPosition(2);
+ }
+ }
+
+ private void sendCtrlKeyEvent(int keyCode) {
+ final InputConnection ic = mInputMethodService.getCurrentInputConnection();
+ if (ic != null) {
+ final long now = android.os.SystemClock.uptimeMillis();
+ ic.sendKeyEvent(new KeyEvent(
+ now, now, KeyEvent.ACTION_DOWN, keyCode, 0, KeyEvent.META_CTRL_ON));
+ ic.sendKeyEvent(new KeyEvent(
+ now, now, KeyEvent.ACTION_UP, keyCode, 0, KeyEvent.META_CTRL_ON));
+ }
+ }
+
@Override
public void onMovingCursorLockEvent(boolean canMoveCursor) {
if(canMoveCursor) {
diff --git a/java/src/org/futo/inputmethod/latin/settings/Settings.java b/java/src/org/futo/inputmethod/latin/settings/Settings.java
index 61ac200881..f3f60b2c01 100644
--- a/java/src/org/futo/inputmethod/latin/settings/Settings.java
+++ b/java/src/org/futo/inputmethod/latin/settings/Settings.java
@@ -143,6 +143,12 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_USE_WESTERN_NUMERALS = "pref_use_western_numerals";
+ public static final String PREF_SWIPE_LEFT_DELETE = "swipe_left_delete";
+ public static final String PREF_SWIPE_RIGHT_SPACE = "swipe_right_space";
+ public static final String PREF_SWIPE_DOWN_PREDICTION = "swipe_down_prediction";
+ public static final String PREF_SWIPE_DOWN_LR_PREDICTION = "swipe_down_lr_prediction";
+ public static final String PREF_SWIPE_UP_UNDO = "swipe_up_undo";
+
public static final int DEFAULT_ALT_SPACES_MODE = SPACES_MODE_ALL;
// Emoji
diff --git a/java/src/org/futo/inputmethod/latin/settings/SettingsValues.java b/java/src/org/futo/inputmethod/latin/settings/SettingsValues.java
index 8c25837adb..b8f859c7d1 100644
--- a/java/src/org/futo/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/org/futo/inputmethod/latin/settings/SettingsValues.java
@@ -107,6 +107,12 @@ public class SettingsValues {
public final int mNumberRowMode;
public final int mAltSpacesMode;
+ public final boolean mSwipeLeftDelete;
+ public final boolean mSwipeRightSpace;
+ public final boolean mSwipeDownPrediction;
+ public final boolean mSwipeDownLRPrediction;
+ public final boolean mSwipeUpUndo;
+
// From the input box
@Nonnull
public final InputAttributes mInputAttributes;
@@ -202,6 +208,12 @@ public SettingsValues(final Context context, final SharedPreferences prefs, fina
: Settings.NUMBER_ROW_MODE_DEFAULT;
mAltSpacesMode = prefs.getInt(Settings.PREF_ALT_SPACES_MODE, Settings.DEFAULT_ALT_SPACES_MODE);
+ mSwipeLeftDelete = prefs.getBoolean(Settings.PREF_SWIPE_LEFT_DELETE, false);
+ mSwipeRightSpace = prefs.getBoolean(Settings.PREF_SWIPE_RIGHT_SPACE, false);
+ mSwipeDownPrediction = prefs.getBoolean(Settings.PREF_SWIPE_DOWN_PREDICTION, false);
+ mSwipeDownLRPrediction = prefs.getBoolean(Settings.PREF_SWIPE_DOWN_LR_PREDICTION, false);
+ mSwipeUpUndo = prefs.getBoolean(Settings.PREF_SWIPE_UP_UNDO, false);
+
mShouldShowLxxSuggestionUi = Settings.SHOULD_SHOW_LXX_SUGGESTION_UI
&& prefs.getBoolean(DebugSettings.PREF_SHOULD_SHOW_LXX_SUGGESTION_UI, true);
// Compute other readable settings
diff --git a/java/src/org/futo/inputmethod/latin/uix/UixManager.kt b/java/src/org/futo/inputmethod/latin/uix/UixManager.kt
index 831f0ff540..9ed08a42b5 100644
--- a/java/src/org/futo/inputmethod/latin/uix/UixManager.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/UixManager.kt
@@ -132,6 +132,7 @@ import org.futo.inputmethod.latin.uix.settings.DataStoreCacheProvider
import org.futo.inputmethod.latin.uix.settings.pages.ActionBarDisplayedSetting
import org.futo.inputmethod.latin.uix.settings.pages.InlineAutofillSetting
import org.futo.inputmethod.latin.uix.settings.useDataStore
+import org.futo.inputmethod.latin.uix.settings.useDataStoreValue
import org.futo.inputmethod.latin.uix.theme.KeyboardSurfaceShaderBackground
import org.futo.inputmethod.latin.uix.theme.Typography
import org.futo.inputmethod.latin.uix.theme.UixThemeAuto
@@ -1428,6 +1429,57 @@ class UixManager(private val latinIME: LatinIME) {
}
}
+ fun getSuggestionAtVisualPosition(position: Int): SuggestedWordInfo? {
+ val words = suggestedWords.value ?: return null
+ val layout = makeSuggestionLayout(words, null)
+
+ if (layout.isGestureBatch || (layout.emojiMatches.isEmpty() && layout.presentableSuggestions.size <= 1)) {
+ return if (position == 1) layout.presentableSuggestions.firstOrNull() else null
+ }
+
+ if (layout.autocorrectMatch != null) {
+ var supplementalIndex = 0
+ val left = if (layout.emojiMatches.isEmpty()) {
+ layout.sortedMatches.getOrNull(supplementalIndex++)
+ } else {
+ layout.emojiMatches.getOrNull(0)
+ }
+ val center = layout.autocorrectMatch
+ val right = if (layout.verbatimWord != null && layout.verbatimWord.mWord != layout.autocorrectMatch.mWord) {
+ layout.verbatimWord
+ } else {
+ layout.sortedMatches.getOrNull(supplementalIndex)
+ }
+ return when (position) {
+ 0 -> left
+ 1 -> center
+ 2 -> right
+ else -> null
+ }
+ }
+
+ // No autocorrect
+ var supplementalIndex = 1
+ val left = if (layout.emojiMatches.isEmpty()) {
+ layout.sortedMatches.getOrNull(supplementalIndex++)
+ } else {
+ layout.emojiMatches.getOrNull(0)
+ }
+ val center = layout.sortedMatches.getOrNull(0)
+ val right = layout.sortedMatches.getOrNull(supplementalIndex)
+ return when (position) {
+ 0 -> left
+ 1 -> center
+ 2 -> right
+ else -> null
+ }
+ }
+
+ fun pickSuggestionAtVisualPosition(position: Int) {
+ val suggestion = getSuggestionAtVisualPosition(position) ?: return
+ latinIME.latinIMELegacy.pickSuggestionManually(suggestion)
+ }
+
fun requestForgetWord(suggestedWordInfo: SuggestedWords.SuggestedWordInfo) {
keyboardManagerForAction.requestDialog(
latinIME.getString(R.string.keyboard_suggest_blacklist_body, suggestedWordInfo.mWord),
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/pages/Typing.kt b/java/src/org/futo/inputmethod/latin/uix/settings/pages/Typing.kt
index de8fea13c6..23daadc1e2 100644
--- a/java/src/org/futo/inputmethod/latin/uix/settings/pages/Typing.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/pages/Typing.kt
@@ -107,6 +107,7 @@ import org.futo.inputmethod.latin.uix.settings.DropDownPickerSettingItem
import org.futo.inputmethod.latin.uix.settings.LocalSharedPrefsCache
import org.futo.inputmethod.latin.uix.settings.NavigationItemStyle
import org.futo.inputmethod.latin.uix.settings.PrimarySettingToggleDataStoreItem
+import org.futo.inputmethod.latin.uix.settings.SettingToggleRaw
import org.futo.inputmethod.latin.uix.settings.ScreenTitle
import org.futo.inputmethod.latin.uix.settings.ScrollableList
import org.futo.inputmethod.latin.uix.settings.SettingItem
@@ -807,16 +808,81 @@ val TypingSettingsMenu = UserSettingsMenu(
AutoSpacesSetting()
}
),
- userSettingToggleSharedPrefs(
- title = R.string.typing_settings_swipe,
+ UserSetting(
+ name = R.string.typing_settings_swipe,
subtitle = R.string.typing_settings_swipe_subtitle,
- key = Settings.PREF_GESTURE_INPUT,
- default = {true},
- icon = {
- Icon(painterResource(id = R.drawable.swipe_icon), contentDescription = null,
- tint = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.75f))
- }
- ),
+ ) {
+ val gestureInput = useSharedPrefsBool(Settings.PREF_GESTURE_INPUT, true)
+ val leftDelete = useSharedPrefsBool(Settings.PREF_SWIPE_LEFT_DELETE, false)
+ val rightSpace = useSharedPrefsBool(Settings.PREF_SWIPE_RIGHT_SPACE, false)
+ val downPrediction = useSharedPrefsBool(Settings.PREF_SWIPE_DOWN_PREDICTION, false)
+ val downLrPrediction = useSharedPrefsBool(Settings.PREF_SWIPE_DOWN_LR_PREDICTION, false)
+ val upUndo = useSharedPrefsBool(Settings.PREF_SWIPE_UP_UNDO, false)
+
+ SettingToggleRaw(
+ title = stringResource(R.string.typing_settings_swipe),
+ subtitle = stringResource(R.string.typing_settings_swipe_subtitle),
+ enabled = gestureInput.value,
+ setValue = { newValue ->
+ gestureInput.setValue(newValue)
+ if (newValue) {
+ leftDelete.setValue(false)
+ rightSpace.setValue(false)
+ downPrediction.setValue(false)
+ downLrPrediction.setValue(false)
+ upUndo.setValue(false)
+ }
+ },
+ icon = {
+ Icon(painterResource(id = R.drawable.swipe_icon), contentDescription = null,
+ tint = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.75f))
+ }
+ )
+ },
+
+ UserSetting(name = R.string.swipe_gestures_title) {
+ val gestureInputOn = useSharedPrefsBool(Settings.PREF_GESTURE_INPUT, true)
+ val enabled = !gestureInputOn.value
+
+ val leftDelete = useSharedPrefsBool(Settings.PREF_SWIPE_LEFT_DELETE, false)
+ val rightSpace = useSharedPrefsBool(Settings.PREF_SWIPE_RIGHT_SPACE, false)
+ val downPrediction = useSharedPrefsBool(Settings.PREF_SWIPE_DOWN_PREDICTION, false)
+ val downLrPrediction = useSharedPrefsBool(Settings.PREF_SWIPE_DOWN_LR_PREDICTION, false)
+ val upUndo = useSharedPrefsBool(Settings.PREF_SWIPE_UP_UNDO, false)
+
+ SettingToggleRaw(
+ title = stringResource(R.string.swipe_gesture_left_delete),
+ enabled = leftDelete.value,
+ disabled = !enabled,
+ setValue = { leftDelete.setValue(it) }
+ )
+ SettingToggleRaw(
+ title = stringResource(R.string.swipe_gesture_right_space),
+ enabled = rightSpace.value,
+ disabled = !enabled,
+ setValue = { rightSpace.setValue(it) }
+ )
+ SettingToggleRaw(
+ title = stringResource(R.string.swipe_gesture_down_prediction),
+ enabled = downPrediction.value,
+ disabled = !enabled,
+ setValue = { downPrediction.setValue(it) }
+ )
+ SettingToggleRaw(
+ title = stringResource(R.string.swipe_gesture_down_lr_prediction),
+ subtitle = stringResource(R.string.swipe_gesture_down_lr_prediction_subtitle),
+ enabled = downLrPrediction.value,
+ disabled = !enabled,
+ setValue = { downLrPrediction.setValue(it) }
+ )
+ SettingToggleRaw(
+ title = stringResource(R.string.swipe_gesture_up_undo),
+ enabled = upUndo.value,
+ disabled = !enabled,
+ setValue = { upUndo.setValue(it) }
+ )
+ },
+
userSettingToggleDataStore(
title = R.string.typing_settings_suggest_emojis,
subtitle = R.string.typing_settings_suggest_emojis_subtitle,