Browse Source

[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>
pull/3174/head
Producdevity 5 days ago
committed by crueter
parent
commit
e4dccd5a5c
No known key found for this signature in database GPG Key ID: 425ACD2D4830EBC6
  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. 35
      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.graphics.Rect
import android.graphics.drawable.Icon
import android.hardware.input.InputManager
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
@ -63,11 +64,12 @@ import kotlin.math.roundToInt
import org.yuzu.yuzu_emu.utils.ForegroundService
import androidx.core.os.BundleCompat
class EmulationActivity : AppCompatActivity(), SensorEventListener {
class EmulationActivity : AppCompatActivity(), SensorEventListener, InputManager.InputDeviceListener {
private lateinit var binding: ActivityEmulationBinding
var isActivityRecreated = false
private lateinit var nfcReader: NfcReader
private lateinit var inputManager: InputManager
private var touchDownTime: Long = 0
private val maxTapDuration = 500L
@ -140,6 +142,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
nfcReader = NfcReader(this)
nfcReader.initialize()
inputManager = getSystemService(INPUT_SERVICE) as InputManager
inputManager.registerInputDeviceListener(this, null)
foregroundService = Intent(this, ForegroundService::class.java)
startForegroundService(foregroundService)
@ -206,9 +211,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
override fun onDestroy() {
super.onDestroy()
inputManager.unregisterInputDeviceListener(this)
stopForegroundService(this)
NativeLibrary.playTimeManagerStop()
}
override fun onUserLeaveHint() {
@ -244,8 +249,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
val isPhysicalKeyboard = event.source and InputDevice.SOURCE_KEYBOARD == InputDevice.SOURCE_KEYBOARD &&
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 &&
!isPhysicalKeyboard
) {
@ -256,12 +263,18 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
return super.dispatchKeyEvent(event)
}
if (isControllerInput && event.action == KeyEvent.ACTION_DOWN) {
notifyControllerInput()
}
return InputHandler.dispatchKeyEvent(event)
}
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_MOUSE != InputDevice.SOURCE_MOUSE
) {
@ -277,9 +290,54 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
return true
}
if (isControllerInput) {
notifyControllerInput()
}
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) {
val rotation = this.display?.rotation
if (rotation == Surface.ROTATION_90) {
@ -519,8 +577,10 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
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.showOverlay()
}

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

@ -52,6 +52,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
SOC_OVERLAY_BACKGROUND("soc_overlay_background"),
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"),
SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"),

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

@ -387,6 +387,13 @@ abstract class SettingsItem(
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(
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 {
add(BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.key)
add(IntSetting.INPUT_OVERLAY_AUTO_HIDE.key)
add(BooleanSetting.HIDE_OVERLAY_ON_CONTROLLER_INPUT.key)
}
}

35
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 java.io.ByteArrayOutputStream
import java.io.File
import kotlin.coroutines.coroutineContext
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@ -106,6 +105,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
val handler = Handler(Looper.getMainLooper())
private var isOverlayVisible = true
private var controllerInputReceived = false
private var _binding: FragmentEmulationBinding? = null
@ -656,6 +656,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
BooleanSetting.SHOW_INPUT_OVERLAY.setBoolean(newState)
updateQuickOverlayMenuEntry(newState)
binding.surfaceInputOverlay.refreshControls()
// Sync view visibility with the setting
if (newState) {
showOverlay()
} else {
hideOverlay()
}
NativeConfig.saveGlobalConfig()
true
}
@ -1901,7 +1907,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
companion object {
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 {
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() {
if (!isOverlayVisible) {
isOverlayVisible = true
// Reset controller input flag so controller can hide overlay again
controllerInputReceived = false
ViewUtils.showView(binding.surfaceInputOverlay, 500)
}
}
@ -1978,7 +1986,26 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
private fun hideOverlay() {
if (isOverlayVisible) {
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::Category::Overlay,
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::Category::Overlay,
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_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="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_description">Configure on-screen controls</string>

Loading…
Cancel
Save