Skip to content

Delete swipe: horizontal-right deselect after a vertical-only swipe - #686

Merged
LeanBitLab merged 1 commit into
LeanBitLab:mainfrom
Zokii0:delete-swipe-deselect
Oct 16, 2026
Merged

LeanBitLab merged 1 commit into
LeanBitLab:mainfrom
Zokii0:delete-swipe-deselect

Conversation

@Zokii0

@Zokii0 Zokii0 commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

Related: issue #467 (follow-up comment about vertical delete swipe).

Problem

After a purely vertical delete swipe (selection grows line by line upwards), swiping right on the delete key did nothing. The user could only shorten the selection on the reached line if the first swipe had been diagonal (i.e. it had also moved left), because rightward movement could only undo a previous leftward movement.

Root cause

KeyboardActionListenerImpl keeps the delete-swipe selection as a line index plus a horizontal offset, not as a real selection start:

  • deleteSwipeLineIndex is changed by vertical steps.
  • deleteSwipeCharOffset (char mode) / deleteSwipeWordIndex (word-by-word mode) is changed by horizontal steps.

Both horizontal values were clamped with coerceAtLeast(0). After a vertical-only swipe they are 0, so a rightward step (stepsX > 0) was clamped back to 0 and no selection change happened.

Chosen approach

Allow the horizontal value to become negative while the selection base is on a line above the swipe start. Negative means "selection start is to the right of the line start", i.e. already selected text on that line is deselected again. This is the same behavior the diagonal swipe already had, now also available after a vertical-only swipe.

The alternative (making the vertical selection itself more precise) is not reliable, because line starts are estimated (getLineBoundariesBackwards wraps long lines at ~40-50 chars at whitespace) and do not match the editor's real visual lines.

Changes

All in app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt:

  1. onMoveDeletePointerChars2D

    • Lower bound of deleteSwipeCharOffset is no longer 0 but -(selectionAnchor - lineBase), where selectionAnchor is the selection start when the swipe began and lineBase is the start of the currently used line. The selection start therefore can never move to the right of where the swipe started, so text that was selected before the swipe is never deselected.
    • On the first line (lineIndex == 0) lineBase == selectionAnchor, so the bound is 0 and behavior is identical to before.
    • When a vertical step actually changes the line and the offset is negative, it is reset to 0 (the selection is re-anchored at the start of the new line).
  2. onMoveDeletePointerWords2D

    • Same idea for deleteSwipeWordIndex: negative values select the n-th word boundary to the right of the line start, limited to the boundaries that exist between the line start and the swipe start. Same reset to 0 on a line change.
    • Positive values and the existing left/up logic are unchanged.
  3. New helper getWordBoundariesForwards(text, textEndOffset, fromOffset) in the companion object. Reuses getWordBoundariesBackwards, filters positions > fromOffset and reverses them, so selecting (left) and deselecting (right) step over exactly the same positions.

  4. Small refactor: the repeated "line base" expression became deleteSwipeLineBase(), and deleteSwipeSelectionAnchor() was added. No behavior change from the refactor itself.

Tests: WordDeleteSwipeTest.kt gets 4 new tests for getWordBoundariesForwards (ordering, empty cases, consistency with the backward boundaries, non-zero text offset).

What is intentionally not changed

  • Forward delete (KeyCode.FORWARD_DELETE / FORWARD_DELETE_WORD): handled in onCodeInput, not in the delete-swipe code. Untouched.
  • 2D backspace swipe deselect drift filter (the ≤45° filter in PointerTracker.kt, release notes v4.2.9-beta5): PointerTracker.kt is not modified. It still zeroes accidental upward stepsY during a rightward swipe before onMoveDeletePointer is called. Since stepsX is computed independently of that filter, the two changes do not interact.
  • Other callers (ClipboardHistoryView, HandwritingView) use the 1-argument onMoveDeletePointer(steps), which passes stepsY = 0. They can never leave line index 0, where the new lower bound is 0, so their behavior is unchanged.
  • No-selection case: with no vertical movement the lower bound is 0 as before, so swiping right on the delete key without having selected anything still does nothing.

Resulting behavior

Gesture Before After
Up (vertical only), then right nothing happens selection start moves right on the reached line (per char / per word)
Up, then right past the swipe start - stops at the swipe start, never deselects earlier selection
Up, then right, then left - moves back left symmetrically, no "dead" steps
Up, right, then up again - negative offset reset, new line starts clean
Diagonal swipe, left then right works unchanged
Right swipe with no vertical swipe before nothing nothing

Not verified

⚠️ I could not build or run the Android project in my environment, so the change is not compiled and the unit tests were not run. The expected values in the new tests were checked with a line-by-line port of the helper logic. Please run ./gradlew testDebugUnitTest (at least WordDeleteSwipeTest) and try the gesture on a device in both char mode and word-by-word mode, including a long wrapped line.

Suggested release note: "Delete swipe: after a vertical swipe, swiping right now deselects text on the reached line (per character or per word)."

@LeanBitLab

Copy link
Copy Markdown
Owner

Oh thats what you meant earlier

@Zokii0

Zokii0 commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

Did you try it, what do you think? Need some fix or?

@LeanBitLab
LeanBitLab merged commit 16f4852 into LeanBitLab:main Oct 16, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants