Browse Source

feat(android): add option to hide overlay on controller input

pull/3127/head
Producdevity 3 weeks ago
committed by crueter
parent
commit
a6b9a4ebd7
  1. 76
      src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
  2. 1
      src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt
  3. 7
      src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
  4. 1
      src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
  5. 31
      src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
  6. 5
      src/android/app/src/main/jni/android_settings.h
  7. 2
      src/android/app/src/main/res/values/strings.xml

76
src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt

@ -18,6 +18,7 @@ import android.content.IntentFilter
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Rect import android.graphics.Rect
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import android.hardware.input.InputManager
import android.hardware.Sensor import android.hardware.Sensor
import android.hardware.SensorEvent import android.hardware.SensorEvent
import android.hardware.SensorEventListener import android.hardware.SensorEventListener
@ -63,11 +64,12 @@ import kotlin.math.roundToInt
import org.yuzu.yuzu_emu.utils.ForegroundService import org.yuzu.yuzu_emu.utils.ForegroundService
import androidx.core.os.BundleCompat import androidx.core.os.BundleCompat
class EmulationActivity : AppCompatActivity(), SensorEventListener {
class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager.InputDeviceListener {
private lateinit var binding: ActivityEmulationBinding private lateinit var binding: ActivityEmulationBinding
var isActivityRecreated = false var isActivityRecreated = false
private lateinit var nfcReader: NfcReader private lateinit var nfcReader: NfcReader
private lateinit var inputManager: InputManager
private var touchDownTime: Long = 0 private var touchDownTime: Long = 0
private val maxTapDuration = 500L private val maxTapDuration = 500L
@ -140,6 +142,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
nfcReader = NfcReader(this) nfcReader = NfcReader(this)
nfcReader.initialize() nfcReader.initialize()
inputManager = getSystemService(INPUT_SERVICE) as InputManager
inputManager.registerInputDeviceListener(this, null)
foregroundService = Intent(this, ForegroundService::class.java) foregroundService = Intent(this, ForegroundService::class.java)
startForegroundService(foregroundService) startForegroundService(foregroundService)
@ -206,9 +211,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
inputManager.unregisterInputDeviceListener(this)
stopForegroundService(this) stopForegroundService(this)
NativeLibrary.playTimeManagerStop() NativeLibrary.playTimeManagerStop()
} }
override fun onUserLeaveHint() { override fun onUserLeaveHint() {
@ -244,8 +249,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
val isPhysicalKeyboard = event.source and InputDevice.SOURCE_KEYBOARD == InputDevice.SOURCE_KEYBOARD && val isPhysicalKeyboard = event.source and InputDevice.SOURCE_KEYBOARD == InputDevice.SOURCE_KEYBOARD &&
event.device?.isVirtual == false event.device?.isVirtual == false
if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD &&
val isControllerInput = event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK ||
event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD
if (!isControllerInput &&
event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE && event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE &&
!isPhysicalKeyboard !isPhysicalKeyboard
) { ) {
@ -256,12 +263,18 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
return super.dispatchKeyEvent(event) return super.dispatchKeyEvent(event)
} }
if (isControllerInput && event.action == KeyEvent.ACTION_DOWN) {
notifyControllerInput()
}
return InputHandler.dispatchKeyEvent(event) return InputHandler.dispatchKeyEvent(event)
} }
override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean { override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD &&
val isControllerInput = event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK ||
event.source and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD
if (!isControllerInput &&
event.source and InputDevice.SOURCE_KEYBOARD != InputDevice.SOURCE_KEYBOARD && event.source and InputDevice.SOURCE_KEYBOARD != InputDevice.SOURCE_KEYBOARD &&
event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE event.source and InputDevice.SOURCE_MOUSE != InputDevice.SOURCE_MOUSE
) { ) {
@ -277,9 +290,54 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
return true return true
} }
if (isControllerInput) {
notifyControllerInput()
}
return InputHandler.dispatchGenericMotionEvent(event) return InputHandler.dispatchGenericMotionEvent(event)
} }
private fun notifyControllerInput() {
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment
val emulationFragment =
navHostFragment?.childFragmentManager?.fragments?.firstOrNull() as? org.yuzu.yuzu_emu.fragments.EmulationFragment
emulationFragment?.onControllerInputDetected()
}
private fun isGameController(deviceId: Int): Boolean {
val device = InputDevice.getDevice(deviceId) ?: return false
val sources = device.sources
return sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD ||
sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK
}
override fun onInputDeviceAdded(deviceId: Int) {
if (isGameController(deviceId)) {
InputHandler.updateControllerData()
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment
val emulationFragment =
navHostFragment?.childFragmentManager?.fragments?.firstOrNull() as? org.yuzu.yuzu_emu.fragments.EmulationFragment
emulationFragment?.onControllerConnected()
}
}
override fun onInputDeviceRemoved(deviceId: Int) {
InputHandler.updateControllerData()
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment
val emulationFragment =
navHostFragment?.childFragmentManager?.fragments?.firstOrNull() as? org.yuzu.yuzu_emu.fragments.EmulationFragment
emulationFragment?.onControllerDisconnected()
}
override fun onInputDeviceChanged(deviceId: Int) {
if (isGameController(deviceId)) {
InputHandler.updateControllerData()
}
}
override fun onSensorChanged(event: SensorEvent) { override fun onSensorChanged(event: SensorEvent) {
val rotation = this.display?.rotation val rotation = this.display?.rotation
if (rotation == Surface.ROTATION_90) { if (rotation == Surface.ROTATION_90) {
@ -519,8 +577,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
when (event.action) { when (event.action) {
MotionEvent.ACTION_DOWN -> { MotionEvent.ACTION_DOWN -> {
touchDownTime = System.currentTimeMillis() touchDownTime = System.currentTimeMillis()
// show overlay immediately on touch and cancel timer
if (!emulationViewModel.drawerOpen.value) {
// show overlay immediately on touch and cancel timer when only auto-hide is enabled
if (!emulationViewModel.drawerOpen.value &&
BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean() &&
!BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.getBoolean()) {
fragment.handler.removeCallbacksAndMessages(null) fragment.handler.removeCallbacksAndMessages(null)
fragment.showOverlay() fragment.showOverlay()
} }

1
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt

@ -57,6 +57,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
// FRAME_SKIPPING("frame_skipping"), // FRAME_SKIPPING("frame_skipping"),
ENABLE_INPUT_OVERLAY_AUTO_HIDE("enable_input_overlay_auto_hide"), ENABLE_INPUT_OVERLAY_AUTO_HIDE("enable_input_overlay_auto_hide"),
HIDE_OVERLAY_ON_CONTROLLER_INPUT("hide_overlay_on_controller_input"),
PERF_OVERLAY_BACKGROUND("perf_overlay_background"), PERF_OVERLAY_BACKGROUND("perf_overlay_background"),
SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"), SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),

7
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt

@ -402,6 +402,13 @@ abstract class SettingsItem(
valueHint = R.string.seconds valueHint = R.string.seconds
) )
) )
put(
SwitchSetting(
BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT,
titleId = R.string.hide_overlay_on_controller_input,
descriptionId = R.string.hide_overlay_on_controller_input_description
)
)
put( put(
SwitchSetting( SwitchSetting(

1
src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt

@ -274,6 +274,7 @@ class SettingsFragmentPresenter(
sl.apply { sl.apply {
add(BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.key) add(BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.key)
add(IntSetting.INPUT_OVERLAY_AUTO_HIDE.key) add(IntSetting.INPUT_OVERLAY_AUTO_HIDE.key)
add(BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.key)
} }
} }

31
src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt

@ -93,7 +93,6 @@ import org.yuzu.yuzu_emu.utils.collect
import org.yuzu.yuzu_emu.utils.CustomSettingsHandler import org.yuzu.yuzu_emu.utils.CustomSettingsHandler
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
import kotlin.coroutines.coroutineContext
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
@ -106,6 +105,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val handler = Handler(Looper.getMainLooper()) val handler = Handler(Looper.getMainLooper())
private var isOverlayVisible = true private var isOverlayVisible = true
private var controllerInputReceived = false
private var _binding: FragmentEmulationBinding? = null private var _binding: FragmentEmulationBinding? = null
@ -656,6 +656,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(newState) BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(newState)
updateQuickOverlayMenuEntry(newState) updateQuickOverlayMenuEntry(newState)
binding.surfaceInputOverlay.refreshControls() binding.surfaceInputOverlay.refreshControls()
// Sync view visibility with the setting
if (newState) {
showOverlay()
} else {
hideOverlay()
}
NativeConfig.saveGlobalConfig() NativeConfig.saveGlobalConfig()
true true
} }
@ -1978,6 +1984,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
fun showOverlay() { fun showOverlay() {
if (!isOverlayVisible) { if (!isOverlayVisible) {
isOverlayVisible = true isOverlayVisible = true
// Reset controller input flag so controller can hide overlay again
controllerInputReceived = false
ViewUtils.showView(binding.surfaceInputOverlay, 500) ViewUtils.showView(binding.surfaceInputOverlay, 500)
} }
} }
@ -1985,7 +1993,26 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private fun hideOverlay() { private fun hideOverlay() {
if (isOverlayVisible) { if (isOverlayVisible) {
isOverlayVisible = false isOverlayVisible = false
ViewUtils.hideView(binding.surfaceInputOverlay, 500)
ViewUtils.hideView(binding.surfaceInputOverlay)
} }
} }
fun onControllerInputDetected() {
if (!BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.getBoolean()) return
if (!BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) return
if (controllerInputReceived) return
controllerInputReceived = true
hideOverlay()
}
fun onControllerConnected() {
controllerInputReceived = false
}
fun onControllerDisconnected() {
if (!BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.getBoolean()) return
if (!BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean()) return
controllerInputReceived = false
showOverlay()
}
} }

5
src/android/app/src/main/jni/android_settings.h

@ -92,6 +92,11 @@ namespace AndroidSettings {
Settings::Setting<u32> input_overlay_auto_hide{linkage, 5, "input_overlay_auto_hide", Settings::Setting<u32> input_overlay_auto_hide{linkage, 5, "input_overlay_auto_hide",
Settings::Category::Overlay, Settings::Category::Overlay,
Settings::Specialization::Default, true, true, &enable_input_overlay_auto_hide}; Settings::Specialization::Default, true, true, &enable_input_overlay_auto_hide};
Settings::Setting<bool> hide_overlay_on_controller_input{linkage, false,
"hide_overlay_on_controller_input",
Settings::Category::Overlay,
Settings::Specialization::Default, true,
true};
Settings::Setting<bool> perf_overlay_background{linkage, false, "perf_overlay_background", Settings::Setting<bool> perf_overlay_background{linkage, false, "perf_overlay_background",
Settings::Category::Overlay, Settings::Category::Overlay,
Settings::Specialization::Default, true, Settings::Specialization::Default, true,

2
src/android/app/src/main/res/values/strings.xml

@ -26,6 +26,8 @@
<string name="overlay_auto_hide">Overlay Auto Hide</string> <string name="overlay_auto_hide">Overlay Auto Hide</string>
<string name="overlay_auto_hide_description">Automatically hide the touch controls overlay after the specified time of inactivity.</string> <string name="overlay_auto_hide_description">Automatically hide the touch controls overlay after the specified time of inactivity.</string>
<string name="enable_input_overlay_auto_hide">Enable Overlay Auto Hide</string> <string name="enable_input_overlay_auto_hide">Enable Overlay Auto Hide</string>
<string name="hide_overlay_on_controller_input">Hide Overlay on Controller Input</string>
<string name="hide_overlay_on_controller_input_description">Automatically hide the touch controls overlay when a physical controller is used. Overlay reappears when controller is disconnected.</string>
<string name="input_overlay_options">Input Overlay</string> <string name="input_overlay_options">Input Overlay</string>
<string name="input_overlay_options_description">Configure on-screen controls</string> <string name="input_overlay_options_description">Configure on-screen controls</string>

Loading…
Cancel
Save