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:
JosJuice 2022-12-27 16:29:01 +01:00
parent 42943672bb
commit c2779aef06
24 changed files with 772 additions and 5 deletions

View File

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

View File

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

View File

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

View File

@ -34,4 +34,6 @@ public class ControlReference
*/
@Nullable
public native String setExpression(String expr);
public native boolean isInput();
}

View File

@ -52,4 +52,14 @@ public final class InputMappingControlSetting extends SettingsItem
{
return mController;
}
public ControlReference getControlReference()
{
return mControlReference;
}
public boolean isInput()
{
return mControlReference.isInput();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

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

View 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: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>

View File

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

View File

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

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

View File

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

View File

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

View File

@ -102,4 +102,12 @@ jclass GetNumericSettingClass();
jfieldID GetNumericSettingPointer();
jmethodID GetNumericSettingConstructor();
jclass GetCoreDeviceClass();
jfieldID GetCoreDevicePointer();
jmethodID GetCoreDeviceConstructor();
jclass GetCoreDeviceControlClass();
jfieldID GetCoreDeviceControlPointer();
jmethodID GetCoreDeviceControlConstructor();
} // namespace IDCache

View File

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

View File

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

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

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

View File

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

View File

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