mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-22 22:00:39 -06:00
Android: Add new input mapping implementation
This commit is contained in:
@ -1,169 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.dialogs;
|
||||
|
||||
import android.content.Context;
|
||||
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.settings.model.view.InputBindingSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter;
|
||||
import org.dolphinemu.dolphinemu.utils.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link AlertDialog} derivative that listens for
|
||||
* motion events from controllers and joysticks.
|
||||
*/
|
||||
public final class MotionAlertDialog extends AlertDialog
|
||||
{
|
||||
// The selected input preference
|
||||
private final InputBindingSetting setting;
|
||||
private final ArrayList<Float> mPreviousValues = new ArrayList<>();
|
||||
private int mPrevDeviceId = 0;
|
||||
private boolean mWaitingForEvent = true;
|
||||
private SettingsAdapter mAdapter;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param context The current {@link Context}.
|
||||
* @param setting The Preference to show this dialog for.
|
||||
*/
|
||||
public MotionAlertDialog(Context context, InputBindingSetting setting, SettingsAdapter adapter)
|
||||
{
|
||||
super(context);
|
||||
|
||||
this.setting = setting;
|
||||
mAdapter = adapter;
|
||||
}
|
||||
|
||||
public boolean onKeyEvent(int keyCode, KeyEvent event)
|
||||
{
|
||||
Log.debug("[MotionAlertDialog] Received key event: " + event.getAction());
|
||||
if (event.getAction() == KeyEvent.ACTION_UP)
|
||||
{
|
||||
if (true)
|
||||
{
|
||||
setting.onKeyInput(mAdapter.getSettings(), event);
|
||||
dismiss();
|
||||
}
|
||||
// Even if we ignore the key, we still consume it. Thus return true regardless.
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event)
|
||||
{
|
||||
// Intended for devices with no touchscreen or mouse
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK)
|
||||
{
|
||||
setting.clearValue(mAdapter.getSettings());
|
||||
dismiss();
|
||||
return true;
|
||||
}
|
||||
return super.onKeyLongPress(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event)
|
||||
{
|
||||
// Handle this key if we care about it, otherwise pass it down the framework
|
||||
return onKeyEvent(event.getKeyCode(), event) || super.dispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchGenericMotionEvent(@NonNull MotionEvent event)
|
||||
{
|
||||
// Handle this event if we care about it, otherwise pass it down the framework
|
||||
return onMotionEvent(event) || super.dispatchGenericMotionEvent(event);
|
||||
}
|
||||
|
||||
private boolean onMotionEvent(MotionEvent event)
|
||||
{
|
||||
if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) == 0)
|
||||
return false;
|
||||
if (event.getAction() != MotionEvent.ACTION_MOVE)
|
||||
return false;
|
||||
|
||||
InputDevice input = event.getDevice();
|
||||
|
||||
List<InputDevice.MotionRange> motionRanges = input.getMotionRanges();
|
||||
|
||||
if (input.getId() != mPrevDeviceId)
|
||||
{
|
||||
mPreviousValues.clear();
|
||||
}
|
||||
mPrevDeviceId = input.getId();
|
||||
boolean firstEvent = mPreviousValues.isEmpty();
|
||||
|
||||
int numMovedAxis = 0;
|
||||
float axisMoveValue = 0.0f;
|
||||
InputDevice.MotionRange lastMovedRange = null;
|
||||
char lastMovedDir = '?';
|
||||
if (mWaitingForEvent)
|
||||
{
|
||||
for (int i = 0; i < motionRanges.size(); i++)
|
||||
{
|
||||
InputDevice.MotionRange range = motionRanges.get(i);
|
||||
int axis = range.getAxis();
|
||||
float value = event.getAxisValue(axis);
|
||||
if (firstEvent)
|
||||
{
|
||||
mPreviousValues.add(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
float previousValue = mPreviousValues.get(i);
|
||||
|
||||
// Only handle the axes that are not neutral (more than 0.5)
|
||||
// but ignore any axis that has a constant value (e.g. always 1)
|
||||
if (Math.abs(value) > 0.5f && value != previousValue)
|
||||
{
|
||||
// It is common to have multiple axes with the same physical input. For example,
|
||||
// shoulder butters are provided as both AXIS_LTRIGGER and AXIS_BRAKE.
|
||||
// To handle this, we ignore an axis motion that's the exact same as a motion
|
||||
// we already saw. This way, we ignore axes with two names, but catch the case
|
||||
// where a joystick is moved in two directions.
|
||||
// ref: bottom of https://developer.android.com/training/game-controllers/controller-input.html
|
||||
if (value != axisMoveValue)
|
||||
{
|
||||
axisMoveValue = value;
|
||||
numMovedAxis++;
|
||||
lastMovedRange = range;
|
||||
lastMovedDir = value < 0.0f ? '-' : '+';
|
||||
}
|
||||
}
|
||||
// Special case for d-pads (axis value jumps between 0 and 1 without any values
|
||||
// in between). Without this, the user would need to press the d-pad twice
|
||||
// due to the first press being caught by the "if (firstEvent)" case further up.
|
||||
else if (Math.abs(value) < 0.25f && Math.abs(previousValue) > 0.75f)
|
||||
{
|
||||
numMovedAxis++;
|
||||
lastMovedRange = range;
|
||||
lastMovedDir = previousValue < 0.0f ? '-' : '+';
|
||||
}
|
||||
}
|
||||
|
||||
mPreviousValues.set(i, value);
|
||||
}
|
||||
|
||||
// If only one axis moved, that's the winner.
|
||||
if (numMovedAxis == 1)
|
||||
{
|
||||
mWaitingForEvent = false;
|
||||
setting.onMotionInput(mAdapter.getSettings(), input, lastMovedRange, lastMovedDir);
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model;
|
||||
|
||||
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.
|
||||
*
|
||||
* @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();
|
||||
|
||||
public static native void save();
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.model.controlleremu;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
/**
|
||||
* Represents a C++ ControllerEmu::Control.
|
||||
*
|
||||
* 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
|
||||
private Control(long pointer)
|
||||
{
|
||||
mPointer = pointer;
|
||||
}
|
||||
|
||||
public native String getUiName();
|
||||
|
||||
public native ControlReference getControlReference();
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
// 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
|
||||
{
|
||||
@Keep
|
||||
private final long mPointer;
|
||||
|
||||
@Keep
|
||||
private ControlGroup(long pointer)
|
||||
{
|
||||
mPointer = pointer;
|
||||
}
|
||||
|
||||
public native String getUiName();
|
||||
|
||||
public native int getControlCount();
|
||||
|
||||
public native Control getControl(int i);
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
// 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);
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
// 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 int getGroupCount();
|
||||
|
||||
public native ControlGroup getGroup(int index);
|
||||
|
||||
public native void updateSingleControlReference(ControlReference controlReference);
|
||||
|
||||
public static native EmulatedController getGcPad(int controllerIndex);
|
||||
|
||||
public static native EmulatedController getWiimote(int controllerIndex);
|
||||
|
||||
public static native EmulatedController getWiimoteAttachment(int controllerIndex,
|
||||
int attachmentIndex);
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
// 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 boolean mRunning = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param activity The current {@link Activity}.
|
||||
* @param setting The setting to show this dialog for.
|
||||
*/
|
||||
public MotionAlertDialog(Activity activity, InputMappingControlSetting setting)
|
||||
{
|
||||
super(activity);
|
||||
|
||||
mActivity = activity;
|
||||
mSetting = setting;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart()
|
||||
{
|
||||
super.onStart();
|
||||
|
||||
mRunning = true;
|
||||
new Thread(() ->
|
||||
{
|
||||
String result = MappingCommon.detectInput();
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,46 +1,38 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.settings.ui.viewholder;
|
||||
package org.dolphinemu.dolphinemu.features.input.ui.viewholder;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemSettingBinding;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.view.InputBindingSetting;
|
||||
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 InputBindingSettingViewHolder extends SettingViewHolder
|
||||
public final class InputMappingControlSettingViewHolder extends SettingViewHolder
|
||||
{
|
||||
private InputBindingSetting mItem;
|
||||
|
||||
private final Context mContext;
|
||||
private InputMappingControlSetting mItem;
|
||||
|
||||
private final ListItemSettingBinding mBinding;
|
||||
|
||||
public InputBindingSettingViewHolder(@NonNull ListItemSettingBinding binding,
|
||||
SettingsAdapter adapter, Context context)
|
||||
public InputMappingControlSettingViewHolder(@NonNull ListItemSettingBinding binding,
|
||||
SettingsAdapter adapter)
|
||||
{
|
||||
super(binding.getRoot(), adapter);
|
||||
mBinding = binding;
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(SettingsItem item)
|
||||
{
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
|
||||
|
||||
mItem = (InputBindingSetting) item;
|
||||
mItem = (InputMappingControlSetting) item;
|
||||
|
||||
mBinding.textSettingName.setText(mItem.getName());
|
||||
mBinding.textSettingDescription
|
||||
.setText(sharedPreferences.getString(mItem.getKey() + mItem.getGameId(), ""));
|
||||
mBinding.textSettingDescription.setText(mItem.getValue());
|
||||
|
||||
setStyle(mBinding.textSettingName, mItem);
|
||||
}
|
||||
@ -54,7 +46,7 @@ public final class InputBindingSettingViewHolder extends SettingViewHolder
|
||||
return;
|
||||
}
|
||||
|
||||
getAdapter().onInputBindingClick(mItem, getBindingAdapterPosition());
|
||||
getAdapter().onInputMappingClick(mItem, getBindingAdapterPosition());
|
||||
|
||||
setStyle(mBinding.textSettingName, mItem);
|
||||
}
|
@ -8,6 +8,7 @@ import android.widget.Toast;
|
||||
|
||||
import org.dolphinemu.dolphinemu.NativeLibrary;
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.features.input.model.MappingCommon;
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivityView;
|
||||
import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile;
|
||||
import org.dolphinemu.dolphinemu.services.GameFileCacheManager;
|
||||
@ -170,6 +171,8 @@ public class Settings implements Closeable
|
||||
SettingsFile.saveFile(entry.getKey(), entry.getValue(), view);
|
||||
}
|
||||
|
||||
MappingCommon.save();
|
||||
|
||||
NativeConfig.save(NativeConfig.LAYER_BASE);
|
||||
|
||||
if (!NativeLibrary.IsRunning())
|
||||
|
@ -13,6 +13,11 @@ public class HeaderSetting extends SettingsItem
|
||||
super(context, titleId, descriptionId);
|
||||
}
|
||||
|
||||
public HeaderSetting(CharSequence title, CharSequence description)
|
||||
{
|
||||
super(title, description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType()
|
||||
{
|
||||
|
@ -1,108 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.settings.model.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.dolphinemu.dolphinemu.DolphinApplication;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.AbstractSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
|
||||
|
||||
public class InputBindingSetting extends SettingsItem
|
||||
{
|
||||
private String mFile;
|
||||
private String mSection;
|
||||
private String mKey;
|
||||
|
||||
private String mGameId;
|
||||
|
||||
public InputBindingSetting(Context context, String file, String section, String key, int titleId,
|
||||
String gameId)
|
||||
{
|
||||
super(context, titleId, 0);
|
||||
mFile = file;
|
||||
mSection = section;
|
||||
mKey = key;
|
||||
mGameId = gameId;
|
||||
}
|
||||
|
||||
public String getKey()
|
||||
{
|
||||
return mKey;
|
||||
}
|
||||
|
||||
public String getValue(Settings settings)
|
||||
{
|
||||
return settings.getSection(mFile, mSection).getString(mKey, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the provided key input setting both to the INI file (so native code can use it) and as
|
||||
* an Android preference (so it persists correctly and is human-readable.)
|
||||
*
|
||||
* @param keyEvent KeyEvent of this key press.
|
||||
*/
|
||||
public void onKeyInput(Settings settings, KeyEvent keyEvent)
|
||||
{
|
||||
InputDevice device = keyEvent.getDevice();
|
||||
String bindStr = "Device '" + device.getDescriptor() + "'-Button " + keyEvent.getKeyCode();
|
||||
String uiString = device.getName() + ": Button " + keyEvent.getKeyCode();
|
||||
setValue(settings, bindStr, uiString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the provided motion input setting both to the INI file (so native code can use it) and as
|
||||
* an Android preference (so it persists correctly and is human-readable.)
|
||||
*
|
||||
* @param device InputDevice from which the input event originated.
|
||||
* @param motionRange MotionRange of the movement
|
||||
* @param axisDir Either '-' or '+'
|
||||
*/
|
||||
public void onMotionInput(Settings settings, InputDevice device,
|
||||
InputDevice.MotionRange motionRange, char axisDir)
|
||||
{
|
||||
String bindStr =
|
||||
"Device '" + device.getDescriptor() + "'-Axis " + motionRange.getAxis() + axisDir;
|
||||
String uiString = device.getName() + ": Axis " + motionRange.getAxis() + axisDir;
|
||||
setValue(settings, bindStr, uiString);
|
||||
}
|
||||
|
||||
public void setValue(Settings settings, String bind, String ui)
|
||||
{
|
||||
SharedPreferences
|
||||
preferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(DolphinApplication.getAppContext());
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
editor.putString(mKey + mGameId, ui);
|
||||
editor.apply();
|
||||
|
||||
settings.getSection(mFile, mSection).setString(mKey, bind);
|
||||
}
|
||||
|
||||
public void clearValue(Settings settings)
|
||||
{
|
||||
setValue(settings, "", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType()
|
||||
{
|
||||
return TYPE_INPUT_BINDING;
|
||||
}
|
||||
|
||||
public String getGameId()
|
||||
{
|
||||
return mGameId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractSetting getSetting()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.settings.model.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Vibrator;
|
||||
import android.view.InputDevice;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import org.dolphinemu.dolphinemu.DolphinApplication;
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.AbstractSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
|
||||
import org.dolphinemu.dolphinemu.utils.Rumble;
|
||||
|
||||
public class RumbleBindingSetting extends InputBindingSetting
|
||||
{
|
||||
public RumbleBindingSetting(Context context, String file, String section, String key, int titleId,
|
||||
String gameId)
|
||||
{
|
||||
super(context, file, section, key, titleId, gameId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Just need the device when saving rumble.
|
||||
*/
|
||||
@Override
|
||||
public void onKeyInput(Settings settings, KeyEvent keyEvent)
|
||||
{
|
||||
saveRumble(settings, keyEvent.getDevice());
|
||||
}
|
||||
|
||||
/**
|
||||
* Just need the device when saving rumble.
|
||||
*/
|
||||
@Override
|
||||
public void onMotionInput(Settings settings, InputDevice device,
|
||||
InputDevice.MotionRange motionRange, char axisDir)
|
||||
{
|
||||
saveRumble(settings, device);
|
||||
}
|
||||
|
||||
private void saveRumble(Settings settings, InputDevice device)
|
||||
{
|
||||
Vibrator vibrator = device.getVibrator();
|
||||
if (vibrator != null && vibrator.hasVibrator())
|
||||
{
|
||||
setValue(settings, device.getDescriptor(), device.getName());
|
||||
Rumble.doRumble(vibrator);
|
||||
}
|
||||
else
|
||||
{
|
||||
setValue(settings, "",
|
||||
DolphinApplication.getAppContext().getString(R.string.rumble_not_found));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getType()
|
||||
{
|
||||
return TYPE_RUMBLE_BINDING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractSetting getSetting()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
@ -21,9 +21,8 @@ public abstract class SettingsItem
|
||||
public static final int TYPE_SINGLE_CHOICE = 2;
|
||||
public static final int TYPE_SLIDER = 3;
|
||||
public static final int TYPE_SUBMENU = 4;
|
||||
public static final int TYPE_INPUT_BINDING = 5;
|
||||
public static final int TYPE_INPUT_MAPPING_CONTROL = 5;
|
||||
public static final int TYPE_STRING_SINGLE_CHOICE = 6;
|
||||
public static final int TYPE_RUMBLE_BINDING = 7;
|
||||
public static final int TYPE_SINGLE_CHOICE_DYNAMIC_DESCRIPTIONS = 8;
|
||||
public static final int TYPE_FILE_PICKER = 9;
|
||||
public static final int TYPE_RUN_RUNNABLE = 10;
|
||||
|
@ -31,14 +31,14 @@ public enum MenuTag
|
||||
GCPAD_2("gcpad", 1),
|
||||
GCPAD_3("gcpad", 2),
|
||||
GCPAD_4("gcpad", 3),
|
||||
WIIMOTE_1("wiimote", 4),
|
||||
WIIMOTE_2("wiimote", 5),
|
||||
WIIMOTE_3("wiimote", 6),
|
||||
WIIMOTE_4("wiimote", 7),
|
||||
WIIMOTE_EXTENSION_1("wiimote_extension", 4),
|
||||
WIIMOTE_EXTENSION_2("wiimote_extension", 5),
|
||||
WIIMOTE_EXTENSION_3("wiimote_extension", 6),
|
||||
WIIMOTE_EXTENSION_4("wiimote_extension", 7);
|
||||
WIIMOTE_1("wiimote", 0),
|
||||
WIIMOTE_2("wiimote", 1),
|
||||
WIIMOTE_3("wiimote", 2),
|
||||
WIIMOTE_4("wiimote", 3),
|
||||
WIIMOTE_EXTENSION_1("wiimote_extension", 0),
|
||||
WIIMOTE_EXTENSION_2("wiimote_extension", 1),
|
||||
WIIMOTE_EXTENSION_3("wiimote_extension", 2),
|
||||
WIIMOTE_EXTENSION_4("wiimote_extension", 3);
|
||||
|
||||
private String tag;
|
||||
private int subType = -1;
|
||||
|
@ -37,15 +37,15 @@ import org.dolphinemu.dolphinemu.databinding.ListItemHeaderBinding;
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemSettingBinding;
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemSettingSwitchBinding;
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemSubmenuBinding;
|
||||
import org.dolphinemu.dolphinemu.dialogs.MotionAlertDialog;
|
||||
import org.dolphinemu.dolphinemu.features.input.ui.MotionAlertDialog;
|
||||
import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting;
|
||||
import org.dolphinemu.dolphinemu.features.input.ui.viewholder.InputMappingControlSettingViewHolder;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.view.DateTimeChoiceSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.view.SwitchSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.view.FilePicker;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.view.FloatSliderSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.view.InputBindingSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.view.IntSliderSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.view.RumbleBindingSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.view.SingleChoiceSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.view.SingleChoiceSettingDynamicDescriptions;
|
||||
@ -57,9 +57,7 @@ import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.DateTimeSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.FilePickerViewHolder;
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.HeaderHyperLinkViewHolder;
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.HeaderViewHolder;
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.InputBindingSettingViewHolder;
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.InputStringSettingViewHolder;
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.RumbleBindingViewHolder;
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.RunRunnableViewHolder;
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SettingViewHolder;
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SingleChoiceViewHolder;
|
||||
@ -124,13 +122,9 @@ public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolde
|
||||
case SettingsItem.TYPE_SUBMENU:
|
||||
return new SubmenuViewHolder(ListItemSubmenuBinding.inflate(inflater), this);
|
||||
|
||||
case SettingsItem.TYPE_INPUT_BINDING:
|
||||
return new InputBindingSettingViewHolder(ListItemSettingBinding.inflate(inflater), this,
|
||||
mContext);
|
||||
|
||||
case SettingsItem.TYPE_RUMBLE_BINDING:
|
||||
return new RumbleBindingViewHolder(ListItemSettingBinding.inflate(inflater), this,
|
||||
mContext);
|
||||
case SettingsItem.TYPE_INPUT_MAPPING_CONTROL:
|
||||
return new InputMappingControlSettingViewHolder(ListItemSettingBinding.inflate(inflater),
|
||||
this);
|
||||
|
||||
case SettingsItem.TYPE_FILE_PICKER:
|
||||
return new FilePickerViewHolder(ListItemSettingBinding.inflate(inflater), this);
|
||||
@ -314,9 +308,9 @@ public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolde
|
||||
mView.loadSubMenu(item.getMenuKey());
|
||||
}
|
||||
|
||||
public void onInputBindingClick(final InputBindingSetting item, final int position)
|
||||
public void onInputMappingClick(final InputMappingControlSetting item, final int position)
|
||||
{
|
||||
final MotionAlertDialog dialog = new MotionAlertDialog(mContext, item, this);
|
||||
final MotionAlertDialog dialog = new MotionAlertDialog(mView.getActivity(), item);
|
||||
|
||||
Drawable background = ContextCompat.getDrawable(mContext, R.drawable.dialog_round);
|
||||
@ColorInt int color = new ElevationOverlayProvider(dialog.getContext()).compositeOverlay(
|
||||
@ -326,13 +320,11 @@ public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolde
|
||||
dialog.getWindow().setBackgroundDrawable(background);
|
||||
|
||||
dialog.setTitle(R.string.input_binding);
|
||||
dialog.setMessage(String.format(mContext.getString(
|
||||
item instanceof RumbleBindingSetting ?
|
||||
R.string.input_rumble_description : R.string.input_binding_description),
|
||||
dialog.setMessage(String.format(mContext.getString(R.string.input_binding_description),
|
||||
item.getName()));
|
||||
dialog.setButton(AlertDialog.BUTTON_NEGATIVE, mContext.getString(R.string.cancel), this);
|
||||
dialog.setButton(AlertDialog.BUTTON_NEUTRAL, mContext.getString(R.string.clear),
|
||||
(dialogInterface, i) -> item.clearValue(getSettings()));
|
||||
(dialogInterface, i) -> item.clearValue());
|
||||
dialog.setOnDismissListener(dialog1 ->
|
||||
{
|
||||
notifyItemChanged(position);
|
||||
|
@ -67,14 +67,14 @@ public final class SettingsFragment extends Fragment implements SettingsFragment
|
||||
titles.put(MenuTag.GCPAD_2, R.string.controller_1);
|
||||
titles.put(MenuTag.GCPAD_3, R.string.controller_2);
|
||||
titles.put(MenuTag.GCPAD_4, R.string.controller_3);
|
||||
titles.put(MenuTag.WIIMOTE_1, R.string.wiimote_4);
|
||||
titles.put(MenuTag.WIIMOTE_2, R.string.wiimote_5);
|
||||
titles.put(MenuTag.WIIMOTE_3, R.string.wiimote_6);
|
||||
titles.put(MenuTag.WIIMOTE_4, R.string.wiimote_7);
|
||||
titles.put(MenuTag.WIIMOTE_EXTENSION_1, R.string.wiimote_extension_4);
|
||||
titles.put(MenuTag.WIIMOTE_EXTENSION_2, R.string.wiimote_extension_5);
|
||||
titles.put(MenuTag.WIIMOTE_EXTENSION_3, R.string.wiimote_extension_6);
|
||||
titles.put(MenuTag.WIIMOTE_EXTENSION_4, R.string.wiimote_extension_7);
|
||||
titles.put(MenuTag.WIIMOTE_1, R.string.wiimote_0);
|
||||
titles.put(MenuTag.WIIMOTE_2, R.string.wiimote_1);
|
||||
titles.put(MenuTag.WIIMOTE_3, R.string.wiimote_2);
|
||||
titles.put(MenuTag.WIIMOTE_4, R.string.wiimote_3);
|
||||
titles.put(MenuTag.WIIMOTE_EXTENSION_1, R.string.wiimote_extension_0);
|
||||
titles.put(MenuTag.WIIMOTE_EXTENSION_2, R.string.wiimote_extension_1);
|
||||
titles.put(MenuTag.WIIMOTE_EXTENSION_3, R.string.wiimote_extension_2);
|
||||
titles.put(MenuTag.WIIMOTE_EXTENSION_4, R.string.wiimote_extension_3);
|
||||
}
|
||||
|
||||
private FragmentSettingsBinding mBinding;
|
||||
|
@ -14,6 +14,9 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||
import org.dolphinemu.dolphinemu.NativeLibrary;
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.activities.UserDataActivity;
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlGroup;
|
||||
import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController;
|
||||
import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.AbstractIntSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.AdHocBooleanSetting;
|
||||
@ -795,14 +798,14 @@ public final class SettingsFragmentPresenter
|
||||
|
||||
private void addWiimoteSettings(ArrayList<SettingsItem> sl)
|
||||
{
|
||||
sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_1_SOURCE, R.string.wiimote_4, 0,
|
||||
R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(4)));
|
||||
sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_2_SOURCE, R.string.wiimote_5, 0,
|
||||
R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(5)));
|
||||
sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_3_SOURCE, R.string.wiimote_6, 0,
|
||||
R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(6)));
|
||||
sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_4_SOURCE, R.string.wiimote_7, 0,
|
||||
R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(7)));
|
||||
sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_1_SOURCE, R.string.wiimote_0, 0,
|
||||
R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(0)));
|
||||
sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_2_SOURCE, R.string.wiimote_1, 0,
|
||||
R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(1)));
|
||||
sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_3_SOURCE, R.string.wiimote_2, 0,
|
||||
R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(2)));
|
||||
sl.add(new SingleChoiceSetting(mContext, IntSetting.WIIMOTE_4_SOURCE, R.string.wiimote_3, 0,
|
||||
R.array.wiimoteTypeEntries, R.array.wiimoteTypeValues, MenuTag.getWiimoteMenuTag(3)));
|
||||
}
|
||||
|
||||
private void addGraphicsSettings(ArrayList<SettingsItem> sl)
|
||||
@ -1073,7 +1076,7 @@ public final class SettingsFragmentPresenter
|
||||
{
|
||||
if (gcPadType == 6) // Emulated
|
||||
{
|
||||
// TODO
|
||||
addControllerSettings(sl, EmulatedController.getGcPad(gcPadNumber));
|
||||
}
|
||||
else if (gcPadType == 12) // Adapter
|
||||
{
|
||||
@ -1086,13 +1089,30 @@ public final class SettingsFragmentPresenter
|
||||
|
||||
private void addWiimoteSubSettings(ArrayList<SettingsItem> sl, int wiimoteNumber)
|
||||
{
|
||||
// TODO
|
||||
addControllerSettings(sl, EmulatedController.getWiimote(wiimoteNumber));
|
||||
}
|
||||
|
||||
private void addExtensionTypeSettings(ArrayList<SettingsItem> sl, int wiimoteNumber,
|
||||
int extentionType)
|
||||
int extensionType)
|
||||
{
|
||||
// TODO
|
||||
addControllerSettings(sl,
|
||||
EmulatedController.getWiimoteAttachment(wiimoteNumber, extensionType));
|
||||
}
|
||||
|
||||
private void addControllerSettings(ArrayList<SettingsItem> sl, EmulatedController controller)
|
||||
{
|
||||
int groupCount = controller.getGroupCount();
|
||||
for (int i = 0; i < groupCount; i++)
|
||||
{
|
||||
ControlGroup group = controller.getGroup(i);
|
||||
sl.add(new HeaderSetting(group.getUiName(), ""));
|
||||
|
||||
int controlCount = group.getControlCount();
|
||||
for (int j = 0; j < controlCount; j++)
|
||||
{
|
||||
sl.add(new InputMappingControlSetting(group.getControl(j), controller));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int getLogVerbosityEntries()
|
||||
|
@ -1,67 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.settings.ui.viewholder;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemSettingBinding;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.view.RumbleBindingSetting;
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem;
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter;
|
||||
|
||||
public class RumbleBindingViewHolder extends SettingViewHolder
|
||||
{
|
||||
private RumbleBindingSetting mItem;
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private final ListItemSettingBinding mBinding;
|
||||
|
||||
public RumbleBindingViewHolder(@NonNull ListItemSettingBinding binding, SettingsAdapter adapter,
|
||||
Context context)
|
||||
{
|
||||
super(binding.getRoot(), adapter);
|
||||
mBinding = binding;
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(SettingsItem item)
|
||||
{
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
|
||||
|
||||
mItem = (RumbleBindingSetting) item;
|
||||
|
||||
mBinding.textSettingName.setText(item.getName());
|
||||
mBinding.textSettingDescription
|
||||
.setText(sharedPreferences.getString(mItem.getKey() + mItem.getGameId(), ""));
|
||||
|
||||
setStyle(mBinding.textSettingName, mItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View clicked)
|
||||
{
|
||||
if (!mItem.isEditable())
|
||||
{
|
||||
showNotRuntimeEditableError();
|
||||
return;
|
||||
}
|
||||
|
||||
getAdapter().onInputBindingClick(mItem, getBindingAdapterPosition());
|
||||
|
||||
setStyle(mBinding.textSettingName, mItem);
|
||||
}
|
||||
|
||||
@Nullable @Override
|
||||
protected SettingsItem getItem()
|
||||
{
|
||||
return mItem;
|
||||
}
|
||||
}
|
@ -11,15 +11,15 @@
|
||||
<string name="controller_2">GameCube Controller 3</string>
|
||||
<string name="controller_3">GameCube Controller 4</string>
|
||||
|
||||
<string name="wiimote_4">Wii Remote 1</string>
|
||||
<string name="wiimote_5">Wii Remote 2</string>
|
||||
<string name="wiimote_6">Wii Remote 3</string>
|
||||
<string name="wiimote_7">Wii Remote 4</string>
|
||||
<string name="wiimote_0">Wii Remote 1</string>
|
||||
<string name="wiimote_1">Wii Remote 2</string>
|
||||
<string name="wiimote_2">Wii Remote 3</string>
|
||||
<string name="wiimote_3">Wii Remote 4</string>
|
||||
|
||||
<string name="wiimote_extension_4">Wii Remote Extension 1</string>
|
||||
<string name="wiimote_extension_5">Wii Remote Extension 2</string>
|
||||
<string name="wiimote_extension_6">Wii Remote Extension 3</string>
|
||||
<string name="wiimote_extension_7">Wii Remote Extension 4</string>
|
||||
<string name="wiimote_extension_0">Wii Remote Extension 1</string>
|
||||
<string name="wiimote_extension_1">Wii Remote Extension 2</string>
|
||||
<string name="wiimote_extension_2">Wii Remote Extension 3</string>
|
||||
<string name="wiimote_extension_3">Wii Remote Extension 4</string>
|
||||
|
||||
<string name="wiimote_extensions">Extension</string>
|
||||
|
||||
|
Reference in New Issue
Block a user