diff --git a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt
index ae170c1c7..fbf3e5525 100644
--- a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt
@@ -796,6 +796,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
true
}
+ R.id.menu_emulation_button_sliding -> {
+ showButtonSlidingMenu()
+ true
+ }
+
R.id.menu_emulation_dpad_slide_enable -> {
EmulationMenuSettings.dpadSlide = !EmulationMenuSettings.dpadSlide
true
@@ -840,6 +845,28 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
popupMenu.show()
}
+ private fun showButtonSlidingMenu() {
+ val editor = preferences.edit()
+
+ val buttonSlidingModes = mutableListOf()
+ buttonSlidingModes.add(getString(R.string.emulation_button_sliding_disabled))
+ buttonSlidingModes.add(getString(R.string.emulation_button_sliding_enabled))
+ buttonSlidingModes.add(getString(R.string.emulation_button_sliding_alternative))
+
+ MaterialAlertDialogBuilder(requireContext())
+ .setTitle(R.string.emulation_button_sliding)
+ .setSingleChoiceItems(
+ buttonSlidingModes.toTypedArray(),
+ EmulationMenuSettings.buttonSlide
+ ) { _: DialogInterface?, which: Int ->
+ EmulationMenuSettings.buttonSlide = which
+ }
+ .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
+ editor.apply()
+ }
+ .show()
+ }
+
private fun showLandscapeScreenLayoutMenu() {
val popupMenu = PopupMenu(
requireContext(),
diff --git a/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlay.kt
index 7540fe529..23dcddc78 100644
--- a/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlay.kt
+++ b/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlay.kt
@@ -96,57 +96,115 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
if (isInEditMode) {
return onTouchWhileEditing(event)
}
- var shouldUpdateView = false
+
+ var hasActiveButtons = false
+ val pointerIndex = event.actionIndex
+ val pointerId = event.getPointerId(pointerIndex)
for (button in overlayButtons) {
- if (!button.updateStatus(event, this)) {
- continue
+ if (button.trackId == pointerId) {
+ hasActiveButtons = true
+ break
}
-
- if (button.id == NativeLibrary.ButtonType.BUTTON_SWAP && button.status == NativeLibrary.ButtonState.PRESSED) {
- swapScreen()
- }
-
- if (button.id == NativeLibrary.ButtonType.BUTTON_TURBO && button.status == NativeLibrary.ButtonState.PRESSED) {
- TurboHelper.toggleTurbo(true)
- }
-
- NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, button.id, button.status)
- shouldUpdateView = true
}
- for (dpad in overlayDpads) {
- if (!dpad.updateStatus(event, EmulationMenuSettings.dpadSlide, this)) {
- continue
+ var hasActiveDpad = false
+ if (!hasActiveButtons) {
+ for (dpad in overlayDpads) {
+ if (dpad.trackId == pointerId) {
+ hasActiveDpad = true
+ break
+ }
}
- NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice, dpad.upId, dpad.upStatus)
- NativeLibrary.onGamePadEvent(
- NativeLibrary.TouchScreenDevice,
- dpad.downId,
- dpad.downStatus
- )
- NativeLibrary.onGamePadEvent(
- NativeLibrary.TouchScreenDevice,
- dpad.leftId,
- dpad.leftStatus
- )
- NativeLibrary.onGamePadEvent(
- NativeLibrary.TouchScreenDevice,
- dpad.rightId,
- dpad.rightStatus
- )
- shouldUpdateView = true
}
- for (joystick in overlayJoysticks) {
- if (!joystick.updateStatus(event, this)) {
- continue
+
+ var hasActiveJoystick = false
+ if(!hasActiveButtons && !hasActiveDpad){
+ for (joystick in overlayJoysticks) {
+ if (joystick.trackId == pointerId) {
+ hasActiveJoystick = true
+ break
+ }
+ }
+ }
+
+ var shouldUpdateView = false
+ if(!hasActiveDpad && !hasActiveJoystick) {
+ for (button in overlayButtons) {
+ val stateChanged = button.updateStatus(event, hasActiveButtons, this)
+ if (!stateChanged) {
+ continue
+ }
+
+ if (button.id == NativeLibrary.ButtonType.BUTTON_SWAP && button.status == NativeLibrary.ButtonState.PRESSED) {
+ swapScreen()
+ }
+ else if (button.id == NativeLibrary.ButtonType.BUTTON_TURBO && button.status == NativeLibrary.ButtonState.PRESSED) {
+ TurboHelper.toggleTurbo(true)
+ }
+
+ NativeLibrary.onGamePadEvent(
+ NativeLibrary.TouchScreenDevice,
+ button.id,
+ button.status
+ )
+
+ shouldUpdateView = true
+ }
+ }
+
+ if(!hasActiveButtons && !hasActiveJoystick) {
+ for (dpad in overlayDpads) {
+ val stateChanged = dpad.updateStatus(
+ event,
+ hasActiveDpad,
+ EmulationMenuSettings.dpadSlide,
+ this
+ )
+ if (!stateChanged) {
+ continue
+ }
+
+ NativeLibrary.onGamePadEvent(
+ NativeLibrary.TouchScreenDevice,
+ dpad.upId,
+ dpad.upStatus
+ )
+ NativeLibrary.onGamePadEvent(
+ NativeLibrary.TouchScreenDevice,
+ dpad.downId,
+ dpad.downStatus
+ )
+ NativeLibrary.onGamePadEvent(
+ NativeLibrary.TouchScreenDevice,
+ dpad.leftId,
+ dpad.leftStatus
+ )
+ NativeLibrary.onGamePadEvent(
+ NativeLibrary.TouchScreenDevice,
+ dpad.rightId,
+ dpad.rightStatus
+ )
+
+ shouldUpdateView = true
+ }
+ }
+
+ if(!hasActiveDpad && !hasActiveButtons) {
+ for (joystick in overlayJoysticks) {
+ val stateChanged = joystick.updateStatus(event, hasActiveJoystick, this)
+ if (!stateChanged) {
+ continue
+ }
+
+ val axisID = joystick.joystickId
+ NativeLibrary.onGamePadMoveEvent(
+ NativeLibrary.TouchScreenDevice,
+ axisID,
+ joystick.xAxis,
+ joystick.yAxis
+ )
+
+ shouldUpdateView = true
}
- val axisID = joystick.joystickId
- NativeLibrary.onGamePadMoveEvent(
- NativeLibrary.TouchScreenDevice,
- axisID,
- joystick.xAxis,
- joystick.yAxis
- )
- shouldUpdateView = true
}
if (shouldUpdateView) {
@@ -157,10 +215,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
return true
}
- val pointerIndex = event.actionIndex
val xPosition = event.getX(pointerIndex).toInt()
val yPosition = event.getY(pointerIndex).toInt()
- val pointerId = event.getPointerId(pointerIndex)
val motionEvent = event.action and MotionEvent.ACTION_MASK
val isActionDown =
motionEvent == MotionEvent.ACTION_DOWN || motionEvent == MotionEvent.ACTION_POINTER_DOWN
diff --git a/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlayDrawableButton.kt b/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlayDrawableButton.kt
index 541d3270f..0e9b0185d 100644
--- a/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlayDrawableButton.kt
+++ b/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlayDrawableButton.kt
@@ -1,4 +1,4 @@
-// Copyright 2023 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -12,6 +12,20 @@ import android.graphics.drawable.BitmapDrawable
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import org.citra.citra_emu.NativeLibrary
+import org.citra.citra_emu.utils.EmulationMenuSettings
+
+enum class ButtonSlidingMode(val int: Int) {
+ // Disabled, buttons can only be triggered by pressing them directly.
+ Disabled(0),
+
+ // Additionally to pressing buttons directly, they can be activated and released by sliding into
+ // and out of their area.
+ Enabled(1),
+
+ // The first button is kept activated until released, further buttons use the simple button
+ // sliding method.
+ Alternative(2)
+}
/**
* Custom [BitmapDrawable] that is capable
@@ -30,6 +44,9 @@ class InputOverlayDrawableButton(
val opacity: Int
) {
var trackId: Int
+
+ private var isMotionFirstButton = false // mark the first activated button with the current motion
+
private var previousTouchX = 0
private var previousTouchY = 0
private var controlPositionX = 0
@@ -53,7 +70,8 @@ class InputOverlayDrawableButton(
*
* @return true if value was changed
*/
- fun updateStatus(event: MotionEvent, overlay:InputOverlay): Boolean {
+ fun updateStatus(event: MotionEvent, hasActiveButtons: Boolean, overlay: InputOverlay): Boolean {
+ val buttonSliding = EmulationMenuSettings.buttonSlide
val pointerIndex = event.actionIndex
val xPosition = event.getX(pointerIndex).toInt()
val yPosition = event.getY(pointerIndex).toInt()
@@ -67,23 +85,60 @@ class InputOverlayDrawableButton(
if (!bounds.contains(xPosition, yPosition)) {
return false
}
- pressedState = true
- trackId = pointerId
- overlay.hapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
+ buttonDown(true, pointerId, overlay)
return true
}
if (isActionUp) {
if (trackId != pointerId) {
return false
}
- pressedState = false
- trackId = -1
- overlay.hapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE)
+ buttonUp(overlay)
return true
}
+
+ val isActionMoving = motionEvent == MotionEvent.ACTION_MOVE
+ if (buttonSliding != ButtonSlidingMode.Disabled.int && isActionMoving) {
+ val inside = bounds.contains(xPosition, yPosition)
+ if (pressedState) {
+ // button is already pressed
+ // check whether we moved out of the button area to update the state
+ if (inside || trackId != pointerId) {
+ return false
+ }
+ // prevent the first (directly pressed) button to deactivate when sliding off
+ if (buttonSliding == ButtonSlidingMode.Alternative.int && isMotionFirstButton) {
+ return false
+ }
+ buttonUp(overlay)
+ return true
+ } else {
+ // button was not yet pressed
+ // check whether we moved into the button area to update the state
+ if (!inside) {
+ return false
+ }
+ buttonDown(!hasActiveButtons, pointerId, overlay)
+ return true
+ }
+ }
+
return false
}
+ private fun buttonDown(firstBtn: Boolean, pointerId: Int, overlay: InputOverlay) {
+ pressedState = true
+ isMotionFirstButton = firstBtn
+ trackId = pointerId
+ overlay.hapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
+ }
+
+ private fun buttonUp(overlay: InputOverlay) {
+ pressedState = false
+ isMotionFirstButton = false
+ trackId = -1
+ overlay.hapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE)
+ }
+
fun onConfigureTouch(event: MotionEvent): Boolean {
val pointerIndex = event.actionIndex
val fingerPositionX = event.getX(pointerIndex).toInt()
diff --git a/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlayDrawableDpad.kt b/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlayDrawableDpad.kt
index bc23201f1..c032ee3bc 100644
--- a/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlayDrawableDpad.kt
+++ b/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlayDrawableDpad.kt
@@ -1,4 +1,4 @@
-// Copyright 2023 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -12,6 +12,7 @@ import android.graphics.drawable.BitmapDrawable
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import org.citra.citra_emu.NativeLibrary
+import org.citra.citra_emu.utils.EmulationMenuSettings
/**
* Custom [BitmapDrawable] that is capable
@@ -62,15 +63,19 @@ class InputOverlayDrawableDpad(
trackId = -1
}
- fun updateStatus(event: MotionEvent, dpadSlide: Boolean, overlay:InputOverlay): Boolean {
+ fun updateStatus(event: MotionEvent, hasActiveButtons: Boolean, dpadSlide: Boolean, overlay: InputOverlay): Boolean {
var isDown = false
val pointerIndex = event.actionIndex
val xPosition = event.getX(pointerIndex).toInt()
val yPosition = event.getY(pointerIndex).toInt()
val pointerId = event.getPointerId(pointerIndex)
val motionEvent = event.action and MotionEvent.ACTION_MASK
- val isActionDown =
+ var isActionDown =
motionEvent == MotionEvent.ACTION_DOWN || motionEvent == MotionEvent.ACTION_POINTER_DOWN
+ if (!isActionDown && EmulationMenuSettings.buttonSlide != ButtonSlidingMode.Disabled.int) {
+ isActionDown = motionEvent == MotionEvent.ACTION_MOVE && !hasActiveButtons
+ }
+
val isActionUp =
motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP
if (isActionDown) {
diff --git a/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlayDrawableJoystick.kt b/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlayDrawableJoystick.kt
index 3d90bad23..b66118c90 100644
--- a/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlayDrawableJoystick.kt
+++ b/src/android/app/src/main/java/org/citra/citra_emu/overlay/InputOverlayDrawableJoystick.kt
@@ -1,4 +1,4 @@
-// Copyright 2023 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -93,14 +93,18 @@ class InputOverlayDrawableJoystick(
currentStateBitmapDrawable.draw(canvas)
}
- fun updateStatus(event: MotionEvent, overlay:InputOverlay): Boolean {
+ fun updateStatus(event: MotionEvent, hasActiveButtons: Boolean, overlay: InputOverlay): Boolean {
val pointerIndex = event.actionIndex
val xPosition = event.getX(pointerIndex).toInt()
val yPosition = event.getY(pointerIndex).toInt()
val pointerId = event.getPointerId(pointerIndex)
val motionEvent = event.action and MotionEvent.ACTION_MASK
- val isActionDown =
+ var isActionDown =
motionEvent == MotionEvent.ACTION_DOWN || motionEvent == MotionEvent.ACTION_POINTER_DOWN
+ if (!isActionDown && EmulationMenuSettings.buttonSlide != ButtonSlidingMode.Disabled.int) {
+ isActionDown = motionEvent == MotionEvent.ACTION_MOVE && !hasActiveButtons
+ }
+
val isActionUp =
motionEvent == MotionEvent.ACTION_UP || motionEvent == MotionEvent.ACTION_POINTER_UP
if (isActionDown) {
diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationMenuSettings.kt b/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationMenuSettings.kt
index 6d633360b..184964549 100644
--- a/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationMenuSettings.kt
+++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/EmulationMenuSettings.kt
@@ -1,4 +1,4 @@
-// Copyright 2023 Citra Emulator Project
+// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -7,6 +7,7 @@ package org.citra.citra_emu.utils
import androidx.drawerlayout.widget.DrawerLayout
import androidx.preference.PreferenceManager
import org.citra.citra_emu.CitraApplication
+import org.citra.citra_emu.overlay.ButtonSlidingMode
object EmulationMenuSettings {
private val preferences =
@@ -26,6 +27,13 @@ object EmulationMenuSettings {
.putBoolean("EmulationMenuSettings_DpadSlideEnable", value)
.apply()
}
+ var buttonSlide: Int
+ get() = preferences.getInt("EmulationMenuSettings_ButtonSlideMode", ButtonSlidingMode.Disabled.int)
+ set(value) {
+ preferences.edit()
+ .putInt("EmulationMenuSettings_ButtonSlideMode", value)
+ .apply()
+ }
var showPerformanceOverlay: Boolean
get() = preferences.getBoolean("EmulationMenuSettings_showPerformanceOverlay", 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 ca6ae47fe..8bb19ee26 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
@@ -86,6 +86,10 @@
android:id="@+id/menu_emulation_adjust_opacity"
android:title="@string/emulation_control_opacity" />
+ -
+
- Configure Controls
Edit Layout
Done
+ Button Sliding
+ Hold originally pressed button
+ Hold currently pressed button
+ Hold original and currently pressed button
Toggle Controls
Adjust Scale
Global Scale