mirror of
https://github.com/Lime3DS/Lime3DS
synced 2024-12-25 16:42:39 -06:00
Android haptic feedback (#56)
* Android: Add haptic feedback to overlay controls * Android: Add haptic feedback to overlay controls --------- Co-authored-by: João Vitor Polverari <polverari.jv@gmail.com>
This commit is contained in:
parent
2c17f11596
commit
b57dc53d93
8 changed files with 67 additions and 9 deletions
|
@ -581,6 +581,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||||
popupMenu.menu.apply {
|
popupMenu.menu.apply {
|
||||||
findItem(R.id.menu_show_overlay).isChecked = EmulationMenuSettings.showOverlay
|
findItem(R.id.menu_show_overlay).isChecked = EmulationMenuSettings.showOverlay
|
||||||
findItem(R.id.menu_show_fps).isChecked = EmulationMenuSettings.showFps
|
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 =
|
findItem(R.id.menu_emulation_joystick_rel_center).isChecked =
|
||||||
EmulationMenuSettings.joystickRelCenter
|
EmulationMenuSettings.joystickRelCenter
|
||||||
findItem(R.id.menu_emulation_dpad_slide_enable).isChecked =
|
findItem(R.id.menu_emulation_dpad_slide_enable).isChecked =
|
||||||
|
@ -601,6 +602,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
R.id.menu_haptic_feedback -> {
|
||||||
|
EmulationMenuSettings.hapticFeedback = !EmulationMenuSettings.hapticFeedback
|
||||||
|
updateShowFpsOverlay()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
R.id.menu_emulation_edit_layout -> {
|
R.id.menu_emulation_edit_layout -> {
|
||||||
editControlsPlacement()
|
editControlsPlacement()
|
||||||
binding.drawerLayout.close()
|
binding.drawerLayout.close()
|
||||||
|
|
|
@ -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 {
|
override fun onTouch(v: View, event: MotionEvent): Boolean {
|
||||||
if (isInEditMode) {
|
if (isInEditMode) {
|
||||||
return onTouchWhileEditing(event)
|
return onTouchWhileEditing(event)
|
||||||
}
|
}
|
||||||
var shouldUpdateView = false
|
var shouldUpdateView = false
|
||||||
for (button in overlayButtons) {
|
for (button in overlayButtons) {
|
||||||
if (!button.updateStatus(event)) {
|
if (!button.updateStatus(event, this)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +108,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
||||||
shouldUpdateView = true
|
shouldUpdateView = true
|
||||||
}
|
}
|
||||||
for (dpad in overlayDpads) {
|
for (dpad in overlayDpads) {
|
||||||
if (!dpad.updateStatus(event, EmulationMenuSettings.dpadSlide)) {
|
if (!dpad.updateStatus(event, EmulationMenuSettings.dpadSlide, this)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.upId, dpad.upStatus)
|
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.upId, dpad.upStatus)
|
||||||
|
@ -125,7 +130,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
||||||
shouldUpdateView = true
|
shouldUpdateView = true
|
||||||
}
|
}
|
||||||
for (joystick in overlayJoysticks) {
|
for (joystick in overlayJoysticks) {
|
||||||
if (!joystick.updateStatus(event)) {
|
if (!joystick.updateStatus(event, this)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
val axisID = joystick.joystickId
|
val axisID = joystick.joystickId
|
||||||
|
|
|
@ -9,6 +9,7 @@ import android.graphics.Bitmap
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
|
import android.view.HapticFeedbackConstants
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import io.github.lime3ds.android.NativeLibrary
|
import io.github.lime3ds.android.NativeLibrary
|
||||||
|
|
||||||
|
@ -51,7 +52,7 @@ class InputOverlayDrawableButton(
|
||||||
*
|
*
|
||||||
* @return true if value was changed
|
* @return true if value was changed
|
||||||
*/
|
*/
|
||||||
fun updateStatus(event: MotionEvent): Boolean {
|
fun updateStatus(event: MotionEvent, overlay:InputOverlay): Boolean {
|
||||||
val pointerIndex = event.actionIndex
|
val pointerIndex = event.actionIndex
|
||||||
val xPosition = event.getX(pointerIndex).toInt()
|
val xPosition = event.getX(pointerIndex).toInt()
|
||||||
val yPosition = event.getY(pointerIndex).toInt()
|
val yPosition = event.getY(pointerIndex).toInt()
|
||||||
|
@ -67,6 +68,7 @@ class InputOverlayDrawableButton(
|
||||||
}
|
}
|
||||||
pressedState = true
|
pressedState = true
|
||||||
trackId = pointerId
|
trackId = pointerId
|
||||||
|
overlay.hapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (isActionUp) {
|
if (isActionUp) {
|
||||||
|
@ -75,6 +77,7 @@ class InputOverlayDrawableButton(
|
||||||
}
|
}
|
||||||
pressedState = false
|
pressedState = false
|
||||||
trackId = -1
|
trackId = -1
|
||||||
|
overlay.hapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -9,6 +9,7 @@ import android.graphics.Bitmap
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
|
import android.view.HapticFeedbackConstants
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import io.github.lime3ds.android.NativeLibrary
|
import io.github.lime3ds.android.NativeLibrary
|
||||||
|
|
||||||
|
@ -59,7 +60,8 @@ class InputOverlayDrawableDpad(
|
||||||
trackId = -1
|
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 pointerIndex = event.actionIndex
|
||||||
val xPosition = event.getX(pointerIndex).toInt()
|
val xPosition = event.getX(pointerIndex).toInt()
|
||||||
val yPosition = event.getY(pointerIndex).toInt()
|
val yPosition = event.getY(pointerIndex).toInt()
|
||||||
|
@ -73,6 +75,7 @@ class InputOverlayDrawableDpad(
|
||||||
if (!bounds.contains(xPosition, yPosition)) {
|
if (!bounds.contains(xPosition, yPosition)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
isDown = true
|
||||||
trackId = pointerId
|
trackId = pointerId
|
||||||
}
|
}
|
||||||
if (isActionUp) {
|
if (isActionUp) {
|
||||||
|
@ -84,6 +87,7 @@ class InputOverlayDrawableDpad(
|
||||||
downButtonState = false
|
downButtonState = false
|
||||||
leftButtonState = false
|
leftButtonState = false
|
||||||
rightButtonState = false
|
rightButtonState = false
|
||||||
|
overlay.hapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (trackId == -1) {
|
if (trackId == -1) {
|
||||||
|
@ -114,7 +118,15 @@ class InputOverlayDrawableDpad(
|
||||||
downButtonState = yAxis > VIRT_AXIS_DEADZONE
|
downButtonState = yAxis > VIRT_AXIS_DEADZONE
|
||||||
leftButtonState = xAxis < -VIRT_AXIS_DEADZONE
|
leftButtonState = xAxis < -VIRT_AXIS_DEADZONE
|
||||||
rightButtonState = 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
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import android.graphics.Bitmap
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
|
import android.view.HapticFeedbackConstants
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import io.github.lime3ds.android.NativeLibrary
|
import io.github.lime3ds.android.NativeLibrary
|
||||||
import io.github.lime3ds.android.utils.EmulationMenuSettings
|
import io.github.lime3ds.android.utils.EmulationMenuSettings
|
||||||
|
@ -41,6 +42,8 @@ class InputOverlayDrawableJoystick(
|
||||||
var trackId = -1
|
var trackId = -1
|
||||||
var xAxis = 0f
|
var xAxis = 0f
|
||||||
var yAxis = 0f
|
var yAxis = 0f
|
||||||
|
var angle = 0f
|
||||||
|
var radius = 0f
|
||||||
private var controlPositionX = 0
|
private var controlPositionX = 0
|
||||||
private var controlPositionY = 0
|
private var controlPositionY = 0
|
||||||
private var previousTouchX = 0
|
private var previousTouchX = 0
|
||||||
|
@ -84,7 +87,7 @@ class InputOverlayDrawableJoystick(
|
||||||
boundsBoxBitmap.draw(canvas)
|
boundsBoxBitmap.draw(canvas)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateStatus(event: MotionEvent): Boolean {
|
fun updateStatus(event: MotionEvent, overlay:InputOverlay): Boolean {
|
||||||
val pointerIndex = event.actionIndex
|
val pointerIndex = event.actionIndex
|
||||||
val xPosition = event.getX(pointerIndex).toInt()
|
val xPosition = event.getX(pointerIndex).toInt()
|
||||||
val yPosition = event.getY(pointerIndex).toInt()
|
val yPosition = event.getY(pointerIndex).toInt()
|
||||||
|
@ -109,6 +112,7 @@ class InputOverlayDrawableJoystick(
|
||||||
}
|
}
|
||||||
boundsBoxBitmap.bounds = virtBounds
|
boundsBoxBitmap.bounds = virtBounds
|
||||||
trackId = pointerId
|
trackId = pointerId
|
||||||
|
overlay.hapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
|
||||||
}
|
}
|
||||||
if (isActionUp) {
|
if (isActionUp) {
|
||||||
if (trackId != pointerId) {
|
if (trackId != pointerId) {
|
||||||
|
@ -117,12 +121,15 @@ class InputOverlayDrawableJoystick(
|
||||||
pressedState = false
|
pressedState = false
|
||||||
xAxis = 0.0f
|
xAxis = 0.0f
|
||||||
yAxis = 0.0f
|
yAxis = 0.0f
|
||||||
|
angle = 0.0f
|
||||||
|
radius = 0.0f
|
||||||
outerBitmap.alpha = 255
|
outerBitmap.alpha = 255
|
||||||
boundsBoxBitmap.alpha = 0
|
boundsBoxBitmap.alpha = 0
|
||||||
virtBounds = Rect(origBounds.left, origBounds.top, origBounds.right, origBounds.bottom)
|
virtBounds = Rect(origBounds.left, origBounds.top, origBounds.right, origBounds.bottom)
|
||||||
bounds = Rect(origBounds.left, origBounds.top, origBounds.right, origBounds.bottom)
|
bounds = Rect(origBounds.left, origBounds.top, origBounds.right, origBounds.bottom)
|
||||||
setInnerBounds()
|
setInnerBounds()
|
||||||
trackId = -1
|
trackId = -1
|
||||||
|
overlay.hapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (trackId == -1) return false
|
if (trackId == -1) return false
|
||||||
|
@ -142,6 +149,8 @@ class InputOverlayDrawableJoystick(
|
||||||
val yAxis = touchY / maxY
|
val yAxis = touchY / maxY
|
||||||
val oldXAxis = this.xAxis
|
val oldXAxis = this.xAxis
|
||||||
val oldYAxis = this.yAxis
|
val oldYAxis = this.yAxis
|
||||||
|
val oldAngle = this.angle
|
||||||
|
val oldRadius = this.radius
|
||||||
|
|
||||||
// Clamp the circle pad input to a circle
|
// Clamp the circle pad input to a circle
|
||||||
val angle = atan2(yAxis.toDouble(), xAxis.toDouble()).toFloat()
|
val angle = atan2(yAxis.toDouble(), xAxis.toDouble()).toFloat()
|
||||||
|
@ -152,6 +161,15 @@ class InputOverlayDrawableJoystick(
|
||||||
this.xAxis = cos(angle.toDouble()).toFloat() * radius
|
this.xAxis = cos(angle.toDouble()).toFloat() * radius
|
||||||
this.yAxis = sin(angle.toDouble()).toFloat() * radius
|
this.yAxis = sin(angle.toDouble()).toFloat() * radius
|
||||||
setInnerBounds()
|
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 oldXAxis != this.xAxis && oldYAxis != this.yAxis
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -41,8 +41,15 @@ object EmulationMenuSettings {
|
||||||
get() = preferences.getBoolean("EmulationMenuSettings_ShowFps", false)
|
get() = preferences.getBoolean("EmulationMenuSettings_ShowFps", false)
|
||||||
set(value) {
|
set(value) {
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putBoolean("EmulationMenuSettings_ShowFps", value)
|
.putBoolean("EmulationMenuSettings_ShowFps", value)
|
||||||
.apply()
|
.apply()
|
||||||
|
}
|
||||||
|
var hapticFeedback: Boolean
|
||||||
|
get() = preferences.getBoolean("EmulationMenuSettings_HapticFeedback", true)
|
||||||
|
set(value) {
|
||||||
|
preferences.edit()
|
||||||
|
.putBoolean("EmulationMenuSettings_HapticFeedback", value)
|
||||||
|
.apply()
|
||||||
}
|
}
|
||||||
var swapScreens: Boolean
|
var swapScreens: Boolean
|
||||||
get() = preferences.getBoolean("EmulationMenuSettings_SwapScreens", false)
|
get() = preferences.getBoolean("EmulationMenuSettings_SwapScreens", false)
|
||||||
|
|
|
@ -11,6 +11,11 @@
|
||||||
android:title="@string/emulation_show_fps"
|
android:title="@string/emulation_show_fps"
|
||||||
android:checkable="true" />
|
android:checkable="true" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_haptic_feedback"
|
||||||
|
android:title="@string/emulation_haptic_feedback"
|
||||||
|
android:checkable="true" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/menu_emulation_edit_layout"
|
android:id="@+id/menu_emulation_edit_layout"
|
||||||
android:title="@string/emulation_edit_layout" />
|
android:title="@string/emulation_edit_layout" />
|
||||||
|
|
|
@ -324,6 +324,7 @@
|
||||||
<string name="emulation_empty_state_slot">Slot %1$d</string>
|
<string name="emulation_empty_state_slot">Slot %1$d</string>
|
||||||
<string name="emulation_occupied_state_slot">Slot %1$d - %2$tF %2$tR</string>
|
<string name="emulation_occupied_state_slot">Slot %1$d - %2$tF %2$tR</string>
|
||||||
<string name="emulation_show_fps">Show FPS</string>
|
<string name="emulation_show_fps">Show FPS</string>
|
||||||
|
<string name="emulation_haptic_feedback">Haptic Feedback</string>
|
||||||
<string name="emulation_overlay_options">Overlay Options</string>
|
<string name="emulation_overlay_options">Overlay Options</string>
|
||||||
<string name="emulation_configure_controls">Configure Controls</string>
|
<string name="emulation_configure_controls">Configure Controls</string>
|
||||||
<string name="emulation_edit_layout">Edit Layout</string>
|
<string name="emulation_edit_layout">Edit Layout</string>
|
||||||
|
|
Loading…
Reference in a new issue