Android: Add hotkey binding support

This commit is contained in:
Connor McLaughlin 2020-12-30 19:34:00 +10:00
parent 2672e2b505
commit 22bb64e7b0
6 changed files with 152 additions and 12 deletions

View file

@ -1170,6 +1170,43 @@ DEFINE_JNI_ARGS_METHOD(jarray, AndroidHostInterface_getGameListEntries, jobject
return entry_array; return entry_array;
} }
DEFINE_JNI_ARGS_METHOD(jobjectArray , AndroidHostInterface_getHotkeyInfoList, jobject obj)
{
jclass entry_class = env->FindClass("com/github/stenzek/duckstation/HotkeyInfo");
Assert(entry_class != nullptr);
jmethodID entry_constructor =
env->GetMethodID(entry_class, "<init>",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
Assert(entry_constructor != nullptr);
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);
const CommonHostInterface::HotkeyInfoList& hotkeys = hi->GetHotkeyInfoList();
if (hotkeys.empty())
return nullptr;
jobjectArray entry_array = env->NewObjectArray(static_cast<jsize>(hotkeys.size()), entry_class, nullptr);
Assert(entry_array != nullptr);
u32 counter = 0;
for (const CommonHostInterface::HotkeyInfo& hk : hotkeys)
{
jstring category = env->NewStringUTF(hk.category.GetCharArray());
jstring name = env->NewStringUTF(hk.name.GetCharArray());
jstring display_name = env->NewStringUTF(hk.display_name.GetCharArray());
jobject entry_jobject = env->NewObject(entry_class, entry_constructor, category, name, display_name);
env->SetObjectArrayElement(entry_array, counter++, entry_jobject);
env->DeleteLocalRef(entry_jobject);
env->DeleteLocalRef(display_name);
env->DeleteLocalRef(name);
env->DeleteLocalRef(category);
}
return entry_array;
}
DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_applySettings, jobject obj) DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_applySettings, jobject obj)
{ {
AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj); AndroidHostInterface* hi = AndroidHelpers::GetNativeClass(env, obj);

View file

@ -81,6 +81,8 @@ public class AndroidHostInterface {
public native boolean loadInputProfile(String name); public native boolean loadInputProfile(String name);
public native boolean saveInputProfile(String name); public native boolean saveInputProfile(String name);
public native HotkeyInfo[] getHotkeyInfoList();
public native void refreshGameList(boolean invalidateCache, boolean invalidateDatabase, AndroidProgressCallback progressCallback); public native void refreshGameList(boolean invalidateCache, boolean invalidateDatabase, AndroidProgressCallback progressCallback);
public native GameListEntry[] getGameListEntries(); public native GameListEntry[] getGameListEntries();

View file

@ -14,10 +14,16 @@ import androidx.preference.PreferenceViewHolder;
import java.util.Set; import java.util.Set;
public class ControllerBindingPreference extends Preference { public class ControllerBindingPreference extends Preference {
private boolean mIsAxis; private enum Type {
BUTTON,
AXIS,
HOTKEY
}
private String mBindingName; private String mBindingName;
private String mValue; private String mValue;
private TextView mValueView; private TextView mValueView;
private Type mType = Type.BUTTON;
private static int getIconForButton(String buttonName) { private static int getIconForButton(String buttonName) {
if (buttonName.equals("Up")) { if (buttonName.equals("Up")) {
@ -57,6 +63,10 @@ public class ControllerBindingPreference extends Preference {
return R.drawable.ic_baseline_radio_button_checked_24; return R.drawable.ic_baseline_radio_button_checked_24;
} }
private static int getIconForHotkey(String hotkeyDisplayName) {
return R.drawable.ic_baseline_category_24;
}
public ControllerBindingPreference(Context context, AttributeSet attrs) { public ControllerBindingPreference(Context context, AttributeSet attrs) {
this(context, attrs, 0); this(context, attrs, 0);
setWidgetLayoutResource(R.layout.layout_controller_binding_preference); setWidgetLayoutResource(R.layout.layout_controller_binding_preference);
@ -83,32 +93,47 @@ public class ControllerBindingPreference extends Preference {
TextView nameView = ((TextView)holder.findViewById(R.id.controller_binding_name)); TextView nameView = ((TextView)holder.findViewById(R.id.controller_binding_name));
mValueView = ((TextView)holder.findViewById(R.id.controller_binding_value)); mValueView = ((TextView)holder.findViewById(R.id.controller_binding_value));
iconView.setImageDrawable(ContextCompat.getDrawable(getContext(), getIconForButton(mBindingName))); int drawableId = R.drawable.ic_baseline_radio_button_checked_24;
switch (mType)
{
case BUTTON: drawableId = getIconForButton(mBindingName); break;
case AXIS: drawableId = getIconForAxis(mBindingName); break;
case HOTKEY: drawableId = getIconForHotkey(mBindingName); break;
}
iconView.setImageDrawable(ContextCompat.getDrawable(getContext(), drawableId));
nameView.setText(mBindingName); nameView.setText(mBindingName);
updateValue(); updateValue();
} }
@Override @Override
protected void onClick() { protected void onClick() {
ControllerBindingDialog dialog = new ControllerBindingDialog(getContext(), mBindingName, getKey(), mValue, mIsAxis); ControllerBindingDialog dialog = new ControllerBindingDialog(getContext(), mBindingName, getKey(), mValue, (mType == Type.AXIS));
dialog.setOnDismissListener((dismissedDialog) -> updateValue()); dialog.setOnDismissListener((dismissedDialog) -> updateValue());
dialog.show(); dialog.show();
} }
public void initButton(int controllerIndex, String buttonName) { public void initButton(int controllerIndex, String buttonName) {
mBindingName = buttonName; mBindingName = buttonName;
mIsAxis = false; mType = Type.BUTTON;
setKey(String.format("Controller%d/Button%s", controllerIndex, buttonName)); setKey(String.format("Controller%d/Button%s", controllerIndex, buttonName));
updateValue(); updateValue();
} }
public void initAxis(int controllerIndex, String axisName) { public void initAxis(int controllerIndex, String axisName) {
mBindingName = axisName; mBindingName = axisName;
mIsAxis = true; mType = Type.AXIS;
setKey(String.format("Controller%d/Axis%s", controllerIndex, axisName)); setKey(String.format("Controller%d/Axis%s", controllerIndex, axisName));
updateValue(); updateValue();
} }
public void initHotkey(HotkeyInfo hotkeyInfo) {
mBindingName = hotkeyInfo.getDisplayName();
mType = Type.HOTKEY;
setKey(hotkeyInfo.getBindingConfigKey());
updateValue();
}
private void updateValue(String value) { private void updateValue(String value) {
mValue = value; mValue = value;
if (mValueView != null) { if (mValueView != null) {

View file

@ -151,11 +151,11 @@ public class ControllerMappingActivity extends AppCompatActivity {
pref.updateValue(); pref.updateValue();
} }
public static class SettingsFragment extends PreferenceFragmentCompat { public static class ControllerPortFragment extends PreferenceFragmentCompat {
private ControllerMappingActivity activity; private ControllerMappingActivity activity;
private int controllerIndex; private int controllerIndex;
public SettingsFragment(ControllerMappingActivity activity, int controllerIndex) { public ControllerPortFragment(ControllerMappingActivity activity, int controllerIndex) {
this.activity = activity; this.activity = activity;
this.controllerIndex = controllerIndex; this.controllerIndex = controllerIndex;
} }
@ -189,6 +189,31 @@ public class ControllerMappingActivity extends AppCompatActivity {
} }
} }
public static class HotkeyFragment extends PreferenceFragmentCompat {
private ControllerMappingActivity activity;
private HotkeyInfo[] mHotkeyInfo;
public HotkeyFragment(ControllerMappingActivity activity) {
this.activity = activity;
this.mHotkeyInfo = AndroidHostInterface.getInstance().getHotkeyInfoList();
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
final PreferenceScreen ps = getPreferenceManager().createPreferenceScreen(getContext());
if (mHotkeyInfo != null) {
for (HotkeyInfo hotkeyInfo : mHotkeyInfo) {
final ControllerBindingPreference cbp = new ControllerBindingPreference(getContext(), null);
cbp.initHotkey(hotkeyInfo);
ps.addPreference(cbp);
activity.mPreferences.add(cbp);
}
}
setPreferenceScreen(ps);
}
}
public static class SettingsCollectionFragment extends Fragment { public static class SettingsCollectionFragment extends Fragment {
private ControllerMappingActivity activity; private ControllerMappingActivity activity;
private SettingsCollectionAdapter adapter; private SettingsCollectionAdapter adapter;
@ -211,9 +236,12 @@ public class ControllerMappingActivity extends AppCompatActivity {
viewPager.setAdapter(adapter); viewPager.setAdapter(adapter);
TabLayout tabLayout = view.findViewById(R.id.tab_layout); TabLayout tabLayout = view.findViewById(R.id.tab_layout);
new TabLayoutMediator(tabLayout, viewPager, new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> {
(tab, position) -> tab.setText(String.format("Port %d", position + 1)) if (position == NUM_CONTROLLER_PORTS)
).attach(); tab.setText("Hotkeys");
else
tab.setText(String.format("Port %d", position + 1));
}).attach();
} }
} }
@ -228,12 +256,15 @@ public class ControllerMappingActivity extends AppCompatActivity {
@NonNull @NonNull
@Override @Override
public Fragment createFragment(int position) { public Fragment createFragment(int position) {
return new SettingsFragment(activity, position + 1); if (position != NUM_CONTROLLER_PORTS)
return new ControllerPortFragment(activity, position + 1);
else
return new HotkeyFragment(activity);
} }
@Override @Override
public int getItemCount() { public int getItemCount() {
return NUM_CONTROLLER_PORTS; return NUM_CONTROLLER_PORTS + 1;
} }
} }
} }

View file

@ -0,0 +1,29 @@
package com.github.stenzek.duckstation;
public class HotkeyInfo {
private String mCategory;
private String mName;
private String mDisplayName;
public HotkeyInfo(String category, String name, String displayName) {
mCategory = category;
mName = name;
mDisplayName = displayName;
}
public String getCategory() {
return mCategory;
}
public String getName() {
return mName;
}
public String getDisplayName() {
return mDisplayName;
}
public String getBindingConfigKey() {
return String.format("Hotkeys/%s", mName);
}
}

View file

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2l-5.5,9h11z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M17.5,17.5m-4.5,0a4.5,4.5 0,1 1,9 0a4.5,4.5 0,1 1,-9 0"/>
<path
android:fillColor="@android:color/white"
android:pathData="M3,13.5h8v8H3z"/>
</vector>