mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-21 05:09:34 -06:00
Android: Don't use separate thread for MotionAlertDialog
This is an Android continuation of bc95c00
. We now call
InputDetector::Update immediately after receiving an input event from
Android instead of periodically calling it in a sleep loop. This
improves detection of very short inputs, which are especially likely to
occur for volume buttons on phones (or at least on my phone) if you
don't intentionally keep them held down.
This commit is contained in:
@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* Waits for the user to press inputs, and reports which inputs were pressed.
|
||||
*
|
||||
* The caller is responsible for forwarding input events from Android to ControllerInterface
|
||||
* and then calling [update].
|
||||
*/
|
||||
class InputDetector {
|
||||
@Keep
|
||||
private val pointer: Long
|
||||
|
||||
constructor() {
|
||||
pointer = createNew()
|
||||
}
|
||||
|
||||
@Keep
|
||||
private constructor(pointer: Long) {
|
||||
this.pointer = pointer
|
||||
}
|
||||
|
||||
external fun finalize()
|
||||
|
||||
private external fun createNew(): Long
|
||||
|
||||
/**
|
||||
* Starts a detection session.
|
||||
*
|
||||
* @param defaultDevice The device to detect inputs from.
|
||||
* @param allDevices Whether to also detect inputs from devices other than the specified one.
|
||||
*/
|
||||
external fun start(defaultDevice: String, allDevices: Boolean)
|
||||
|
||||
/**
|
||||
* Checks what inputs are currently pressed and updates internal state.
|
||||
*
|
||||
* During a detection session, this should be called after each call to
|
||||
* [ControllerInterface.dispatchKeyEvent] and [ControllerInterface#dispatchGenericMotionEvent].
|
||||
*/
|
||||
external fun update()
|
||||
|
||||
/**
|
||||
* Returns whether a detection session has finished.
|
||||
*
|
||||
* A detection session can end once the user has pressed and released an input or once a timeout
|
||||
* has been reached.
|
||||
*/
|
||||
external fun isComplete(): Boolean
|
||||
|
||||
/**
|
||||
* Returns the result of a detection session.
|
||||
*
|
||||
* The result of each detection session is only returned once. If this method is called more
|
||||
* than once without starting a new detection session, the second call onwards will return an
|
||||
* empty string.
|
||||
*
|
||||
* @param defaultDevice The device to detect inputs from. Should normally be the same as the one
|
||||
* passed to [start].
|
||||
*
|
||||
* @return The input(s) pressed by the user in the form of an InputCommon expression,
|
||||
* or an empty string if there were no inputs.
|
||||
*/
|
||||
external fun takeResults(defaultDevice: String): String
|
||||
}
|
@ -2,23 +2,7 @@
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model
|
||||
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController
|
||||
|
||||
object MappingCommon {
|
||||
/**
|
||||
* Waits until the user presses one or more inputs or until a timeout,
|
||||
* then returns the pressed inputs.
|
||||
*
|
||||
* When this is being called, a separate thread must be calling ControllerInterface's
|
||||
* dispatchKeyEvent and dispatchGenericMotionEvent, otherwise no inputs will be registered.
|
||||
*
|
||||
* @param controller The device to detect inputs from.
|
||||
* @param allDevices Whether to also detect inputs from devices other than the specified one.
|
||||
* @return The input(s) pressed by the user in the form of an InputCommon expression,
|
||||
* or an empty string if there were no inputs.
|
||||
*/
|
||||
external fun detectInput(controller: EmulatedController, allDevices: Boolean): String
|
||||
|
||||
external fun getExpressionForControl(
|
||||
control: String,
|
||||
device: String,
|
||||
|
@ -3,12 +3,14 @@
|
||||
package org.dolphinemu.dolphinemu.features.input.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Looper
|
||||
import android.os.Handler
|
||||
import android.view.InputDevice
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface
|
||||
import org.dolphinemu.dolphinemu.features.input.model.MappingCommon
|
||||
import org.dolphinemu.dolphinemu.features.input.model.InputDetector
|
||||
import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting
|
||||
|
||||
/**
|
||||
@ -24,21 +26,15 @@ class MotionAlertDialog(
|
||||
private val setting: InputMappingControlSetting,
|
||||
private val allDevices: Boolean
|
||||
) : AlertDialog(activity) {
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private val inputDetector: InputDetector = InputDetector()
|
||||
private var running = false
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
running = true
|
||||
Thread {
|
||||
val result = MappingCommon.detectInput(setting.controller, allDevices)
|
||||
activity.runOnUiThread {
|
||||
if (running) {
|
||||
setting.value = result
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
inputDetector.start(setting.controller.getDefaultDevice(), allDevices)
|
||||
periodicUpdate()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
@ -48,9 +44,11 @@ class MotionAlertDialog(
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
ControllerInterface.dispatchKeyEvent(event)
|
||||
updateInputDetector()
|
||||
if (event.keyCode == KeyEvent.KEYCODE_BACK && event.isLongPress) {
|
||||
// Special case: Let the user cancel by long-pressing Back (intended for non-touch devices)
|
||||
setting.clearValue()
|
||||
running = false
|
||||
dismiss()
|
||||
}
|
||||
return true
|
||||
@ -63,6 +61,29 @@ class MotionAlertDialog(
|
||||
}
|
||||
|
||||
ControllerInterface.dispatchGenericMotionEvent(event)
|
||||
updateInputDetector()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun updateInputDetector() {
|
||||
if (running) {
|
||||
if (inputDetector.isComplete()) {
|
||||
setting.value = inputDetector.takeResults(setting.controller.getDefaultDevice())
|
||||
running = false
|
||||
|
||||
// Quirk: If this method has been called from onStart, calling dismiss directly
|
||||
// doesn't seem to do anything. As a workaround, post a call to dismiss instead.
|
||||
handler.post(this::dismiss)
|
||||
} else {
|
||||
inputDetector.update()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun periodicUpdate() {
|
||||
updateInputDetector()
|
||||
if (running) {
|
||||
handler.postDelayed(this::periodicUpdate, 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user