Add sliding cursor/deletion

This commit is contained in:
Aleksandras Kostarevas 2024-01-07 16:22:17 +02:00
parent e2999ada34
commit 7f17d66072
4 changed files with 153 additions and 6 deletions

View File

@ -101,6 +101,11 @@ public interface KeyboardActionListener {
*/ */
public boolean onCustomRequest(int requestCode); public boolean onCustomRequest(int requestCode);
public void onMovePointer(int steps);
public void onMoveDeletePointer(int steps);
public void onUpWithDeletePointerActive();
public void onUpWithPointerActive();
public static final KeyboardActionListener EMPTY_LISTENER = new Adapter(); public static final KeyboardActionListener EMPTY_LISTENER = new Adapter();
public static class Adapter implements KeyboardActionListener { public static class Adapter implements KeyboardActionListener {
@ -125,8 +130,14 @@ public interface KeyboardActionListener {
@Override @Override
public void onFinishSlidingInput() {} public void onFinishSlidingInput() {}
@Override @Override
public boolean onCustomRequest(int requestCode) { public boolean onCustomRequest(int requestCode) { return false; }
return false; @Override
} public void onMovePointer(int steps) {}
@Override
public void onMoveDeletePointer(int steps) {}
@Override
public void onUpWithDeletePointerActive() {}
@Override
public void onUpWithPointerActive() {}
} }
} }

View File

@ -85,6 +85,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
// Parameters for pointer handling. // Parameters for pointer handling.
private static PointerTrackerParams sParams; private static PointerTrackerParams sParams;
private static final int sPointerStep = (int)(16.0 * Resources.getSystem().getDisplayMetrics().density);
private static GestureStrokeRecognitionParams sGestureStrokeRecognitionParams; private static GestureStrokeRecognitionParams sGestureStrokeRecognitionParams;
private static GestureStrokeDrawingParams sGestureStrokeDrawingParams; private static GestureStrokeDrawingParams sGestureStrokeDrawingParams;
private static boolean sNeedsPhantomSuddenMoveEventHack; private static boolean sNeedsPhantomSuddenMoveEventHack;
@ -128,6 +130,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
private int mLastX; private int mLastX;
private int mLastY; private int mLastY;
private int mStartX;
private int mStartY;
private long mStartTime;
private boolean mCursorMoved = false;
// true if keyboard layout has been changed. // true if keyboard layout has been changed.
private boolean mKeyboardLayoutHasBeenChanged; private boolean mKeyboardLayoutHasBeenChanged;
@ -691,6 +698,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
startRepeatKey(key); startRepeatKey(key);
startLongPressTimer(key); startLongPressTimer(key);
setPressedKeyGraphics(key, eventTime); setPressedKeyGraphics(key, eventTime);
mStartX = x;
mStartY = y;
mStartTime = System.currentTimeMillis();
} }
} }
@ -892,6 +903,29 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
final int lastX = mLastX; final int lastX = mLastX;
final int lastY = mLastY; final int lastY = mLastY;
final Key oldKey = mCurrentKey; final Key oldKey = mCurrentKey;
if (oldKey != null && oldKey.getCode() == Constants.CODE_SPACE) {
int steps = (x - mStartX) / sPointerStep;
final int swipeIgnoreTime = Settings.getInstance().getCurrent().mKeyLongpressTimeout / MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT;
if (steps != 0 && mStartTime + swipeIgnoreTime < System.currentTimeMillis()) {
mCursorMoved = true;
mStartX += steps * sPointerStep;
sListener.onMovePointer(steps);
}
return;
}
if (oldKey != null && oldKey.getCode() == Constants.CODE_DELETE) {
int steps = (x - mStartX) / sPointerStep;
if (steps != 0) {
sTimerProxy.cancelKeyTimersOf(this);
mCursorMoved = true;
mStartX += steps * sPointerStep;
sListener.onMoveDeletePointer(steps);
}
return;
}
final Key newKey = onMoveKey(x, y); final Key newKey = onMoveKey(x, y);
if (sGestureEnabler.shouldHandleGesture()) { if (sGestureEnabler.shouldHandleGesture()) {
@ -966,6 +1000,14 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
// Release the last pressed key. // Release the last pressed key.
setReleasedKeyGraphics(currentKey, true /* withAnimation */); setReleasedKeyGraphics(currentKey, true /* withAnimation */);
if(mCursorMoved && currentKey.getCode() == Constants.CODE_DELETE) {
sListener.onUpWithDeletePointerActive();
}
if(mCursorMoved) {
sListener.onUpWithPointerActive();
}
if (isShowingMoreKeysPanel()) { if (isShowingMoreKeysPanel()) {
if (!mIsTrackingForActionDisabled) { if (!mIsTrackingForActionDisabled) {
final int translatedX = mMoreKeysPanel.translateX(x); final int translatedX = mMoreKeysPanel.translateX(x);
@ -988,6 +1030,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
return; return;
} }
if (mCursorMoved) {
mCursorMoved = false;
return;
}
if (mIsTrackingForActionDisabled) { if (mIsTrackingForActionDisabled) {
return; return;
} }
@ -1018,6 +1064,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
if (isShowingMoreKeysPanel()) { if (isShowingMoreKeysPanel()) {
return; return;
} }
if (mCursorMoved) {
return;
}
final Key key = getKey(); final Key key = getKey();
if (key == null) { if (key == null) {
return; return;

View File

@ -41,6 +41,7 @@ import android.os.IBinder;
import android.os.Message; import android.os.Message;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.text.InputType; import android.text.InputType;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.util.PrintWriterPrinter; import android.util.PrintWriterPrinter;
import android.util.Printer; import android.util.Printer;
@ -158,6 +159,7 @@ public class LatinIMELegacy implements KeyboardActionListener,
private static final String SCHEME_PACKAGE = "package"; private static final String SCHEME_PACKAGE = "package";
final Settings mSettings; final Settings mSettings;
private Locale mLocale;
final DictionaryFacilitator mDictionaryFacilitator = final DictionaryFacilitator mDictionaryFacilitator =
DictionaryFacilitatorProvider.getDictionaryFacilitator( DictionaryFacilitatorProvider.getDictionaryFacilitator(
false /* isNeededForSpellChecking */); false /* isNeededForSpellChecking */);
@ -671,18 +673,18 @@ public class LatinIMELegacy implements KeyboardActionListener,
// Has to be package-visible for unit tests // Has to be package-visible for unit tests
@UsedForTesting @UsedForTesting
void loadSettings() { void loadSettings() {
final Locale locale = mRichImm.getCurrentSubtypeLocale(); mLocale = mRichImm.getCurrentSubtypeLocale();
final EditorInfo editorInfo = mInputMethodService.getCurrentInputEditorInfo(); final EditorInfo editorInfo = mInputMethodService.getCurrentInputEditorInfo();
final InputAttributes inputAttributes = new InputAttributes( final InputAttributes inputAttributes = new InputAttributes(
editorInfo, mInputMethodService.isFullscreenMode(), mInputMethodService.getPackageName()); editorInfo, mInputMethodService.isFullscreenMode(), mInputMethodService.getPackageName());
mSettings.loadSettings(mInputMethodService, locale, inputAttributes); mSettings.loadSettings(mInputMethodService, mLocale, inputAttributes);
final SettingsValues currentSettingsValues = mSettings.getCurrent(); final SettingsValues currentSettingsValues = mSettings.getCurrent();
AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(currentSettingsValues); AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(currentSettingsValues);
// This method is called on startup and language switch, before the new layout has // This method is called on startup and language switch, before the new layout has
// been displayed. Opening dictionaries never affects responsivity as dictionaries are // been displayed. Opening dictionaries never affects responsivity as dictionaries are
// asynchronously loaded. // asynchronously loaded.
if (!mHandler.hasPendingReopenDictionaries()) { if (!mHandler.hasPendingReopenDictionaries()) {
resetDictionaryFacilitator(locale); resetDictionaryFacilitator(mLocale);
} }
refreshPersonalizationDictionarySession(currentSettingsValues); refreshPersonalizationDictionarySession(currentSettingsValues);
resetDictionaryFacilitatorIfNecessary(); resetDictionaryFacilitatorIfNecessary();
@ -1365,6 +1367,54 @@ public class LatinIMELegacy implements KeyboardActionListener,
return false; return false;
} }
@Override
public void onMovePointer(int steps) {
if (mInputLogic.mConnection.hasCursorPosition()) {
if (TextUtils.getLayoutDirectionFromLocale(mLocale) == View.LAYOUT_DIRECTION_RTL)
steps = -steps;
steps = mInputLogic.mConnection.getUnicodeSteps(steps, true);
final int end = mInputLogic.mConnection.getExpectedSelectionEnd() + steps;
final int start = mInputLogic.mConnection.hasSelection() ? mInputLogic.mConnection.getExpectedSelectionStart() : end;
mInputLogic.finishInput();
mInputLogic.mConnection.setSelection(start, end);
} else {
for (; steps < 0; steps++)
mInputLogic.sendDownUpKeyEvent(KeyEvent.KEYCODE_DPAD_LEFT, 0);
for (; steps > 0; steps--)
mInputLogic.sendDownUpKeyEvent(KeyEvent.KEYCODE_DPAD_RIGHT, 0);
}
}
@Override
public void onMoveDeletePointer(int steps) {
if (mInputLogic.mConnection.hasCursorPosition()) {
steps = mInputLogic.mConnection.getUnicodeSteps(steps, false);
final int end = mInputLogic.mConnection.getExpectedSelectionEnd();
final int start = mInputLogic.mConnection.getExpectedSelectionStart() + steps;
if (start > end)
return;
mInputLogic.finishInput();
mInputLogic.mConnection.setSelection(start, end);
} else {
for (; steps < 0; steps++)
mInputLogic.sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL, 0);
}
}
@Override
public void onUpWithDeletePointerActive() {
if (mInputLogic.mConnection.hasSelection())
mInputLogic.sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL, 0);
}
@Override
public void onUpWithPointerActive() {
mInputLogic.restartSuggestionsOnWordTouchedByCursor(mSettings.getCurrent(), false, mKeyboardSwitcher.getCurrentKeyboardScriptId());
}
private boolean isShowingOptionDialog() { private boolean isShowingOptionDialog() {
return mOptionsDialog != null && mOptionsDialog.isShowing(); return mOptionsDialog != null && mOptionsDialog.isShowing();
} }

View File

@ -1044,4 +1044,41 @@ public final class RichInputConnection implements PrivateCommandPerformer {
return InputConnectionCompatUtils.requestCursorUpdates( return InputConnectionCompatUtils.requestCursorUpdates(
mIC, enableMonitor, requestImmediateCallback); mIC, enableMonitor, requestImmediateCallback);
} }
public boolean hasCursorPosition() {
return mExpectedSelStart != INVALID_CURSOR_POSITION && mExpectedSelEnd != INVALID_CURSOR_POSITION;
}
/**
* Some chars, such as emoji consist of 2 chars (surrogate pairs). We should treat them as one character.
*/
public int getUnicodeSteps(int chars, boolean rightSidePointer) {
int steps = 0;
if (chars < 0) {
CharSequence charsBeforeCursor = rightSidePointer && hasSelection() ?
getSelectedText(0) :
getTextBeforeCursor(-chars * 2, 0);
if (charsBeforeCursor != null) {
for (int i = charsBeforeCursor.length() - 1; i >= 0 && chars < 0; i--, chars++, steps--) {
if (Character.isSurrogate(charsBeforeCursor.charAt(i))) {
steps--;
i--;
}
}
}
} else if (chars > 0) {
CharSequence charsAfterCursor = !rightSidePointer && hasSelection() ?
getSelectedText(0) :
getTextAfterCursor(chars * 2, 0);
if (charsAfterCursor != null) {
for (int i = 0; i < charsAfterCursor.length() && chars > 0; i++, chars--, steps++) {
if (Character.isSurrogate(charsAfterCursor.charAt(i))) {
steps++;
i++;
}
}
}
}
return steps;
}
} }