mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-11-14 13:27:45 -07:00
Android: Add the advanced input mapping dialog
It's missing a lot of features from the PC version for now, like buttons for inserting functions and the ability to see what the expression evaluates to. I mostly just wanted to get something in place so you can set up rumble. Co-authored-by: Charles Lombardo <clombardo169@gmail.com>
This commit is contained in:
parent
42943672bb
commit
c2779aef06
@ -97,6 +97,9 @@ public final class ControllerInterface
|
||||
|
||||
public static native String[] getAllDeviceStrings();
|
||||
|
||||
@Nullable
|
||||
public static native CoreDevice getDevice(String deviceString);
|
||||
|
||||
@Keep
|
||||
private static void registerInputDeviceListener()
|
||||
{
|
||||
|
@ -0,0 +1,48 @@
|
||||
// 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();
|
||||
}
|
@ -27,5 +27,8 @@ public final class MappingCommon
|
||||
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();
|
||||
}
|
||||
|
@ -34,4 +34,6 @@ public class ControlReference
|
||||
*/
|
||||
@Nullable
|
||||
public native String setExpression(String expr);
|
||||
|
||||
public native boolean isInput();
|
||||
}
|
||||
|
@ -52,4 +52,14 @@ public final class InputMappingControlSetting extends SettingsItem
|
||||
{
|
||||
return mController;
|
||||
}
|
||||
|
||||
public ControlReference getControlReference()
|
||||
{
|
||||
return mControlReference;
|
||||
}
|
||||
|
||||
public boolean isInput()
|
||||
{
|
||||
return mControlReference.isInput();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,55 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemAdvancedMappingControlBinding;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class AdvancedMappingControlAdapter
|
||||
extends RecyclerView.Adapter<AdvancedMappingControlViewHolder>
|
||||
{
|
||||
private final Consumer<String> mOnClickCallback;
|
||||
|
||||
private String[] mControls = new String[0];
|
||||
|
||||
public AdvancedMappingControlAdapter(Consumer<String> onClickCallback)
|
||||
{
|
||||
mOnClickCallback = onClickCallback;
|
||||
}
|
||||
|
||||
@NonNull @Override
|
||||
public AdvancedMappingControlViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
|
||||
int viewType)
|
||||
{
|
||||
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
||||
|
||||
ListItemAdvancedMappingControlBinding binding =
|
||||
ListItemAdvancedMappingControlBinding.inflate(inflater);
|
||||
return new AdvancedMappingControlViewHolder(binding, mOnClickCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull AdvancedMappingControlViewHolder holder, int position)
|
||||
{
|
||||
holder.bind(mControls[position]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount()
|
||||
{
|
||||
return mControls.length;
|
||||
}
|
||||
|
||||
public void setControls(String[] controls)
|
||||
{
|
||||
mControls = controls;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.input.ui;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemAdvancedMappingControlBinding;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class AdvancedMappingControlViewHolder extends RecyclerView.ViewHolder
|
||||
{
|
||||
private final ListItemAdvancedMappingControlBinding mBinding;
|
||||
|
||||
private String mName;
|
||||
|
||||
public AdvancedMappingControlViewHolder(@NonNull ListItemAdvancedMappingControlBinding binding,
|
||||
Consumer<String> onClickCallback)
|
||||
{
|
||||
super(binding.getRoot());
|
||||
|
||||
mBinding = binding;
|
||||
|
||||
binding.getRoot().setOnClickListener(view -> onClickCallback.accept(mName));
|
||||
}
|
||||
|
||||
public void bind(String name)
|
||||
{
|
||||
mName = name;
|
||||
|
||||
mBinding.textName.setText(name);
|
||||
}
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
// 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;
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ import android.view.View;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemSettingBinding;
|
||||
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;
|
||||
@ -17,9 +17,9 @@ public final class InputMappingControlSettingViewHolder extends SettingViewHolde
|
||||
{
|
||||
private InputMappingControlSetting mItem;
|
||||
|
||||
private final ListItemSettingBinding mBinding;
|
||||
private final ListItemMappingBinding mBinding;
|
||||
|
||||
public InputMappingControlSettingViewHolder(@NonNull ListItemSettingBinding binding,
|
||||
public InputMappingControlSettingViewHolder(@NonNull ListItemMappingBinding binding,
|
||||
SettingsAdapter adapter)
|
||||
{
|
||||
super(binding.getRoot(), adapter);
|
||||
@ -33,6 +33,7 @@ public final class InputMappingControlSettingViewHolder extends SettingViewHolde
|
||||
|
||||
mBinding.textSettingName.setText(mItem.getName());
|
||||
mBinding.textSettingDescription.setText(mItem.getValue());
|
||||
mBinding.buttonAdvancedSettings.setOnClickListener(this::onLongClick);
|
||||
|
||||
setStyle(mBinding.textSettingName, mItem);
|
||||
}
|
||||
@ -46,11 +47,28 @@ public final class InputMappingControlSettingViewHolder extends SettingViewHolde
|
||||
return;
|
||||
}
|
||||
|
||||
getAdapter().onInputMappingClick(mItem, getBindingAdapterPosition());
|
||||
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()
|
||||
{
|
||||
|
@ -31,12 +31,15 @@ import com.google.android.material.timepicker.MaterialTimePicker;
|
||||
import com.google.android.material.timepicker.TimeFormat;
|
||||
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.databinding.DialogAdvancedMappingBinding;
|
||||
import org.dolphinemu.dolphinemu.databinding.DialogInputStringBinding;
|
||||
import org.dolphinemu.dolphinemu.databinding.DialogSliderBinding;
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemHeaderBinding;
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemMappingBinding;
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemSettingBinding;
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemSettingSwitchBinding;
|
||||
import org.dolphinemu.dolphinemu.databinding.ListItemSubmenuBinding;
|
||||
import org.dolphinemu.dolphinemu.features.input.ui.AdvancedMappingDialog;
|
||||
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;
|
||||
@ -123,7 +126,7 @@ public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolde
|
||||
return new SubmenuViewHolder(ListItemSubmenuBinding.inflate(inflater), this);
|
||||
|
||||
case SettingsItem.TYPE_INPUT_MAPPING_CONTROL:
|
||||
return new InputMappingControlSettingViewHolder(ListItemSettingBinding.inflate(inflater),
|
||||
return new InputMappingControlSettingViewHolder(ListItemMappingBinding.inflate(inflater),
|
||||
this);
|
||||
|
||||
case SettingsItem.TYPE_FILE_PICKER:
|
||||
@ -359,6 +362,44 @@ public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolde
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
public void onAdvancedInputMappingClick(final InputMappingControlSetting item, final int position)
|
||||
{
|
||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
||||
|
||||
DialogAdvancedMappingBinding binding = DialogAdvancedMappingBinding.inflate(inflater);
|
||||
|
||||
final AdvancedMappingDialog dialog = new AdvancedMappingDialog(mContext, binding,
|
||||
item.getControlReference(), item.getController());
|
||||
|
||||
Drawable background = ContextCompat.getDrawable(mContext, R.drawable.dialog_round);
|
||||
@ColorInt int color = new ElevationOverlayProvider(dialog.getContext()).compositeOverlay(
|
||||
MaterialColors.getColor(dialog.getWindow().getDecorView(), R.attr.colorSurface),
|
||||
dialog.getWindow().getDecorView().getElevation());
|
||||
background.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
|
||||
dialog.getWindow().setBackgroundDrawable(background);
|
||||
|
||||
dialog.setTitle(item.isInput() ?
|
||||
R.string.input_configure_input : R.string.input_configure_output);
|
||||
dialog.setView(binding.getRoot());
|
||||
dialog.setButton(AlertDialog.BUTTON_POSITIVE, mContext.getString(R.string.ok),
|
||||
(dialogInterface, i) ->
|
||||
{
|
||||
item.setValue(dialog.getExpression());
|
||||
notifyItemChanged(position);
|
||||
mView.onSettingChanged();
|
||||
});
|
||||
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();
|
||||
notifyItemChanged(position);
|
||||
mView.onSettingChanged();
|
||||
});
|
||||
dialog.setCanceledOnTouchOutside(false);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
public void onFilePickerDirectoryClick(SettingsItem item, int position)
|
||||
{
|
||||
mClickedItem = item;
|
||||
|
9
Source/Android/app/src/main/res/drawable/ic_more.xml
Normal file
9
Source/Android/app/src/main/res/drawable/ic_more.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z" />
|
||||
</vector>
|
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout android:id="@+id/root"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:nextFocusLeft="@id/button_advanced_settings">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_setting_name"
|
||||
style="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginEnd="@dimen/spacing_large"
|
||||
android:layout_marginStart="@dimen/spacing_large"
|
||||
android:layout_marginTop="@dimen/spacing_large"
|
||||
android:textSize="16sp"
|
||||
android:textAlignment="viewStart"
|
||||
android:layout_toStartOf="@+id/button_more_settings"
|
||||
tools:text="Setting Name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_setting_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignStart="@+id/text_setting_name"
|
||||
android:layout_below="@+id/text_setting_name"
|
||||
android:layout_marginBottom="@dimen/spacing_large"
|
||||
android:layout_marginEnd="@dimen/spacing_large"
|
||||
android:layout_marginStart="@dimen/spacing_large"
|
||||
android:layout_marginTop="@dimen/spacing_small"
|
||||
android:layout_toStartOf="@+id/button_advanced_settings"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="@string/overclock_enable_description" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_advanced_settings"
|
||||
style="?attr/materialIconButtonStyle"
|
||||
android:contentDescription="@string/advanced_settings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/spacing_small"
|
||||
android:nextFocusRight="@id/root"
|
||||
app:icon="@drawable/ic_more"
|
||||
app:iconTint="?attr/colorOnPrimaryContainer" />
|
||||
|
||||
</RelativeLayout>
|
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingHorizontal="@dimen/spacing_large"
|
||||
android:paddingTop="@dimen/spacing_medlarge">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/list_control"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_above="@+id/expression"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/device" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/device"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:hint="@string/input_device">
|
||||
|
||||
<com.google.android.material.textfield.MaterialAutoCompleteTextView
|
||||
android:id="@+id/dropdown_device"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/expression"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:hint="@string/input_expression">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/edit_expression"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start"
|
||||
android:importantForAutofill="no"
|
||||
android:typeface="monospace" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</RelativeLayout>
|
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="54dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:focusable="true"
|
||||
android:clickable="true">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
tools:text="Button A"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginStart="@dimen/spacing_large"
|
||||
android:layout_marginEnd="@dimen/spacing_large"
|
||||
android:layout_marginTop="@dimen/spacing_large"
|
||||
android:id="@+id/text_name"
|
||||
android:textAlignment="viewStart"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</RelativeLayout>
|
56
Source/Android/app/src/main/res/layout/list_item_mapping.xml
Normal file
56
Source/Android/app/src/main/res/layout/list_item_mapping.xml
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout android:id="@+id/root"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:nextFocusRight="@id/button_advanced_settings">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_setting_name"
|
||||
style="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginEnd="@dimen/spacing_large"
|
||||
android:layout_marginStart="@dimen/spacing_large"
|
||||
android:layout_marginTop="@dimen/spacing_large"
|
||||
android:textSize="16sp"
|
||||
android:textAlignment="viewStart"
|
||||
android:layout_toStartOf="@+id/button_advanced_settings"
|
||||
tools:text="Setting Name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_setting_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignStart="@+id/text_setting_name"
|
||||
android:layout_below="@+id/text_setting_name"
|
||||
android:layout_marginBottom="@dimen/spacing_large"
|
||||
android:layout_marginEnd="@dimen/spacing_large"
|
||||
android:layout_marginStart="@dimen/spacing_large"
|
||||
android:layout_marginTop="@dimen/spacing_small"
|
||||
android:layout_toStartOf="@+id/button_advanced_settings"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="@string/overclock_enable_description" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_advanced_settings"
|
||||
style="?attr/materialIconButtonStyle"
|
||||
android:contentDescription="@string/advanced_settings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/spacing_small"
|
||||
android:nextFocusLeft="@id/root"
|
||||
app:icon="@drawable/ic_more"
|
||||
app:iconTint="?attr/colorOnPrimaryContainer" />
|
||||
|
||||
</RelativeLayout>
|
@ -50,6 +50,9 @@
|
||||
<string name="input_binding">Input Binding</string>
|
||||
<string name="input_binding_description">Press or move an input to bind it to %1$s.</string>
|
||||
<string name="input_binding_no_device">You need to select a device first!</string>
|
||||
<string name="input_configure_input">Configure Input</string>
|
||||
<string name="input_configure_output">Configure Output</string>
|
||||
<string name="input_expression">Expression</string>
|
||||
|
||||
<!-- Main Preference Fragment -->
|
||||
<string name="settings">Settings</string>
|
||||
@ -420,6 +423,7 @@
|
||||
<string name="other">Other</string>
|
||||
<string name="continue_anyway">Continue Anyway</string>
|
||||
<string name="more_settings">More Settings</string>
|
||||
<string name="advanced_settings">Advanced Settings</string>
|
||||
|
||||
<!-- Game Grid Screen-->
|
||||
<string name="platform_gamecube">GameCube Games</string>
|
||||
|
@ -103,6 +103,14 @@ static jclass s_emulated_controller_class;
|
||||
static jfieldID s_emulated_controller_pointer;
|
||||
static jmethodID s_emulated_controller_constructor;
|
||||
|
||||
static jclass s_core_device_class;
|
||||
static jfieldID s_core_device_pointer;
|
||||
static jmethodID s_core_device_constructor;
|
||||
|
||||
static jclass s_core_device_control_class;
|
||||
static jfieldID s_core_device_control_pointer;
|
||||
static jmethodID s_core_device_control_constructor;
|
||||
|
||||
namespace IDCache
|
||||
{
|
||||
JNIEnv* GetEnvForThread()
|
||||
@ -478,6 +486,36 @@ jmethodID GetNumericSettingConstructor()
|
||||
return s_numeric_setting_constructor;
|
||||
}
|
||||
|
||||
jclass GetCoreDeviceClass()
|
||||
{
|
||||
return s_core_device_class;
|
||||
}
|
||||
|
||||
jfieldID GetCoreDevicePointer()
|
||||
{
|
||||
return s_core_device_pointer;
|
||||
}
|
||||
|
||||
jmethodID GetCoreDeviceConstructor()
|
||||
{
|
||||
return s_core_device_constructor;
|
||||
}
|
||||
|
||||
jclass GetCoreDeviceControlClass()
|
||||
{
|
||||
return s_core_device_control_class;
|
||||
}
|
||||
|
||||
jfieldID GetCoreDeviceControlPointer()
|
||||
{
|
||||
return s_core_device_control_pointer;
|
||||
}
|
||||
|
||||
jmethodID GetCoreDeviceControlConstructor()
|
||||
{
|
||||
return s_core_device_control_constructor;
|
||||
}
|
||||
|
||||
} // namespace IDCache
|
||||
|
||||
extern "C" {
|
||||
@ -672,6 +710,23 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
||||
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_constructor = env->GetMethodID(core_device_class, "<init>", "(J)V");
|
||||
env->DeleteLocalRef(core_device_class);
|
||||
|
||||
const jclass core_device_control_class =
|
||||
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_constructor =
|
||||
env->GetMethodID(core_device_control_class, "<init>",
|
||||
"(Lorg/dolphinemu/dolphinemu/features/input/model/CoreDevice;J)V");
|
||||
env->DeleteLocalRef(core_device_control_class);
|
||||
|
||||
return JNI_VERSION;
|
||||
}
|
||||
|
||||
@ -704,5 +759,7 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved)
|
||||
env->DeleteGlobalRef(s_control_reference_class);
|
||||
env->DeleteGlobalRef(s_emulated_controller_class);
|
||||
env->DeleteGlobalRef(s_numeric_setting_class);
|
||||
env->DeleteGlobalRef(s_core_device_class);
|
||||
env->DeleteGlobalRef(s_core_device_control_class);
|
||||
}
|
||||
}
|
||||
|
@ -102,4 +102,12 @@ jclass GetNumericSettingClass();
|
||||
jfieldID GetNumericSettingPointer();
|
||||
jmethodID GetNumericSettingConstructor();
|
||||
|
||||
jclass GetCoreDeviceClass();
|
||||
jfieldID GetCoreDevicePointer();
|
||||
jmethodID GetCoreDeviceConstructor();
|
||||
|
||||
jclass GetCoreDeviceControlClass();
|
||||
jfieldID GetCoreDeviceControlPointer();
|
||||
jmethodID GetCoreDeviceControlConstructor();
|
||||
|
||||
} // namespace IDCache
|
||||
|
@ -16,6 +16,8 @@ add_library(main SHARED
|
||||
Input/ControlGroup.h
|
||||
Input/ControlReference.cpp
|
||||
Input/ControlReference.h
|
||||
Input/CoreDevice.cpp
|
||||
Input/CoreDevice.h
|
||||
Input/EmulatedController.cpp
|
||||
Input/EmulatedController.h
|
||||
Input/InputOverrider.cpp
|
||||
|
@ -51,4 +51,11 @@ Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_ControlReferen
|
||||
ControlReferenceFromJava(env, obj)->SetExpression(GetJString(env, expr));
|
||||
return result ? ToJString(env, *result) : nullptr;
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_input_model_controlleremu_ControlReference_isInput(
|
||||
JNIEnv* env, jobject obj)
|
||||
{
|
||||
return static_cast<jboolean>(ControlReferenceFromJava(env, obj)->IsInput());
|
||||
}
|
||||
}
|
||||
|
84
Source/Android/jni/Input/CoreDevice.cpp
Normal file
84
Source/Android/jni/Input/CoreDevice.cpp
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "jni/Input/CoreDevice.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include "InputCommon/ControllerInterface/CoreDevice.h"
|
||||
#include "jni/AndroidCommon/AndroidCommon.h"
|
||||
#include "jni/AndroidCommon/IDCache.h"
|
||||
|
||||
static ciface::Core::Device::Control* GetControlPointer(JNIEnv* env, jobject obj)
|
||||
{
|
||||
return reinterpret_cast<ciface::Core::Device::Control*>(
|
||||
env->GetLongField(obj, IDCache::GetCoreDeviceControlPointer()));
|
||||
}
|
||||
|
||||
static jobject CoreDeviceControlToJava(JNIEnv* env, jobject device,
|
||||
ciface::Core::Device::Control* control)
|
||||
{
|
||||
if (!control)
|
||||
return nullptr;
|
||||
|
||||
return env->NewObject(IDCache::GetCoreDeviceControlClass(),
|
||||
IDCache::GetCoreDeviceControlConstructor(), device,
|
||||
reinterpret_cast<jlong>(control));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static jobjectArray CoreDeviceControlVectorToJava(JNIEnv* env, jobject device,
|
||||
const std::vector<T*>& controls)
|
||||
{
|
||||
return VectorToJObjectArray(
|
||||
env, controls, IDCache::GetCoreDeviceControlClass(),
|
||||
[device](JNIEnv* env, T* control) { return CoreDeviceControlToJava(env, device, control); });
|
||||
}
|
||||
|
||||
static std::shared_ptr<ciface::Core::Device>* GetDevicePointer(JNIEnv* env, jobject obj)
|
||||
{
|
||||
return reinterpret_cast<std::shared_ptr<ciface::Core::Device>*>(
|
||||
env->GetLongField(obj, IDCache::GetCoreDevicePointer()));
|
||||
}
|
||||
|
||||
jobject CoreDeviceToJava(JNIEnv* env, std::shared_ptr<ciface::Core::Device> device)
|
||||
{
|
||||
if (!device)
|
||||
return nullptr;
|
||||
|
||||
return env->NewObject(
|
||||
IDCache::GetCoreDeviceClass(), IDCache::GetCoreDeviceConstructor(),
|
||||
reinterpret_cast<jlong>(new std::shared_ptr<ciface::Core::Device>(std::move(device))));
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_input_model_CoreDevice_00024Control_getName(JNIEnv* env,
|
||||
jobject obj)
|
||||
{
|
||||
return ToJString(env, GetControlPointer(env, obj)->GetName());
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_input_model_CoreDevice_finalize(JNIEnv* env, jobject obj)
|
||||
{
|
||||
delete GetDevicePointer(env, obj);
|
||||
}
|
||||
|
||||
JNIEXPORT jobjectArray JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_input_model_CoreDevice_getInputs(JNIEnv* env, jobject obj)
|
||||
{
|
||||
return CoreDeviceControlVectorToJava(env, obj, (*GetDevicePointer(env, obj))->Inputs());
|
||||
}
|
||||
|
||||
JNIEXPORT jobjectArray JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_input_model_CoreDevice_getOutputs(JNIEnv* env, jobject obj)
|
||||
{
|
||||
return CoreDeviceControlVectorToJava(env, obj, (*GetDevicePointer(env, obj))->Outputs());
|
||||
}
|
||||
}
|
15
Source/Android/jni/Input/CoreDevice.h
Normal file
15
Source/Android/jni/Input/CoreDevice.h
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
namespace ciface::Core
|
||||
{
|
||||
class Device;
|
||||
}
|
||||
|
||||
jobject CoreDeviceToJava(JNIEnv* env, std::shared_ptr<ciface::Core::Device> device);
|
@ -53,6 +53,19 @@ Java_org_dolphinemu_dolphinemu_features_input_model_MappingCommon_detectInput(
|
||||
ciface::MappingCommon::Quote::On));
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_input_model_MappingCommon_getExpressionForControl(
|
||||
JNIEnv* env, jclass, jstring j_control, jstring j_device, jstring j_default_device)
|
||||
{
|
||||
ciface::Core::DeviceQualifier device_qualifier, default_device_qualifier;
|
||||
device_qualifier.FromString(GetJString(env, j_device));
|
||||
default_device_qualifier.FromString(GetJString(env, j_default_device));
|
||||
|
||||
return ToJString(env, ciface::MappingCommon::GetExpressionForControl(GetJString(env, j_control),
|
||||
device_qualifier,
|
||||
default_device_qualifier));
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_input_model_MappingCommon_save(JNIEnv* env, jclass)
|
||||
{
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
#include "jni/AndroidCommon/AndroidCommon.h"
|
||||
#include "jni/AndroidCommon/IDCache.h"
|
||||
#include "jni/Input/CoreDevice.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@ -1129,4 +1130,13 @@ Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_getAllDe
|
||||
{
|
||||
return VectorToJStringArray(env, g_controller_interface.GetAllDeviceStrings());
|
||||
}
|
||||
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_getDevice(
|
||||
JNIEnv* env, jclass, jstring j_device_string)
|
||||
{
|
||||
ciface::Core::DeviceQualifier qualifier;
|
||||
qualifier.FromString(GetJString(env, j_device_string));
|
||||
return CoreDeviceToJava(env, g_controller_interface.FindDevice(qualifier));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user