[android] setting to auto hide overlay on controller input (#3127)
Setting `HIDE_OVERLAY_ON_CONTROLLER_INPUT` in *Advanced settings → Input Overlay* **Behavior:** - First controller input -> hides overlay - Controller disconnect → shows overlay again - Subsequent controller inputs → ignored (already hidden, so no retrigger needed) - Touch screen → does **not** show overlay (so you can use a controller and touchscreen to interact with games) - Sidebar "Show/Hide controller" button → still works as master toggle **State reset: The "first input" detection resets when:** 1. Controller disconnects 2. Overlay is shown via sidebar button 3. Controller reconnects **Interaction with other settings:** - Requires `SHOW_INPUT_OVERLAY` to be enabled (basicaly a master switch) - Independent from `ENABLE_INPUT_OVERLAY_AUTO_HIDE` (timer-based hide, was already implemented) - When both are enabled, touch-to-show is disabled (controller-hide takes precedence) Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3127 Reviewed-by: Caio Oliveira <caiooliveirafarias0@gmail.com> Reviewed-by: Maufeat <sahyno1996@gmail.com> Co-authored-by: Producdevity <y.gherbi.dev@gmail.com> Co-committed-by: Producdevity <y.gherbi.dev@gmail.com>
This commit is contained in:
parent
b9530ae80f
commit
e4dccd5a5c
|
|
@ -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 &&
|
val isControllerInput = event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK ||
|
||||||
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD &&
|
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 &&
|
val isControllerInput = event.source and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK ||
|
||||||
event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD &&
|
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
|
// show overlay immediately on touch and cancel timer when only auto-hide is enabled
|
||||||
if (!emulationViewModel.drawerOpen.value) {
|
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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||||
|
|
||||||
SOC_OVERLAY_BACKGROUND("soc_overlay_background"),
|
SOC_OVERLAY_BACKGROUND("soc_overlay_background"),
|
||||||
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"),
|
||||||
|
|
|
||||||
|
|
@ -387,6 +387,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(
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
@ -1901,7 +1907,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromValue(value: Int): AmiiboState =
|
fun fromValue(value: Int): AmiiboState =
|
||||||
values().firstOrNull { it.value == value } ?: Disabled
|
entries.firstOrNull { it.value == value } ?: Disabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1914,7 +1920,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromValue(value: Int): AmiiboLoadResult =
|
fun fromValue(value: Int): AmiiboLoadResult =
|
||||||
values().firstOrNull { it.value == value } ?: Unknown
|
entries.firstOrNull { it.value == value } ?: Unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1971,6 +1977,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1978,7 +1986,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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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…
Reference in New Issue