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 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 class Adapter implements KeyboardActionListener {
@ -125,8 +130,14 @@ public interface KeyboardActionListener {
@Override
public void onFinishSlidingInput() {}
@Override
public boolean onCustomRequest(int requestCode) {
return false;
}
public boolean onCustomRequest(int requestCode) { 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.
private static PointerTrackerParams sParams;
private static final int sPointerStep = (int)(16.0 * Resources.getSystem().getDisplayMetrics().density);
private static GestureStrokeRecognitionParams sGestureStrokeRecognitionParams;
private static GestureStrokeDrawingParams sGestureStrokeDrawingParams;
private static boolean sNeedsPhantomSuddenMoveEventHack;
@ -128,6 +130,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
private int mLastX;
private int mLastY;
private int mStartX;
private int mStartY;
private long mStartTime;
private boolean mCursorMoved = false;
// true if keyboard layout has been changed.
private boolean mKeyboardLayoutHasBeenChanged;
@ -691,6 +698,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
startRepeatKey(key);
startLongPressTimer(key);
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 lastY = mLastY;
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);
if (sGestureEnabler.shouldHandleGesture()) {
@ -966,6 +1000,14 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
// Release the last pressed key.
setReleasedKeyGraphics(currentKey, true /* withAnimation */);
if(mCursorMoved && currentKey.getCode() == Constants.CODE_DELETE) {
sListener.onUpWithDeletePointerActive();
}
if(mCursorMoved) {
sListener.onUpWithPointerActive();
}
if (isShowingMoreKeysPanel()) {
if (!mIsTrackingForActionDisabled) {
final int translatedX = mMoreKeysPanel.translateX(x);
@ -988,6 +1030,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
return;
}
if (mCursorMoved) {
mCursorMoved = false;
return;
}
if (mIsTrackingForActionDisabled) {
return;
}
@ -1018,6 +1064,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element,
if (isShowingMoreKeysPanel()) {
return;
}
if (mCursorMoved) {
return;
}
final Key key = getKey();
if (key == null) {
return;

View File

@ -41,6 +41,7 @@ import android.os.IBinder;
import android.os.Message;
import android.preference.PreferenceManager;
import android.text.InputType;
import android.text.TextUtils;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Printer;
@ -158,6 +159,7 @@ public class LatinIMELegacy implements KeyboardActionListener,
private static final String SCHEME_PACKAGE = "package";
final Settings mSettings;
private Locale mLocale;
final DictionaryFacilitator mDictionaryFacilitator =
DictionaryFacilitatorProvider.getDictionaryFacilitator(
false /* isNeededForSpellChecking */);
@ -671,18 +673,18 @@ public class LatinIMELegacy implements KeyboardActionListener,
// Has to be package-visible for unit tests
@UsedForTesting
void loadSettings() {
final Locale locale = mRichImm.getCurrentSubtypeLocale();
mLocale = mRichImm.getCurrentSubtypeLocale();
final EditorInfo editorInfo = mInputMethodService.getCurrentInputEditorInfo();
final InputAttributes inputAttributes = new InputAttributes(
editorInfo, mInputMethodService.isFullscreenMode(), mInputMethodService.getPackageName());
mSettings.loadSettings(mInputMethodService, locale, inputAttributes);
mSettings.loadSettings(mInputMethodService, mLocale, inputAttributes);
final SettingsValues currentSettingsValues = mSettings.getCurrent();
AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(currentSettingsValues);
// This method is called on startup and language switch, before the new layout has
// been displayed. Opening dictionaries never affects responsivity as dictionaries are
// asynchronously loaded.
if (!mHandler.hasPendingReopenDictionaries()) {
resetDictionaryFacilitator(locale);
resetDictionaryFacilitator(mLocale);
}
refreshPersonalizationDictionarySession(currentSettingsValues);
resetDictionaryFacilitatorIfNecessary();
@ -1365,6 +1367,54 @@ public class LatinIMELegacy implements KeyboardActionListener,
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() {
return mOptionsDialog != null && mOptionsDialog.isShowing();
}

View File

@ -1044,4 +1044,41 @@ public final class RichInputConnection implements PrivateCommandPerformer {
return InputConnectionCompatUtils.requestCursorUpdates(
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;
}
}