Android: Add new input mapping implementation

This commit is contained in:
JosJuice
2022-01-23 21:47:57 +01:00
parent dd8976f18d
commit 2c529b9db1
31 changed files with 847 additions and 487 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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