Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backspace swipe right to delete word and Slide gestures improvements #439

Merged
merged 24 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a4405f4
Grouped together 'slide gestures checkbox' and 'slide sensitivity sli…
sslater11 Sep 23, 2023
f284722
Enable/Disable 'Slide sensitivity' slider when the 'slide gestures' c…
sslater11 Sep 23, 2023
09fe4c5
Better swipe selection toggle and better swipe delete toggle.
sslater11 Sep 23, 2023
c0dd12c
Made swiping right on backspace key delete a whole word to the right.
sslater11 Sep 23, 2023
0f3d1f1
Merge branch 'main' into slide-gestures
dessalines Sep 26, 2023
824b91a
Swipe up or down to toggle slide gestures text selection.
sslater11 Oct 5, 2023
4d79ac6
Added a deadzone for slide gestures to allow normal swipes on spaceba…
sslater11 Oct 8, 2023
3ed9680
Added up and down swipes to spacebar to move cursor up and down.
sslater11 Oct 8, 2023
6dd9648
Merge branch 'slide-gestures' of https://github.com/sslater11/thumb-k…
sslater11 Oct 8, 2023
b0bb7d2
Merge branch 'main' into slide-gestures
sslater11 Oct 8, 2023
0020b1d
Fixes #410 - Added cursor acceleration for slide gestures on the spac…
sslater11 Oct 11, 2023
e6afd91
Format kotlin
sslater11 Oct 11, 2023
de489ba
Merge branch 'main' into slide-gestures
dessalines Oct 11, 2023
b867939
Fixing merge issue.
dessalines Oct 11, 2023
2220dc3
Fixed issue with selected text being deleted when we use the spacebar…
sslater11 Oct 12, 2023
a0c03d3
Copy/Cut actions now copy/cut all text if nothing is selected (#469)
sslater11 Oct 12, 2023
cf01818
Adding threshold acceleration
WadeWT Oct 19, 2023
5dcd7ae
Added more cursor acceleration modes.
sslater11 Oct 19, 2023
7c0f76d
DB Migration: Added settings menu for slide gestures cursor accelerat…
sslater11 Oct 19, 2023
ffe3c1e
Grouped all slide gesture settings together.
sslater11 Oct 19, 2023
543c7cb
Merge branch 'slide-gestures' into threshold-acceleration
sslater11 Oct 19, 2023
7350ea7
Merge pull request #2 from WadeWT/threshold-acceleration
sslater11 Oct 19, 2023
ccb67be
Added slide gestures Threshold Acceleration to the settings menu.
sslater11 Oct 19, 2023
e20636f
Merged with upstream/main
sslater11 Oct 20, 2023
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
Prev Previous commit
Next Next commit
Fixes #410 - Added cursor acceleration for slide gestures on the spac…
…ebar and backspace key.
  • Loading branch information
sslater11 committed Oct 11, 2023
commit 0020b1d4c69d20b477bd0ca2d6486e3da84c4e5d
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
Expand All @@ -50,6 +51,7 @@ import com.dessalines.thumbkey.utils.KeyItemC
import com.dessalines.thumbkey.utils.Selection
import com.dessalines.thumbkey.utils.SlideType
import com.dessalines.thumbkey.utils.SwipeDirection
import com.dessalines.thumbkey.utils.acceleratingCursorDistance
import com.dessalines.thumbkey.utils.buildTapActions
import com.dessalines.thumbkey.utils.colorVariantToColor
import com.dessalines.thumbkey.utils.doneKeyAction
Expand All @@ -58,6 +60,8 @@ import com.dessalines.thumbkey.utils.performKeyAction
import com.dessalines.thumbkey.utils.startSelection
import com.dessalines.thumbkey.utils.swipeDirection
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min

@Composable
fun KeyboardKey(
Expand Down Expand Up @@ -108,6 +112,7 @@ fun KeyboardKey(
var offsetX by remember { mutableFloatStateOf(0f) }
var offsetY by remember { mutableFloatStateOf(0f) }
var hasSlideMoveCursorTriggered by remember { mutableStateOf(false) }
var timeOfLastAccelerationInput by remember { mutableLongStateOf(0L) }

var selection by remember { mutableStateOf(Selection()) }

Expand Down Expand Up @@ -194,93 +199,90 @@ fun KeyboardKey(
if (key.slideType == SlideType.MOVE_CURSOR && slideEnabled) {
val slideSelectionOffsetTrigger = (keySizeDp.toPx() * 1.25) + minSwipeLength
if (abs(offsetY) > slideSelectionOffsetTrigger) {
hasSlideMoveCursorTriggered = true
// If user slides upwards, enable selection
hasSlideMoveCursorTriggered = true
if (!selection.active) {
// Activate selection
selection = startSelection(ime)
}
if (abs(offsetX) > slideSensitivity) {
if (offsetX < 0.00) {
selection.left()

val cursorMovement = acceleratingCursorDistance(offsetX, timeOfLastAccelerationInput)
timeOfLastAccelerationInput = System.currentTimeMillis()
if (cursorMovement >= 1 || cursorMovement <= -1) {
if (cursorMovement < 0.00) {
selection.left(abs(cursorMovement))
} else {
selection.right()
selection.right(abs(cursorMovement))
}
ime.currentInputConnection.setSelection(
selection.start,
selection.end,
)
// reset offsetX, do not reset offsetY when sliding, it will break selecting
offsetX = 0f
}
} else if ((abs(offsetX) > slideOffsetTrigger) ||
((abs(offsetX) > slideSensitivity) && hasSlideMoveCursorTriggered)
) {
hasSlideMoveCursorTriggered = true
} else if (((abs(offsetX) > slideOffsetTrigger) && !hasSlideMoveCursorTriggered) || hasSlideMoveCursorTriggered) {
// If user slides horizontally only, move cursor
if (selection.active) selection = Selection(0, 0, false)
val direction: Int
var shouldMove = false
if (offsetX < 0.00) {
// move left
if (ime.currentInputConnection.getTextBeforeCursor(
1,
0,
)?.length != 0
) {
shouldMove = true
}
direction = KeyEvent.KEYCODE_DPAD_LEFT
} else {
// move right
if (ime.currentInputConnection.getTextAfterCursor(
1,
0,
)?.length != 0
) {
shouldMove = true
}
direction = KeyEvent.KEYCODE_DPAD_RIGHT

// The first time we enter this, reset offsetX, because of the slideOffsetTrigger
if (!hasSlideMoveCursorTriggered) {
// reset offsetX, do not reset offsetY when sliding, it will break selecting
offsetX = 0f
hasSlideMoveCursorTriggered = true
}
if (shouldMove) {
val action = KeyAction.SendEvent(
KeyEvent(
KeyEvent.ACTION_DOWN,
direction,
),
)
performKeyAction(
action = action,
ime = ime,
autoCapitalize = autoCapitalize,
onToggleShiftMode = onToggleShiftMode,
onToggleNumericMode = onToggleNumericMode,
onToggleEmojiMode = onToggleEmojiMode,
onToggleCapsLock = onToggleCapsLock,
onAutoCapitalize = onAutoCapitalize,
onSwitchLanguage = onSwitchLanguage,
onSwitchPosition = onSwitchPosition,

if (selection.active) {
// Move the cursor to the beginning or end of the selection and exit selection.
val location = if (offsetX < 0) {
min(selection.start, selection.end)
} else {
max(selection.start, selection.end)
}

selection = Selection(location, location, false)
ime.currentInputConnection.setSelection(
selection.start,
selection.end,
)
}
// reset offsetX, do not reset offsetY when sliding, it will break selecting
offsetX = 0f

var cursorMovement = acceleratingCursorDistance(offsetX, timeOfLastAccelerationInput)
timeOfLastAccelerationInput = System.currentTimeMillis()
if (cursorMovement > 0) {
// Increment distance by one, because a value of 2 moves the cursor by 1 character.
cursorMovement += 1
}
// For some reason, '2' moves the cursor to the right by 1 character.
// '-1' moves the cursor to the left by 1 character.
if (cursorMovement >= 2 || cursorMovement <= -1) {
ime.currentInputConnection.commitText("", cursorMovement)
// reset offsetX, do not reset offsetY when sliding, it will break selecting
offsetX = 0f
}
}
} else if (key.slideType == SlideType.DELETE && slideEnabled) {
if (!selection.active) {
timeOfLastAccelerationInput = System.currentTimeMillis()
// Activate selection, first detection is large enough to preserve swipe actions.
if ((abs(offsetX) > slideOffsetTrigger)) {
// reset offsetX, do not reset offsetY when sliding, it will break selecting
offsetX = 0f
selection = startSelection(ime)
}
} else {
if (abs(offsetX) > slideSensitivity) {
if (offsetX < 0.00) {
selection.left()
val cursorMovement = acceleratingCursorDistance(offsetX, timeOfLastAccelerationInput)
timeOfLastAccelerationInput = System.currentTimeMillis()
if (cursorMovement >= 1 || cursorMovement <= -1) {
if (cursorMovement < 0.00) {
selection.left(abs(cursorMovement))
} else {
selection.right()
selection.right(abs(cursorMovement))
}
ime.currentInputConnection.setSelection(
selection.start,
selection.end,
)
// reset offsetX, do not reset offsetY when sliding, it will break selecting
offsetX = 0f
}
}
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/com/dessalines/thumbkey/utils/Types.kt
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,13 @@ data class Selection(
fun left() {
end -= 1
}
fun left(index: Int) {
end -= index
}
fun right() {
end += 1
}
fun right(index: Int) {
end += index
}
}
26 changes: 26 additions & 0 deletions app/src/main/java/com/dessalines/thumbkey/utils/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,32 @@ const val TAG = "com.thumbkey"

const val THUMBKEY_IME_NAME = "com.dessalines.thumbkey/.IMEService"

fun acceleratingCursorDistance(offsetX: Float, timeOfLastAccelerationInput: Long): Int {
val timeDifference = System.currentTimeMillis() - timeOfLastAccelerationInput
// Prevent division by 0 error.
var distance = if( timeDifference == 0L ) {
0f
} else {
offsetX / timeDifference
}

// Quadratic equation to make the swipe acceleration work along a curve.
val accelerationCurve = 0.3f // Fast and almost perfect.
// var accelerationCurve = 0.2f // Fast and almost perfect.
// var accelerationCurve = 0.1f // Slowish and moves almost a full line at a time.
// var accelerationCurve = 0.01f // is slow, only 1 char at a time.
if (distance < 0) {
distance = accelerationCurve * distance.pow(2)
// Set the value back to negative.
// A distance of -1 will move the cursor left by 1 character
distance *= -1
} else {
distance = accelerationCurve * distance.pow(2)
}

return distance.toInt()
}

@Composable
fun colorVariantToColor(colorVariant: ColorVariant): Color {
return when (colorVariant) {
Expand Down