mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-05-09 04:13:28 -05:00
Merge 0f12ed1b39 into d19952cc11
This commit is contained in:
commit
8492bb7f71
|
|
@ -34,6 +34,7 @@ import com.google.android.material.slider.Slider
|
|||
import org.dolphinemu.dolphinemu.NativeLibrary
|
||||
import org.dolphinemu.dolphinemu.R
|
||||
import org.dolphinemu.dolphinemu.databinding.ActivityEmulationBinding
|
||||
import org.dolphinemu.dolphinemu.databinding.DialogOverlayHapticsBinding
|
||||
import org.dolphinemu.dolphinemu.databinding.DialogInputAdjustBinding
|
||||
import org.dolphinemu.dolphinemu.databinding.DialogNfcFiguresManagerBinding
|
||||
import org.dolphinemu.dolphinemu.features.infinitybase.InfinityConfig
|
||||
|
|
@ -42,7 +43,9 @@ import org.dolphinemu.dolphinemu.features.infinitybase.ui.FigureSlot
|
|||
import org.dolphinemu.dolphinemu.features.infinitybase.ui.FigureSlotAdapter
|
||||
import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface
|
||||
import org.dolphinemu.dolphinemu.features.input.model.DolphinSensorEventListener
|
||||
import org.dolphinemu.dolphinemu.features.input.model.DolphinVibratorManagerFactory
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.FloatSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.IntSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.StringSetting
|
||||
|
|
@ -56,6 +59,7 @@ import org.dolphinemu.dolphinemu.fragments.EmulationFragment
|
|||
import org.dolphinemu.dolphinemu.fragments.MenuFragment
|
||||
import org.dolphinemu.dolphinemu.fragments.SaveLoadStateFragment
|
||||
import org.dolphinemu.dolphinemu.fragments.SaveLoadStateFragment.SaveOrLoad
|
||||
import org.dolphinemu.dolphinemu.model.HapticEffect
|
||||
import org.dolphinemu.dolphinemu.overlay.InputOverlay
|
||||
import org.dolphinemu.dolphinemu.overlay.InputOverlayPointer
|
||||
import org.dolphinemu.dolphinemu.ui.main.MainPresenter
|
||||
|
|
@ -63,6 +67,7 @@ import org.dolphinemu.dolphinemu.ui.main.ThemeProvider
|
|||
import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner
|
||||
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization
|
||||
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper
|
||||
import org.dolphinemu.dolphinemu.utils.HapticsProvider
|
||||
import org.dolphinemu.dolphinemu.utils.RateLimiter
|
||||
import org.dolphinemu.dolphinemu.utils.ThemeHelper
|
||||
import kotlin.math.roundToInt
|
||||
|
|
@ -443,6 +448,12 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
menu.findItem(R.id.menu_emulation_ir_recenter).isChecked =
|
||||
BooleanSetting.MAIN_IR_ALWAYS_RECENTER.boolean
|
||||
}
|
||||
// Hide the haptic feedback menu item if the device has no vibrator
|
||||
if (!DolphinVibratorManagerFactory.getSystemVibratorManager().getDefaultVibrator()
|
||||
.hasVibrator()
|
||||
) {
|
||||
menu.findItem(R.id.menu_emulation_touch_haptics).isVisible = false
|
||||
}
|
||||
popup.setOnMenuItemClickListener { item: MenuItem -> onOptionsItemSelected(item) }
|
||||
popup.show()
|
||||
}
|
||||
|
|
@ -518,6 +529,7 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
MENU_ACTION_SKYLANDERS -> showSkylanderPortalSettings()
|
||||
MENU_ACTION_INFINITY_BASE -> showInfinityBaseSettings()
|
||||
MENU_ACTION_EXIT -> emulationFragment!!.stopEmulation()
|
||||
MENU_ACTION_ADJUST_OVERLAY_HAPTICS -> adjustOverlayHaptics()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -699,6 +711,78 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
.show()
|
||||
}
|
||||
|
||||
private fun adjustOverlayHaptics() {
|
||||
val dialogBinding = DialogOverlayHapticsBinding.inflate(layoutInflater)
|
||||
val hapticsProvider = HapticsProvider()
|
||||
dialogBinding.apply {
|
||||
val checkboxes = listOf(
|
||||
overlayHapticsOnPressCheckbox,
|
||||
overlayHapticsOnReleaseCheckbox,
|
||||
overlayHapticsJoystickCheckbox
|
||||
)
|
||||
val toggleVibrationSettings = {
|
||||
checkboxes.any { it.isChecked }.let { enabled ->
|
||||
overlayHapticsUseVibratorDirectlyText.isEnabled = enabled
|
||||
overlayHapticsUseVibratorDirectlySwitch.isEnabled = enabled
|
||||
(overlayHapticsUseVibratorDirectlySwitch.isChecked && enabled).let {
|
||||
overlayHapticsIntensityName.isEnabled = it
|
||||
overlayHapticsIntensitySlider.isEnabled = it
|
||||
overlayHapticsIntensityValue.isEnabled = it
|
||||
}
|
||||
}
|
||||
}
|
||||
overlayHapticsOnPressCheckbox.isChecked =
|
||||
BooleanSetting.MAIN_OVERLAY_HAPTICS_ON_PRESS.boolean
|
||||
overlayHapticsOnReleaseCheckbox.isChecked =
|
||||
BooleanSetting.MAIN_OVERLAY_HAPTICS_ON_RELEASE.boolean
|
||||
overlayHapticsJoystickCheckbox.isChecked =
|
||||
BooleanSetting.MAIN_OVERLAY_HAPTICS_JOYSTICK.boolean
|
||||
overlayHapticsUseVibratorDirectlySwitch.isChecked =
|
||||
BooleanSetting.MAIN_OVERLAY_HAPTICS_USE_VIBRATOR_DIRECTLY.boolean
|
||||
toggleVibrationSettings()
|
||||
checkboxes.forEach {
|
||||
it.setOnCheckedChangeListener { _, _ -> toggleVibrationSettings() }
|
||||
}
|
||||
overlayHapticsUseVibratorDirectlySwitch.setOnCheckedChangeListener { _, _ ->
|
||||
toggleVibrationSettings()
|
||||
}
|
||||
overlayHapticsIntensitySlider.apply {
|
||||
val setValueText = { value: Float ->
|
||||
overlayHapticsIntensityValue.text =
|
||||
getString(R.string.slider_setting_value, value * 100f, '%')
|
||||
}
|
||||
stepSize = 0.1f
|
||||
valueFrom = 0.1f
|
||||
valueTo = 1.0f
|
||||
value = FloatSetting.MAIN_OVERLAY_HAPTICS_SCALE.float.also { setValueText(it) }
|
||||
addOnChangeListener { _: Slider, value: Float, _: Boolean ->
|
||||
setValueText(value)
|
||||
hapticsProvider.provideFeedback(HapticEffect.JOYSTICK, null, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setView(dialogBinding.root)
|
||||
.setPositiveButton(R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
BooleanSetting.MAIN_OVERLAY_HAPTICS_ON_PRESS.setBoolean(
|
||||
settings, dialogBinding.overlayHapticsOnPressCheckbox.isChecked
|
||||
)
|
||||
BooleanSetting.MAIN_OVERLAY_HAPTICS_ON_RELEASE.setBoolean(
|
||||
settings, dialogBinding.overlayHapticsOnReleaseCheckbox.isChecked
|
||||
)
|
||||
BooleanSetting.MAIN_OVERLAY_HAPTICS_JOYSTICK.setBoolean(
|
||||
settings, dialogBinding.overlayHapticsJoystickCheckbox.isChecked
|
||||
)
|
||||
BooleanSetting.MAIN_OVERLAY_HAPTICS_USE_VIBRATOR_DIRECTLY.setBoolean(
|
||||
settings, dialogBinding.overlayHapticsUseVibratorDirectlySwitch.isChecked
|
||||
)
|
||||
FloatSetting.MAIN_OVERLAY_HAPTICS_SCALE.setFloat(
|
||||
settings, dialogBinding.overlayHapticsIntensitySlider.value
|
||||
)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun chooseDoubleTapButton() {
|
||||
val currentValue = IntSetting.MAIN_DOUBLE_TAP_BUTTON.int
|
||||
|
||||
|
|
@ -1077,6 +1161,7 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
const val MENU_ACTION_SKYLANDERS = 36
|
||||
const val MENU_ACTION_INFINITY_BASE = 37
|
||||
const val MENU_ACTION_LATCHING_CONTROLS = 38
|
||||
const val MENU_ACTION_ADJUST_OVERLAY_HAPTICS = 39
|
||||
|
||||
init {
|
||||
buttonsActionsMap.apply {
|
||||
|
|
@ -1090,6 +1175,7 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
append(R.id.menu_emulation_ir_recenter, MENU_SET_IR_RECENTER)
|
||||
append(R.id.menu_emulation_set_ir_mode, MENU_SET_IR_MODE)
|
||||
append(R.id.menu_emulation_choose_doubletap, MENU_ACTION_CHOOSE_DOUBLETAP)
|
||||
append(R.id.menu_emulation_touch_haptics, MENU_ACTION_ADJUST_OVERLAY_HAPTICS)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import android.os.HandlerThread
|
|||
import android.os.Looper
|
||||
import android.os.VibrationEffect
|
||||
import android.os.Vibrator
|
||||
import android.os.VibratorManager
|
||||
import android.view.InputDevice
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
|
|
@ -154,28 +153,13 @@ object ControllerInterface {
|
|||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
private fun getVibratorManager(device: InputDevice): DolphinVibratorManager {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
DolphinVibratorManagerPassthrough(device.vibratorManager)
|
||||
} else {
|
||||
DolphinVibratorManagerCompat(device.vibrator)
|
||||
}
|
||||
}
|
||||
private fun getDeviceVibratorManager(device: InputDevice): DolphinVibratorManager =
|
||||
DolphinVibratorManagerFactory.getDeviceVibratorManager(device)
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
private fun getSystemVibratorManager(): DolphinVibratorManager {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
val vibratorManager = DolphinApplication.getAppContext()
|
||||
.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager?
|
||||
if (vibratorManager != null) {
|
||||
return DolphinVibratorManagerPassthrough(vibratorManager)
|
||||
}
|
||||
}
|
||||
val vibrator = DolphinApplication.getAppContext()
|
||||
.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||
return DolphinVibratorManagerCompat(vibrator)
|
||||
}
|
||||
private fun getSystemVibratorManager(): DolphinVibratorManager =
|
||||
DolphinVibratorManagerFactory.getSystemVibratorManager()
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
|
|
|
|||
|
|
@ -13,4 +13,6 @@ interface DolphinVibratorManager {
|
|||
fun getVibrator(vibratorId: Int): Vibrator
|
||||
|
||||
fun getVibratorIds(): IntArray
|
||||
|
||||
fun getDefaultVibrator(): Vibrator
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,4 +21,6 @@ class DolphinVibratorManagerCompat(vibrator: Vibrator) : DolphinVibratorManager
|
|||
}
|
||||
|
||||
override fun getVibratorIds(): IntArray = vibratorIds
|
||||
|
||||
override fun getDefaultVibrator(): Vibrator = vibrator
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Vibrator
|
||||
import android.os.VibratorManager
|
||||
import android.view.InputDevice
|
||||
import org.dolphinemu.dolphinemu.DolphinApplication
|
||||
|
||||
object DolphinVibratorManagerFactory {
|
||||
fun getDeviceVibratorManager(device: InputDevice): DolphinVibratorManager {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
DolphinVibratorManagerPassthrough(device.vibratorManager)
|
||||
} else {
|
||||
DolphinVibratorManagerCompat(device.vibrator)
|
||||
}
|
||||
}
|
||||
|
||||
fun getSystemVibratorManager(): DolphinVibratorManager {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
val vibratorManager = DolphinApplication.getAppContext()
|
||||
.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager?
|
||||
if (vibratorManager != null) {
|
||||
return DolphinVibratorManagerPassthrough(vibratorManager)
|
||||
}
|
||||
}
|
||||
val vibrator = DolphinApplication.getAppContext()
|
||||
.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
||||
return DolphinVibratorManagerCompat(vibrator)
|
||||
}
|
||||
}
|
||||
|
|
@ -13,4 +13,6 @@ class DolphinVibratorManagerPassthrough(private val vibratorManager: VibratorMan
|
|||
override fun getVibrator(vibratorId: Int): Vibrator = vibratorManager.getVibrator(vibratorId)
|
||||
|
||||
override fun getVibratorIds(): IntArray = vibratorManager.vibratorIds
|
||||
|
||||
override fun getDefaultVibrator(): Vibrator = vibratorManager.defaultVibrator
|
||||
}
|
||||
|
|
|
|||
|
|
@ -672,6 +672,30 @@ enum class BooleanSetting(
|
|||
"ButtonLatchingNunchukZ",
|
||||
false
|
||||
),
|
||||
MAIN_OVERLAY_HAPTICS_ON_PRESS(
|
||||
Settings.FILE_DOLPHIN,
|
||||
Settings.SECTION_INI_ANDROID,
|
||||
"OverlayHapticsOnPress",
|
||||
true
|
||||
),
|
||||
MAIN_OVERLAY_HAPTICS_ON_RELEASE(
|
||||
Settings.FILE_DOLPHIN,
|
||||
Settings.SECTION_INI_ANDROID,
|
||||
"OverlayHapticsOnRelease",
|
||||
true
|
||||
),
|
||||
MAIN_OVERLAY_HAPTICS_JOYSTICK(
|
||||
Settings.FILE_DOLPHIN,
|
||||
Settings.SECTION_INI_ANDROID,
|
||||
"OverlayHapticsJoystick",
|
||||
false
|
||||
),
|
||||
MAIN_OVERLAY_HAPTICS_USE_VIBRATOR_DIRECTLY(
|
||||
Settings.FILE_DOLPHIN,
|
||||
Settings.SECTION_INI_ANDROID,
|
||||
"OverlayHapticsUseVibratorDirectly",
|
||||
false
|
||||
),
|
||||
SYSCONF_SCREENSAVER(Settings.FILE_SYSCONF, "IPL", "SSV", false),
|
||||
SYSCONF_WIDESCREEN(Settings.FILE_SYSCONF, "IPL", "AR", true),
|
||||
SYSCONF_PROGRESSIVE_SCAN(Settings.FILE_SYSCONF, "IPL", "PGS", true),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,12 @@ enum class FloatSetting(
|
|||
private val key: String,
|
||||
private val defaultValue: Float
|
||||
) : AbstractFloatSetting {
|
||||
MAIN_OVERLAY_HAPTICS_SCALE(
|
||||
Settings.FILE_DOLPHIN,
|
||||
Settings.SECTION_INI_ANDROID,
|
||||
"OverlayHapticsScale",
|
||||
0.5f
|
||||
),
|
||||
// These entries have the same names and order as in C++, just for consistency.
|
||||
MAIN_EMULATION_SPEED(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "EmulationSpeed", 1.0f),
|
||||
MAIN_OVERCLOCK(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "Overclock", 1.0f),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
package org.dolphinemu.dolphinemu.model
|
||||
|
||||
import android.os.Build
|
||||
import android.os.VibrationEffect
|
||||
import android.os.Vibrator
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.view.HapticFeedbackConstantsCompat
|
||||
|
||||
/**
|
||||
* Enum to represent haptic effects.
|
||||
*
|
||||
* @param feedbackConstant One of the constants defined in [HapticFeedbackConstantsCompat].
|
||||
* @param maxTimings The maximum timing values, in milliseconds, of the timing / amplitude pairs.
|
||||
* Note that lower-end vibrators may not be able to vibrate for short (<50ms) durations.
|
||||
* @param maxAmplitudes The maximum amplitude values of the timing / amplitude pairs.
|
||||
* Amplitude values must be between 0 and 255. An amplitude value of 0 implies the motor is off.
|
||||
* @param primaryPrimitive The primary primitive ID, or null if the SDK version is too low.
|
||||
* @param fallbackPrimitive The fallback primitive ID, or null if the SDK version is too low.
|
||||
*/
|
||||
enum class HapticEffect(
|
||||
val feedbackConstant: Int,
|
||||
private val maxTimings: LongArray,
|
||||
private val maxAmplitudes: IntArray,
|
||||
val primaryPrimitive: Int?,
|
||||
val fallbackPrimitive: Int? = null
|
||||
) {
|
||||
PRESS(
|
||||
feedbackConstant = HapticFeedbackConstantsCompat.VIRTUAL_KEY,
|
||||
maxTimings = longArrayOf(0L, 100L),
|
||||
maxAmplitudes = intArrayOf(0, 180),
|
||||
primaryPrimitive = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
VibrationEffect.Composition.PRIMITIVE_QUICK_FALL
|
||||
} else {
|
||||
null
|
||||
},
|
||||
fallbackPrimitive = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
VibrationEffect.Composition.PRIMITIVE_CLICK
|
||||
} else {
|
||||
null
|
||||
}
|
||||
),
|
||||
RELEASE(
|
||||
feedbackConstant = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
|
||||
HapticFeedbackConstantsCompat.VIRTUAL_KEY_RELEASE
|
||||
} else {
|
||||
HapticFeedbackConstantsCompat.CONTEXT_CLICK // Better than a no-op.
|
||||
},
|
||||
maxTimings = longArrayOf(0L, 70L),
|
||||
maxAmplitudes = intArrayOf(0, 128),
|
||||
primaryPrimitive = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
VibrationEffect.Composition.PRIMITIVE_QUICK_RISE
|
||||
} else {
|
||||
null
|
||||
}
|
||||
),
|
||||
JOYSTICK(
|
||||
feedbackConstant = HapticFeedbackConstantsCompat.SEGMENT_FREQUENT_TICK,
|
||||
maxTimings = longArrayOf(50L),
|
||||
maxAmplitudes = intArrayOf(90),
|
||||
primaryPrimitive = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
VibrationEffect.Composition.PRIMITIVE_LOW_TICK
|
||||
} else {
|
||||
null
|
||||
},
|
||||
fallbackPrimitive = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
VibrationEffect.Composition.PRIMITIVE_TICK
|
||||
} else {
|
||||
null
|
||||
}
|
||||
);
|
||||
|
||||
fun getMaxTimings(): LongArray = maxTimings.copyOf()
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun getMaxAmplitudes(): IntArray = maxAmplitudes.copyOf()
|
||||
|
||||
fun scaleTimings(@FloatRange(from = 0.0, to = 1.0) scale: Float): LongArray =
|
||||
maxTimings.map { (it.toFloat() * scale).toLong() }.toLongArray()
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun scaleAmplitudes(@FloatRange(from = 0.0, to = 1.0) scale: Float): IntArray =
|
||||
maxAmplitudes.map { (it.toFloat() * scale).toInt() }.toIntArray()
|
||||
|
||||
/**
|
||||
* Gets this effect's preferred primitive ID that is supported by the provided [vibrator].
|
||||
* @param vibrator A [Vibrator] instance for checking whether primitives are supported by it.
|
||||
* @return The best supported primitive ID of this effect, or null if there is none.
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.R)
|
||||
fun getSupportedPrimitive(vibrator: Vibrator): Int? =
|
||||
listOf(primaryPrimitive, fallbackPrimitive).firstOrNull {
|
||||
it != null && vibrator.areAllPrimitivesSupported(it)
|
||||
}
|
||||
}
|
||||
|
|
@ -27,9 +27,12 @@ import org.dolphinemu.dolphinemu.features.input.model.InputOverrider
|
|||
import org.dolphinemu.dolphinemu.features.input.model.InputOverrider.ControlId
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.FloatSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.IntSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.IntSetting.Companion.getSettingForSIDevice
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.IntSetting.Companion.getSettingForWiimoteSource
|
||||
import org.dolphinemu.dolphinemu.model.HapticEffect
|
||||
import org.dolphinemu.dolphinemu.utils.HapticsProvider
|
||||
import java.util.Arrays
|
||||
|
||||
/**
|
||||
|
|
@ -41,6 +44,7 @@ import java.util.Arrays
|
|||
*/
|
||||
class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(context, attrs),
|
||||
OnTouchListener {
|
||||
private val hapticsProvider: HapticsProvider = HapticsProvider()
|
||||
private val overlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet()
|
||||
private val overlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet()
|
||||
private val overlayJoysticks: MutableSet<InputOverlayDrawableJoystick> = HashSet()
|
||||
|
|
@ -140,6 +144,11 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
val firstPointer = action != MotionEvent.ACTION_POINTER_DOWN &&
|
||||
action != MotionEvent.ACTION_POINTER_UP
|
||||
val pointerIndex = if (firstPointer) 0 else event.actionIndex
|
||||
val hapticsScale = FloatSetting.MAIN_OVERLAY_HAPTICS_SCALE.float
|
||||
val hapticsOnPress = BooleanSetting.MAIN_OVERLAY_HAPTICS_ON_PRESS.boolean
|
||||
val hapticsOnRelease = BooleanSetting.MAIN_OVERLAY_HAPTICS_ON_RELEASE.boolean
|
||||
val hapticsView =
|
||||
if (BooleanSetting.MAIN_OVERLAY_HAPTICS_USE_VIBRATOR_DIRECTLY.boolean) null else v
|
||||
// Tracks if any button/joystick is pressed down
|
||||
var pressed = false
|
||||
|
||||
|
|
@ -154,7 +163,25 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
event.getY(pointerIndex).toInt()
|
||||
)
|
||||
) {
|
||||
button.setPressedState(if (button.latching) !button.getPressedState() else true)
|
||||
if (button.latching && button.getPressedState()) {
|
||||
button.setPressedState(false)
|
||||
if (hapticsOnRelease) {
|
||||
hapticsProvider.provideFeedback(
|
||||
HapticEffect.RELEASE,
|
||||
hapticsView,
|
||||
hapticsScale
|
||||
)
|
||||
}
|
||||
} else {
|
||||
button.setPressedState(true)
|
||||
if (hapticsOnPress) {
|
||||
hapticsProvider.provideFeedback(
|
||||
HapticEffect.PRESS,
|
||||
hapticsView,
|
||||
hapticsScale
|
||||
)
|
||||
}
|
||||
}
|
||||
button.trackId = event.getPointerId(pointerIndex)
|
||||
pressed = true
|
||||
InputOverrider.setControlState(controllerIndex, button.control, if (button.getPressedState()) 1.0 else 0.0)
|
||||
|
|
@ -173,8 +200,16 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
MotionEvent.ACTION_POINTER_UP -> {
|
||||
// If a pointer ends, release the button it was pressing.
|
||||
if (button.trackId == event.getPointerId(pointerIndex)) {
|
||||
if (!button.latching)
|
||||
if (!button.latching) {
|
||||
button.setPressedState(false)
|
||||
if (hapticsOnRelease) {
|
||||
hapticsProvider.provideFeedback(
|
||||
HapticEffect.RELEASE,
|
||||
hapticsView,
|
||||
hapticsScale
|
||||
)
|
||||
}
|
||||
}
|
||||
InputOverrider.setControlState(controllerIndex, button.control, if (button.getPressedState()) 1.0 else 0.0)
|
||||
|
||||
val analogControl = getAnalogControlForTrigger(button.control)
|
||||
|
|
@ -227,12 +262,26 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
// Release the buttons first, then press
|
||||
for (i in dpadPressed.indices) {
|
||||
if (!dpadPressed[i]) {
|
||||
if (hapticsOnRelease && dpad.isPressed(i)) {
|
||||
hapticsProvider.provideFeedback(
|
||||
HapticEffect.RELEASE,
|
||||
hapticsView,
|
||||
hapticsScale
|
||||
)
|
||||
}
|
||||
InputOverrider.setControlState(
|
||||
controllerIndex,
|
||||
dpad.getControl(i),
|
||||
0.0
|
||||
)
|
||||
} else {
|
||||
if (hapticsOnPress && !dpad.isPressed(i)) {
|
||||
hapticsProvider.provideFeedback(
|
||||
HapticEffect.PRESS,
|
||||
hapticsView,
|
||||
hapticsScale
|
||||
)
|
||||
}
|
||||
InputOverrider.setControlState(
|
||||
controllerIndex,
|
||||
dpad.getControl(i),
|
||||
|
|
@ -240,8 +289,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
)
|
||||
}
|
||||
}
|
||||
setDpadState(
|
||||
dpad,
|
||||
dpad.setPressed(
|
||||
dpadPressed[0],
|
||||
dpadPressed[1],
|
||||
dpadPressed[2],
|
||||
|
|
@ -255,13 +303,20 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
// If a pointer ends, release the buttons.
|
||||
if (dpad.trackId == event.getPointerId(pointerIndex)) {
|
||||
for (i in 0 until 4) {
|
||||
dpad.setState(InputOverlayDrawableDpad.STATE_DEFAULT)
|
||||
if (hapticsOnRelease && dpad.isPressed(i)) {
|
||||
hapticsProvider.provideFeedback(
|
||||
HapticEffect.RELEASE,
|
||||
hapticsView,
|
||||
hapticsScale
|
||||
)
|
||||
}
|
||||
InputOverrider.setControlState(
|
||||
controllerIndex,
|
||||
dpad.getControl(i),
|
||||
0.0
|
||||
)
|
||||
}
|
||||
dpad.setPressed(up = false, down = false, left = false, right = false)
|
||||
dpad.trackId = -1
|
||||
}
|
||||
}
|
||||
|
|
@ -269,7 +324,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
}
|
||||
|
||||
for (joystick in overlayJoysticks) {
|
||||
if (joystick.trackEvent(event)) {
|
||||
if (joystick.trackEvent(v, event)) {
|
||||
if (joystick.trackId != -1)
|
||||
pressed = true
|
||||
}
|
||||
|
|
@ -455,40 +510,6 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
else -> -1
|
||||
}
|
||||
|
||||
private fun setDpadState(
|
||||
dpad: InputOverlayDrawableDpad,
|
||||
up: Boolean,
|
||||
down: Boolean,
|
||||
left: Boolean,
|
||||
right: Boolean
|
||||
) {
|
||||
if (up) {
|
||||
if (left) {
|
||||
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_UP_LEFT)
|
||||
} else {
|
||||
if (right) {
|
||||
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_UP_RIGHT)
|
||||
} else {
|
||||
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_UP)
|
||||
}
|
||||
}
|
||||
} else if (down) {
|
||||
if (left) {
|
||||
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_DOWN_LEFT)
|
||||
} else {
|
||||
if (right) {
|
||||
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_DOWN_RIGHT)
|
||||
} else {
|
||||
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_DOWN)
|
||||
}
|
||||
}
|
||||
} else if (left) {
|
||||
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_LEFT)
|
||||
} else if (right) {
|
||||
dpad.setState(InputOverlayDrawableDpad.STATE_PRESSED_RIGHT)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addGameCubeOverlayControls(orientation: String) {
|
||||
if (BooleanSetting.MAIN_BUTTON_TOGGLE_GC_0.boolean) {
|
||||
overlayButtons.add(
|
||||
|
|
@ -1349,7 +1370,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
|||
legacyId,
|
||||
xControl,
|
||||
yControl,
|
||||
controllerIndex
|
||||
controllerIndex,
|
||||
hapticsProvider
|
||||
)
|
||||
|
||||
// Need to set the image's position
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ class InputOverlayDrawableDpad(
|
|||
private val defaultStateBitmap: BitmapDrawable
|
||||
private val pressedOneDirectionStateBitmap: BitmapDrawable
|
||||
private val pressedTwoDirectionsStateBitmap: BitmapDrawable
|
||||
private val pressedArray = BooleanArray(4)
|
||||
private var pressState = STATE_DEFAULT
|
||||
|
||||
init {
|
||||
|
|
@ -171,10 +172,32 @@ class InputOverlayDrawableDpad(
|
|||
val bounds: Rect
|
||||
get() = defaultStateBitmap.bounds
|
||||
|
||||
fun setState(pressState: Int) {
|
||||
this.pressState = pressState
|
||||
fun setPressed(up: Boolean, down: Boolean, left: Boolean, right: Boolean) {
|
||||
pressedArray[0] = up
|
||||
pressedArray[1] = down
|
||||
pressedArray[2] = left
|
||||
pressedArray[3] = right
|
||||
pressState = when {
|
||||
up -> when {
|
||||
left -> STATE_PRESSED_UP_LEFT
|
||||
right -> STATE_PRESSED_UP_RIGHT
|
||||
else -> STATE_PRESSED_UP
|
||||
}
|
||||
|
||||
down -> when {
|
||||
left -> STATE_PRESSED_DOWN_LEFT
|
||||
right -> STATE_PRESSED_DOWN_RIGHT
|
||||
else -> STATE_PRESSED_DOWN
|
||||
}
|
||||
|
||||
left -> STATE_PRESSED_LEFT
|
||||
right -> STATE_PRESSED_RIGHT
|
||||
else -> STATE_DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
fun isPressed(index: Int): Boolean = pressedArray.getOrNull(index) ?: false
|
||||
|
||||
companion object {
|
||||
const val STATE_DEFAULT = 0
|
||||
const val STATE_PRESSED_UP = 1
|
||||
|
|
|
|||
|
|
@ -8,11 +8,16 @@ import android.graphics.Canvas
|
|||
import android.graphics.Rect
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import org.dolphinemu.dolphinemu.features.input.model.InputOverrider
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.FloatSetting
|
||||
import org.dolphinemu.dolphinemu.model.HapticEffect
|
||||
import org.dolphinemu.dolphinemu.utils.HapticsProvider
|
||||
import kotlin.math.atan2
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.hypot
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sin
|
||||
|
||||
/**
|
||||
|
|
@ -28,6 +33,7 @@ import kotlin.math.sin
|
|||
* @param legacyId Legacy identifier (ButtonType) for which joystick this is.
|
||||
* @param xControl The control which the x value of the joystick will be written to.
|
||||
* @param yControl The control which the y value of the joystick will be written to.
|
||||
* @param hapticsProvider An instance of [HapticsProvider] for providing haptic feedback.
|
||||
*/
|
||||
class InputOverlayDrawableJoystick(
|
||||
res: Resources,
|
||||
|
|
@ -39,7 +45,8 @@ class InputOverlayDrawableJoystick(
|
|||
val legacyId: Int,
|
||||
val xControl: Int,
|
||||
val yControl: Int,
|
||||
private val controllerIndex: Int
|
||||
private val controllerIndex: Int,
|
||||
private val hapticsProvider: HapticsProvider
|
||||
) {
|
||||
var x = 0.0f
|
||||
private set
|
||||
|
|
@ -47,6 +54,9 @@ class InputOverlayDrawableJoystick(
|
|||
private set
|
||||
var trackId = -1
|
||||
private set
|
||||
private var maxRadius = 0.0
|
||||
private var hapticsPreviousX = 0.0f
|
||||
private var hapticsPreviousY = 0.0f
|
||||
private var controlPositionX = 0
|
||||
private var controlPositionY = 0
|
||||
private var previousTouchX = 0
|
||||
|
|
@ -94,12 +104,15 @@ class InputOverlayDrawableJoystick(
|
|||
boundsBoxBitmap.draw(canvas)
|
||||
}
|
||||
|
||||
fun trackEvent(event: MotionEvent): Boolean {
|
||||
fun trackEvent(v: View, event: MotionEvent): Boolean {
|
||||
val reCenter = BooleanSetting.MAIN_JOYSTICK_REL_CENTER.boolean
|
||||
val action = event.actionMasked
|
||||
val firstPointer = action != MotionEvent.ACTION_POINTER_DOWN &&
|
||||
action != MotionEvent.ACTION_POINTER_UP
|
||||
val pointerIndex = if (firstPointer) 0 else event.actionIndex
|
||||
val hapticsScale = FloatSetting.MAIN_OVERLAY_HAPTICS_SCALE.float
|
||||
val hapticsView =
|
||||
if (BooleanSetting.MAIN_OVERLAY_HAPTICS_USE_VIBRATOR_DIRECTLY.boolean) null else v
|
||||
var pressed = false
|
||||
|
||||
when (action) {
|
||||
|
|
@ -112,6 +125,13 @@ class InputOverlayDrawableJoystick(
|
|||
) {
|
||||
pressed = true
|
||||
pressedState = true
|
||||
if (BooleanSetting.MAIN_OVERLAY_HAPTICS_ON_PRESS.boolean) {
|
||||
hapticsProvider.provideFeedback(
|
||||
HapticEffect.PRESS,
|
||||
hapticsView,
|
||||
hapticsScale
|
||||
)
|
||||
}
|
||||
outerBitmap.alpha = 0
|
||||
boundsBoxBitmap.alpha = opacity
|
||||
if (reCenter) {
|
||||
|
|
@ -130,8 +150,17 @@ class InputOverlayDrawableJoystick(
|
|||
if (trackId == event.getPointerId(pointerIndex)) {
|
||||
pressed = true
|
||||
pressedState = false
|
||||
if (BooleanSetting.MAIN_OVERLAY_HAPTICS_ON_RELEASE.boolean) {
|
||||
hapticsProvider.provideFeedback(
|
||||
HapticEffect.RELEASE,
|
||||
hapticsView,
|
||||
hapticsScale
|
||||
)
|
||||
}
|
||||
y = 0f
|
||||
x = y
|
||||
hapticsPreviousX = x
|
||||
hapticsPreviousY = y
|
||||
outerBitmap.alpha = opacity
|
||||
boundsBoxBitmap.alpha = 0
|
||||
virtBounds =
|
||||
|
|
@ -161,6 +190,20 @@ class InputOverlayDrawableJoystick(
|
|||
y = touchY / maxY
|
||||
|
||||
setInnerBounds()
|
||||
if (BooleanSetting.MAIN_OVERLAY_HAPTICS_JOYSTICK.boolean) {
|
||||
val radiusThreshold = maxRadius * 0.1
|
||||
val deltaX = x - hapticsPreviousX
|
||||
val deltaY = y - hapticsPreviousY
|
||||
if (deltaX.pow(2) + deltaY.pow(2) > radiusThreshold.pow(2)) {
|
||||
hapticsProvider.provideFeedback(
|
||||
HapticEffect.JOYSTICK,
|
||||
hapticsView,
|
||||
hapticsScale
|
||||
)
|
||||
hapticsPreviousX = x
|
||||
hapticsPreviousY = y
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return pressed
|
||||
|
|
@ -211,7 +254,7 @@ class InputOverlayDrawableJoystick(
|
|||
|
||||
val angle = atan2(y, x) + Math.PI + Math.PI
|
||||
val radius = hypot(y, x)
|
||||
val maxRadius = InputOverrider.getGateRadiusAtAngle(controllerIndex, xControl, angle)
|
||||
maxRadius = InputOverrider.getGateRadiusAtAngle(controllerIndex, xControl, angle)
|
||||
if (radius > maxRadius) {
|
||||
x = maxRadius * cos(angle)
|
||||
y = maxRadius * sin(angle)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.utils
|
||||
|
||||
import android.os.Build
|
||||
import android.os.VibrationEffect
|
||||
import android.os.Vibrator
|
||||
import android.view.View
|
||||
import androidx.annotation.FloatRange
|
||||
import androidx.core.view.ViewCompat
|
||||
import org.dolphinemu.dolphinemu.features.input.model.DolphinVibratorManagerFactory
|
||||
import org.dolphinemu.dolphinemu.model.HapticEffect
|
||||
|
||||
/**
|
||||
* Provides haptic feedback to the user.
|
||||
*
|
||||
* @property vibrator The [Vibrator] instance to be used for vibration.
|
||||
* Defaults to the system's default vibrator.
|
||||
*/
|
||||
class HapticsProvider(
|
||||
private val vibrator: Vibrator =
|
||||
DolphinVibratorManagerFactory.getSystemVibratorManager().getDefaultVibrator()
|
||||
) {
|
||||
private val supportedPrimitivesMap: Map<HapticEffect, Int?>
|
||||
private val primitiveSupport: Boolean
|
||||
|
||||
init {
|
||||
supportedPrimitivesMap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
HapticEffect.entries.associateWith { it.getSupportedPrimitive(vibrator) }
|
||||
} else {
|
||||
HapticEffect.entries.associateWith { null }
|
||||
}
|
||||
primitiveSupport = supportedPrimitivesMap.values.all { it != null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform haptic feedback natively (if a [View] is provided),
|
||||
* or by composing primitives (if supported), falling back to a waveform or a legacy vibration.
|
||||
*
|
||||
* @param effect The [HapticEffect] of the feedback.
|
||||
* @param view The [View] to perform the feedback on, can be null to use the [vibrator] directly.
|
||||
* @param scale The intensity scale of the feedback, will only be used if [view] is not set.
|
||||
*/
|
||||
fun provideFeedback(
|
||||
effect: HapticEffect,
|
||||
view: View? = null,
|
||||
@FloatRange(from = 0.0, to = 1.0) scale: Float = 0.5f
|
||||
) {
|
||||
if (view != null) {
|
||||
ViewCompat.performHapticFeedback(view, effect.feedbackConstant)
|
||||
} else if (primitiveSupport && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
vibrator.vibrate(
|
||||
VibrationEffect
|
||||
.startComposition()
|
||||
.addPrimitive(supportedPrimitivesMap[effect]!!, scale)
|
||||
.compose()
|
||||
)
|
||||
} else {
|
||||
val scaledTimings = effect.scaleTimings(scale)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
vibrator.vibrate(
|
||||
VibrationEffect.createWaveform(scaledTimings, effect.scaleAmplitudes(scale), -1)
|
||||
)
|
||||
} else {
|
||||
vibrator.vibrate(scaledTimings.sum())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="@dimen/spacing_xtralarge"
|
||||
android:paddingTop="@dimen/spacing_large">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/overlay_haptics_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/spacing_small"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/overlay_haptics_header_text"
|
||||
style="@style/TextAppearance.AppCompat.Title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/emulation_touch_haptics" />
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/overlay_haptics_checkboxes"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_small">
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/overlay_haptics_on_press_checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="48dp"
|
||||
android:minWidth="48dp"
|
||||
android:text="@string/overlay_haptics_on_press"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/overlay_haptics_on_release_checkbox"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/overlay_haptics_on_release_checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="48dp"
|
||||
android:minWidth="48dp"
|
||||
android:text="@string/overlay_haptics_on_release"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/overlay_haptics_joystick_checkbox"
|
||||
app:layout_constraintStart_toEndOf="@+id/overlay_haptics_on_press_checkbox"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/overlay_haptics_joystick_checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="48dp"
|
||||
android:minWidth="48dp"
|
||||
android:text="@string/overlay_haptics_joystick"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/overlay_haptics_on_release_checkbox"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/overlay_haptics_use_vibrator_directly"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/spacing_small">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/overlay_haptics_use_vibrator_directly_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/spacing_large"
|
||||
android:text="@string/overlay_haptics_use_vibrator_directly"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/overlay_haptics_use_vibrator_directly_switch"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/overlay_haptics_use_vibrator_directly_switch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/overlay_haptics_use_vibrator_directly"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/overlay_haptics_use_vibrator_directly_text"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/overlay_haptics_intensity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="@dimen/spacing_small">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/overlay_haptics_intensity_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/overlay_haptics_intensity"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/overlay_haptics_intensity_slider"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/overlay_haptics_intensity_value"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/overlay_haptics_intensity_slider"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="50%" />
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/overlay_haptics_intensity_slider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/spacing_medlarge"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/overlay_haptics_intensity_value"
|
||||
app:layout_constraintStart_toEndOf="@id/overlay_haptics_intensity_name"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
|
@ -27,6 +27,10 @@
|
|||
android:id="@+id/menu_emulation_choose_controller"
|
||||
android:title="@string/emulation_choose_controller"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_emulation_touch_haptics"
|
||||
android:title="@string/emulation_touch_haptics"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_emulation_reset_overlay"
|
||||
android:title="@string/emulation_touch_overlay_reset"/>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@
|
|||
android:id="@+id/menu_emulation_choose_controller"
|
||||
android:title="@string/emulation_choose_controller"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_emulation_touch_haptics"
|
||||
android:title="@string/emulation_touch_haptics"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_emulation_ir_group"
|
||||
android:title="@string/emulation_ir_group">
|
||||
|
|
|
|||
|
|
@ -637,6 +637,7 @@ It can efficiently compress both junk data and encrypted Wii data.
|
|||
<string name="emulation_ir_mode">IR Mode</string>
|
||||
<string name="emulation_ir_sensitivity">IR Sensitivity</string>
|
||||
<string name="emulation_choose_doubletap">Double tap button</string>
|
||||
<string name="emulation_touch_haptics">Touch Haptics</string>
|
||||
|
||||
<!-- GC Adapter Menu-->
|
||||
<string name="gc_adapter_rumble">Enable Vibration</string>
|
||||
|
|
@ -841,6 +842,13 @@ It can efficiently compress both junk data and encrypted Wii data.
|
|||
<string name="ir_follow">Follow</string>
|
||||
<string name="ir_drag">Drag</string>
|
||||
|
||||
<!-- Overlay Haptics -->
|
||||
<string name="overlay_haptics_on_press">On Press</string>
|
||||
<string name="overlay_haptics_on_release">On Release</string>
|
||||
<string name="overlay_haptics_joystick">Joystick</string>
|
||||
<string name="overlay_haptics_use_vibrator_directly">Use vibrator directly</string>
|
||||
<string name="overlay_haptics_intensity">Intensity</string>
|
||||
|
||||
<!-- Double Tap Buttons -->
|
||||
<string name="double_tap_a">Button A</string>
|
||||
<string name="double_tap_b">Button B</string>
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ jmethodID s_motion_event_get_source;
|
|||
jclass s_controller_interface_class;
|
||||
jmethodID s_controller_interface_register_input_device_listener;
|
||||
jmethodID s_controller_interface_unregister_input_device_listener;
|
||||
jmethodID s_controller_interface_get_vibrator_manager;
|
||||
jmethodID s_controller_interface_get_device_vibrator_manager;
|
||||
jmethodID s_controller_interface_get_system_vibrator_manager;
|
||||
jmethodID s_controller_interface_vibrate;
|
||||
|
||||
|
|
@ -771,7 +771,8 @@ private:
|
|||
void AddMotors(JNIEnv* env, jobject input_device)
|
||||
{
|
||||
jobject vibrator_manager = env->CallStaticObjectMethod(
|
||||
s_controller_interface_class, s_controller_interface_get_vibrator_manager, input_device);
|
||||
s_controller_interface_class, s_controller_interface_get_device_vibrator_manager,
|
||||
input_device);
|
||||
AddMotorsFromManager(env, vibrator_manager);
|
||||
env->DeleteLocalRef(vibrator_manager);
|
||||
}
|
||||
|
|
@ -883,8 +884,8 @@ InputBackend::InputBackend(ControllerInterface* controller_interface)
|
|||
env->GetStaticMethodID(s_controller_interface_class, "registerInputDeviceListener", "()V");
|
||||
s_controller_interface_unregister_input_device_listener =
|
||||
env->GetStaticMethodID(s_controller_interface_class, "unregisterInputDeviceListener", "()V");
|
||||
s_controller_interface_get_vibrator_manager =
|
||||
env->GetStaticMethodID(s_controller_interface_class, "getVibratorManager",
|
||||
s_controller_interface_get_device_vibrator_manager =
|
||||
env->GetStaticMethodID(s_controller_interface_class, "getDeviceVibratorManager",
|
||||
"(Landroid/view/InputDevice;)Lorg/dolphinemu/dolphinemu/features/"
|
||||
"input/model/DolphinVibratorManager;");
|
||||
s_controller_interface_get_system_vibrator_manager = env->GetStaticMethodID(
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user