Merge pull request #11919 from t895/kotlin-controls
Android: Convert "features.input" package to Kotlin
This commit is contained in:
commit
f9959656e7
|
@ -1,52 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlGroup;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
|
||||
|
||||
public class ControlGroupEnabledSetting implements AbstractBooleanSetting
|
||||
{
|
||||
private final ControlGroup mControlGroup;
|
||||
|
||||
public ControlGroupEnabledSetting(ControlGroup controlGroup)
|
||||
{
|
||||
mControlGroup = controlGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean()
|
||||
{
|
||||
return mControlGroup.getEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBoolean(@NonNull Settings settings, boolean newValue)
|
||||
{
|
||||
mControlGroup.setEnabled(newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOverridden()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRuntimeEditable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(@NonNull Settings settings)
|
||||
{
|
||||
boolean newValue = mControlGroup.getDefaultEnabledValue() != ControlGroup.DEFAULT_ENABLED_NO;
|
||||
mControlGroup.setEnabled(newValue);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model
|
||||
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlGroup
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings
|
||||
|
||||
class ControlGroupEnabledSetting(private val controlGroup: ControlGroup) : AbstractBooleanSetting {
|
||||
override val boolean: Boolean
|
||||
get() = controlGroup.getEnabled()
|
||||
|
||||
override fun setBoolean(settings: Settings, newValue: Boolean) =
|
||||
controlGroup.setEnabled(newValue)
|
||||
|
||||
override val isOverridden: Boolean = false
|
||||
|
||||
override val isRuntimeEditable: Boolean = true
|
||||
|
||||
override fun delete(settings: Settings): Boolean {
|
||||
val newValue = controlGroup.getDefaultEnabledValue() != ControlGroup.DEFAULT_ENABLED_NO
|
||||
controlGroup.setEnabled(newValue)
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,178 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.os.VibratorManager;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.dolphinemu.dolphinemu.DolphinApplication;
|
||||
import org.dolphinemu.dolphinemu.utils.LooperThread;
|
||||
|
||||
/**
|
||||
* This class interfaces with the native ControllerInterface,
|
||||
* which is where the emulator core gets inputs from.
|
||||
*/
|
||||
public final class ControllerInterface
|
||||
{
|
||||
private static final class InputDeviceListener implements InputManager.InputDeviceListener
|
||||
{
|
||||
@Override
|
||||
public void onInputDeviceAdded(int deviceId)
|
||||
{
|
||||
// Simple implementation for now. We could do something fancier if we wanted to.
|
||||
refreshDevices();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceRemoved(int deviceId)
|
||||
{
|
||||
// Simple implementation for now. We could do something fancier if we wanted to.
|
||||
refreshDevices();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputDeviceChanged(int deviceId)
|
||||
{
|
||||
// Simple implementation for now. We could do something fancier if we wanted to.
|
||||
refreshDevices();
|
||||
}
|
||||
}
|
||||
|
||||
private static InputDeviceListener mInputDeviceListener;
|
||||
private static LooperThread mLooperThread;
|
||||
|
||||
/**
|
||||
* Activities which want to pass on inputs to native code
|
||||
* should call this in their own dispatchKeyEvent method.
|
||||
*
|
||||
* @return true if the emulator core seems to be interested in this event.
|
||||
* false if the event should be passed on to the default dispatchKeyEvent.
|
||||
*/
|
||||
public static native boolean dispatchKeyEvent(KeyEvent event);
|
||||
|
||||
/**
|
||||
* Activities which want to pass on inputs to native code
|
||||
* should call this in their own dispatchGenericMotionEvent method.
|
||||
*
|
||||
* @return true if the emulator core seems to be interested in this event.
|
||||
* false if the event should be passed on to the default dispatchGenericMotionEvent.
|
||||
*/
|
||||
public static native boolean dispatchGenericMotionEvent(MotionEvent event);
|
||||
|
||||
/**
|
||||
* {@link DolphinSensorEventListener} calls this for each axis of a received SensorEvent.
|
||||
*
|
||||
* @return true if the emulator core seems to be interested in this event.
|
||||
* false if the sensor can be suspended to save battery.
|
||||
*/
|
||||
public static native boolean dispatchSensorEvent(String deviceQualifier, String axisName,
|
||||
float value);
|
||||
|
||||
/**
|
||||
* Called when a sensor is suspended or unsuspended.
|
||||
*
|
||||
* @param deviceQualifier A string used by native code for uniquely identifying devices.
|
||||
* @param axisNames The name of all axes for the sensor.
|
||||
* @param suspended Whether the sensor is now suspended.
|
||||
*/
|
||||
public static native void notifySensorSuspendedState(String deviceQualifier, String[] axisNames,
|
||||
boolean suspended);
|
||||
|
||||
/**
|
||||
* Rescans for input devices.
|
||||
*/
|
||||
public static native void refreshDevices();
|
||||
|
||||
public static native String[] getAllDeviceStrings();
|
||||
|
||||
@Nullable
|
||||
public static native CoreDevice getDevice(String deviceString);
|
||||
|
||||
@Keep
|
||||
private static void registerInputDeviceListener()
|
||||
{
|
||||
if (mLooperThread == null)
|
||||
{
|
||||
mLooperThread = new LooperThread("Hotplug thread");
|
||||
mLooperThread.start();
|
||||
}
|
||||
|
||||
if (mInputDeviceListener == null)
|
||||
{
|
||||
InputManager im = (InputManager)
|
||||
DolphinApplication.getAppContext().getSystemService(Context.INPUT_SERVICE);
|
||||
|
||||
mInputDeviceListener = new InputDeviceListener();
|
||||
im.registerInputDeviceListener(mInputDeviceListener, new Handler(mLooperThread.getLooper()));
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
private static void unregisterInputDeviceListener()
|
||||
{
|
||||
if (mInputDeviceListener != null)
|
||||
{
|
||||
InputManager im = (InputManager)
|
||||
DolphinApplication.getAppContext().getSystemService(Context.INPUT_SERVICE);
|
||||
|
||||
im.unregisterInputDeviceListener(mInputDeviceListener);
|
||||
mInputDeviceListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Keep @NonNull
|
||||
private static DolphinVibratorManager getVibratorManager(InputDevice device)
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
{
|
||||
return new DolphinVibratorManagerPassthrough(device.getVibratorManager());
|
||||
}
|
||||
else
|
||||
{
|
||||
return new DolphinVibratorManagerCompat(device.getVibrator());
|
||||
}
|
||||
}
|
||||
|
||||
@Keep @NonNull
|
||||
private static DolphinVibratorManager getSystemVibratorManager()
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
{
|
||||
VibratorManager vibratorManager = (VibratorManager)
|
||||
DolphinApplication.getAppContext().getSystemService(Context.VIBRATOR_MANAGER_SERVICE);
|
||||
|
||||
if (vibratorManager != null)
|
||||
return new DolphinVibratorManagerPassthrough(vibratorManager);
|
||||
}
|
||||
|
||||
Vibrator vibrator = (Vibrator)
|
||||
DolphinApplication.getAppContext().getSystemService(Context.VIBRATOR_SERVICE);
|
||||
|
||||
return new DolphinVibratorManagerCompat(vibrator);
|
||||
}
|
||||
|
||||
@Keep
|
||||
private static void vibrate(@NonNull Vibrator vibrator)
|
||||
{
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
||||
{
|
||||
vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
|
||||
}
|
||||
else
|
||||
{
|
||||
vibrator.vibrate(100);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model
|
||||
|
||||
import android.content.Context
|
||||
import android.hardware.input.InputManager
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.VibrationEffect
|
||||
import android.os.Vibrator
|
||||
import android.os.VibratorManager
|
||||
import android.view.InputDevice
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import androidx.annotation.Keep
|
||||
import org.dolphinemu.dolphinemu.DolphinApplication
|
||||
import org.dolphinemu.dolphinemu.utils.LooperThread
|
||||
|
||||
/**
|
||||
* This class interfaces with the native ControllerInterface,
|
||||
* which is where the emulator core gets inputs from.
|
||||
*/
|
||||
object ControllerInterface {
|
||||
private var inputDeviceListener: InputDeviceListener? = null
|
||||
private lateinit var looperThread: LooperThread
|
||||
|
||||
/**
|
||||
* Activities which want to pass on inputs to native code
|
||||
* should call this in their own dispatchKeyEvent method.
|
||||
*
|
||||
* @return true if the emulator core seems to be interested in this event.
|
||||
* false if the event should be passed on to the default dispatchKeyEvent.
|
||||
*/
|
||||
external fun dispatchKeyEvent(event: KeyEvent): Boolean
|
||||
|
||||
/**
|
||||
* Activities which want to pass on inputs to native code
|
||||
* should call this in their own dispatchGenericMotionEvent method.
|
||||
*
|
||||
* @return true if the emulator core seems to be interested in this event.
|
||||
* false if the event should be passed on to the default dispatchGenericMotionEvent.
|
||||
*/
|
||||
external fun dispatchGenericMotionEvent(event: MotionEvent): Boolean
|
||||
|
||||
/**
|
||||
* [DolphinSensorEventListener] calls this for each axis of a received SensorEvent.
|
||||
*
|
||||
* @return true if the emulator core seems to be interested in this event.
|
||||
* false if the sensor can be suspended to save battery.
|
||||
*/
|
||||
external fun dispatchSensorEvent(
|
||||
deviceQualifier: String,
|
||||
axisName: String,
|
||||
value: Float
|
||||
): Boolean
|
||||
|
||||
/**
|
||||
* Called when a sensor is suspended or unsuspended.
|
||||
*
|
||||
* @param deviceQualifier A string used by native code for uniquely identifying devices.
|
||||
* @param axisNames The name of all axes for the sensor.
|
||||
* @param suspended Whether the sensor is now suspended.
|
||||
*/
|
||||
external fun notifySensorSuspendedState(
|
||||
deviceQualifier: String,
|
||||
axisNames: Array<String>,
|
||||
suspended: Boolean
|
||||
)
|
||||
|
||||
/**
|
||||
* Rescans for input devices.
|
||||
*/
|
||||
external fun refreshDevices()
|
||||
|
||||
external fun getAllDeviceStrings(): Array<String>
|
||||
|
||||
external fun getDevice(deviceString: String): CoreDevice?
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
private fun registerInputDeviceListener() {
|
||||
looperThread = LooperThread("Hotplug thread")
|
||||
looperThread.start()
|
||||
|
||||
if (inputDeviceListener == null) {
|
||||
val im = DolphinApplication.getAppContext()
|
||||
.getSystemService(Context.INPUT_SERVICE) as InputManager?
|
||||
|
||||
inputDeviceListener = InputDeviceListener()
|
||||
im!!.registerInputDeviceListener(inputDeviceListener, Handler(looperThread.looper))
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
private fun unregisterInputDeviceListener() {
|
||||
if (inputDeviceListener != null) {
|
||||
val im = DolphinApplication.getAppContext()
|
||||
.getSystemService(Context.INPUT_SERVICE) as InputManager?
|
||||
|
||||
im!!.unregisterInputDeviceListener(inputDeviceListener)
|
||||
inputDeviceListener = null
|
||||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
private fun vibrate(vibrator: Vibrator) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE))
|
||||
} else {
|
||||
vibrator.vibrate(100)
|
||||
}
|
||||
}
|
||||
|
||||
private class InputDeviceListener : InputManager.InputDeviceListener {
|
||||
// Simple implementation for now. We could do something fancier if we wanted to.
|
||||
override fun onInputDeviceAdded(deviceId: Int) = refreshDevices()
|
||||
|
||||
// Simple implementation for now. We could do something fancier if we wanted to.
|
||||
override fun onInputDeviceRemoved(deviceId: Int) = refreshDevices()
|
||||
|
||||
// Simple implementation for now. We could do something fancier if we wanted to.
|
||||
override fun onInputDeviceChanged(deviceId: Int) = refreshDevices()
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
/**
|
||||
* Represents a C++ ciface::Core::Device.
|
||||
*/
|
||||
public final class CoreDevice
|
||||
{
|
||||
/**
|
||||
* Represents a C++ ciface::Core::Device::Control.
|
||||
*
|
||||
* This class is non-static to ensure that the CoreDevice parent does not get garbage collected
|
||||
* while a Control is still accessible. (CoreDevice's finalizer may delete the native controls.)
|
||||
*/
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public final class Control
|
||||
{
|
||||
@Keep
|
||||
private final long mPointer;
|
||||
|
||||
@Keep
|
||||
private Control(long pointer)
|
||||
{
|
||||
mPointer = pointer;
|
||||
}
|
||||
|
||||
public native String getName();
|
||||
}
|
||||
|
||||
@Keep
|
||||
private final long mPointer;
|
||||
|
||||
@Keep
|
||||
private CoreDevice(long pointer)
|
||||
{
|
||||
mPointer = pointer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected native void finalize();
|
||||
|
||||
public native Control[] getInputs();
|
||||
|
||||
public native Control[] getOutputs();
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* Represents a C++ ciface::Core::Device.
|
||||
*/
|
||||
@Keep
|
||||
class CoreDevice private constructor(private val pointer: Long) {
|
||||
/**
|
||||
* Represents a C++ ciface::Core::Device::Control.
|
||||
*
|
||||
* This class is marked inner to ensure that the CoreDevice parent does not get garbage collected
|
||||
* while a Control is still accessible. (CoreDevice's finalizer may delete the native controls.)
|
||||
*/
|
||||
@Keep
|
||||
inner class Control private constructor(private val pointer: Long) {
|
||||
external fun getName(): String
|
||||
}
|
||||
|
||||
protected external fun finalize()
|
||||
|
||||
external fun getInputs(): Array<Control>
|
||||
|
||||
external fun getOutputs(): Array<Control>
|
||||
}
|
|
@ -1,440 +0,0 @@
|
|||
package org.dolphinemu.dolphinemu.features.input.model;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Build;
|
||||
import android.view.InputDevice;
|
||||
import android.view.Surface;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
import org.dolphinemu.dolphinemu.DolphinApplication;
|
||||
import org.dolphinemu.dolphinemu.utils.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class DolphinSensorEventListener implements SensorEventListener
|
||||
{
|
||||
// Set of three axes. Creates a negative companion to each axis, and corrects for device rotation.
|
||||
private static final int AXIS_SET_TYPE_DEVICE_COORDINATES = 0;
|
||||
// Set of three axes. Creates a negative companion to each axis.
|
||||
private static final int AXIS_SET_TYPE_OTHER_COORDINATES = 1;
|
||||
|
||||
private static class AxisSetDetails
|
||||
{
|
||||
public final int firstAxisOfSet;
|
||||
public final int axisSetType;
|
||||
|
||||
public AxisSetDetails(int firstAxisOfSet, int axisSetType)
|
||||
{
|
||||
this.firstAxisOfSet = firstAxisOfSet;
|
||||
this.axisSetType = axisSetType;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SensorDetails
|
||||
{
|
||||
public final int sensorType;
|
||||
public final String[] axisNames;
|
||||
public final AxisSetDetails[] axisSetDetails;
|
||||
public boolean isSuspended = true;
|
||||
|
||||
public SensorDetails(int sensorType, String[] axisNames, AxisSetDetails[] axisSetDetails)
|
||||
{
|
||||
this.sensorType = sensorType;
|
||||
this.axisNames = axisNames;
|
||||
this.axisSetDetails = axisSetDetails;
|
||||
}
|
||||
}
|
||||
|
||||
private static int sDeviceRotation = Surface.ROTATION_0;
|
||||
|
||||
private final SensorManager mSensorManager;
|
||||
|
||||
private final HashMap<Sensor, SensorDetails> mSensorDetails = new HashMap<>();
|
||||
|
||||
private final boolean mRotateCoordinatesForScreenOrientation;
|
||||
|
||||
private String mDeviceQualifier = "";
|
||||
|
||||
// The fastest sampling rate Android lets us use without declaring the HIGH_SAMPLING_RATE_SENSORS
|
||||
// permission is 200 Hz. This is also the sampling rate of a Wii Remote, so it fits us perfectly.
|
||||
private static final int SAMPLING_PERIOD_US = 1000000 / 200;
|
||||
|
||||
@Keep
|
||||
public DolphinSensorEventListener()
|
||||
{
|
||||
mSensorManager = (SensorManager)
|
||||
DolphinApplication.getAppContext().getSystemService(Context.SENSOR_SERVICE);
|
||||
mRotateCoordinatesForScreenOrientation = true;
|
||||
|
||||
addSensors();
|
||||
}
|
||||
|
||||
@Keep
|
||||
public DolphinSensorEventListener(InputDevice inputDevice)
|
||||
{
|
||||
mRotateCoordinatesForScreenOrientation = false;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 31)
|
||||
{
|
||||
mSensorManager = inputDevice.getSensorManager();
|
||||
|
||||
// TODO: There is a bug where after suspending sensors, onSensorChanged can get called for
|
||||
// a sensor that we never registered as a listener for. The way our code is currently written,
|
||||
// this causes a NullPointerException, but if we checked for null we would instead have the
|
||||
// problem of being spammed with onSensorChanged calls even though the sensor shouldn't be
|
||||
// enabled. For now, let's comment out the ability to use InputDevice sensors.
|
||||
|
||||
//addSensors();
|
||||
}
|
||||
else
|
||||
{
|
||||
mSensorManager = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void addSensors()
|
||||
{
|
||||
tryAddSensor(Sensor.TYPE_ACCELEROMETER, new String[]{"Accel Right", "Accel Left",
|
||||
"Accel Forward", "Accel Backward", "Accel Up", "Accel Down"},
|
||||
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||
|
||||
tryAddSensor(Sensor.TYPE_GYROSCOPE, new String[]{"Gyro Pitch Up", "Gyro Pitch Down",
|
||||
"Gyro Roll Right", "Gyro Roll Left", "Gyro Yaw Left", "Gyro Yaw Right"},
|
||||
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||
|
||||
tryAddSensor(Sensor.TYPE_LIGHT, "Light");
|
||||
|
||||
tryAddSensor(Sensor.TYPE_PRESSURE, "Pressure");
|
||||
|
||||
tryAddSensor(Sensor.TYPE_TEMPERATURE, "Device Temperature");
|
||||
|
||||
tryAddSensor(Sensor.TYPE_PROXIMITY, "Proximity");
|
||||
|
||||
tryAddSensor(Sensor.TYPE_GRAVITY, new String[]{"Gravity Right", "Gravity Left",
|
||||
"Gravity Forward", "Gravity Backward", "Gravity Up", "Gravity Down"},
|
||||
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||
|
||||
tryAddSensor(Sensor.TYPE_LINEAR_ACCELERATION,
|
||||
new String[]{"Linear Acceleration Right", "Linear Acceleration Left",
|
||||
"Linear Acceleration Forward", "Linear Acceleration Backward",
|
||||
"Linear Acceleration Up", "Linear Acceleration Down"},
|
||||
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||
|
||||
// The values provided by this sensor can be interpreted as an Euler vector or a quaternion.
|
||||
// The directions of X and Y are flipped to match the Wii Remote coordinate system.
|
||||
tryAddSensor(Sensor.TYPE_ROTATION_VECTOR,
|
||||
new String[]{"Rotation Vector X-", "Rotation Vector X+", "Rotation Vector Y-",
|
||||
"Rotation Vector Y+", "Rotation Vector Z+",
|
||||
"Rotation Vector Z-", "Rotation Vector R", "Rotation Vector Heading Accuracy"},
|
||||
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||
|
||||
tryAddSensor(Sensor.TYPE_RELATIVE_HUMIDITY, "Relative Humidity");
|
||||
|
||||
tryAddSensor(Sensor.TYPE_AMBIENT_TEMPERATURE, "Ambient Temperature");
|
||||
|
||||
// The values provided by this sensor can be interpreted as an Euler vector or a quaternion.
|
||||
// The directions of X and Y are flipped to match the Wii Remote coordinate system.
|
||||
tryAddSensor(Sensor.TYPE_GAME_ROTATION_VECTOR,
|
||||
new String[]{"Game Rotation Vector X-", "Game Rotation Vector X+",
|
||||
"Game Rotation Vector Y-", "Game Rotation Vector Y+", "Game Rotation Vector Z+",
|
||||
"Game Rotation Vector Z-", "Game Rotation Vector R"},
|
||||
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||
|
||||
tryAddSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED,
|
||||
new String[]{"Gyro Uncalibrated Pitch Up", "Gyro Uncalibrated Pitch Down",
|
||||
"Gyro Uncalibrated Roll Right", "Gyro Uncalibrated Roll Left",
|
||||
"Gyro Uncalibrated Yaw Left", "Gyro Uncalibrated Yaw Right",
|
||||
"Gyro Drift Pitch Up", "Gyro Drift Pitch Down", "Gyro Drift Roll Right",
|
||||
"Gyro Drift Roll Left", "Gyro Drift Yaw Left", "Gyro Drift Yaw Right"},
|
||||
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES),
|
||||
new AxisSetDetails(3, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||
|
||||
tryAddSensor(Sensor.TYPE_HEART_RATE, "Heart Rate");
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24)
|
||||
{
|
||||
tryAddSensor(Sensor.TYPE_HEART_BEAT, "Heart Beat");
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26)
|
||||
{
|
||||
tryAddSensor(Sensor.TYPE_ACCELEROMETER_UNCALIBRATED,
|
||||
new String[]{"Accel Uncalibrated Right", "Accel Uncalibrated Left",
|
||||
"Accel Uncalibrated Forward", "Accel Uncalibrated Backward",
|
||||
"Accel Uncalibrated Up", "Accel Uncalibrated Down",
|
||||
"Accel Bias Right", "Accel Bias Left", "Accel Bias Forward",
|
||||
"Accel Bias Backward", "Accel Bias Up", "Accel Bias Down"},
|
||||
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES),
|
||||
new AxisSetDetails(3, AXIS_SET_TYPE_DEVICE_COORDINATES)});
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 30)
|
||||
{
|
||||
tryAddSensor(Sensor.TYPE_HINGE_ANGLE, "Hinge Angle");
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 33)
|
||||
{
|
||||
// The values provided by this sensor can be interpreted as an Euler vector.
|
||||
// The directions of X and Y are flipped to match the Wii Remote coordinate system.
|
||||
tryAddSensor(Sensor.TYPE_HEAD_TRACKER,
|
||||
new String[]{"Head Rotation Vector X-", "Head Rotation Vector X+",
|
||||
"Head Rotation Vector Y-", "Head Rotation Vector Y+",
|
||||
"Head Rotation Vector Z+", "Head Rotation Vector Z-",
|
||||
"Head Pitch Up", "Head Pitch Down", "Head Roll Right", "Head Roll Left",
|
||||
"Head Yaw Left", "Head Yaw Right"},
|
||||
new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_OTHER_COORDINATES),
|
||||
new AxisSetDetails(3, AXIS_SET_TYPE_OTHER_COORDINATES)});
|
||||
|
||||
tryAddSensor(Sensor.TYPE_HEADING, new String[]{"Heading", "Heading Accuracy"},
|
||||
new AxisSetDetails[]{});
|
||||
}
|
||||
}
|
||||
|
||||
private void tryAddSensor(int sensorType, String axisName)
|
||||
{
|
||||
tryAddSensor(sensorType, new String[]{axisName}, new AxisSetDetails[]{});
|
||||
}
|
||||
|
||||
private void tryAddSensor(int sensorType, String[] axisNames, AxisSetDetails[] axisSetDetails)
|
||||
{
|
||||
Sensor sensor = mSensorManager.getDefaultSensor(sensorType);
|
||||
if (sensor != null)
|
||||
{
|
||||
mSensorDetails.put(sensor, new SensorDetails(sensorType, axisNames, axisSetDetails));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent sensorEvent)
|
||||
{
|
||||
final SensorDetails sensorDetails = mSensorDetails.get(sensorEvent.sensor);
|
||||
|
||||
final float[] values = sensorEvent.values;
|
||||
final String[] axisNames = sensorDetails.axisNames;
|
||||
final AxisSetDetails[] axisSetDetails = sensorDetails.axisSetDetails;
|
||||
|
||||
int eventAxisIndex = 0;
|
||||
int detailsAxisIndex = 0;
|
||||
int detailsAxisSetIndex = 0;
|
||||
boolean keepSensorAlive = false;
|
||||
while (eventAxisIndex < values.length && detailsAxisIndex < axisNames.length)
|
||||
{
|
||||
if (detailsAxisSetIndex < axisSetDetails.length &&
|
||||
axisSetDetails[detailsAxisSetIndex].firstAxisOfSet == eventAxisIndex)
|
||||
{
|
||||
int rotation = Surface.ROTATION_0;
|
||||
if (mRotateCoordinatesForScreenOrientation &&
|
||||
axisSetDetails[detailsAxisSetIndex].axisSetType == AXIS_SET_TYPE_DEVICE_COORDINATES)
|
||||
{
|
||||
rotation = sDeviceRotation;
|
||||
}
|
||||
|
||||
float x, y;
|
||||
switch (rotation)
|
||||
{
|
||||
default:
|
||||
case Surface.ROTATION_0:
|
||||
x = values[eventAxisIndex];
|
||||
y = values[eventAxisIndex + 1];
|
||||
break;
|
||||
case Surface.ROTATION_90:
|
||||
x = -values[eventAxisIndex + 1];
|
||||
y = values[eventAxisIndex];
|
||||
break;
|
||||
case Surface.ROTATION_180:
|
||||
x = -values[eventAxisIndex];
|
||||
y = -values[eventAxisIndex + 1];
|
||||
break;
|
||||
case Surface.ROTATION_270:
|
||||
x = values[eventAxisIndex + 1];
|
||||
y = -values[eventAxisIndex];
|
||||
break;
|
||||
}
|
||||
|
||||
float z = values[eventAxisIndex + 2];
|
||||
|
||||
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
|
||||
axisNames[detailsAxisIndex], x);
|
||||
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
|
||||
axisNames[detailsAxisIndex + 1], x);
|
||||
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
|
||||
axisNames[detailsAxisIndex + 2], y);
|
||||
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
|
||||
axisNames[detailsAxisIndex + 3], y);
|
||||
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
|
||||
axisNames[detailsAxisIndex + 4], z);
|
||||
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
|
||||
axisNames[detailsAxisIndex + 5], z);
|
||||
|
||||
eventAxisIndex += 3;
|
||||
detailsAxisIndex += 6;
|
||||
detailsAxisSetIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier,
|
||||
axisNames[detailsAxisIndex], values[eventAxisIndex]);
|
||||
|
||||
eventAxisIndex++;
|
||||
detailsAxisIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!keepSensorAlive)
|
||||
{
|
||||
setSensorSuspended(sensorEvent.sensor, sensorDetails, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int i)
|
||||
{
|
||||
// We don't care about this
|
||||
}
|
||||
|
||||
/**
|
||||
* The device qualifier set here will be passed on to native code,
|
||||
* for the purpose of letting native code identify which device this object belongs to.
|
||||
*/
|
||||
@Keep
|
||||
public void setDeviceQualifier(String deviceQualifier)
|
||||
{
|
||||
mDeviceQualifier = deviceQualifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a sensor has been suspended to save battery, this unsuspends it.
|
||||
* If the sensor isn't currently suspended, nothing happens.
|
||||
*
|
||||
* @param axisName The name of any of the sensor's axes.
|
||||
*/
|
||||
@Keep
|
||||
public void requestUnsuspendSensor(String axisName)
|
||||
{
|
||||
for (Map.Entry<Sensor, SensorDetails> entry : mSensorDetails.entrySet())
|
||||
{
|
||||
if (Arrays.asList(entry.getValue().axisNames).contains(axisName))
|
||||
{
|
||||
setSensorSuspended(entry.getKey(), entry.getValue(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setSensorSuspended(Sensor sensor, SensorDetails sensorDetails, boolean suspend)
|
||||
{
|
||||
boolean changeOccurred = false;
|
||||
|
||||
synchronized (sensorDetails)
|
||||
{
|
||||
if (sensorDetails.isSuspended != suspend)
|
||||
{
|
||||
ControllerInterface.notifySensorSuspendedState(mDeviceQualifier, sensorDetails.axisNames,
|
||||
suspend);
|
||||
|
||||
if (suspend)
|
||||
mSensorManager.unregisterListener(this, sensor);
|
||||
else
|
||||
mSensorManager.registerListener(this, sensor, SAMPLING_PERIOD_US);
|
||||
|
||||
sensorDetails.isSuspended = suspend;
|
||||
|
||||
changeOccurred = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (changeOccurred)
|
||||
{
|
||||
Log.info((suspend ? "Suspended sensor " : "Unsuspended sensor ") + sensor.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
public String[] getAxisNames()
|
||||
{
|
||||
ArrayList<String> axisNames = new ArrayList<>();
|
||||
|
||||
for (SensorDetails sensorDetails : getSensorDetailsSorted())
|
||||
{
|
||||
Collections.addAll(axisNames, sensorDetails.axisNames);
|
||||
}
|
||||
|
||||
return axisNames.toArray(new String[]{});
|
||||
}
|
||||
|
||||
@Keep
|
||||
public boolean[] getNegativeAxes()
|
||||
{
|
||||
ArrayList<Boolean> negativeAxes = new ArrayList<>();
|
||||
|
||||
for (SensorDetails sensorDetails : getSensorDetailsSorted())
|
||||
{
|
||||
int eventAxisIndex = 0;
|
||||
int detailsAxisIndex = 0;
|
||||
int detailsAxisSetIndex = 0;
|
||||
while (detailsAxisIndex < sensorDetails.axisNames.length)
|
||||
{
|
||||
if (detailsAxisSetIndex < sensorDetails.axisSetDetails.length &&
|
||||
sensorDetails.axisSetDetails[detailsAxisSetIndex].firstAxisOfSet == eventAxisIndex)
|
||||
{
|
||||
negativeAxes.add(false);
|
||||
negativeAxes.add(true);
|
||||
negativeAxes.add(false);
|
||||
negativeAxes.add(true);
|
||||
negativeAxes.add(false);
|
||||
negativeAxes.add(true);
|
||||
|
||||
eventAxisIndex += 3;
|
||||
detailsAxisIndex += 6;
|
||||
detailsAxisSetIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
negativeAxes.add(false);
|
||||
|
||||
eventAxisIndex++;
|
||||
detailsAxisIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean[] result = new boolean[negativeAxes.size()];
|
||||
for (int i = 0; i < result.length; i++)
|
||||
{
|
||||
result[i] = negativeAxes.get(i);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<SensorDetails> getSensorDetailsSorted()
|
||||
{
|
||||
ArrayList<SensorDetails> sensorDetails = new ArrayList<>(mSensorDetails.values());
|
||||
Collections.sort(sensorDetails, Comparator.comparingInt(s -> s.sensorType));
|
||||
return sensorDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called when an activity or other component that uses sensor events is resumed.
|
||||
*
|
||||
* Sensor events that contain device coordinates will have the coordinates rotated by the value
|
||||
* passed to this function.
|
||||
*
|
||||
* @param deviceRotation The current rotation of the device (i.e. rotation of the default display)
|
||||
*/
|
||||
public static void setDeviceRotation(int deviceRotation)
|
||||
{
|
||||
sDeviceRotation = deviceRotation;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,504 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model
|
||||
|
||||
import android.content.Context
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorEvent
|
||||
import android.hardware.SensorEventListener
|
||||
import android.hardware.SensorManager
|
||||
import android.os.Build
|
||||
import android.view.InputDevice
|
||||
import android.view.Surface
|
||||
import androidx.annotation.Keep
|
||||
import org.dolphinemu.dolphinemu.DolphinApplication
|
||||
import org.dolphinemu.dolphinemu.utils.Log
|
||||
import java.util.Collections
|
||||
|
||||
class DolphinSensorEventListener : SensorEventListener {
|
||||
private class AxisSetDetails(val firstAxisOfSet: Int, val axisSetType: Int)
|
||||
|
||||
private class SensorDetails(
|
||||
val sensorType: Int,
|
||||
val axisNames: Array<String>,
|
||||
val axisSetDetails: Array<AxisSetDetails>
|
||||
) {
|
||||
var isSuspended = true
|
||||
}
|
||||
|
||||
private val sensorManager: SensorManager?
|
||||
|
||||
private val sensorDetails = HashMap<Sensor, SensorDetails>()
|
||||
|
||||
private val rotateCoordinatesForScreenOrientation: Boolean
|
||||
|
||||
private var deviceQualifier = ""
|
||||
|
||||
@Keep
|
||||
constructor() {
|
||||
sensorManager = DolphinApplication.getAppContext()
|
||||
.getSystemService(Context.SENSOR_SERVICE) as SensorManager?
|
||||
rotateCoordinatesForScreenOrientation = true
|
||||
addSensors()
|
||||
}
|
||||
|
||||
@Keep
|
||||
constructor(inputDevice: InputDevice) {
|
||||
rotateCoordinatesForScreenOrientation = false
|
||||
sensorManager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
inputDevice.sensorManager
|
||||
|
||||
// TODO: There is a bug where after suspending sensors, onSensorChanged can get called for
|
||||
// a sensor that we never registered as a listener for. The way our code is currently written,
|
||||
// this causes a NullPointerException, but if we checked for null we would instead have the
|
||||
// problem of being spammed with onSensorChanged calls even though the sensor shouldn't be
|
||||
// enabled. For now, let's comment out the ability to use InputDevice sensors.
|
||||
|
||||
//addSensors();
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun addSensors() {
|
||||
tryAddSensor(
|
||||
Sensor.TYPE_ACCELEROMETER,
|
||||
arrayOf(
|
||||
"Accel Right",
|
||||
"Accel Left",
|
||||
"Accel Forward",
|
||||
"Accel Backward",
|
||||
"Accel Up",
|
||||
"Accel Down"
|
||||
),
|
||||
arrayOf(AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES))
|
||||
)
|
||||
|
||||
tryAddSensor(
|
||||
Sensor.TYPE_GYROSCOPE,
|
||||
arrayOf(
|
||||
"Gyro Pitch Up",
|
||||
"Gyro Pitch Down",
|
||||
"Gyro Roll Right",
|
||||
"Gyro Roll Left",
|
||||
"Gyro Yaw Left",
|
||||
"Gyro Yaw Right"
|
||||
),
|
||||
arrayOf(AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES))
|
||||
)
|
||||
|
||||
tryAddSensor(Sensor.TYPE_LIGHT, "Light")
|
||||
|
||||
tryAddSensor(Sensor.TYPE_PRESSURE, "Pressure")
|
||||
|
||||
tryAddSensor(Sensor.TYPE_TEMPERATURE, "Device Temperature")
|
||||
|
||||
tryAddSensor(Sensor.TYPE_PROXIMITY, "Proximity")
|
||||
|
||||
tryAddSensor(
|
||||
Sensor.TYPE_GRAVITY,
|
||||
arrayOf(
|
||||
"Gravity Right",
|
||||
"Gravity Left",
|
||||
"Gravity Forward",
|
||||
"Gravity Backward",
|
||||
"Gravity Up",
|
||||
"Gravity Down"
|
||||
),
|
||||
arrayOf(AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES))
|
||||
)
|
||||
|
||||
tryAddSensor(
|
||||
Sensor.TYPE_LINEAR_ACCELERATION,
|
||||
arrayOf(
|
||||
"Linear Acceleration Right",
|
||||
"Linear Acceleration Left",
|
||||
"Linear Acceleration Forward",
|
||||
"Linear Acceleration Backward",
|
||||
"Linear Acceleration Up",
|
||||
"Linear Acceleration Down"
|
||||
),
|
||||
arrayOf(AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES))
|
||||
)
|
||||
|
||||
// The values provided by this sensor can be interpreted as an Euler vector or a quaternion.
|
||||
// The directions of X and Y are flipped to match the Wii Remote coordinate system.
|
||||
tryAddSensor(
|
||||
Sensor.TYPE_ROTATION_VECTOR,
|
||||
arrayOf(
|
||||
"Rotation Vector X-",
|
||||
"Rotation Vector X+",
|
||||
"Rotation Vector Y-",
|
||||
"Rotation Vector Y+",
|
||||
"Rotation Vector Z+",
|
||||
"Rotation Vector Z-",
|
||||
"Rotation Vector R",
|
||||
"Rotation Vector Heading Accuracy"
|
||||
),
|
||||
arrayOf(AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES))
|
||||
)
|
||||
|
||||
tryAddSensor(Sensor.TYPE_RELATIVE_HUMIDITY, "Relative Humidity")
|
||||
|
||||
tryAddSensor(Sensor.TYPE_AMBIENT_TEMPERATURE, "Ambient Temperature")
|
||||
|
||||
// The values provided by this sensor can be interpreted as an Euler vector or a quaternion.
|
||||
// The directions of X and Y are flipped to match the Wii Remote coordinate system.
|
||||
tryAddSensor(
|
||||
Sensor.TYPE_GAME_ROTATION_VECTOR,
|
||||
arrayOf(
|
||||
"Game Rotation Vector X-",
|
||||
"Game Rotation Vector X+",
|
||||
"Game Rotation Vector Y-",
|
||||
"Game Rotation Vector Y+",
|
||||
"Game Rotation Vector Z+",
|
||||
"Game Rotation Vector Z-",
|
||||
"Game Rotation Vector R"
|
||||
),
|
||||
arrayOf(AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES))
|
||||
)
|
||||
|
||||
tryAddSensor(
|
||||
Sensor.TYPE_GYROSCOPE_UNCALIBRATED,
|
||||
arrayOf(
|
||||
"Gyro Uncalibrated Pitch Up",
|
||||
"Gyro Uncalibrated Pitch Down",
|
||||
"Gyro Uncalibrated Roll Right",
|
||||
"Gyro Uncalibrated Roll Left",
|
||||
"Gyro Uncalibrated Yaw Left",
|
||||
"Gyro Uncalibrated Yaw Right",
|
||||
"Gyro Drift Pitch Up",
|
||||
"Gyro Drift Pitch Down",
|
||||
"Gyro Drift Roll Right",
|
||||
"Gyro Drift Roll Left",
|
||||
"Gyro Drift Yaw Left",
|
||||
"Gyro Drift Yaw Right"
|
||||
),
|
||||
arrayOf(
|
||||
AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES),
|
||||
AxisSetDetails(3, AXIS_SET_TYPE_DEVICE_COORDINATES)
|
||||
)
|
||||
)
|
||||
|
||||
tryAddSensor(Sensor.TYPE_HEART_RATE, "Heart Rate")
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
tryAddSensor(Sensor.TYPE_HEART_BEAT, "Heart Beat")
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
tryAddSensor(
|
||||
Sensor.TYPE_ACCELEROMETER_UNCALIBRATED,
|
||||
arrayOf(
|
||||
"Accel Uncalibrated Right",
|
||||
"Accel Uncalibrated Left",
|
||||
"Accel Uncalibrated Forward",
|
||||
"Accel Uncalibrated Backward",
|
||||
"Accel Uncalibrated Up",
|
||||
"Accel Uncalibrated Down",
|
||||
"Accel Bias Right",
|
||||
"Accel Bias Left",
|
||||
"Accel Bias Forward",
|
||||
"Accel Bias Backward",
|
||||
"Accel Bias Up",
|
||||
"Accel Bias Down"
|
||||
),
|
||||
arrayOf(
|
||||
AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES),
|
||||
AxisSetDetails(3, AXIS_SET_TYPE_DEVICE_COORDINATES)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 30) {
|
||||
tryAddSensor(Sensor.TYPE_HINGE_ANGLE, "Hinge Angle")
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
// The values provided by this sensor can be interpreted as an Euler vector.
|
||||
// The directions of X and Y are flipped to match the Wii Remote coordinate system.
|
||||
tryAddSensor(
|
||||
Sensor.TYPE_HEAD_TRACKER,
|
||||
arrayOf(
|
||||
"Head Rotation Vector X-",
|
||||
"Head Rotation Vector X+",
|
||||
"Head Rotation Vector Y-",
|
||||
"Head Rotation Vector Y+",
|
||||
"Head Rotation Vector Z+",
|
||||
"Head Rotation Vector Z-",
|
||||
"Head Pitch Up",
|
||||
"Head Pitch Down",
|
||||
"Head Roll Right",
|
||||
"Head Roll Left",
|
||||
"Head Yaw Left",
|
||||
"Head Yaw Right"
|
||||
),
|
||||
arrayOf(
|
||||
AxisSetDetails(0, AXIS_SET_TYPE_OTHER_COORDINATES),
|
||||
AxisSetDetails(3, AXIS_SET_TYPE_OTHER_COORDINATES)
|
||||
)
|
||||
)
|
||||
|
||||
tryAddSensor(Sensor.TYPE_HEADING, arrayOf("Heading", "Heading Accuracy"), arrayOf())
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryAddSensor(sensorType: Int, axisName: String) {
|
||||
tryAddSensor(sensorType, arrayOf(axisName), arrayOf())
|
||||
}
|
||||
|
||||
private fun tryAddSensor(
|
||||
sensorType: Int,
|
||||
axisNames: Array<String>,
|
||||
axisSetDetails: Array<AxisSetDetails>
|
||||
) {
|
||||
val sensor = sensorManager!!.getDefaultSensor(sensorType)
|
||||
if (sensor != null) {
|
||||
sensorDetails[sensor] = SensorDetails(sensorType, axisNames, axisSetDetails)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSensorChanged(sensorEvent: SensorEvent) {
|
||||
val sensorDetails = sensorDetails[sensorEvent.sensor]
|
||||
|
||||
val values = sensorEvent.values
|
||||
val axisNames = sensorDetails!!.axisNames
|
||||
val axisSetDetails = sensorDetails.axisSetDetails
|
||||
|
||||
var eventAxisIndex = 0
|
||||
var detailsAxisIndex = 0
|
||||
var detailsAxisSetIndex = 0
|
||||
var keepSensorAlive = false
|
||||
while (eventAxisIndex < values.size && detailsAxisIndex < axisNames.size) {
|
||||
if (detailsAxisSetIndex < axisSetDetails.size &&
|
||||
axisSetDetails[detailsAxisSetIndex].firstAxisOfSet == eventAxisIndex
|
||||
) {
|
||||
var rotation = Surface.ROTATION_0
|
||||
if (rotateCoordinatesForScreenOrientation &&
|
||||
axisSetDetails[detailsAxisSetIndex].axisSetType == AXIS_SET_TYPE_DEVICE_COORDINATES
|
||||
) {
|
||||
rotation = deviceRotation
|
||||
}
|
||||
|
||||
var x: Float
|
||||
var y: Float
|
||||
when (rotation) {
|
||||
Surface.ROTATION_0 -> {
|
||||
x = values[eventAxisIndex]
|
||||
y = values[eventAxisIndex + 1]
|
||||
}
|
||||
|
||||
Surface.ROTATION_90 -> {
|
||||
x = -values[eventAxisIndex + 1]
|
||||
y = values[eventAxisIndex]
|
||||
}
|
||||
|
||||
Surface.ROTATION_180 -> {
|
||||
x = -values[eventAxisIndex]
|
||||
y = -values[eventAxisIndex + 1]
|
||||
}
|
||||
|
||||
Surface.ROTATION_270 -> {
|
||||
x = values[eventAxisIndex + 1]
|
||||
y = -values[eventAxisIndex]
|
||||
}
|
||||
|
||||
else -> {
|
||||
x = values[eventAxisIndex]
|
||||
y = values[eventAxisIndex + 1]
|
||||
}
|
||||
}
|
||||
|
||||
val z = values[eventAxisIndex + 2]
|
||||
|
||||
keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent(
|
||||
deviceQualifier,
|
||||
axisNames[detailsAxisIndex],
|
||||
x
|
||||
)
|
||||
keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent(
|
||||
deviceQualifier,
|
||||
axisNames[detailsAxisIndex + 1],
|
||||
x
|
||||
)
|
||||
keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent(
|
||||
deviceQualifier,
|
||||
axisNames[detailsAxisIndex + 2],
|
||||
y
|
||||
)
|
||||
keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent(
|
||||
deviceQualifier,
|
||||
axisNames[detailsAxisIndex + 3],
|
||||
y
|
||||
)
|
||||
keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent(
|
||||
deviceQualifier,
|
||||
axisNames[detailsAxisIndex + 4],
|
||||
z
|
||||
)
|
||||
keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent(
|
||||
deviceQualifier,
|
||||
axisNames[detailsAxisIndex + 5],
|
||||
z
|
||||
)
|
||||
|
||||
eventAxisIndex += 3
|
||||
detailsAxisIndex += 6
|
||||
detailsAxisSetIndex++
|
||||
} else {
|
||||
keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent(
|
||||
deviceQualifier,
|
||||
axisNames[detailsAxisIndex], values[eventAxisIndex]
|
||||
)
|
||||
|
||||
eventAxisIndex++
|
||||
detailsAxisIndex++
|
||||
}
|
||||
}
|
||||
if (!keepSensorAlive) {
|
||||
setSensorSuspended(sensorEvent.sensor, sensorDetails, true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAccuracyChanged(sensor: Sensor, i: Int) {
|
||||
// We don't care about this
|
||||
}
|
||||
|
||||
/**
|
||||
* The device qualifier set here will be passed on to native code,
|
||||
* for the purpose of letting native code identify which device this object belongs to.
|
||||
*/
|
||||
@Keep
|
||||
fun setDeviceQualifier(deviceQualifier: String) {
|
||||
this.deviceQualifier = deviceQualifier
|
||||
}
|
||||
|
||||
/**
|
||||
* If a sensor has been suspended to save battery, this unsuspends it.
|
||||
* If the sensor isn't currently suspended, nothing happens.
|
||||
*
|
||||
* @param axisName The name of any of the sensor's axes.
|
||||
*/
|
||||
@Keep
|
||||
fun requestUnsuspendSensor(axisName: String) {
|
||||
for ((key, value) in sensorDetails) {
|
||||
if (listOf(*value.axisNames).contains(axisName)) {
|
||||
setSensorSuspended(key, value, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSensorSuspended(
|
||||
sensor: Sensor,
|
||||
sensorDetails: SensorDetails,
|
||||
suspend: Boolean
|
||||
) {
|
||||
var changeOccurred = false
|
||||
|
||||
synchronized(sensorDetails) {
|
||||
if (sensorDetails.isSuspended != suspend) {
|
||||
ControllerInterface.notifySensorSuspendedState(
|
||||
deviceQualifier,
|
||||
sensorDetails.axisNames,
|
||||
suspend
|
||||
)
|
||||
|
||||
if (suspend)
|
||||
sensorManager!!.unregisterListener(this, sensor)
|
||||
else
|
||||
sensorManager!!.registerListener(this, sensor, SAMPLING_PERIOD_US)
|
||||
|
||||
sensorDetails.isSuspended = suspend
|
||||
|
||||
changeOccurred = true
|
||||
}
|
||||
}
|
||||
|
||||
if (changeOccurred) {
|
||||
Log.info((if (suspend) "Suspended sensor " else "Unsuspended sensor ") + sensor.name)
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
fun getAxisNames(): Array<String> {
|
||||
val axisNames = ArrayList<String>()
|
||||
for (sensorDetails in sensorDetailsSorted) {
|
||||
sensorDetails.axisNames.forEach { axisNames.add(it) }
|
||||
}
|
||||
return axisNames.toArray(arrayOf())
|
||||
}
|
||||
|
||||
@Keep
|
||||
fun getNegativeAxes(): BooleanArray {
|
||||
val negativeAxes = ArrayList<Boolean>()
|
||||
|
||||
for (sensorDetails in sensorDetailsSorted) {
|
||||
var eventAxisIndex = 0
|
||||
var detailsAxisIndex = 0
|
||||
var detailsAxisSetIndex = 0
|
||||
while (detailsAxisIndex < sensorDetails.axisNames.size) {
|
||||
if (detailsAxisSetIndex < sensorDetails.axisSetDetails.size &&
|
||||
sensorDetails.axisSetDetails[detailsAxisSetIndex].firstAxisOfSet == eventAxisIndex
|
||||
) {
|
||||
negativeAxes.add(false)
|
||||
negativeAxes.add(true)
|
||||
negativeAxes.add(false)
|
||||
negativeAxes.add(true)
|
||||
negativeAxes.add(false)
|
||||
negativeAxes.add(true)
|
||||
|
||||
eventAxisIndex += 3
|
||||
detailsAxisIndex += 6
|
||||
detailsAxisSetIndex++
|
||||
} else {
|
||||
negativeAxes.add(false)
|
||||
|
||||
eventAxisIndex++
|
||||
detailsAxisIndex++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val result = BooleanArray(negativeAxes.size)
|
||||
for (i in result.indices) {
|
||||
result[i] = negativeAxes[i]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private val sensorDetailsSorted: List<SensorDetails>
|
||||
get() {
|
||||
val sensorDetails = ArrayList(sensorDetails.values)
|
||||
Collections.sort(
|
||||
sensorDetails,
|
||||
Comparator.comparingInt { s: SensorDetails -> s.sensorType }
|
||||
)
|
||||
return sensorDetails
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Set of three axes. Creates a negative companion to each axis, and corrects for device rotation.
|
||||
private const val AXIS_SET_TYPE_DEVICE_COORDINATES = 0
|
||||
|
||||
// Set of three axes. Creates a negative companion to each axis.
|
||||
private const val AXIS_SET_TYPE_OTHER_COORDINATES = 1
|
||||
private var deviceRotation = Surface.ROTATION_0
|
||||
|
||||
// The fastest sampling rate Android lets us use without declaring the HIGH_SAMPLING_RATE_SENSORS
|
||||
// permission is 200 Hz. This is also the sampling rate of a Wii Remote, so it fits us perfectly.
|
||||
private const val SAMPLING_PERIOD_US = 1000000 / 200
|
||||
|
||||
/**
|
||||
* Should be called when an activity or other component that uses sensor events is resumed.
|
||||
*
|
||||
* Sensor events that contain device coordinates will have the coordinates rotated by the value
|
||||
* passed to this function.
|
||||
*
|
||||
* @param deviceRotation The current rotation of the device (i.e. rotation of the default display)
|
||||
*/
|
||||
fun setDeviceRotation(deviceRotation: Int) {
|
||||
this.deviceRotation = deviceRotation
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model;
|
||||
|
||||
import android.os.Vibrator;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* A wrapper around {@link android.os.VibratorManager}, for backwards compatibility.
|
||||
*/
|
||||
public interface DolphinVibratorManager
|
||||
{
|
||||
@Keep @NonNull
|
||||
Vibrator getVibrator(int vibratorId);
|
||||
|
||||
@Keep @NonNull
|
||||
int[] getVibratorIds();
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model
|
||||
|
||||
import android.os.Vibrator
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* A wrapper around [android.os.VibratorManager], for backwards compatibility.
|
||||
*/
|
||||
@Keep
|
||||
interface DolphinVibratorManager {
|
||||
fun getVibrator(vibratorId: Int): Vibrator
|
||||
|
||||
fun getVibratorIds(): IntArray
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model;
|
||||
|
||||
import android.os.Vibrator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public final class DolphinVibratorManagerCompat implements DolphinVibratorManager
|
||||
{
|
||||
private final Vibrator mVibrator;
|
||||
private final int[] mIds;
|
||||
|
||||
public DolphinVibratorManagerCompat(@Nullable Vibrator vibrator)
|
||||
{
|
||||
mVibrator = vibrator;
|
||||
mIds = vibrator != null && vibrator.hasVibrator() ? new int[]{0} : new int[]{};
|
||||
}
|
||||
|
||||
@Override @NonNull
|
||||
public Vibrator getVibrator(int vibratorId)
|
||||
{
|
||||
if (vibratorId > mIds.length)
|
||||
throw new IndexOutOfBoundsException();
|
||||
|
||||
return mVibrator;
|
||||
}
|
||||
|
||||
@Override @NonNull
|
||||
public int[] getVibratorIds()
|
||||
{
|
||||
return mIds;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model
|
||||
|
||||
import android.os.Vibrator
|
||||
|
||||
class DolphinVibratorManagerCompat(vibrator: Vibrator) : DolphinVibratorManager {
|
||||
private val vibrator: Vibrator
|
||||
private val vibratorIds: IntArray
|
||||
|
||||
init {
|
||||
this.vibrator = vibrator
|
||||
vibratorIds = if (vibrator.hasVibrator()) intArrayOf(0) else intArrayOf()
|
||||
}
|
||||
|
||||
override fun getVibrator(vibratorId: Int): Vibrator {
|
||||
if (vibratorId > vibratorIds.size)
|
||||
throw IndexOutOfBoundsException()
|
||||
|
||||
return vibrator
|
||||
}
|
||||
|
||||
override fun getVibratorIds(): IntArray = vibratorIds
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Vibrator;
|
||||
import android.os.VibratorManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.S)
|
||||
public final class DolphinVibratorManagerPassthrough implements DolphinVibratorManager
|
||||
{
|
||||
private final VibratorManager mVibratorManager;
|
||||
|
||||
public DolphinVibratorManagerPassthrough(@NonNull VibratorManager vibratorManager)
|
||||
{
|
||||
mVibratorManager = vibratorManager;
|
||||
}
|
||||
|
||||
@Override @NonNull
|
||||
public Vibrator getVibrator(int vibratorId)
|
||||
{
|
||||
return mVibratorManager.getVibrator(vibratorId);
|
||||
}
|
||||
|
||||
@Override @NonNull
|
||||
public int[] getVibratorIds()
|
||||
{
|
||||
return mVibratorManager.getVibratorIds();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Vibrator
|
||||
import android.os.VibratorManager
|
||||
import androidx.annotation.RequiresApi
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.S)
|
||||
class DolphinVibratorManagerPassthrough(private val vibratorManager: VibratorManager) :
|
||||
DolphinVibratorManager {
|
||||
override fun getVibrator(vibratorId: Int): Vibrator = vibratorManager.getVibrator(vibratorId)
|
||||
|
||||
override fun getVibratorIds(): IntArray = vibratorManager.vibratorIds
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
|
||||
|
||||
public class InputMappingBooleanSetting implements AbstractBooleanSetting
|
||||
{
|
||||
private final NumericSetting mNumericSetting;
|
||||
|
||||
public InputMappingBooleanSetting(NumericSetting numericSetting)
|
||||
{
|
||||
mNumericSetting = numericSetting;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBoolean()
|
||||
{
|
||||
return mNumericSetting.getBooleanValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBoolean(@NonNull Settings settings, boolean newValue)
|
||||
{
|
||||
mNumericSetting.setBooleanValue(newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOverridden()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRuntimeEditable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(@NonNull Settings settings)
|
||||
{
|
||||
mNumericSetting.setBooleanValue(mNumericSetting.getBooleanDefaultValue());
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model
|
||||
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings
|
||||
|
||||
class InputMappingBooleanSetting(private val numericSetting: NumericSetting) :
|
||||
AbstractBooleanSetting {
|
||||
override val boolean: Boolean
|
||||
get() = numericSetting.getBooleanValue()
|
||||
|
||||
override fun setBoolean(settings: Settings, newValue: Boolean) =
|
||||
numericSetting.setBooleanValue(newValue)
|
||||
|
||||
override val isOverridden: Boolean = false
|
||||
|
||||
override val isRuntimeEditable: Boolean = true
|
||||
|
||||
override fun delete(settings: Settings): Boolean {
|
||||
numericSetting.setBooleanValue(numericSetting.getBooleanDefaultValue())
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.AbstractFloatSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
|
||||
|
||||
// Yes, floats are not the same thing as doubles... They're close enough, though
|
||||
public class InputMappingDoubleSetting implements AbstractFloatSetting
|
||||
{
|
||||
private final NumericSetting mNumericSetting;
|
||||
|
||||
public InputMappingDoubleSetting(NumericSetting numericSetting)
|
||||
{
|
||||
mNumericSetting = numericSetting;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFloat()
|
||||
{
|
||||
return (float) mNumericSetting.getDoubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFloat(@NonNull Settings settings, float newValue)
|
||||
{
|
||||
mNumericSetting.setDoubleValue(newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOverridden()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRuntimeEditable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(@NonNull Settings settings)
|
||||
{
|
||||
mNumericSetting.setDoubleValue(mNumericSetting.getDoubleDefaultValue());
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model
|
||||
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.AbstractFloatSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings
|
||||
|
||||
// Yes, floats are not the same thing as doubles... They're close enough, though
|
||||
class InputMappingDoubleSetting(private val numericSetting: NumericSetting) : AbstractFloatSetting {
|
||||
override val float: Float
|
||||
get() = numericSetting.getDoubleValue().toFloat()
|
||||
|
||||
override fun setFloat(settings: Settings, newValue: Float) =
|
||||
numericSetting.setDoubleValue(newValue.toDouble())
|
||||
|
||||
override val isOverridden: Boolean = false
|
||||
|
||||
override val isRuntimeEditable: Boolean = true
|
||||
|
||||
override fun delete(settings: Settings): Boolean {
|
||||
numericSetting.setDoubleValue(numericSetting.getDoubleDefaultValue())
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.AbstractIntSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
|
||||
|
||||
public class InputMappingIntSetting implements AbstractIntSetting
|
||||
{
|
||||
private final NumericSetting mNumericSetting;
|
||||
|
||||
public InputMappingIntSetting(NumericSetting numericSetting)
|
||||
{
|
||||
mNumericSetting = numericSetting;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInt()
|
||||
{
|
||||
return mNumericSetting.getIntValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInt(@NonNull Settings settings, int newValue)
|
||||
{
|
||||
mNumericSetting.setIntValue(newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOverridden()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRuntimeEditable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(@NonNull Settings settings)
|
||||
{
|
||||
mNumericSetting.setIntValue(mNumericSetting.getIntDefaultValue());
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model
|
||||
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.AbstractIntSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings
|
||||
|
||||
class InputMappingIntSetting(private val numericSetting: NumericSetting) : AbstractIntSetting {
|
||||
override val int: Int
|
||||
get() = numericSetting.getIntValue()
|
||||
|
||||
override fun setInt(settings: Settings, newValue: Int) = numericSetting.setIntValue(newValue)
|
||||
|
||||
override val isOverridden: Boolean = false
|
||||
|
||||
override val isRuntimeEditable: Boolean = true
|
||||
|
||||
override fun delete(settings: Settings): Boolean {
|
||||
numericSetting.setIntValue(numericSetting.getIntDefaultValue())
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model;
|
||||
|
||||
public final class InputOverrider
|
||||
{
|
||||
public static final class ControlId
|
||||
{
|
||||
public static final int GCPAD_A_BUTTON = 0;
|
||||
public static final int GCPAD_B_BUTTON = 1;
|
||||
public static final int GCPAD_X_BUTTON = 2;
|
||||
public static final int GCPAD_Y_BUTTON = 3;
|
||||
public static final int GCPAD_Z_BUTTON = 4;
|
||||
public static final int GCPAD_START_BUTTON = 5;
|
||||
public static final int GCPAD_DPAD_UP = 6;
|
||||
public static final int GCPAD_DPAD_DOWN = 7;
|
||||
public static final int GCPAD_DPAD_LEFT = 8;
|
||||
public static final int GCPAD_DPAD_RIGHT = 9;
|
||||
public static final int GCPAD_L_DIGITAL = 10;
|
||||
public static final int GCPAD_R_DIGITAL = 11;
|
||||
public static final int GCPAD_L_ANALOG = 12;
|
||||
public static final int GCPAD_R_ANALOG = 13;
|
||||
public static final int GCPAD_MAIN_STICK_X = 14;
|
||||
public static final int GCPAD_MAIN_STICK_Y = 15;
|
||||
public static final int GCPAD_C_STICK_X = 16;
|
||||
public static final int GCPAD_C_STICK_Y = 17;
|
||||
|
||||
public static final int WIIMOTE_A_BUTTON = 18;
|
||||
public static final int WIIMOTE_B_BUTTON = 19;
|
||||
public static final int WIIMOTE_ONE_BUTTON = 20;
|
||||
public static final int WIIMOTE_TWO_BUTTON = 21;
|
||||
public static final int WIIMOTE_PLUS_BUTTON = 22;
|
||||
public static final int WIIMOTE_MINUS_BUTTON = 23;
|
||||
public static final int WIIMOTE_HOME_BUTTON = 24;
|
||||
public static final int WIIMOTE_DPAD_UP = 25;
|
||||
public static final int WIIMOTE_DPAD_DOWN = 26;
|
||||
public static final int WIIMOTE_DPAD_LEFT = 27;
|
||||
public static final int WIIMOTE_DPAD_RIGHT = 28;
|
||||
public static final int WIIMOTE_IR_X = 29;
|
||||
public static final int WIIMOTE_IR_Y = 30;
|
||||
|
||||
public static final int NUNCHUK_C_BUTTON = 31;
|
||||
public static final int NUNCHUK_Z_BUTTON = 32;
|
||||
public static final int NUNCHUK_STICK_X = 33;
|
||||
public static final int NUNCHUK_STICK_Y = 34;
|
||||
|
||||
public static final int CLASSIC_A_BUTTON = 35;
|
||||
public static final int CLASSIC_B_BUTTON = 36;
|
||||
public static final int CLASSIC_X_BUTTON = 37;
|
||||
public static final int CLASSIC_Y_BUTTON = 38;
|
||||
public static final int CLASSIC_ZL_BUTTON = 39;
|
||||
public static final int CLASSIC_ZR_BUTTON = 40;
|
||||
public static final int CLASSIC_PLUS_BUTTON = 41;
|
||||
public static final int CLASSIC_MINUS_BUTTON = 42;
|
||||
public static final int CLASSIC_HOME_BUTTON = 43;
|
||||
public static final int CLASSIC_DPAD_UP = 44;
|
||||
public static final int CLASSIC_DPAD_DOWN = 45;
|
||||
public static final int CLASSIC_DPAD_LEFT = 46;
|
||||
public static final int CLASSIC_DPAD_RIGHT = 47;
|
||||
public static final int CLASSIC_L_DIGITAL = 48;
|
||||
public static final int CLASSIC_R_DIGITAL = 49;
|
||||
public static final int CLASSIC_L_ANALOG = 50;
|
||||
public static final int CLASSIC_R_ANALOG = 51;
|
||||
public static final int CLASSIC_LEFT_STICK_X = 52;
|
||||
public static final int CLASSIC_LEFT_STICK_Y = 53;
|
||||
public static final int CLASSIC_RIGHT_STICK_X = 54;
|
||||
public static final int CLASSIC_RIGHT_STICK_Y = 55;
|
||||
}
|
||||
|
||||
public static native void registerGameCube(int controllerIndex);
|
||||
|
||||
public static native void registerWii(int controllerIndex);
|
||||
|
||||
public static native void unregisterGameCube(int controllerIndex);
|
||||
|
||||
public static native void unregisterWii(int controllerIndex);
|
||||
|
||||
public static native void setControlState(int controllerIndex, int control, double state);
|
||||
|
||||
public static native void clearControlState(int controllerIndex, int control);
|
||||
|
||||
// Angle is in radians and should be non-negative
|
||||
public static native double getGateRadiusAtAngle(int emuPadId, int stick, double angle);
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model
|
||||
|
||||
object InputOverrider {
|
||||
external fun registerGameCube(controllerIndex: Int)
|
||||
|
||||
external fun registerWii(controllerIndex: Int)
|
||||
|
||||
external fun unregisterGameCube(controllerIndex: Int)
|
||||
|
||||
external fun unregisterWii(controllerIndex: Int)
|
||||
|
||||
external fun setControlState(controllerIndex: Int, control: Int, state: Double)
|
||||
|
||||
external fun clearControlState(controllerIndex: Int, control: Int)
|
||||
|
||||
// Angle is in radians and should be non-negative
|
||||
external fun getGateRadiusAtAngle(emuPadId: Int, stick: Int, angle: Double): Double
|
||||
|
||||
object ControlId {
|
||||
const val GCPAD_A_BUTTON = 0
|
||||
const val GCPAD_B_BUTTON = 1
|
||||
const val GCPAD_X_BUTTON = 2
|
||||
const val GCPAD_Y_BUTTON = 3
|
||||
const val GCPAD_Z_BUTTON = 4
|
||||
const val GCPAD_START_BUTTON = 5
|
||||
const val GCPAD_DPAD_UP = 6
|
||||
const val GCPAD_DPAD_DOWN = 7
|
||||
const val GCPAD_DPAD_LEFT = 8
|
||||
const val GCPAD_DPAD_RIGHT = 9
|
||||
const val GCPAD_L_DIGITAL = 10
|
||||
const val GCPAD_R_DIGITAL = 11
|
||||
const val GCPAD_L_ANALOG = 12
|
||||
const val GCPAD_R_ANALOG = 13
|
||||
const val GCPAD_MAIN_STICK_X = 14
|
||||
const val GCPAD_MAIN_STICK_Y = 15
|
||||
const val GCPAD_C_STICK_X = 16
|
||||
const val GCPAD_C_STICK_Y = 17
|
||||
|
||||
const val WIIMOTE_A_BUTTON = 18
|
||||
const val WIIMOTE_B_BUTTON = 19
|
||||
const val WIIMOTE_ONE_BUTTON = 20
|
||||
const val WIIMOTE_TWO_BUTTON = 21
|
||||
const val WIIMOTE_PLUS_BUTTON = 22
|
||||
const val WIIMOTE_MINUS_BUTTON = 23
|
||||
const val WIIMOTE_HOME_BUTTON = 24
|
||||
const val WIIMOTE_DPAD_UP = 25
|
||||
const val WIIMOTE_DPAD_DOWN = 26
|
||||
const val WIIMOTE_DPAD_LEFT = 27
|
||||
const val WIIMOTE_DPAD_RIGHT = 28
|
||||
const val WIIMOTE_IR_X = 29
|
||||
const val WIIMOTE_IR_Y = 30
|
||||
|
||||
const val NUNCHUK_C_BUTTON = 31
|
||||
const val NUNCHUK_Z_BUTTON = 32
|
||||
const val NUNCHUK_STICK_X = 33
|
||||
const val NUNCHUK_STICK_Y = 34
|
||||
|
||||
const val CLASSIC_A_BUTTON = 35
|
||||
const val CLASSIC_B_BUTTON = 36
|
||||
const val CLASSIC_X_BUTTON = 37
|
||||
const val CLASSIC_Y_BUTTON = 38
|
||||
const val CLASSIC_ZL_BUTTON = 39
|
||||
const val CLASSIC_ZR_BUTTON = 40
|
||||
const val CLASSIC_PLUS_BUTTON = 41
|
||||
const val CLASSIC_MINUS_BUTTON = 42
|
||||
const val CLASSIC_HOME_BUTTON = 43
|
||||
const val CLASSIC_DPAD_UP = 44
|
||||
const val CLASSIC_DPAD_DOWN = 45
|
||||
const val CLASSIC_DPAD_LEFT = 46
|
||||
const val CLASSIC_DPAD_RIGHT = 47
|
||||
const val CLASSIC_L_DIGITAL = 48
|
||||
const val CLASSIC_R_DIGITAL = 49
|
||||
const val CLASSIC_L_ANALOG = 50
|
||||
const val CLASSIC_R_ANALOG = 51
|
||||
const val CLASSIC_LEFT_STICK_X = 52
|
||||
const val CLASSIC_LEFT_STICK_Y = 53
|
||||
const val CLASSIC_RIGHT_STICK_X = 54
|
||||
const val CLASSIC_RIGHT_STICK_Y = 55
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController;
|
||||
|
||||
public final class MappingCommon
|
||||
{
|
||||
private 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.
|
||||
*/
|
||||
public static native String detectInput(@NonNull EmulatedController controller,
|
||||
boolean allDevices);
|
||||
|
||||
public static native String getExpressionForControl(String control, String device,
|
||||
String defaultDevice);
|
||||
|
||||
public static native void save();
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
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,
|
||||
defaultDevice: String
|
||||
): String
|
||||
|
||||
external fun save()
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model.controlleremu;
|
||||
package org.dolphinemu.dolphinemu.features.input.model.controlleremu
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* Represents a C++ ControllerEmu::Control.
|
||||
|
@ -10,18 +10,9 @@ import androidx.annotation.Keep;
|
|||
* The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed
|
||||
* in C++ is undefined behavior!
|
||||
*/
|
||||
public class Control
|
||||
{
|
||||
@Keep
|
||||
private final long mPointer;
|
||||
@Keep
|
||||
class Control private constructor(private val pointer: Long) {
|
||||
external fun getUiName(): String
|
||||
|
||||
@Keep
|
||||
private Control(long pointer)
|
||||
{
|
||||
mPointer = pointer;
|
||||
}
|
||||
|
||||
public native String getUiName();
|
||||
|
||||
public native ControlReference getControlReference();
|
||||
external fun getControlReference(): ControlReference
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model.controlleremu;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
/**
|
||||
* Represents a C++ ControllerEmu::ControlGroup.
|
||||
*
|
||||
* The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed
|
||||
* in C++ is undefined behavior!
|
||||
*/
|
||||
public class ControlGroup
|
||||
{
|
||||
public static final int TYPE_OTHER = 0;
|
||||
public static final int TYPE_STICK = 1;
|
||||
public static final int TYPE_MIXED_TRIGGERS = 2;
|
||||
public static final int TYPE_BUTTONS = 3;
|
||||
public static final int TYPE_FORCE = 4;
|
||||
public static final int TYPE_ATTACHMENTS = 5;
|
||||
public static final int TYPE_TILT = 6;
|
||||
public static final int TYPE_CURSOR = 7;
|
||||
public static final int TYPE_TRIGGERS = 8;
|
||||
public static final int TYPE_SLIDER = 9;
|
||||
public static final int TYPE_SHAKE = 10;
|
||||
public static final int TYPE_IMU_ACCELEROMETER = 11;
|
||||
public static final int TYPE_IMU_GYROSCOPE = 12;
|
||||
public static final int TYPE_IMU_CURSOR = 13;
|
||||
|
||||
public static final int DEFAULT_ENABLED_ALWAYS = 0;
|
||||
public static final int DEFAULT_ENABLED_YES = 1;
|
||||
public static final int DEFAULT_ENABLED_NO = 2;
|
||||
|
||||
@Keep
|
||||
private final long mPointer;
|
||||
|
||||
@Keep
|
||||
private ControlGroup(long pointer)
|
||||
{
|
||||
mPointer = pointer;
|
||||
}
|
||||
|
||||
public native String getUiName();
|
||||
|
||||
public native int getGroupType();
|
||||
|
||||
public native int getDefaultEnabledValue();
|
||||
|
||||
public native boolean getEnabled();
|
||||
|
||||
public native void setEnabled(boolean value);
|
||||
|
||||
public native int getControlCount();
|
||||
|
||||
public native Control getControl(int i);
|
||||
|
||||
public native int getNumericSettingCount();
|
||||
|
||||
public native NumericSetting getNumericSetting(int i);
|
||||
|
||||
/**
|
||||
* If getGroupType returns TYPE_ATTACHMENTS, this returns the attachment selection setting.
|
||||
* Otherwise, undefined behavior!
|
||||
*/
|
||||
public native NumericSetting getAttachmentSetting();
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model.controlleremu
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* Represents a C++ ControllerEmu::ControlGroup.
|
||||
*
|
||||
* The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed
|
||||
* in C++ is undefined behavior!
|
||||
*/
|
||||
@Keep
|
||||
class ControlGroup private constructor(private val pointer: Long) {
|
||||
external fun getUiName(): String
|
||||
|
||||
external fun getGroupType(): Int
|
||||
|
||||
external fun getDefaultEnabledValue(): Int
|
||||
|
||||
external fun getEnabled(): Boolean
|
||||
|
||||
external fun setEnabled(value: Boolean)
|
||||
|
||||
external fun getControlCount(): Int
|
||||
|
||||
external fun getControl(i: Int): Control
|
||||
|
||||
external fun getNumericSettingCount(): Int
|
||||
|
||||
external fun getNumericSetting(i: Int): NumericSetting
|
||||
|
||||
/**
|
||||
* If getGroupType returns TYPE_ATTACHMENTS, this returns the attachment selection setting.
|
||||
* Otherwise, undefined behavior!
|
||||
*/
|
||||
external fun getAttachmentSetting(): NumericSetting
|
||||
|
||||
companion object {
|
||||
const val TYPE_OTHER = 0
|
||||
const val TYPE_STICK = 1
|
||||
const val TYPE_MIXED_TRIGGERS = 2
|
||||
const val TYPE_BUTTONS = 3
|
||||
const val TYPE_FORCE = 4
|
||||
const val TYPE_ATTACHMENTS = 5
|
||||
const val TYPE_TILT = 6
|
||||
const val TYPE_CURSOR = 7
|
||||
const val TYPE_TRIGGERS = 8
|
||||
const val TYPE_SLIDER = 9
|
||||
const val TYPE_SHAKE = 10
|
||||
const val TYPE_IMU_ACCELEROMETER = 11
|
||||
const val TYPE_IMU_GYROSCOPE = 12
|
||||
const val TYPE_IMU_CURSOR = 13
|
||||
|
||||
const val DEFAULT_ENABLED_ALWAYS = 0
|
||||
const val DEFAULT_ENABLED_YES = 1
|
||||
const val DEFAULT_ENABLED_NO = 2
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model.controlleremu;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a C++ ControlReference.
|
||||
*
|
||||
* The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed
|
||||
* in C++ is undefined behavior!
|
||||
*/
|
||||
public class ControlReference
|
||||
{
|
||||
@Keep
|
||||
private final long mPointer;
|
||||
|
||||
@Keep
|
||||
private ControlReference(long pointer)
|
||||
{
|
||||
mPointer = pointer;
|
||||
}
|
||||
|
||||
public native double getState();
|
||||
|
||||
public native String getExpression();
|
||||
|
||||
/**
|
||||
* Sets the expression for this control reference.
|
||||
*
|
||||
* @param expr The new expression
|
||||
* @return null on success, a human-readable error on failure
|
||||
*/
|
||||
@Nullable
|
||||
public native String setExpression(String expr);
|
||||
|
||||
public native boolean isInput();
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model.controlleremu
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* Represents a C++ ControlReference.
|
||||
*
|
||||
* The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed
|
||||
* in C++ is undefined behavior!
|
||||
*/
|
||||
@Keep
|
||||
class ControlReference private constructor(private val pointer: Long) {
|
||||
external fun getState(): Double
|
||||
|
||||
external fun getExpression(): String
|
||||
|
||||
/**
|
||||
* Sets the expression for this control reference.
|
||||
*
|
||||
* @param expr The new expression
|
||||
* @return null on success, a human-readable error on failure
|
||||
*/
|
||||
external fun setExpression(expr: String): String?
|
||||
|
||||
external fun isInput(): Boolean
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model.controlleremu;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
/**
|
||||
* Represents a C++ ControllerEmu::EmulatedController.
|
||||
*
|
||||
* The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed
|
||||
* in C++ is undefined behavior!
|
||||
*/
|
||||
public class EmulatedController
|
||||
{
|
||||
@Keep
|
||||
private final long mPointer;
|
||||
|
||||
@Keep
|
||||
private EmulatedController(long pointer)
|
||||
{
|
||||
mPointer = pointer;
|
||||
}
|
||||
|
||||
public native String getDefaultDevice();
|
||||
|
||||
public native void setDefaultDevice(String device);
|
||||
|
||||
public native int getGroupCount();
|
||||
|
||||
public native ControlGroup getGroup(int index);
|
||||
|
||||
public native void updateSingleControlReference(ControlReference controlReference);
|
||||
|
||||
public native void loadDefaultSettings();
|
||||
|
||||
public native void clearSettings();
|
||||
|
||||
public native void loadProfile(String path);
|
||||
|
||||
public native void saveProfile(String path);
|
||||
|
||||
public static native EmulatedController getGcPad(int controllerIndex);
|
||||
|
||||
public static native EmulatedController getWiimote(int controllerIndex);
|
||||
|
||||
public static native EmulatedController getWiimoteAttachment(int controllerIndex,
|
||||
int attachmentIndex);
|
||||
|
||||
public static native int getSelectedWiimoteAttachment(int controllerIndex);
|
||||
|
||||
public static native NumericSetting getSidewaysWiimoteSetting(int controllerIndex);
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model.controlleremu
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* Represents a C++ ControllerEmu::EmulatedController.
|
||||
*
|
||||
* The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed
|
||||
* in C++ is undefined behavior!
|
||||
*/
|
||||
@Keep
|
||||
class EmulatedController private constructor(private val pointer: Long) {
|
||||
external fun getDefaultDevice(): String
|
||||
|
||||
external fun setDefaultDevice(device: String)
|
||||
|
||||
external fun getGroupCount(): Int
|
||||
|
||||
external fun getGroup(index: Int): ControlGroup
|
||||
|
||||
external fun updateSingleControlReference(controlReference: ControlReference)
|
||||
|
||||
external fun loadDefaultSettings()
|
||||
|
||||
external fun clearSettings()
|
||||
|
||||
external fun loadProfile(path: String)
|
||||
|
||||
external fun saveProfile(path: String)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
external fun getGcPad(controllerIndex: Int): EmulatedController
|
||||
|
||||
@JvmStatic
|
||||
external fun getWiimote(controllerIndex: Int): EmulatedController
|
||||
|
||||
@JvmStatic
|
||||
external fun getWiimoteAttachment(
|
||||
controllerIndex: Int,
|
||||
attachmentIndex: Int
|
||||
): EmulatedController
|
||||
|
||||
@JvmStatic
|
||||
external fun getSelectedWiimoteAttachment(controllerIndex: Int): Int
|
||||
|
||||
@JvmStatic
|
||||
external fun getSidewaysWiimoteSetting(controllerIndex: Int): NumericSetting
|
||||
}
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model.controlleremu;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
/**
|
||||
* Represents a C++ ControllerEmu::NumericSetting.
|
||||
*
|
||||
* The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed
|
||||
* in C++ is undefined behavior!
|
||||
*/
|
||||
public class NumericSetting
|
||||
{
|
||||
public static final int TYPE_INT = 0;
|
||||
public static final int TYPE_DOUBLE = 1;
|
||||
public static final int TYPE_BOOLEAN = 2;
|
||||
|
||||
@Keep
|
||||
private final long mPointer;
|
||||
|
||||
@Keep
|
||||
private NumericSetting(long pointer)
|
||||
{
|
||||
mPointer = pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The name used in the UI.
|
||||
*/
|
||||
public native String getUiName();
|
||||
|
||||
/**
|
||||
* @return A string applied to the number in the UI (unit of measure).
|
||||
*/
|
||||
public native String getUiSuffix();
|
||||
|
||||
/**
|
||||
* @return Detailed description of the setting.
|
||||
*/
|
||||
public native String getUiDescription();
|
||||
|
||||
/**
|
||||
* @return TYPE_INT, TYPE_DOUBLE or TYPE_BOOLEAN
|
||||
*/
|
||||
public native int getType();
|
||||
|
||||
public native ControlReference getControlReference();
|
||||
|
||||
/**
|
||||
* If the type is TYPE_INT, gets the current value. Otherwise, undefined behavior!
|
||||
*/
|
||||
public native int getIntValue();
|
||||
|
||||
/**
|
||||
* If the type is TYPE_INT, sets the current value. Otherwise, undefined behavior!
|
||||
*/
|
||||
public native void setIntValue(int value);
|
||||
|
||||
/**
|
||||
* If the type is TYPE_INT, gets the default value. Otherwise, undefined behavior!
|
||||
*/
|
||||
public native int getIntDefaultValue();
|
||||
|
||||
/**
|
||||
* If the type is TYPE_DOUBLE, gets the current value. Otherwise, undefined behavior!
|
||||
*/
|
||||
public native double getDoubleValue();
|
||||
|
||||
/**
|
||||
* If the type is TYPE_DOUBLE, sets the current value. Otherwise, undefined behavior!
|
||||
*/
|
||||
public native void setDoubleValue(double value);
|
||||
|
||||
/**
|
||||
* If the type is TYPE_DOUBLE, gets the default value. Otherwise, undefined behavior!
|
||||
*/
|
||||
public native double getDoubleDefaultValue();
|
||||
|
||||
/**
|
||||
* If the type is TYPE_DOUBLE, returns the minimum valid value. Otherwise, undefined behavior!
|
||||
*/
|
||||
public native double getDoubleMin();
|
||||
|
||||
/**
|
||||
* If the type is TYPE_DOUBLE, returns the maximum valid value. Otherwise, undefined behavior!
|
||||
*/
|
||||
public native double getDoubleMax();
|
||||
|
||||
/**
|
||||
* If the type is TYPE_BOOLEAN, gets the current value. Otherwise, undefined behavior!
|
||||
*/
|
||||
public native boolean getBooleanValue();
|
||||
|
||||
/**
|
||||
* If the type is TYPE_BOOLEAN, sets the current value. Otherwise, undefined behavior!
|
||||
*/
|
||||
public native void setBooleanValue(boolean value);
|
||||
|
||||
/**
|
||||
* If the type is TYPE_BOOLEAN, gets the default value. Otherwise, undefined behavior!
|
||||
*/
|
||||
public native boolean getBooleanDefaultValue();
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model.controlleremu
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* Represents a C++ ControllerEmu::NumericSetting.
|
||||
*
|
||||
* The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed
|
||||
* in C++ is undefined behavior!
|
||||
*/
|
||||
@Keep
|
||||
class NumericSetting private constructor(private val pointer: Long) {
|
||||
/**
|
||||
* @return The name used in the UI.
|
||||
*/
|
||||
external fun getUiName(): String
|
||||
|
||||
/**
|
||||
* @return A string applied to the number in the UI (unit of measure).
|
||||
*/
|
||||
external fun getUiSuffix(): String
|
||||
|
||||
/**
|
||||
* @return Detailed description of the setting.
|
||||
*/
|
||||
external fun getUiDescription(): String
|
||||
|
||||
/**
|
||||
* @return TYPE_INT, TYPE_DOUBLE or TYPE_BOOLEAN
|
||||
*/
|
||||
external fun getType(): Int
|
||||
|
||||
external fun getControlReference(): ControlReference
|
||||
|
||||
/**
|
||||
* If the type is TYPE_INT, gets the current value. Otherwise, undefined behavior!
|
||||
*/
|
||||
external fun getIntValue(): Int
|
||||
|
||||
/**
|
||||
* If the type is TYPE_INT, sets the current value. Otherwise, undefined behavior!
|
||||
*/
|
||||
external fun setIntValue(value: Int)
|
||||
|
||||
/**
|
||||
* If the type is TYPE_INT, gets the default value. Otherwise, undefined behavior!
|
||||
*/
|
||||
external fun getIntDefaultValue(): Int
|
||||
|
||||
/**
|
||||
* If the type is TYPE_DOUBLE, gets the current value. Otherwise, undefined behavior!
|
||||
*/
|
||||
external fun getDoubleValue(): Double
|
||||
|
||||
/**
|
||||
* If the type is TYPE_DOUBLE, sets the current value. Otherwise, undefined behavior!
|
||||
*/
|
||||
external fun setDoubleValue(value: Double)
|
||||
|
||||
/**
|
||||
* If the type is TYPE_DOUBLE, gets the default value. Otherwise, undefined behavior!
|
||||
*/
|
||||
external fun getDoubleDefaultValue(): Double
|
||||
|
||||
/**
|
||||
* If the type is TYPE_DOUBLE, returns the minimum valid value. Otherwise, undefined behavior!
|
||||
*/
|
||||
external fun getDoubleMin(): Double
|
||||
|
||||
/**
|
||||
* If the type is TYPE_DOUBLE, returns the maximum valid value. Otherwise, undefined behavior!
|
||||
*/
|
||||
external fun getDoubleMax(): Double
|
||||
|
||||
/**
|
||||
* If the type is TYPE_BOOLEAN, gets the current value. Otherwise, undefined behavior!
|
||||
*/
|
||||
external fun getBooleanValue(): Boolean
|
||||
|
||||
/**
|
||||
* If the type is TYPE_BOOLEAN, sets the current value. Otherwise, undefined behavior!
|
||||
*/
|
||||
external fun setBooleanValue(value: Boolean)
|
||||
|
||||
/**
|
||||
* If the type is TYPE_BOOLEAN, gets the default value. Otherwise, undefined behavior!
|
||||
*/
|
||||
external fun getBooleanDefaultValue(): Boolean
|
||||
|
||||
companion object {
|
||||
const val TYPE_INT = 0
|
||||
const val TYPE_DOUBLE = 1
|
||||
const val TYPE_BOOLEAN = 2
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model.view;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface;
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.view.StringSingleChoiceSetting;
|
||||
|
||||
public class InputDeviceSetting extends StringSingleChoiceSetting
|
||||
{
|
||||
private final EmulatedController mController;
|
||||
|
||||
public InputDeviceSetting(Context context, int titleId, int descriptionId,
|
||||
EmulatedController controller)
|
||||
{
|
||||
super(context, null, titleId, descriptionId, null, null, null);
|
||||
|
||||
mController = controller;
|
||||
|
||||
refreshChoicesAndValues();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSelectedChoice()
|
||||
{
|
||||
return mController.getDefaultDevice();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSelectedValue()
|
||||
{
|
||||
return mController.getDefaultDevice();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelectedValue(Settings settings, String newValue)
|
||||
{
|
||||
mController.setDefaultDevice(newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshChoicesAndValues()
|
||||
{
|
||||
String[] devices = ControllerInterface.getAllDeviceStrings();
|
||||
|
||||
setChoices(devices);
|
||||
setValues(devices);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEditable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canClear()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(Settings settings)
|
||||
{
|
||||
setSelectedValue(settings, "");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model.view
|
||||
|
||||
import android.content.Context
|
||||
import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.view.StringSingleChoiceSetting
|
||||
|
||||
class InputDeviceSetting(
|
||||
context: Context,
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
private val controller: EmulatedController
|
||||
) : StringSingleChoiceSetting(context, null, titleId, descriptionId, null, null, null) {
|
||||
init {
|
||||
refreshChoicesAndValues()
|
||||
}
|
||||
|
||||
override val selectedChoice: String
|
||||
get() = controller.getDefaultDevice()
|
||||
|
||||
override val selectedValue: String
|
||||
get() = controller.getDefaultDevice()
|
||||
|
||||
override fun setSelectedValue(settings: Settings, selection: String) =
|
||||
controller.setDefaultDevice(selection)
|
||||
|
||||
override fun refreshChoicesAndValues() {
|
||||
val devices = ControllerInterface.getAllDeviceStrings()
|
||||
|
||||
choices = devices
|
||||
values = devices
|
||||
}
|
||||
|
||||
override val isEditable: Boolean = true
|
||||
|
||||
override fun canClear(): Boolean = true
|
||||
|
||||
override fun clear(settings: Settings) = setSelectedValue(settings, "")
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model.view;
|
||||
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.Control;
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlReference;
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.AbstractSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem;
|
||||
|
||||
public final class InputMappingControlSetting extends SettingsItem
|
||||
{
|
||||
private final ControlReference mControlReference;
|
||||
private final EmulatedController mController;
|
||||
|
||||
public InputMappingControlSetting(Control control, EmulatedController controller)
|
||||
{
|
||||
super(control.getUiName(), "");
|
||||
mControlReference = control.getControlReference();
|
||||
mController = controller;
|
||||
}
|
||||
|
||||
public String getValue()
|
||||
{
|
||||
return mControlReference.getExpression();
|
||||
}
|
||||
|
||||
public void setValue(String expr)
|
||||
{
|
||||
mControlReference.setExpression(expr);
|
||||
mController.updateSingleControlReference(mControlReference);
|
||||
}
|
||||
|
||||
public void clearValue()
|
||||
{
|
||||
setValue("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType()
|
||||
{
|
||||
return TYPE_INPUT_MAPPING_CONTROL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractSetting getSetting()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEditable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public EmulatedController getController()
|
||||
{
|
||||
return mController;
|
||||
}
|
||||
|
||||
public ControlReference getControlReference()
|
||||
{
|
||||
return mControlReference;
|
||||
}
|
||||
|
||||
public boolean isInput()
|
||||
{
|
||||
return mControlReference.isInput();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model.view
|
||||
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.Control
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.AbstractSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem
|
||||
|
||||
class InputMappingControlSetting(var control: Control, val controller: EmulatedController) :
|
||||
SettingsItem(control.getUiName(), "") {
|
||||
val controlReference get() = control.getControlReference()
|
||||
|
||||
var value: String
|
||||
get() = controlReference.getExpression()
|
||||
set(expr) {
|
||||
controlReference.setExpression(expr)
|
||||
controller.updateSingleControlReference(controlReference)
|
||||
}
|
||||
|
||||
fun clearValue() {
|
||||
value = ""
|
||||
}
|
||||
|
||||
override val type: Int = TYPE_INPUT_MAPPING_CONTROL
|
||||
|
||||
override val setting: AbstractSetting? = null
|
||||
|
||||
override val isEditable: Boolean = true
|
||||
|
||||
val isInput: Boolean
|
||||
get() = controlReference.isInput()
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemAdvancedMappingControlBinding;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class AdvancedMappingControlAdapter
|
||||
extends RecyclerView.Adapter<AdvancedMappingControlViewHolder>
|
||||
{
|
||||
private final Consumer<String> mOnClickCallback;
|
||||
|
||||
private String[] mControls = new String[0];
|
||||
|
||||
public AdvancedMappingControlAdapter(Consumer<String> onClickCallback)
|
||||
{
|
||||
mOnClickCallback = onClickCallback;
|
||||
}
|
||||
|
||||
@NonNull @Override
|
||||
public AdvancedMappingControlViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
|
||||
int viewType)
|
||||
{
|
||||
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
||||
|
||||
ListItemAdvancedMappingControlBinding binding =
|
||||
ListItemAdvancedMappingControlBinding.inflate(inflater);
|
||||
return new AdvancedMappingControlViewHolder(binding, mOnClickCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull AdvancedMappingControlViewHolder holder, int position)
|
||||
{
|
||||
holder.bind(mControls[position]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount()
|
||||
{
|
||||
return mControls.length;
|
||||
}
|
||||
|
||||
public void setControls(String[] controls)
|
||||
{
|
||||
mControls = controls;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemAdvancedMappingControlBinding
|
||||
import java.util.function.Consumer
|
||||
|
||||
class AdvancedMappingControlAdapter(private val onClickCallback: Consumer<String>) :
|
||||
RecyclerView.Adapter<AdvancedMappingControlViewHolder>() {
|
||||
private var controls = emptyArray<String>()
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): AdvancedMappingControlViewHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
val binding = ListItemAdvancedMappingControlBinding.inflate(inflater)
|
||||
return AdvancedMappingControlViewHolder(binding, onClickCallback)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: AdvancedMappingControlViewHolder, position: Int) =
|
||||
holder.bind(controls[position])
|
||||
|
||||
override fun getItemCount(): Int = controls.size
|
||||
|
||||
fun setControls(controls: Array<String>) {
|
||||
this.controls = controls
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemAdvancedMappingControlBinding;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class AdvancedMappingControlViewHolder extends RecyclerView.ViewHolder
|
||||
{
|
||||
private final ListItemAdvancedMappingControlBinding mBinding;
|
||||
|
||||
private String mName;
|
||||
|
||||
public AdvancedMappingControlViewHolder(@NonNull ListItemAdvancedMappingControlBinding binding,
|
||||
Consumer<String> onClickCallback)
|
||||
{
|
||||
super(binding.getRoot());
|
||||
|
||||
mBinding = binding;
|
||||
|
||||
binding.getRoot().setOnClickListener(view -> onClickCallback.accept(mName));
|
||||
}
|
||||
|
||||
public void bind(String name)
|
||||
{
|
||||
mName = name;
|
||||
|
||||
mBinding.textName.setText(name);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemAdvancedMappingControlBinding
|
||||
import java.util.function.Consumer
|
||||
|
||||
class AdvancedMappingControlViewHolder(
|
||||
private val binding: ListItemAdvancedMappingControlBinding,
|
||||
onClickCallback: Consumer<String>
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
private lateinit var name: String
|
||||
|
||||
init {
|
||||
binding.root.setOnClickListener { onClickCallback.accept(name) }
|
||||
}
|
||||
|
||||
fun bind(name: String) {
|
||||
this.name = name
|
||||
binding.textName.text = name
|
||||
}
|
||||
}
|
|
@ -1,151 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import com.google.android.material.divider.MaterialDividerItemDecoration;
|
||||
|
||||
import org.dolphinemu.dolphinemu.databinding.DialogAdvancedMappingBinding;
|
||||
import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface;
|
||||
import org.dolphinemu.dolphinemu.features.input.model.CoreDevice;
|
||||
import org.dolphinemu.dolphinemu.features.input.model.MappingCommon;
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlReference;
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class AdvancedMappingDialog extends AlertDialog
|
||||
implements AdapterView.OnItemClickListener
|
||||
{
|
||||
private final DialogAdvancedMappingBinding mBinding;
|
||||
private final ControlReference mControlReference;
|
||||
private final EmulatedController mController;
|
||||
private final String[] mDevices;
|
||||
private final AdvancedMappingControlAdapter mControlAdapter;
|
||||
|
||||
private String mSelectedDevice;
|
||||
|
||||
public AdvancedMappingDialog(Context context, DialogAdvancedMappingBinding binding,
|
||||
ControlReference controlReference, EmulatedController controller)
|
||||
{
|
||||
super(context);
|
||||
|
||||
mBinding = binding;
|
||||
mControlReference = controlReference;
|
||||
mController = controller;
|
||||
|
||||
mDevices = ControllerInterface.getAllDeviceStrings();
|
||||
|
||||
// TODO: Remove workaround for text filtering issue in material components when fixed
|
||||
// https://github.com/material-components/material-components-android/issues/1464
|
||||
mBinding.dropdownDevice.setSaveEnabled(false);
|
||||
|
||||
binding.dropdownDevice.setOnItemClickListener(this);
|
||||
|
||||
ArrayAdapter<String> deviceAdapter = new ArrayAdapter<>(
|
||||
context, android.R.layout.simple_spinner_dropdown_item, mDevices);
|
||||
binding.dropdownDevice.setAdapter(deviceAdapter);
|
||||
|
||||
mControlAdapter = new AdvancedMappingControlAdapter(this::onControlClicked);
|
||||
mBinding.listControl.setAdapter(mControlAdapter);
|
||||
mBinding.listControl.setLayoutManager(new LinearLayoutManager(context));
|
||||
|
||||
MaterialDividerItemDecoration divider =
|
||||
new MaterialDividerItemDecoration(context, LinearLayoutManager.VERTICAL);
|
||||
divider.setLastItemDecorated(false);
|
||||
mBinding.listControl.addItemDecoration(divider);
|
||||
|
||||
binding.editExpression.setText(controlReference.getExpression());
|
||||
|
||||
selectDefaultDevice();
|
||||
}
|
||||
|
||||
public String getExpression()
|
||||
{
|
||||
return mBinding.editExpression.getText().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id)
|
||||
{
|
||||
setSelectedDevice(mDevices[position]);
|
||||
}
|
||||
|
||||
private void setSelectedDevice(String deviceString)
|
||||
{
|
||||
mSelectedDevice = deviceString;
|
||||
|
||||
CoreDevice device = ControllerInterface.getDevice(deviceString);
|
||||
if (device == null)
|
||||
setControls(new CoreDevice.Control[0]);
|
||||
else if (mControlReference.isInput())
|
||||
setControls(device.getInputs());
|
||||
else
|
||||
setControls(device.getOutputs());
|
||||
}
|
||||
|
||||
private void setControls(CoreDevice.Control[] controls)
|
||||
{
|
||||
mControlAdapter.setControls(
|
||||
Arrays.stream(controls)
|
||||
.map(CoreDevice.Control::getName)
|
||||
.toArray(String[]::new));
|
||||
}
|
||||
|
||||
private void onControlClicked(String control)
|
||||
{
|
||||
String expression = MappingCommon.getExpressionForControl(control, mSelectedDevice,
|
||||
mController.getDefaultDevice());
|
||||
|
||||
int start = Math.max(mBinding.editExpression.getSelectionStart(), 0);
|
||||
int end = Math.max(mBinding.editExpression.getSelectionEnd(), 0);
|
||||
mBinding.editExpression.getText().replace(
|
||||
Math.min(start, end), Math.max(start, end), expression, 0, expression.length());
|
||||
}
|
||||
|
||||
private void selectDefaultDevice()
|
||||
{
|
||||
String defaultDevice = mController.getDefaultDevice();
|
||||
boolean isInput = mControlReference.isInput();
|
||||
|
||||
if (Arrays.asList(mDevices).contains(defaultDevice) &&
|
||||
(isInput || deviceHasOutputs(defaultDevice)))
|
||||
{
|
||||
// The default device is available, and it's an appropriate choice. Pick it
|
||||
setSelectedDevice(defaultDevice);
|
||||
mBinding.dropdownDevice.setText(defaultDevice, false);
|
||||
return;
|
||||
}
|
||||
else if (!isInput)
|
||||
{
|
||||
// Find the first device that has an output. (Most built-in devices don't have any)
|
||||
Optional<String> deviceWithOutputs = Arrays.stream(mDevices)
|
||||
.filter(AdvancedMappingDialog::deviceHasOutputs)
|
||||
.findFirst();
|
||||
|
||||
if (deviceWithOutputs.isPresent())
|
||||
{
|
||||
setSelectedDevice(deviceWithOutputs.get());
|
||||
mBinding.dropdownDevice.setText(deviceWithOutputs.get(), false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing found
|
||||
setSelectedDevice("");
|
||||
}
|
||||
|
||||
private static boolean deviceHasOutputs(String deviceString)
|
||||
{
|
||||
CoreDevice device = ControllerInterface.getDevice(deviceString);
|
||||
return device != null && device.getOutputs().length > 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.AdapterView.OnItemClickListener
|
||||
import android.widget.ArrayAdapter
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.divider.MaterialDividerItemDecoration
|
||||
import org.dolphinemu.dolphinemu.databinding.DialogAdvancedMappingBinding
|
||||
import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface
|
||||
import org.dolphinemu.dolphinemu.features.input.model.CoreDevice
|
||||
import org.dolphinemu.dolphinemu.features.input.model.MappingCommon
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlReference
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController
|
||||
|
||||
class AdvancedMappingDialog(
|
||||
context: Context,
|
||||
private val binding: DialogAdvancedMappingBinding,
|
||||
private val controlReference: ControlReference,
|
||||
private val controller: EmulatedController
|
||||
) : AlertDialog(context), OnItemClickListener {
|
||||
private val devices: Array<String> = ControllerInterface.getAllDeviceStrings()
|
||||
private val controlAdapter: AdvancedMappingControlAdapter
|
||||
private lateinit var selectedDevice: String
|
||||
|
||||
init {
|
||||
// TODO: Remove workaround for text filtering issue in material components when fixed
|
||||
// https://github.com/material-components/material-components-android/issues/1464
|
||||
binding.dropdownDevice.isSaveEnabled = false
|
||||
|
||||
binding.dropdownDevice.onItemClickListener = this
|
||||
|
||||
val deviceAdapter =
|
||||
ArrayAdapter(context, android.R.layout.simple_spinner_dropdown_item, devices)
|
||||
binding.dropdownDevice.setAdapter(deviceAdapter)
|
||||
|
||||
controlAdapter =
|
||||
AdvancedMappingControlAdapter { control: String -> onControlClicked(control) }
|
||||
binding.listControl.adapter = controlAdapter
|
||||
binding.listControl.layoutManager = LinearLayoutManager(context)
|
||||
|
||||
val divider = MaterialDividerItemDecoration(context, LinearLayoutManager.VERTICAL)
|
||||
divider.isLastItemDecorated = false
|
||||
binding.listControl.addItemDecoration(divider)
|
||||
|
||||
binding.editExpression.setText(controlReference.getExpression())
|
||||
|
||||
selectDefaultDevice()
|
||||
}
|
||||
|
||||
val expression: String
|
||||
get() = binding.editExpression.text.toString()
|
||||
|
||||
override fun onItemClick(adapterView: AdapterView<*>?, view: View, position: Int, id: Long) =
|
||||
setSelectedDevice(devices[position])
|
||||
|
||||
private fun setSelectedDevice(deviceString: String) {
|
||||
selectedDevice = deviceString
|
||||
|
||||
val device = ControllerInterface.getDevice(deviceString)
|
||||
if (device == null)
|
||||
setControls(emptyArray())
|
||||
else if (controlReference.isInput())
|
||||
setControls(device.getInputs())
|
||||
else
|
||||
setControls(device.getOutputs())
|
||||
}
|
||||
|
||||
private fun setControls(controls: Array<CoreDevice.Control>) =
|
||||
controlAdapter.setControls(controls.map { it.getName() }.toTypedArray())
|
||||
|
||||
private fun onControlClicked(control: String) {
|
||||
val expression =
|
||||
MappingCommon.getExpressionForControl(control, selectedDevice, controller.getDefaultDevice())
|
||||
|
||||
val start = binding.editExpression.selectionStart.coerceAtLeast(0)
|
||||
val end = binding.editExpression.selectionEnd.coerceAtLeast(0)
|
||||
binding.editExpression.text?.replace(
|
||||
start.coerceAtMost(end),
|
||||
start.coerceAtLeast(end),
|
||||
expression,
|
||||
0,
|
||||
expression.length
|
||||
)
|
||||
}
|
||||
|
||||
private fun selectDefaultDevice() {
|
||||
val defaultDevice = controller.getDefaultDevice()
|
||||
val isInput = controlReference.isInput()
|
||||
|
||||
if (listOf(*devices).contains(defaultDevice) &&
|
||||
(isInput || deviceHasOutputs(defaultDevice))
|
||||
) {
|
||||
// The default device is available, and it's an appropriate choice. Pick it
|
||||
setSelectedDevice(defaultDevice)
|
||||
binding.dropdownDevice.setText(defaultDevice, false)
|
||||
return
|
||||
} else if (!isInput) {
|
||||
// Find the first device that has an output. (Most built-in devices don't have any)
|
||||
val deviceWithOutputs = devices.first { deviceHasOutputs(it) }
|
||||
if (deviceWithOutputs.isNotEmpty()) {
|
||||
setSelectedDevice(deviceWithOutputs)
|
||||
binding.dropdownDevice.setText(deviceWithOutputs, false)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing found
|
||||
setSelectedDevice("")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun deviceHasOutputs(deviceString: String): Boolean {
|
||||
val device = ControllerInterface.getDevice(deviceString)
|
||||
return device != null && device.getOutputs().isNotEmpty()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
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.view.InputMappingControlSetting;
|
||||
|
||||
/**
|
||||
* {@link AlertDialog} derivative that listens for
|
||||
* motion events from controllers and joysticks.
|
||||
*/
|
||||
public final class MotionAlertDialog extends AlertDialog
|
||||
{
|
||||
private final Activity mActivity;
|
||||
private final InputMappingControlSetting mSetting;
|
||||
private final boolean mAllDevices;
|
||||
private boolean mRunning = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param activity The current {@link Activity}.
|
||||
* @param setting The setting to show this dialog for.
|
||||
* @param allDevices Whether to detect inputs from devices other than the configured one.
|
||||
*/
|
||||
public MotionAlertDialog(Activity activity, InputMappingControlSetting setting,
|
||||
boolean allDevices)
|
||||
{
|
||||
super(activity);
|
||||
|
||||
mActivity = activity;
|
||||
mSetting = setting;
|
||||
mAllDevices = allDevices;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart()
|
||||
{
|
||||
super.onStart();
|
||||
|
||||
mRunning = true;
|
||||
new Thread(() ->
|
||||
{
|
||||
String result = MappingCommon.detectInput(mSetting.getController(), mAllDevices);
|
||||
mActivity.runOnUiThread(() ->
|
||||
{
|
||||
if (mRunning)
|
||||
{
|
||||
mSetting.setValue(result);
|
||||
dismiss();
|
||||
}
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop()
|
||||
{
|
||||
super.onStop();
|
||||
mRunning = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event)
|
||||
{
|
||||
ControllerInterface.dispatchKeyEvent(event);
|
||||
|
||||
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.isLongPress())
|
||||
{
|
||||
// Special case: Let the user cancel by long-pressing Back (intended for non-touch devices)
|
||||
mSetting.clearValue();
|
||||
dismiss();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchGenericMotionEvent(@NonNull MotionEvent event)
|
||||
{
|
||||
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0)
|
||||
{
|
||||
// Special case: Let the user cancel by touching an on-screen button
|
||||
return super.dispatchGenericMotionEvent(event);
|
||||
}
|
||||
|
||||
ControllerInterface.dispatchGenericMotionEvent(event);
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui
|
||||
|
||||
import android.app.Activity
|
||||
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.view.InputMappingControlSetting
|
||||
|
||||
/**
|
||||
* [AlertDialog] derivative that listens for
|
||||
* motion events from controllers and joysticks.
|
||||
*
|
||||
* @param activity The current [Activity].
|
||||
* @param setting The setting to show this dialog for.
|
||||
* @param allDevices Whether to detect inputs from devices other than the configured one.
|
||||
*/
|
||||
class MotionAlertDialog(
|
||||
private val activity: Activity,
|
||||
private val setting: InputMappingControlSetting,
|
||||
private val allDevices: Boolean
|
||||
) : AlertDialog(activity) {
|
||||
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()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
running = false
|
||||
}
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
ControllerInterface.dispatchKeyEvent(event)
|
||||
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()
|
||||
dismiss()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
|
||||
if (event.source and InputDevice.SOURCE_CLASS_POINTER != 0) {
|
||||
// Special case: Let the user cancel by touching an on-screen button
|
||||
return super.dispatchGenericMotionEvent(event)
|
||||
}
|
||||
|
||||
ControllerInterface.dispatchGenericMotionEvent(event)
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemProfileBinding;
|
||||
|
||||
public final class ProfileAdapter extends RecyclerView.Adapter<ProfileViewHolder>
|
||||
{
|
||||
private final Context mContext;
|
||||
private final ProfileDialogPresenter mPresenter;
|
||||
|
||||
private final String[] mStockProfileNames;
|
||||
private final String[] mUserProfileNames;
|
||||
|
||||
public ProfileAdapter(Context context, ProfileDialogPresenter presenter)
|
||||
{
|
||||
mContext = context;
|
||||
mPresenter = presenter;
|
||||
|
||||
mStockProfileNames = presenter.getProfileNames(true);
|
||||
mUserProfileNames = presenter.getProfileNames(false);
|
||||
}
|
||||
|
||||
@NonNull @Override
|
||||
public ProfileViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
|
||||
{
|
||||
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
||||
|
||||
ListItemProfileBinding binding = ListItemProfileBinding.inflate(inflater, parent, false);
|
||||
return new ProfileViewHolder(mPresenter, binding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ProfileViewHolder holder, int position)
|
||||
{
|
||||
if (position < mStockProfileNames.length)
|
||||
{
|
||||
holder.bind(mStockProfileNames[position], true);
|
||||
return;
|
||||
}
|
||||
|
||||
position -= mStockProfileNames.length;
|
||||
|
||||
if (position < mUserProfileNames.length)
|
||||
{
|
||||
holder.bind(mUserProfileNames[position], false);
|
||||
return;
|
||||
}
|
||||
|
||||
holder.bindAsEmpty(mContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount()
|
||||
{
|
||||
return mStockProfileNames.length + mUserProfileNames.length + 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemProfileBinding
|
||||
|
||||
class ProfileAdapter(
|
||||
private val context: Context,
|
||||
private val presenter: ProfileDialogPresenter
|
||||
) : RecyclerView.Adapter<ProfileViewHolder>() {
|
||||
private val stockProfileNames: Array<String> = presenter.getProfileNames(true)
|
||||
private val userProfileNames: Array<String> = presenter.getProfileNames(false)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileViewHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
val binding = ListItemProfileBinding.inflate(inflater, parent, false)
|
||||
return ProfileViewHolder(presenter, binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ProfileViewHolder, position: Int) {
|
||||
var profilePosition = position
|
||||
if (profilePosition < stockProfileNames.size) {
|
||||
holder.bind(stockProfileNames[profilePosition], true)
|
||||
return
|
||||
}
|
||||
|
||||
profilePosition -= stockProfileNames.size
|
||||
|
||||
if (profilePosition < userProfileNames.size) {
|
||||
holder.bind(userProfileNames[profilePosition], false)
|
||||
return
|
||||
}
|
||||
|
||||
holder.bindAsEmpty(context)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = stockProfileNames.size + userProfileNames.size + 1
|
||||
}
|
|
@ -16,13 +16,13 @@ import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag
|
|||
import org.dolphinemu.dolphinemu.utils.SerializableHelper.serializable
|
||||
|
||||
class ProfileDialog : BottomSheetDialogFragment() {
|
||||
private var presenter: ProfileDialogPresenter? = null
|
||||
private lateinit var presenter: ProfileDialogPresenter
|
||||
|
||||
private var _binding: DialogInputProfilesBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
val menuTag = requireArguments().serializable<MenuTag>(KEY_MENU_TAG)
|
||||
val menuTag = requireArguments().serializable<MenuTag>(KEY_MENU_TAG)!!
|
||||
|
||||
presenter = ProfileDialogPresenter(this, menuTag)
|
||||
|
||||
|
@ -39,7 +39,7 @@ class ProfileDialog : BottomSheetDialogFragment() {
|
|||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding.profileList.adapter = ProfileAdapter(context, presenter)
|
||||
binding.profileList.adapter = ProfileAdapter(requireContext(), presenter)
|
||||
binding.profileList.layoutManager = LinearLayoutManager(context)
|
||||
val divider = MaterialDividerItemDecoration(requireActivity(), LinearLayoutManager.VERTICAL)
|
||||
divider.isLastItemDecorated = false
|
||||
|
|
|
@ -1,157 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.databinding.DialogInputStringBinding;
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag;
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivityView;
|
||||
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.Collator;
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class ProfileDialogPresenter
|
||||
{
|
||||
private static final String EXTENSION = ".ini";
|
||||
|
||||
private final Context mContext;
|
||||
private final DialogFragment mDialog;
|
||||
private final MenuTag mMenuTag;
|
||||
|
||||
public ProfileDialogPresenter(MenuTag menuTag)
|
||||
{
|
||||
mContext = null;
|
||||
mDialog = null;
|
||||
mMenuTag = menuTag;
|
||||
}
|
||||
|
||||
public ProfileDialogPresenter(DialogFragment dialog, MenuTag menuTag)
|
||||
{
|
||||
mContext = dialog.getContext();
|
||||
mDialog = dialog;
|
||||
mMenuTag = menuTag;
|
||||
}
|
||||
|
||||
public String[] getProfileNames(boolean stock)
|
||||
{
|
||||
File[] profiles = new File(getProfileDirectoryPath(stock)).listFiles(
|
||||
file -> !file.isDirectory() && file.getName().endsWith(EXTENSION));
|
||||
|
||||
if (profiles == null)
|
||||
return new String[0];
|
||||
|
||||
return Arrays.stream(profiles)
|
||||
.map(file -> file.getName().substring(0, file.getName().length() - EXTENSION.length()))
|
||||
.sorted(Collator.getInstance())
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
public void loadProfile(@NonNull String profileName, boolean stock)
|
||||
{
|
||||
new MaterialAlertDialogBuilder(mContext)
|
||||
.setMessage(mContext.getString(R.string.input_profile_confirm_load, profileName))
|
||||
.setPositiveButton(R.string.yes, (dialogInterface, i) ->
|
||||
{
|
||||
mMenuTag.getCorrespondingEmulatedController()
|
||||
.loadProfile(getProfilePath(profileName, stock));
|
||||
((SettingsActivityView) mDialog.requireActivity()).onControllerSettingsChanged();
|
||||
mDialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
public void saveProfile(@NonNull String profileName)
|
||||
{
|
||||
// If the user is saving over an existing profile, we should show an overwrite warning.
|
||||
// If the user is creating a new profile, we normally shouldn't show a warning,
|
||||
// but if they've entered the name of an existing profile, we should shown an overwrite warning.
|
||||
|
||||
String profilePath = getProfilePath(profileName, false);
|
||||
if (!new File(profilePath).exists())
|
||||
{
|
||||
mMenuTag.getCorrespondingEmulatedController().saveProfile(profilePath);
|
||||
mDialog.dismiss();
|
||||
}
|
||||
else
|
||||
{
|
||||
new MaterialAlertDialogBuilder(mContext)
|
||||
.setMessage(mContext.getString(R.string.input_profile_confirm_save, profileName))
|
||||
.setPositiveButton(R.string.yes, (dialogInterface, i) ->
|
||||
{
|
||||
mMenuTag.getCorrespondingEmulatedController().saveProfile(profilePath);
|
||||
mDialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
public void saveProfileAndPromptForName()
|
||||
{
|
||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
||||
|
||||
DialogInputStringBinding binding = DialogInputStringBinding.inflate(inflater);
|
||||
TextInputEditText input = binding.input;
|
||||
|
||||
new MaterialAlertDialogBuilder(mContext)
|
||||
.setView(binding.getRoot())
|
||||
.setPositiveButton(R.string.ok, (dialogInterface, i) ->
|
||||
saveProfile(input.getText().toString()))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
public void deleteProfile(@NonNull String profileName)
|
||||
{
|
||||
new MaterialAlertDialogBuilder(mContext)
|
||||
.setMessage(mContext.getString(R.string.input_profile_confirm_delete, profileName))
|
||||
.setPositiveButton(R.string.yes, (dialogInterface, i) ->
|
||||
{
|
||||
new File(getProfilePath(profileName, false)).delete();
|
||||
mDialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private String getProfileDirectoryName()
|
||||
{
|
||||
if (mMenuTag.isGCPadMenu())
|
||||
return "GCPad";
|
||||
else if (mMenuTag.isWiimoteMenu())
|
||||
return "Wiimote";
|
||||
else
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private String getProfileDirectoryPath(boolean stock)
|
||||
{
|
||||
if (stock)
|
||||
{
|
||||
return DirectoryInitialization.getSysDirectory() + "/Profiles/" + getProfileDirectoryName() +
|
||||
'/';
|
||||
}
|
||||
else
|
||||
{
|
||||
return DirectoryInitialization.getUserDirectory() + "/Config/Profiles/" +
|
||||
getProfileDirectoryName() + '/';
|
||||
}
|
||||
}
|
||||
|
||||
private String getProfilePath(String profileName, boolean stock)
|
||||
{
|
||||
return getProfileDirectoryPath(stock) + profileName + EXTENSION;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.view.LayoutInflater
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.dolphinemu.dolphinemu.R
|
||||
import org.dolphinemu.dolphinemu.databinding.DialogInputStringBinding
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivityView
|
||||
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
|
||||
class ProfileDialogPresenter {
|
||||
private val context: Context?
|
||||
private val dialog: DialogFragment?
|
||||
private val menuTag: MenuTag
|
||||
|
||||
constructor(menuTag: MenuTag) {
|
||||
context = null
|
||||
dialog = null
|
||||
this.menuTag = menuTag
|
||||
}
|
||||
|
||||
constructor(dialog: DialogFragment, menuTag: MenuTag) {
|
||||
context = dialog.context
|
||||
this.dialog = dialog
|
||||
this.menuTag = menuTag
|
||||
}
|
||||
|
||||
fun getProfileNames(stock: Boolean): Array<String> {
|
||||
val profiles = File(getProfileDirectoryPath(stock)).listFiles { file: File ->
|
||||
!file.isDirectory && file.name.endsWith(EXTENSION)
|
||||
} ?: return emptyArray()
|
||||
|
||||
return profiles.map { it.name.substring(0, it.name.length - EXTENSION.length) }
|
||||
.sortedBy { it.lowercase(Locale.getDefault()) }
|
||||
.toTypedArray()
|
||||
}
|
||||
|
||||
fun loadProfile(profileName: String, stock: Boolean) {
|
||||
MaterialAlertDialogBuilder(context!!)
|
||||
.setMessage(context.getString(R.string.input_profile_confirm_load, profileName))
|
||||
.setPositiveButton(R.string.yes) { _: DialogInterface?, _: Int ->
|
||||
menuTag.correspondingEmulatedController
|
||||
.loadProfile(getProfilePath(profileName, stock))
|
||||
(dialog!!.requireActivity() as SettingsActivityView).onControllerSettingsChanged()
|
||||
dialog.dismiss()
|
||||
}
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
fun saveProfile(profileName: String) {
|
||||
// If the user is saving over an existing profile, we should show an overwrite warning.
|
||||
// If the user is creating a new profile, we normally shouldn't show a warning,
|
||||
// but if they've entered the name of an existing profile, we should shown an overwrite warning.
|
||||
val profilePath = getProfilePath(profileName, false)
|
||||
if (!File(profilePath).exists()) {
|
||||
menuTag.correspondingEmulatedController.saveProfile(profilePath)
|
||||
dialog!!.dismiss()
|
||||
} else {
|
||||
MaterialAlertDialogBuilder(context!!)
|
||||
.setMessage(context.getString(R.string.input_profile_confirm_save, profileName))
|
||||
.setPositiveButton(R.string.yes) { _: DialogInterface?, _: Int ->
|
||||
menuTag.correspondingEmulatedController.saveProfile(profilePath)
|
||||
dialog!!.dismiss()
|
||||
}
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
fun saveProfileAndPromptForName() {
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val binding = DialogInputStringBinding.inflate(inflater)
|
||||
val input = binding.input
|
||||
|
||||
MaterialAlertDialogBuilder(context!!)
|
||||
.setView(binding.root)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
saveProfile(input.text.toString())
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
fun deleteProfile(profileName: String) {
|
||||
MaterialAlertDialogBuilder(context!!)
|
||||
.setMessage(context.getString(R.string.input_profile_confirm_delete, profileName))
|
||||
.setPositiveButton(R.string.yes) { _: DialogInterface?, _: Int ->
|
||||
File(getProfilePath(profileName, false)).delete()
|
||||
dialog!!.dismiss()
|
||||
}
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private val profileDirectoryName: String
|
||||
get() = if (menuTag.isGCPadMenu) "GCPad" else if (menuTag.isWiimoteMenu) "Wiimote" else throw UnsupportedOperationException()
|
||||
|
||||
private fun getProfileDirectoryPath(stock: Boolean): String =
|
||||
if (stock) {
|
||||
"${DirectoryInitialization.getSysDirectory()}/Profiles/$profileDirectoryName/"
|
||||
} else {
|
||||
"${DirectoryInitialization.getUserDirectory()}/Config/Profiles/$profileDirectoryName/"
|
||||
}
|
||||
|
||||
private fun getProfilePath(profileName: String, stock: Boolean): String =
|
||||
getProfileDirectoryPath(stock) + profileName + EXTENSION
|
||||
|
||||
companion object {
|
||||
private const val EXTENSION = ".ini"
|
||||
}
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemProfileBinding;
|
||||
|
||||
public class ProfileViewHolder extends RecyclerView.ViewHolder
|
||||
{
|
||||
private final ProfileDialogPresenter mPresenter;
|
||||
private final ListItemProfileBinding mBinding;
|
||||
|
||||
private String mProfileName;
|
||||
private boolean mStock;
|
||||
|
||||
public ProfileViewHolder(@NonNull ProfileDialogPresenter presenter,
|
||||
@NonNull ListItemProfileBinding binding)
|
||||
{
|
||||
super(binding.getRoot());
|
||||
|
||||
mPresenter = presenter;
|
||||
mBinding = binding;
|
||||
|
||||
binding.buttonLoad.setOnClickListener(view -> loadProfile());
|
||||
binding.buttonSave.setOnClickListener(view -> saveProfile());
|
||||
binding.buttonDelete.setOnClickListener(view -> deleteProfile());
|
||||
}
|
||||
|
||||
public void bind(String profileName, boolean stock)
|
||||
{
|
||||
mProfileName = profileName;
|
||||
mStock = stock;
|
||||
|
||||
mBinding.textName.setText(profileName);
|
||||
|
||||
mBinding.buttonLoad.setVisibility(View.VISIBLE);
|
||||
mBinding.buttonSave.setVisibility(stock ? View.GONE : View.VISIBLE);
|
||||
mBinding.buttonDelete.setVisibility(stock ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
public void bindAsEmpty(Context context)
|
||||
{
|
||||
mProfileName = null;
|
||||
mStock = false;
|
||||
|
||||
mBinding.textName.setText(context.getText(R.string.input_profile_new));
|
||||
|
||||
mBinding.buttonLoad.setVisibility(View.GONE);
|
||||
mBinding.buttonSave.setVisibility(View.VISIBLE);
|
||||
mBinding.buttonDelete.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void loadProfile()
|
||||
{
|
||||
mPresenter.loadProfile(mProfileName, mStock);
|
||||
}
|
||||
|
||||
private void saveProfile()
|
||||
{
|
||||
if (mProfileName == null)
|
||||
mPresenter.saveProfileAndPromptForName();
|
||||
else
|
||||
mPresenter.saveProfile(mProfileName);
|
||||
}
|
||||
|
||||
private void deleteProfile()
|
||||
{
|
||||
mPresenter.deleteProfile(mProfileName);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.dolphinemu.dolphinemu.R
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemProfileBinding
|
||||
|
||||
class ProfileViewHolder(
|
||||
private val presenter: ProfileDialogPresenter,
|
||||
private val binding: ListItemProfileBinding
|
||||
) : RecyclerView.ViewHolder(binding.getRoot()) {
|
||||
private var profileName: String? = null
|
||||
private var stock = false
|
||||
|
||||
init {
|
||||
binding.buttonLoad.setOnClickListener { loadProfile() }
|
||||
binding.buttonSave.setOnClickListener { saveProfile() }
|
||||
binding.buttonDelete.setOnClickListener { deleteProfile() }
|
||||
}
|
||||
|
||||
fun bind(profileName: String, stock: Boolean) {
|
||||
this.profileName = profileName
|
||||
this.stock = stock
|
||||
|
||||
binding.textName.text = profileName
|
||||
|
||||
binding.buttonLoad.visibility = View.VISIBLE
|
||||
binding.buttonSave.visibility = if (stock) View.GONE else View.VISIBLE
|
||||
binding.buttonDelete.visibility = if (stock) View.GONE else View.VISIBLE
|
||||
}
|
||||
|
||||
fun bindAsEmpty(context: Context) {
|
||||
profileName = null
|
||||
stock = false
|
||||
|
||||
binding.textName.text = context.getText(R.string.input_profile_new)
|
||||
|
||||
binding.buttonLoad.visibility = View.GONE
|
||||
binding.buttonSave.visibility = View.VISIBLE
|
||||
binding.buttonDelete.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun loadProfile() = presenter.loadProfile(profileName!!, stock)
|
||||
|
||||
private fun saveProfile() {
|
||||
if (profileName == null)
|
||||
presenter.saveProfileAndPromptForName()
|
||||
else
|
||||
presenter.saveProfile(profileName!!)
|
||||
}
|
||||
|
||||
private fun deleteProfile() = presenter.deleteProfile(profileName!!)
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui.viewholder;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemMappingBinding;
|
||||
import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem;
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter;
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SettingViewHolder;
|
||||
|
||||
public final class InputMappingControlSettingViewHolder extends SettingViewHolder
|
||||
{
|
||||
private InputMappingControlSetting mItem;
|
||||
|
||||
private final ListItemMappingBinding mBinding;
|
||||
|
||||
public InputMappingControlSettingViewHolder(@NonNull ListItemMappingBinding binding,
|
||||
SettingsAdapter adapter)
|
||||
{
|
||||
super(binding.getRoot(), adapter);
|
||||
mBinding = binding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(SettingsItem item)
|
||||
{
|
||||
mItem = (InputMappingControlSetting) item;
|
||||
|
||||
mBinding.textSettingName.setText(mItem.getName());
|
||||
mBinding.textSettingDescription.setText(mItem.getValue());
|
||||
mBinding.buttonAdvancedSettings.setOnClickListener(this::onLongClick);
|
||||
|
||||
setStyle(mBinding.textSettingName, mItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View clicked)
|
||||
{
|
||||
if (!mItem.isEditable())
|
||||
{
|
||||
showNotRuntimeEditableError();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mItem.isInput())
|
||||
getAdapter().onInputMappingClick(mItem, getBindingAdapterPosition());
|
||||
else
|
||||
getAdapter().onAdvancedInputMappingClick(mItem, getBindingAdapterPosition());
|
||||
|
||||
setStyle(mBinding.textSettingName, mItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View clicked)
|
||||
{
|
||||
if (!mItem.isEditable())
|
||||
{
|
||||
showNotRuntimeEditableError();
|
||||
return true;
|
||||
}
|
||||
|
||||
getAdapter().onAdvancedInputMappingClick(mItem, getBindingAdapterPosition());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable @Override
|
||||
protected SettingsItem getItem()
|
||||
{
|
||||
return mItem;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui.viewholder
|
||||
|
||||
import android.view.View
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemMappingBinding
|
||||
import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SettingViewHolder
|
||||
|
||||
class InputMappingControlSettingViewHolder(
|
||||
private val binding: ListItemMappingBinding,
|
||||
adapter: SettingsAdapter
|
||||
) : SettingViewHolder(binding.getRoot(), adapter) {
|
||||
lateinit var setting: InputMappingControlSetting
|
||||
|
||||
override val item: SettingsItem
|
||||
get() = setting
|
||||
|
||||
override fun bind(item: SettingsItem) {
|
||||
setting = item as InputMappingControlSetting
|
||||
|
||||
binding.textSettingName.text = setting.name
|
||||
binding.textSettingDescription.text = setting.value
|
||||
binding.buttonAdvancedSettings.setOnClickListener { clicked: View -> onLongClick(clicked) }
|
||||
|
||||
setStyle(binding.textSettingName, setting)
|
||||
}
|
||||
|
||||
override fun onClick(clicked: View) {
|
||||
if (!setting.isEditable) {
|
||||
showNotRuntimeEditableError()
|
||||
return
|
||||
}
|
||||
|
||||
if (setting.isInput)
|
||||
adapter.onInputMappingClick(setting, bindingAdapterPosition)
|
||||
else
|
||||
adapter.onAdvancedInputMappingClick(setting, bindingAdapterPosition)
|
||||
|
||||
setStyle(binding.textSettingName, setting)
|
||||
}
|
||||
|
||||
override fun onLongClick(clicked: View): Boolean {
|
||||
if (!setting.isEditable) {
|
||||
showNotRuntimeEditableError()
|
||||
return true
|
||||
}
|
||||
|
||||
adapter.onAdvancedInputMappingClick(setting, bindingAdapterPosition)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -4,14 +4,14 @@ package org.dolphinemu.dolphinemu.features.settings.model
|
|||
|
||||
object PostProcessing {
|
||||
@JvmStatic
|
||||
val shaderList: Array<String?>
|
||||
val shaderList: Array<String>
|
||||
external get
|
||||
|
||||
@JvmStatic
|
||||
val anaglyphShaderList: Array<String?>
|
||||
val anaglyphShaderList: Array<String>
|
||||
external get
|
||||
|
||||
@JvmStatic
|
||||
val passiveShaderList: Array<String?>
|
||||
val passiveShaderList: Array<String>
|
||||
external get
|
||||
}
|
||||
|
|
|
@ -17,9 +17,9 @@ open class StringSingleChoiceSetting : SettingsItem {
|
|||
override val setting: AbstractSetting?
|
||||
get() = stringSetting
|
||||
|
||||
var choices: Array<String?>?
|
||||
var choices: Array<String>?
|
||||
protected set
|
||||
var values: Array<String?>?
|
||||
var values: Array<String>?
|
||||
protected set
|
||||
val menuTag: MenuTag?
|
||||
var noChoicesAvailableString = 0
|
||||
|
@ -37,8 +37,8 @@ open class StringSingleChoiceSetting : SettingsItem {
|
|||
setting: AbstractStringSetting?,
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
choices: Array<String?>?,
|
||||
values: Array<String?>?,
|
||||
choices: Array<String>?,
|
||||
values: Array<String>?,
|
||||
menuTag: MenuTag? = null
|
||||
) : super(context, titleId, descriptionId) {
|
||||
stringSetting = setting
|
||||
|
@ -52,8 +52,8 @@ open class StringSingleChoiceSetting : SettingsItem {
|
|||
setting: AbstractStringSetting,
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
choices: Array<String?>,
|
||||
values: Array<String?>,
|
||||
choices: Array<String>,
|
||||
values: Array<String>,
|
||||
noChoicesAvailableString: Int
|
||||
) : this(context, setting, titleId, descriptionId, choices, values) {
|
||||
this.noChoicesAvailableString = noChoicesAvailableString
|
||||
|
@ -102,8 +102,8 @@ open class StringSingleChoiceSetting : SettingsItem {
|
|||
return -1
|
||||
}
|
||||
|
||||
open fun setSelectedValue(settings: Settings?, selection: String?) {
|
||||
stringSetting!!.setString(settings!!, selection!!)
|
||||
open fun setSelectedValue(settings: Settings, selection: String) {
|
||||
stringSetting!!.setString(settings, selection)
|
||||
}
|
||||
|
||||
open fun refreshChoicesAndValues() {}
|
||||
|
|
|
@ -258,7 +258,7 @@ class SettingsAdapter(
|
|||
}
|
||||
|
||||
fun onInputMappingClick(item: InputMappingControlSetting, position: Int) {
|
||||
if (item.controller.defaultDevice.isEmpty() && !fragmentView.isMappingAllDevices) {
|
||||
if (item.controller.getDefaultDevice().isEmpty() && !fragmentView.isMappingAllDevices) {
|
||||
MaterialAlertDialogBuilder(fragmentView.fragmentActivity)
|
||||
.setMessage(R.string.input_binding_no_device)
|
||||
.setPositiveButton(R.string.ok, this)
|
||||
|
@ -474,7 +474,7 @@ class SettingsAdapter(
|
|||
val value = scSetting.getValueAt(which)
|
||||
if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
|
||||
|
||||
scSetting.setSelectedValue(settings, value)
|
||||
scSetting.setSelectedValue(settings!!, value!!)
|
||||
|
||||
closeDialog()
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ class SettingsFragmentPresenter(
|
|||
private val fragmentView: SettingsFragmentView,
|
||||
private val context: Context
|
||||
) {
|
||||
private var menuTag: MenuTag? = null
|
||||
private lateinit var menuTag: MenuTag
|
||||
private var gameId: String? = null
|
||||
|
||||
private var settingsList: ArrayList<SettingsItem>? = null
|
||||
|
@ -78,7 +78,7 @@ class SettingsFragmentPresenter(
|
|||
}
|
||||
}
|
||||
|
||||
fun onViewCreated(menuTag: MenuTag?, settings: Settings?) {
|
||||
fun onViewCreated(menuTag: MenuTag, settings: Settings?) {
|
||||
this.menuTag = menuTag
|
||||
|
||||
if (!TextUtils.isEmpty(gameId)) {
|
||||
|
@ -1339,13 +1339,8 @@ class SettingsFragmentPresenter(
|
|||
val shaderList =
|
||||
if (stereoModeValue == anaglyphMode) PostProcessing.anaglyphShaderList else PostProcessing.shaderList
|
||||
|
||||
val shaderListEntries = arrayOfNulls<String>(shaderList.size + 1)
|
||||
shaderListEntries[0] = context.getString(R.string.off)
|
||||
System.arraycopy(shaderList, 0, shaderListEntries, 1, shaderList.size)
|
||||
|
||||
val shaderListValues = arrayOfNulls<String>(shaderList.size + 1)
|
||||
shaderListValues[0] = ""
|
||||
System.arraycopy(shaderList, 0, shaderListValues, 1, shaderList.size)
|
||||
val shaderListEntries = arrayOf(context.getString(R.string.off), *shaderList)
|
||||
val shaderListValues = arrayOf("", *shaderList)
|
||||
|
||||
sl.add(
|
||||
StringSingleChoiceSetting(
|
||||
|
@ -2153,7 +2148,7 @@ class SettingsFragmentPresenter(
|
|||
profileString: String,
|
||||
controllerNumber: Int
|
||||
) {
|
||||
val profiles = ProfileDialogPresenter(menuTag!!).getProfileNames(false)
|
||||
val profiles = ProfileDialogPresenter(menuTag).getProfileNames(false)
|
||||
val profileKey = profileString + "Profile" + (controllerNumber + 1)
|
||||
sl.add(
|
||||
StringSingleChoiceSetting(
|
||||
|
@ -2232,7 +2227,7 @@ class SettingsFragmentPresenter(
|
|||
0,
|
||||
0,
|
||||
true
|
||||
) { fragmentView.showDialogFragment(ProfileDialog.create(menuTag!!)) })
|
||||
) { fragmentView.showDialogFragment(ProfileDialog.create(menuTag)) })
|
||||
|
||||
updateOldControllerSettingsWarningVisibility(controller)
|
||||
}
|
||||
|
@ -2251,15 +2246,15 @@ class SettingsFragmentPresenter(
|
|||
) {
|
||||
updateOldControllerSettingsWarningVisibility(controller)
|
||||
|
||||
val groupCount = controller.groupCount
|
||||
val groupCount = controller.getGroupCount()
|
||||
for (i in 0 until groupCount) {
|
||||
val group = controller.getGroup(i)
|
||||
val groupType = group.groupType
|
||||
val groupType = group.getGroupType()
|
||||
if (groupTypeFilter != null && !groupTypeFilter.contains(groupType)) continue
|
||||
|
||||
sl.add(HeaderSetting(group.uiName, ""))
|
||||
sl.add(HeaderSetting(group.getUiName(), ""))
|
||||
|
||||
if (group.defaultEnabledValue != ControlGroup.DEFAULT_ENABLED_ALWAYS) {
|
||||
if (group.getDefaultEnabledValue() != ControlGroup.DEFAULT_ENABLED_ALWAYS) {
|
||||
sl.add(
|
||||
SwitchSetting(
|
||||
context,
|
||||
|
@ -2270,13 +2265,13 @@ class SettingsFragmentPresenter(
|
|||
)
|
||||
}
|
||||
|
||||
val controlCount = group.controlCount
|
||||
val controlCount = group.getControlCount()
|
||||
for (j in 0 until controlCount) {
|
||||
sl.add(InputMappingControlSetting(group.getControl(j), controller))
|
||||
}
|
||||
|
||||
if (groupType == ControlGroup.TYPE_ATTACHMENTS) {
|
||||
val attachmentSetting = group.attachmentSetting
|
||||
val attachmentSetting = group.getAttachmentSetting()
|
||||
sl.add(
|
||||
SingleChoiceSetting(
|
||||
context, InputMappingIntSetting(attachmentSetting),
|
||||
|
@ -2287,7 +2282,7 @@ class SettingsFragmentPresenter(
|
|||
)
|
||||
}
|
||||
|
||||
val numericSettingCount = group.numericSettingCount
|
||||
val numericSettingCount = group.getNumericSettingCount()
|
||||
for (j in 0 until numericSettingCount) {
|
||||
addNumericSetting(sl, group.getNumericSetting(j))
|
||||
}
|
||||
|
@ -2295,34 +2290,34 @@ class SettingsFragmentPresenter(
|
|||
}
|
||||
|
||||
private fun addNumericSetting(sl: ArrayList<SettingsItem>, setting: NumericSetting) {
|
||||
when (setting.type) {
|
||||
when (setting.getType()) {
|
||||
NumericSetting.TYPE_DOUBLE -> sl.add(
|
||||
FloatSliderSetting(
|
||||
InputMappingDoubleSetting(setting),
|
||||
setting.uiName,
|
||||
setting.getUiName(),
|
||||
"",
|
||||
ceil(setting.doubleMin).toInt(),
|
||||
floor(setting.doubleMax).toInt(),
|
||||
setting.uiSuffix
|
||||
ceil(setting.getDoubleMin()).toInt(),
|
||||
floor(setting.getDoubleMax()).toInt(),
|
||||
setting.getUiSuffix()
|
||||
)
|
||||
)
|
||||
|
||||
NumericSetting.TYPE_BOOLEAN -> sl.add(
|
||||
SwitchSetting(
|
||||
InputMappingBooleanSetting(setting),
|
||||
setting.uiName,
|
||||
setting.uiDescription
|
||||
setting.getUiName(),
|
||||
setting.getUiDescription()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateOldControllerSettingsWarningVisibility() {
|
||||
updateOldControllerSettingsWarningVisibility(menuTag!!.correspondingEmulatedController)
|
||||
updateOldControllerSettingsWarningVisibility(menuTag.correspondingEmulatedController)
|
||||
}
|
||||
|
||||
private fun updateOldControllerSettingsWarningVisibility(controller: EmulatedController) {
|
||||
val defaultDevice = controller.defaultDevice
|
||||
val defaultDevice = controller.getDefaultDevice()
|
||||
|
||||
hasOldControllerSettings = defaultDevice.startsWith("Android/") &&
|
||||
defaultDevice.endsWith("/Touchscreen")
|
||||
|
|
|
@ -703,21 +703,21 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
|||
const jclass control_class =
|
||||
env->FindClass("org/dolphinemu/dolphinemu/features/input/model/controlleremu/Control");
|
||||
s_control_class = reinterpret_cast<jclass>(env->NewGlobalRef(control_class));
|
||||
s_control_pointer = env->GetFieldID(control_class, "mPointer", "J");
|
||||
s_control_pointer = env->GetFieldID(control_class, "pointer", "J");
|
||||
s_control_constructor = env->GetMethodID(control_class, "<init>", "(J)V");
|
||||
env->DeleteLocalRef(control_class);
|
||||
|
||||
const jclass control_group_class =
|
||||
env->FindClass("org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlGroup");
|
||||
s_control_group_class = reinterpret_cast<jclass>(env->NewGlobalRef(control_group_class));
|
||||
s_control_group_pointer = env->GetFieldID(control_group_class, "mPointer", "J");
|
||||
s_control_group_pointer = env->GetFieldID(control_group_class, "pointer", "J");
|
||||
s_control_group_constructor = env->GetMethodID(control_group_class, "<init>", "(J)V");
|
||||
env->DeleteLocalRef(control_group_class);
|
||||
|
||||
const jclass control_reference_class = env->FindClass(
|
||||
"org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlReference");
|
||||
s_control_reference_class = reinterpret_cast<jclass>(env->NewGlobalRef(control_reference_class));
|
||||
s_control_reference_pointer = env->GetFieldID(control_reference_class, "mPointer", "J");
|
||||
s_control_reference_pointer = env->GetFieldID(control_reference_class, "pointer", "J");
|
||||
s_control_reference_constructor = env->GetMethodID(control_reference_class, "<init>", "(J)V");
|
||||
env->DeleteLocalRef(control_reference_class);
|
||||
|
||||
|
@ -725,21 +725,21 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
|||
"org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController");
|
||||
s_emulated_controller_class =
|
||||
reinterpret_cast<jclass>(env->NewGlobalRef(emulated_controller_class));
|
||||
s_emulated_controller_pointer = env->GetFieldID(emulated_controller_class, "mPointer", "J");
|
||||
s_emulated_controller_pointer = env->GetFieldID(emulated_controller_class, "pointer", "J");
|
||||
s_emulated_controller_constructor = env->GetMethodID(emulated_controller_class, "<init>", "(J)V");
|
||||
env->DeleteLocalRef(emulated_controller_class);
|
||||
|
||||
const jclass numeric_setting_class =
|
||||
env->FindClass("org/dolphinemu/dolphinemu/features/input/model/controlleremu/NumericSetting");
|
||||
s_numeric_setting_class = reinterpret_cast<jclass>(env->NewGlobalRef(numeric_setting_class));
|
||||
s_numeric_setting_pointer = env->GetFieldID(numeric_setting_class, "mPointer", "J");
|
||||
s_numeric_setting_pointer = env->GetFieldID(numeric_setting_class, "pointer", "J");
|
||||
s_numeric_setting_constructor = env->GetMethodID(numeric_setting_class, "<init>", "(J)V");
|
||||
env->DeleteLocalRef(numeric_setting_class);
|
||||
|
||||
const jclass core_device_class =
|
||||
env->FindClass("org/dolphinemu/dolphinemu/features/input/model/CoreDevice");
|
||||
s_core_device_class = reinterpret_cast<jclass>(env->NewGlobalRef(core_device_class));
|
||||
s_core_device_pointer = env->GetFieldID(core_device_class, "mPointer", "J");
|
||||
s_core_device_pointer = env->GetFieldID(core_device_class, "pointer", "J");
|
||||
s_core_device_constructor = env->GetMethodID(core_device_class, "<init>", "(J)V");
|
||||
env->DeleteLocalRef(core_device_class);
|
||||
|
||||
|
@ -747,7 +747,7 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
|||
env->FindClass("org/dolphinemu/dolphinemu/features/input/model/CoreDevice$Control");
|
||||
s_core_device_control_class =
|
||||
reinterpret_cast<jclass>(env->NewGlobalRef(core_device_control_class));
|
||||
s_core_device_control_pointer = env->GetFieldID(core_device_control_class, "mPointer", "J");
|
||||
s_core_device_control_pointer = env->GetFieldID(core_device_control_class, "pointer", "J");
|
||||
s_core_device_control_constructor =
|
||||
env->GetMethodID(core_device_control_class, "<init>",
|
||||
"(Lorg/dolphinemu/dolphinemu/features/input/model/CoreDevice;J)V");
|
||||
|
|
Loading…
Reference in New Issue