From b57dc53d9383e28485d29c2a8b0db8103e312b96 Mon Sep 17 00:00:00 2001 From: gperrio Date: Wed, 10 Apr 2024 17:26:39 +0200 Subject: [PATCH] Android haptic feedback (#56) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Android: Add haptic feedback to overlay controls * Android: Add haptic feedback to overlay controls --------- Co-authored-by: João Vitor Polverari --- .../android/fragments/EmulationFragment.kt | 7 +++++++ .../lime3ds/android/overlay/InputOverlay.kt | 11 +++++++--- .../overlay/InputOverlayDrawableButton.kt | 5 ++++- .../overlay/InputOverlayDrawableDpad.kt | 16 +++++++++++++-- .../overlay/InputOverlayDrawableJoystick.kt | 20 ++++++++++++++++++- .../android/utils/EmulationMenuSettings.kt | 11 ++++++++-- .../main/res/menu/menu_overlay_options.xml | 5 +++++ .../app/src/main/res/values/strings.xml | 1 + 8 files changed, 67 insertions(+), 9 deletions(-) diff --git a/src/android/app/src/main/java/io/github/lime3ds/android/fragments/EmulationFragment.kt b/src/android/app/src/main/java/io/github/lime3ds/android/fragments/EmulationFragment.kt index a104802d6..7c5408167 100644 --- a/src/android/app/src/main/java/io/github/lime3ds/android/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/io/github/lime3ds/android/fragments/EmulationFragment.kt @@ -581,6 +581,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram popupMenu.menu.apply { findItem(R.id.menu_show_overlay).isChecked = EmulationMenuSettings.showOverlay findItem(R.id.menu_show_fps).isChecked = EmulationMenuSettings.showFps + findItem(R.id.menu_haptic_feedback).isChecked = EmulationMenuSettings.hapticFeedback findItem(R.id.menu_emulation_joystick_rel_center).isChecked = EmulationMenuSettings.joystickRelCenter findItem(R.id.menu_emulation_dpad_slide_enable).isChecked = @@ -601,6 +602,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram true } + R.id.menu_haptic_feedback -> { + EmulationMenuSettings.hapticFeedback = !EmulationMenuSettings.hapticFeedback + updateShowFpsOverlay() + true + } + R.id.menu_emulation_edit_layout -> { editControlsPlacement() binding.drawerLayout.close() diff --git a/src/android/app/src/main/java/io/github/lime3ds/android/overlay/InputOverlay.kt b/src/android/app/src/main/java/io/github/lime3ds/android/overlay/InputOverlay.kt index b9b91e244..54da5c7d3 100644 --- a/src/android/app/src/main/java/io/github/lime3ds/android/overlay/InputOverlay.kt +++ b/src/android/app/src/main/java/io/github/lime3ds/android/overlay/InputOverlay.kt @@ -85,13 +85,18 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex ) } + fun hapticFeedback(type:Int){ + if(EmulationMenuSettings.hapticFeedback) + performHapticFeedback(type) + } + override fun onTouch(v: View, event: MotionEvent): Boolean { if (isInEditMode) { return onTouchWhileEditing(event) } var shouldUpdateView = false for (button in overlayButtons) { - if (!button.updateStatus(event)) { + if (!button.updateStatus(event, this)) { continue } @@ -103,7 +108,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex shouldUpdateView = true } for (dpad in overlayDpads) { - if (!dpad.updateStatus(event, EmulationMenuSettings.dpadSlide)) { + if (!dpad.updateStatus(event, EmulationMenuSettings.dpadSlide, this)) { continue } NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.upId, dpad.upStatus) @@ -125,7 +130,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex shouldUpdateView = true } for (joystick in overlayJoysticks) { - if (!joystick.updateStatus(event)) { + if (!joystick.updateStatus(event, this)) { continue } val axisID = joystick.joystickId diff --git a/src/android/app/src/main/java/io/github/lime3ds/android/overlay/InputOverlayDrawableButton.kt b/src/android/app/src/main/java/io/github/lime3ds/android/overlay/InputOverlayDrawableButton.kt index 5aede6fe7..49b7ce023 100644 --- a/src/android/app/src/main/java/io/github/lime3ds/android/overlay/InputOverlayDrawableButton.kt +++ b/src/android/app/src/main/java/io/github/lime3ds/android/overlay/InputOverlayDrawableButton.kt @@ -9,6 +9,7 @@ import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Rect import android.graphics.drawable.BitmapDrawable +import android.view.HapticFeedbackConstants import android.view.MotionEvent import io.github.lime3ds.android.NativeLibrary @@ -51,7 +52,7 @@ class InputOverlayDrawableButton( * * @return true if value was changed */ - fun updateStatus(event: MotionEvent): Boolean { + fun updateStatus(event: MotionEvent, overlay:InputOverlay): Boolean { val pointerIndex = event.actionIndex val xPosition = event.getX(pointerIndex).toInt() val yPosition = event.getY(pointerIndex).toInt() @@ -67,6 +68,7 @@ class InputOverlayDrawableButton( } pressedState = true trackId = pointerId + overlay.hapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY) return true } if (isActionUp) { @@ -75,6 +77,7 @@ class InputOverlayDrawableButton( } pressedState = false trackId = -1 + overlay.hapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE) return true } return false diff --git a/src/android/app/src/main/java/io/github/lime3ds/android/overlay/InputOverlayDrawableDpad.kt b/src/android/app/src/main/java/io/github/lime3ds/android/overlay/InputOverlayDrawableDpad.kt index bbebc7500..e28fdafd5 100644 --- a/src/android/app/src/main/java/io/github/lime3ds/android/overlay/InputOverlayDrawableDpad.kt +++ b/src/android/app/src/main/java/io/github/lime3ds/android/overlay/InputOverlayDrawableDpad.kt @@ -9,6 +9,7 @@ import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Rect import android.graphics.drawable.BitmapDrawable +import android.view.HapticFeedbackConstants import android.view.MotionEvent import io.github.lime3ds.android.NativeLibrary @@ -59,7 +60,8 @@ class InputOverlayDrawableDpad( trackId = -1 } - fun updateStatus(event: MotionEvent, dpadSlide: Boolean): Boolean { + fun updateStatus(event: MotionEvent, dpadSlide: Boolean, overlay:InputOverlay): Boolean { + var isDown = false val pointerIndex = event.actionIndex val xPosition = event.getX(pointerIndex).toInt() val yPosition = event.getY(pointerIndex).toInt() @@ -73,6 +75,7 @@ class InputOverlayDrawableDpad( if (!bounds.contains(xPosition, yPosition)) { return false } + isDown = true trackId = pointerId } if (isActionUp) { @@ -84,6 +87,7 @@ class InputOverlayDrawableDpad( downButtonState = false leftButtonState = false rightButtonState = false + overlay.hapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE) return true } if (trackId == -1) { @@ -114,7 +118,15 @@ class InputOverlayDrawableDpad( downButtonState = yAxis > VIRT_AXIS_DEADZONE leftButtonState = xAxis < -VIRT_AXIS_DEADZONE rightButtonState = xAxis > VIRT_AXIS_DEADZONE - return upState != upButtonState || downState != downButtonState || leftState != leftButtonState || rightState != rightButtonState + + val stateChanged = upState != upButtonState || downState != downButtonState || leftState != leftButtonState || rightState != rightButtonState + + if(stateChanged) + overlay.hapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY) + else if(isDown) + overlay.hapticFeedback(HapticFeedbackConstants.CLOCK_TICK) + + return stateChanged } return false } diff --git a/src/android/app/src/main/java/io/github/lime3ds/android/overlay/InputOverlayDrawableJoystick.kt b/src/android/app/src/main/java/io/github/lime3ds/android/overlay/InputOverlayDrawableJoystick.kt index e963ad898..f3fcff6f1 100644 --- a/src/android/app/src/main/java/io/github/lime3ds/android/overlay/InputOverlayDrawableJoystick.kt +++ b/src/android/app/src/main/java/io/github/lime3ds/android/overlay/InputOverlayDrawableJoystick.kt @@ -9,6 +9,7 @@ import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Rect import android.graphics.drawable.BitmapDrawable +import android.view.HapticFeedbackConstants import android.view.MotionEvent import io.github.lime3ds.android.NativeLibrary import io.github.lime3ds.android.utils.EmulationMenuSettings @@ -41,6 +42,8 @@ class InputOverlayDrawableJoystick( var trackId = -1 var xAxis = 0f var yAxis = 0f + var angle = 0f + var radius = 0f private var controlPositionX = 0 private var controlPositionY = 0 private var previousTouchX = 0 @@ -84,7 +87,7 @@ class InputOverlayDrawableJoystick( boundsBoxBitmap.draw(canvas) } - fun updateStatus(event: MotionEvent): Boolean { + fun updateStatus(event: MotionEvent, overlay:InputOverlay): Boolean { val pointerIndex = event.actionIndex val xPosition = event.getX(pointerIndex).toInt() val yPosition = event.getY(pointerIndex).toInt() @@ -109,6 +112,7 @@ class InputOverlayDrawableJoystick( } boundsBoxBitmap.bounds = virtBounds trackId = pointerId + overlay.hapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY) } if (isActionUp) { if (trackId != pointerId) { @@ -117,12 +121,15 @@ class InputOverlayDrawableJoystick( pressedState = false xAxis = 0.0f yAxis = 0.0f + angle = 0.0f + radius = 0.0f outerBitmap.alpha = 255 boundsBoxBitmap.alpha = 0 virtBounds = Rect(origBounds.left, origBounds.top, origBounds.right, origBounds.bottom) bounds = Rect(origBounds.left, origBounds.top, origBounds.right, origBounds.bottom) setInnerBounds() trackId = -1 + overlay.hapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE) return true } if (trackId == -1) return false @@ -142,6 +149,8 @@ class InputOverlayDrawableJoystick( val yAxis = touchY / maxY val oldXAxis = this.xAxis val oldYAxis = this.yAxis + val oldAngle = this.angle + val oldRadius = this.radius // Clamp the circle pad input to a circle val angle = atan2(yAxis.toDouble(), xAxis.toDouble()).toFloat() @@ -152,6 +161,15 @@ class InputOverlayDrawableJoystick( this.xAxis = cos(angle.toDouble()).toFloat() * radius this.yAxis = sin(angle.toDouble()).toFloat() * radius setInnerBounds() + + if (kotlin.math.abs(oldRadius - radius) > .34f + || radius > .5f && kotlin.math.abs(oldAngle - angle) > kotlin.math.PI / 8) { + this.radius = radius + this.angle = angle + + overlay.hapticFeedback(HapticFeedbackConstants.CLOCK_TICK) + } + return oldXAxis != this.xAxis && oldYAxis != this.yAxis } return false diff --git a/src/android/app/src/main/java/io/github/lime3ds/android/utils/EmulationMenuSettings.kt b/src/android/app/src/main/java/io/github/lime3ds/android/utils/EmulationMenuSettings.kt index 560cb963f..904caec41 100644 --- a/src/android/app/src/main/java/io/github/lime3ds/android/utils/EmulationMenuSettings.kt +++ b/src/android/app/src/main/java/io/github/lime3ds/android/utils/EmulationMenuSettings.kt @@ -41,8 +41,15 @@ object EmulationMenuSettings { get() = preferences.getBoolean("EmulationMenuSettings_ShowFps", false) set(value) { preferences.edit() - .putBoolean("EmulationMenuSettings_ShowFps", value) - .apply() + .putBoolean("EmulationMenuSettings_ShowFps", value) + .apply() + } + var hapticFeedback: Boolean + get() = preferences.getBoolean("EmulationMenuSettings_HapticFeedback", true) + set(value) { + preferences.edit() + .putBoolean("EmulationMenuSettings_HapticFeedback", value) + .apply() } var swapScreens: Boolean get() = preferences.getBoolean("EmulationMenuSettings_SwapScreens", false) diff --git a/src/android/app/src/main/res/menu/menu_overlay_options.xml b/src/android/app/src/main/res/menu/menu_overlay_options.xml index 18b315c88..605c15213 100644 --- a/src/android/app/src/main/res/menu/menu_overlay_options.xml +++ b/src/android/app/src/main/res/menu/menu_overlay_options.xml @@ -11,6 +11,11 @@ android:title="@string/emulation_show_fps" android:checkable="true" /> + + diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index da2b4b3ea..fe1832ce7 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -324,6 +324,7 @@ Slot %1$d Slot %1$d - %2$tF %2$tR Show FPS + Haptic Feedback Overlay Options Configure Controls Edit Layout