Merge pull request #11919 from t895/kotlin-controls

Android: Convert "features.input" package to Kotlin
This commit is contained in:
JosJuice 2023-08-26 19:10:56 +02:00 committed by GitHub
commit f9959656e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 1827 additions and 2256 deletions

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -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);
}
}
}

View File

@ -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()
}
}

View File

@ -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();
}

View File

@ -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>
}

View File

@ -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;
}
}

View File

@ -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
}
}
}

View File

@ -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();
}

View File

@ -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
}

View File

@ -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;
}
}

View File

@ -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
}

View File

@ -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();
}
}

View File

@ -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
}

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -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);
}

View File

@ -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
}
}

View File

@ -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();
}

View File

@ -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()
}

View File

@ -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
}

View File

@ -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();
}

View File

@ -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
}
}

View File

@ -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();
}

View File

@ -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
}

View File

@ -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);
}

View File

@ -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
}
}

View File

@ -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();
}

View File

@ -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
}
}

View File

@ -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, "");
}
}

View File

@ -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, "")
}

View File

@ -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();
}
}

View File

@ -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()
}

View File

@ -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();
}
}

View File

@ -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()
}
}

View File

@ -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);
}
}

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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()
}
}
}

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -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;
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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"
}
}

View File

@ -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);
}
}

View File

@ -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!!)
}

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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() {}

View File

@ -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()
}

View File

@ -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")

View File

@ -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");