0
0
mirror of https://github.com/florisboard/florisboard.git synced 2024-09-19 19:42:20 +02:00

Improve moving gestures detection and UX

This commit is contained in:
Patrick Goldinger 2021-01-31 03:52:16 +01:00
parent c2998c9a2e
commit adb69dc365
2 changed files with 139 additions and 55 deletions

View File

@ -63,36 +63,59 @@ abstract class SwipeGesture {
* @property listener The listener to report detected swipes to.
*/
class Detector(private val context: Context, private val listener: Listener) {
private val eventList: MutableList<MotionEvent> = mutableListOf()
private var indexFirst: Int = 0
private var indexLastMoveRecognized: Int = 0
private var firstMotionEvent: MotionEvent? = null
private var lastMotionEvent: MotionEvent? = null
private var absUnitCountX: Int = 0
private var absUnitCountY: Int = 0
private var unitWidth: Double = numericValue(context, DistanceThreshold.NORMAL) / 4.0
var distanceThreshold: DistanceThreshold = DistanceThreshold.NORMAL
set(value) {
field = value
unitWidth = numericValue(context, value) / 4.0
}
var velocityThreshold: VelocityThreshold = VelocityThreshold.NORMAL
fun onTouchEvent(event: MotionEvent): Boolean {
/**
* Method which evaluates if a given [event] is a gesture.
*
* @param event The MotionEvent which should be checked for a gesture.
* @param alwaysTriggerOnMove Set to true if the moving detection algorithm should always
* trigger, regardless of the distance from the previous event. Defaults to false.
* @return True if the given [event] is a gesture, false otherwise.
*/
fun onTouchEvent(event: MotionEvent, alwaysTriggerOnMove: Boolean = false): Boolean {
try {
when (event.actionMasked) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_POINTER_DOWN -> {
clearEventList()
eventList.add(MotionEvent.obtainNoHistory(event))
resetState()
firstMotionEvent = MotionEvent.obtainNoHistory(event)
lastMotionEvent = firstMotionEvent
}
MotionEvent.ACTION_MOVE -> {
eventList.add(MotionEvent.obtainNoHistory(event))
val firstEvent = eventList[indexFirst]
val lastEvent = eventList[indexLastMoveRecognized]
val diffX = event.x - lastEvent.x
val diffY = event.y - lastEvent.y
val distanceThresholdNV = numericValue(context, distanceThreshold) / 4.0f
return if (abs(diffX) > distanceThresholdNV || abs(diffY) > distanceThresholdNV) {
indexLastMoveRecognized = eventList.size - 1
val direction = detectDirection(diffX.toDouble(), diffY.toDouble())
val firstEvent = firstMotionEvent ?: return false
val absDiffX = event.x - firstEvent.x
val absDiffY = event.y - firstEvent.y
val lastEvent = lastMotionEvent ?: return false
val relDiffX = event.x - lastEvent.x
val relDiffY = event.y - lastEvent.y
return if (alwaysTriggerOnMove || abs(relDiffX) > unitWidth || abs(relDiffY) > unitWidth) {
lastMotionEvent = MotionEvent.obtainNoHistory(event)
val direction = detectDirection(relDiffX.toDouble(), relDiffY.toDouble())
val newAbsUnitCountX = (absDiffX / unitWidth).toInt()
val newAbsUnitCountY = (absDiffY / unitWidth).toInt()
val relUnitCountX = newAbsUnitCountX - absUnitCountX
val relUnitCountY = newAbsUnitCountY - absUnitCountY
absUnitCountX = newAbsUnitCountX
absUnitCountY = newAbsUnitCountY
listener.onSwipe(Event(
direction = direction,
type = Type.TOUCH_MOVE,
diffX = event.x - firstEvent.x,
diffY = event.y - firstEvent.y
absUnitCountX,
absUnitCountY,
relUnitCountX,
relUnitCountY
))
} else {
false
@ -100,32 +123,36 @@ abstract class SwipeGesture {
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> {
val firstEvent = eventList[indexFirst]
val diffX = event.x - firstEvent.x
val diffY = event.y - firstEvent.y
val distanceThresholdNV = numericValue(context, distanceThreshold)
val firstEvent = firstMotionEvent ?: return false
val absDiffX = event.x - firstEvent.x
val absDiffY = event.y - firstEvent.y
/*val velocityThresholdNV = numericValue(velocityThreshold)
val velocity =
((convertPixelsToDp(
sqrt(diffX.pow(2) + diffY.pow(2)),
context
) / event.downTime) * 10.0f.pow(8)).toInt()*/
clearEventList()
// return if ((abs(diffX) > distanceThresholdNV || abs(diffY) > distanceThresholdNV) && velocity >= velocityThresholdNV) {
return if ((abs(diffX) > distanceThresholdNV || abs(diffY) > distanceThresholdNV)) {
val direction = detectDirection(diffX.toDouble(), diffY.toDouble())
val ret = if ((abs(absDiffX) > (unitWidth * 4.0) || abs(absDiffY) > (unitWidth * 4.0))) {
val direction = detectDirection(absDiffX.toDouble(), absDiffY.toDouble())
absUnitCountX = (absDiffX / unitWidth).toInt()
absUnitCountY = (absDiffY / unitWidth).toInt()
listener.onSwipe(Event(
direction = direction,
type = Type.TOUCH_UP,
diffX = diffX,
diffY = diffY
absUnitCountX,
absUnitCountY,
absUnitCountX,
absUnitCountY
))
} else {
false
}
resetState()
return ret
}
MotionEvent.ACTION_CANCEL -> {
clearEventList()
resetState()
}
else -> return false
}
@ -175,22 +202,45 @@ abstract class SwipeGesture {
}
/**
* Cleans up and clears the event list.
* Resets the state.
*/
private fun clearEventList() {
for (event in eventList) {
event.recycle()
}
eventList.clear()
indexFirst = 0
indexLastMoveRecognized = 0
private fun resetState() {
firstMotionEvent = null
lastMotionEvent = null
absUnitCountX = 0
absUnitCountY = 0
}
}
interface Listener {
/**
* An interface which provides an abstract callback function, which will be called for any
* detected swipe event.
*/
fun interface Listener {
fun onSwipe(event: Event): Boolean
}
/**
* Data class which describes a single gesture event.
*/
data class Event(
/** The direction of the swipe. */
val direction: Direction,
/** The type of the swipe. */
val type: Type,
/** The unit count on the x-axis, measured from the first event (ACTION_DOWN). */
val absUnitCountX: Int,
/** The unit count on the y-axis, measured from the first event (ACTION_DOWN). */
val absUnitCountY: Int,
/** The unit count on the x-axis, measured from the last event (ACTION_MOVE). */
val relUnitCountX: Int,
/** The unit count on the y-axis, measured from the last event (ACTION_MOVE). */
val relUnitCountY: Int
)
/**
* ENum which defines the direction of the detected swipe.
*/
enum class Direction {
UP_LEFT,
UP,
@ -202,13 +252,9 @@ abstract class SwipeGesture {
LEFT,
}
data class Event(
val direction: Direction,
val type: Type,
val diffX: Float,
val diffY: Float
)
/**
* Enum which defines the type of the gesture.
*/
enum class Type {
TOUCH_UP,
TOUCH_MOVE;

View File

@ -42,6 +42,7 @@ import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.ime.theme.ThemeValue
import dev.patrickgold.florisboard.util.*
import java.util.*
import kotlin.math.abs
/**
* View class for managing the rendering and the events of a single keyboard key.
@ -61,6 +62,8 @@ class KeyView(
field = value
updateKeyPressedBackground()
}
private var initSelectionStart: Int = 0
private var initSelectionEnd: Int = 0
private var hasTriggeredGestureMove: Boolean = false
private var keyHintMode: KeyHintMode = KeyHintMode.DISABLED
private val longKeyPressHandler: Handler = Handler(context.mainLooper)
@ -204,7 +207,11 @@ class KeyView(
*/
fun onFlorisTouchEvent(event: MotionEvent?): Boolean {
if (event == null || !isEnabled) return false
if (swipeGestureDetector.onTouchEvent(event)) {
val alwaysTriggerOnMove = (hasTriggeredGestureMove
&& florisboard?.activeEditorInstance?.isRawInputEditor == false
&& (data.code == KeyCode.DELETE && prefs.gestures.deleteKeySwipeLeft == SwipeAction.DELETE_CHARACTERS_PRECISELY
|| data.code == KeyCode.SPACE))
if (swipeGestureDetector.onTouchEvent(event, alwaysTriggerOnMove)) {
isKeyPressed = false
longKeyPressHandler.cancelAll()
repeatedKeyPressHandler.cancelAll()
@ -246,6 +253,8 @@ class KeyView(
}
}
if (data.code == KeyCode.SPACE) {
initSelectionStart = florisboard?.activeEditorInstance?.selection?.start ?: 0
initSelectionEnd = florisboard?.activeEditorInstance?.selection?.end ?: 0
longKeyPressHandler.postDelayed((delayMillis * 2.5f).toLong()) {
when (prefs.gestures.spaceBarLongPress) {
SwipeAction.NO_ACTION,
@ -290,7 +299,6 @@ class KeyView(
repeatedKeyPressHandler.cancelAll()
if (data.code != KeyCode.SHIFT) {
if (hasTriggeredGestureMove && data.code == KeyCode.DELETE) {
hasTriggeredGestureMove = false
florisboard?.activeEditorInstance?.apply {
if (selection.isSelectionMode) {
deleteBackwards()
@ -306,6 +314,7 @@ class KeyView(
keyboardView.popupManager.hide()
}
}
hasTriggeredGestureMove = false
isKeyPressed = false
}
else -> return false
@ -318,14 +327,14 @@ class KeyView(
* defined in the prefs.
*/
override fun onSwipe(event: SwipeGesture.Event): Boolean {
val florisboard = florisboard ?: return false
return when (data.code) {
KeyCode.DELETE -> when (event.type) {
SwipeGesture.Type.TOUCH_MOVE -> when (prefs.gestures.deleteKeySwipeLeft) {
SwipeAction.DELETE_CHARACTERS_PRECISELY -> {
val charWidth = SwipeGesture.numericValue(context, swipeGestureDetector.distanceThreshold) / 4.0f
florisboard?.activeEditorInstance?.apply {
florisboard.activeEditorInstance.apply {
setSelection(
(selection.end - (event.diffX.times(-1) / charWidth).toInt()).coerceIn(0, selection.end),
(selection.end + event.absUnitCountX).coerceIn(0, selection.end),
selection.end
)
}
@ -335,7 +344,7 @@ class KeyView(
}
SwipeAction.DELETE_WORDS_PRECISELY -> when (event.direction) {
SwipeGesture.Direction.LEFT -> {
florisboard?.activeEditorInstance?.apply {
florisboard.activeEditorInstance.apply {
leftAppendWordToSelection()
}
hasTriggeredGestureMove = true
@ -343,7 +352,7 @@ class KeyView(
true
}
SwipeGesture.Direction.RIGHT -> {
florisboard?.activeEditorInstance?.apply {
florisboard.activeEditorInstance.apply {
leftPopWordFromSelection()
}
shouldBlockNextKeyCode = true
@ -358,17 +367,46 @@ class KeyView(
KeyCode.SPACE -> when (event.type) {
SwipeGesture.Type.TOUCH_MOVE -> when (event.direction) {
SwipeGesture.Direction.UP -> {
florisboard?.executeSwipeAction(prefs.gestures.spaceBarSwipeUp)
shouldBlockNextKeyCode = true
true
if (event.absUnitCountY.times(-1) >= 6) {
florisboard.executeSwipeAction(prefs.gestures.spaceBarSwipeUp)
hasTriggeredGestureMove = true
shouldBlockNextKeyCode = true
true
} else {
false
}
}
SwipeGesture.Direction.LEFT -> {
florisboard?.executeSwipeAction(prefs.gestures.spaceBarSwipeLeft)
if (prefs.gestures.spaceBarSwipeLeft == SwipeAction.MOVE_CURSOR_LEFT) {
if (!florisboard.activeEditorInstance.isRawInputEditor) {
val s = (initSelectionEnd + event.absUnitCountX).coerceIn(0, florisboard.activeEditorInstance.cachedText.length)
florisboard.activeEditorInstance.setSelection(s, s)
} else {
for (n in 0 until abs(event.relUnitCountX)) {
florisboard.executeSwipeAction(prefs.gestures.spaceBarSwipeLeft)
}
}
} else {
florisboard.executeSwipeAction(prefs.gestures.spaceBarSwipeLeft)
}
hasTriggeredGestureMove = true
shouldBlockNextKeyCode = true
true
}
SwipeGesture.Direction.RIGHT -> {
florisboard?.executeSwipeAction(prefs.gestures.spaceBarSwipeRight)
if (prefs.gestures.spaceBarSwipeRight == SwipeAction.MOVE_CURSOR_RIGHT) {
if (!florisboard.activeEditorInstance.isRawInputEditor) {
val s = (initSelectionEnd + event.absUnitCountX).coerceIn(0, florisboard.activeEditorInstance.cachedText.length)
florisboard.activeEditorInstance.setSelection(s, s)
} else {
for (n in 0 until abs(event.relUnitCountX)) {
florisboard.executeSwipeAction(prefs.gestures.spaceBarSwipeRight)
}
}
} else {
florisboard.executeSwipeAction(prefs.gestures.spaceBarSwipeRight)
}
hasTriggeredGestureMove = true
shouldBlockNextKeyCode = true
true
}