Refactor adapters & add audio, graphics and game folder settings

This commit is contained in:
SSimco
2023-07-04 20:27:09 +03:00
parent ddc29f1206
commit 26f5d8dfab
31 changed files with 930 additions and 213 deletions
+111 -4
View File
@@ -26,6 +26,21 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
return JNI_VERSION_1_6;
}
void initializeAudioDevices() {
auto &config = g_config.data();
if (!config.tv_device.empty())
AndroidAudio::createAudioDevice(IAudioAPI::AudioAPI::Cubeb,
config.tv_channels,
config.tv_volume,
true);
if (!config.pad_device.empty())
AndroidAudio::createAudioDevice(IAudioAPI::AudioAPI::Cubeb,
config.pad_channels,
config.pad_volume,
false);
}
extern "C"
JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setSurface(JNIEnv *env, jclass clazz, jobject surface,
@@ -60,6 +75,7 @@ Java_info_cemu_Cemu_NativeLibrary_setSurfaceSize(JNIEnv *env, jclass clazz, jint
extern "C"
JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_startGame(JNIEnv *env, jclass clazz, jlong title_id) {
initializeAudioDevices();
gui_startGame(*reinterpret_cast<TitleId *>(&title_id));
}
@@ -301,10 +317,6 @@ Java_info_cemu_Cemu_NativeLibrary_initializeEmulation(JNIEnv *env, jclass clazz)
InitializeGlobalVulkan();
createCemuDirectories();
g_config.Load();
auto &config = g_config.data();
AndroidAudio::createAudioDevice(IAudioAPI::AudioAPI::Cubeb,
config.tv_channels,
config.tv_volume);
}
extern "C"
JNIEXPORT void JNICALL
@@ -512,3 +524,98 @@ Java_info_cemu_Cemu_NativeLibrary_onAxisEvent(JNIEnv *env, jclass clazz, jstring
auto deviceName = JNIUtils::JStringToString(env, device_name);
androidControllerProvider->on_axis_event(deviceDescriptor, deviceName, axis_code, value);
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_info_cemu_Cemu_NativeLibrary_getAsyncShaderCompile(JNIEnv *env, jclass clazz) {
return g_config.data().async_compile;
}
extern "C"
JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setAsyncShaderCompile(JNIEnv *env, jclass clazz,
jboolean enabled) {
g_config.data().async_compile = enabled;
g_config.Save();
}
extern "C"
JNIEXPORT jint JNICALL
Java_info_cemu_Cemu_NativeLibrary_getVSyncMode(JNIEnv *env, jclass clazz) {
return g_config.data().vsync;
}
extern "C"
JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setVSyncMode(JNIEnv *env, jclass clazz,
jint vsync_mode) {
g_config.data().vsync = vsync_mode;
g_config.Save();
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_info_cemu_Cemu_NativeLibrary_getAccurateBarriers(JNIEnv *env, jclass clazz) {
return g_config.data().vk_accurate_barriers;
}
extern "C"
JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setAccurateBarriers(JNIEnv *env, jclass clazz,
jboolean enabled) {
g_config.data().vk_accurate_barriers = enabled;
g_config.Save();
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_info_cemu_Cemu_NativeLibrary_getAudioDeviceEnabled(JNIEnv *env, jclass clazz, jboolean tv) {
const auto &device = tv ? g_config.data().tv_device : g_config.data().pad_device;
return !device.empty();
}
extern "C"
JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setAudioDeviceEnabled(JNIEnv *env, jclass clazz,
jboolean enabled, jboolean tv) {
auto &device = tv ? g_config.data().tv_device : g_config.data().pad_device;
if (enabled)
device = L"Default";
else
device.clear();
g_config.Save();
}
extern "C"
JNIEXPORT jint JNICALL
Java_info_cemu_Cemu_NativeLibrary_getAudioDeviceChannels(JNIEnv *env, jclass clazz, jboolean tv) {
const auto &deviceChannels = tv ? g_config.data().tv_channels : g_config.data().pad_channels;
return deviceChannels;
}
extern "C"
JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setAudioDeviceChannels(JNIEnv *env, jclass clazz,
jint channels, jboolean tv) {
auto &deviceChannels = tv ? g_config.data().tv_channels : g_config.data().pad_channels;
deviceChannels = static_cast<AudioChannels>(channels);
g_config.Save();
}
extern "C"
JNIEXPORT jint JNICALL
Java_info_cemu_Cemu_NativeLibrary_getAudioDeviceVolume(JNIEnv *env, jclass clazz, jboolean tv) {
const auto &deviceVolume = tv ? g_config.data().tv_volume : g_config.data().pad_volume;
return deviceVolume;
}
extern "C"
JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setAudioDeviceVolume(JNIEnv *env, jclass clazz,
jint volume, jboolean tv) {
auto &deviceVolume = tv ? g_config.data().tv_volume : g_config.data().pad_volume;
deviceVolume = volume;
g_config.Save();
}
@@ -0,0 +1,79 @@
package info.cemu.Cemu;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import info.cemu.Cemu.databinding.FragmentAudioSettingsBinding;
public class AudioSettingsFragment extends Fragment {
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
FragmentAudioSettingsBinding binding = FragmentAudioSettingsBinding.inflate(inflater, container, false);
GenericRecyclerViewAdapter genericRecyclerViewAdapter = new GenericRecyclerViewAdapter();
CheckboxRecyclerViewItem tvDeviceCheckbox = new CheckboxRecyclerViewItem(getString(R.string.tv),
getString(R.string.tv_audio_description), NativeLibrary.getAudioDeviceEnabled(true),
checked -> NativeLibrary.setAudioDeviceEnabled(checked, true));
genericRecyclerViewAdapter.addRecyclerViewItem(tvDeviceCheckbox);
var tvChannelsChoices = Stream.of(NativeLibrary.AUDIO_CHANNELS_MONO, NativeLibrary.AUDIO_CHANNELS_STEREO, NativeLibrary.AUDIO_CHANNELS_SURROUND)
.map(channels -> new SelectionAdapter.ChoiceItem<>(NativeLibrary.channelsToResourceNameId(channels), channels))
.collect(Collectors.toList());
int tvChannels = NativeLibrary.getAudioDeviceChannels(true);
SelectionAdapter<Integer> tvChannelsSelectionAdapter = new SelectionAdapter<>(tvChannelsChoices, tvChannels);
SingleSelectionRecyclerViewItem<Integer> tvChannelsModeSelection = new SingleSelectionRecyclerViewItem<>(getString(R.string.tv_channels),
getString(NativeLibrary.channelsToResourceNameId(tvChannels)), tvChannelsSelectionAdapter,
(channels, selectionRecyclerViewItem) -> {
NativeLibrary.setAudioDeviceChannels(channels, true);
selectionRecyclerViewItem.setDescription(getString(NativeLibrary.channelsToResourceNameId(channels)));
});
genericRecyclerViewAdapter.addRecyclerViewItem(tvChannelsModeSelection);
SliderRecyclerViewItem tvVolumeSlider = new SliderRecyclerViewItem(getString(R.string.tv_volume),
NativeLibrary.AUDIO_MIN_VOLUME,
NativeLibrary.AUDIO_MAX_VOLUME,
NativeLibrary.getAudioDeviceVolume(true),
value -> NativeLibrary.setAudioDeviceVolume((int) value, true),
value -> (int) value + "%");
genericRecyclerViewAdapter.addRecyclerViewItem(tvVolumeSlider);
CheckboxRecyclerViewItem padDeviceCheckbox = new CheckboxRecyclerViewItem(getString(R.string.gamepad),
getString(R.string.gamepad_audio_description), NativeLibrary.getAudioDeviceEnabled(true),
checked -> NativeLibrary.setAudioDeviceEnabled(checked, true));
genericRecyclerViewAdapter.addRecyclerViewItem(padDeviceCheckbox);
var gamepadChannelsChoices = List.of(new SelectionAdapter.ChoiceItem<>(NativeLibrary.channelsToResourceNameId(NativeLibrary.AUDIO_CHANNELS_STEREO), NativeLibrary.AUDIO_CHANNELS_STEREO));
int gamepadChannels = NativeLibrary.getAudioDeviceChannels(false);
SelectionAdapter<Integer> gamepadChannelsSelectionAdapter = new SelectionAdapter<>(gamepadChannelsChoices, gamepadChannels);
SingleSelectionRecyclerViewItem<Integer> gamepadChannelsModeSelection = new SingleSelectionRecyclerViewItem<>(getString(R.string.gamepad_channels),
getString(NativeLibrary.channelsToResourceNameId(gamepadChannels)), gamepadChannelsSelectionAdapter,
(channels, selectionRecyclerViewItem) -> {
NativeLibrary.setAudioDeviceChannels(channels, false);
selectionRecyclerViewItem.setDescription(getString(NativeLibrary.channelsToResourceNameId(channels)));
});
genericRecyclerViewAdapter.addRecyclerViewItem(gamepadChannelsModeSelection);
SliderRecyclerViewItem padVolumeSlider = new SliderRecyclerViewItem(getString(R.string.pad_volume),
NativeLibrary.AUDIO_MIN_VOLUME,
NativeLibrary.AUDIO_MAX_VOLUME,
NativeLibrary.getAudioDeviceVolume(false),
value -> NativeLibrary.setAudioDeviceVolume((int) value, false),
value -> (int) value + "%");
genericRecyclerViewAdapter.addRecyclerViewItem(padVolumeSlider);
binding.audioSettingsRecyclerView.setAdapter(genericRecyclerViewAdapter);
return binding.getRoot();
}
}
@@ -24,8 +24,8 @@ public class ButtonRecyclerViewItem implements RecyclerViewItem {
}
private final OnButtonClickListener onButtonClickListener;
private String text;
private String description;
private final String text;
private final String description;
public ButtonRecyclerViewItem(String text, String description, OnButtonClickListener onButtonClickListener) {
this.text = text;
@@ -1,11 +1,8 @@
package info.cemu.Cemu;
import android.app.Application;
import android.os.FileUtils;
import android.util.DisplayMetrics;
import java.util.ArrayList;
public class CemuApplication extends Application {
@Override
public void onCreate() {
@@ -0,0 +1,6 @@
package info.cemu.Cemu;
public class CemuPreferences {
public static final String PREFERENCES_NAME = "CEMU_PREFERENCES";
public static final String GAMES_PATH_KEY = "GAME_PATHS_KEY";
}
@@ -0,0 +1,59 @@
package info.cemu.Cemu;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.checkbox.MaterialCheckBox;
public class CheckboxRecyclerViewItem implements RecyclerViewItem {
public interface OnCheckedChangeListener {
void onCheckChanged(boolean checked);
}
private static class CheckBoxViewHolder extends RecyclerView.ViewHolder {
TextView label;
TextView description;
MaterialCheckBox checkBox;
public CheckBoxViewHolder(View itemView) {
super(itemView);
label = itemView.findViewById(R.id.checkbox_label);
description = itemView.findViewById(R.id.checkbox_description);
checkBox = itemView.findViewById(R.id.checkbox);
}
}
private final String label;
private final String description;
private boolean checked;
private final OnCheckedChangeListener onCheckedChangeListener;
public CheckboxRecyclerViewItem(String label, String description, boolean checked, OnCheckedChangeListener onCheckedChangeListener) {
this.label = label;
this.description = description;
this.checked = checked;
this.onCheckedChangeListener = onCheckedChangeListener;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
return new CheckBoxViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_checkbox, parent, false));
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, RecyclerView.Adapter<RecyclerView.ViewHolder> adapter) {
CheckBoxViewHolder checkBoxViewHolder = (CheckBoxViewHolder) viewHolder;
checkBoxViewHolder.label.setText(label);
checkBoxViewHolder.description.setText(description);
checkBoxViewHolder.checkBox.setChecked(checked);
checkBoxViewHolder.itemView.setOnClickListener(view -> {
checked = !checkBoxViewHolder.checkBox.isChecked();
checkBoxViewHolder.checkBox.setChecked(checked);
if (onCheckedChangeListener != null) onCheckedChangeListener.onCheckChanged(checked);
});
}
}
@@ -35,10 +35,15 @@ public class ControllerInputsFragment extends Fragment {
}
private void setControllerInputs(Map<Integer, String> inputs) {
emulatedControllerTypeAdapter.setCurrentControllerType(controllerType);
emulatedControllerTypeAdapter.setSelectedValue(controllerType);
emulatedControllerTypeAdapter.setControllerTypeCounts(NativeLibrary.getVPADControllersCount(), NativeLibrary.getWPADControllersCount());
SpinnerRecyclerViewItem emulatedControllerSpinner = new SpinnerRecyclerViewItem(R.string.emulated_controller_label, emulatedControllerTypeAdapter, emulatedControllerTypeAdapter.getPosition(controllerType), position -> onTypeChanged(emulatedControllerTypeAdapter.getItem(position)));
genericRecyclerViewAdapter.addRecyclerViewItem(emulatedControllerSpinner);
String controllerTypeName = getString(NativeLibrary.controllerTypeToResourceNameId(controllerType));
SingleSelectionRecyclerViewItem<Integer> emulatedControllerSelection = new SingleSelectionRecyclerViewItem<>(getString(R.string.emulated_controller_label),
getString(R.string.emulated_controller_selection_description, controllerTypeName),
emulatedControllerTypeAdapter,
(controllerType, selectionRecyclerViewItem) -> onTypeChanged(controllerType));
genericRecyclerViewAdapter.addRecyclerViewItem(emulatedControllerSelection);
for (var controllerInputsGroups : controllerInputsDataProvider.getControllerInputs(controllerType, inputs)) {
genericRecyclerViewAdapter.addRecyclerViewItem(new HeaderRecyclerViewItem(controllerInputsGroups.groupResourceIdName));
for (var controllerInput : controllerInputsGroups.inputs) {
@@ -1,23 +1,22 @@
package info.cemu.Cemu;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.List;
public class EmulatedControllerTypeAdapter extends BaseAdapter {
private final List<Integer> controllerTypes = List.of(NativeLibrary.EMULATED_CONTROLLER_TYPE_DISABLED, NativeLibrary.EMULATED_CONTROLLER_TYPE_VPAD, NativeLibrary.EMULATED_CONTROLLER_TYPE_PRO, NativeLibrary.EMULATED_CONTROLLER_TYPE_CLASSIC, NativeLibrary.EMULATED_CONTROLLER_TYPE_WIIMOTE);
public class EmulatedControllerTypeAdapter extends SelectionAdapter<Integer> {
private int vpadCount = 0;
private int wpadCount = 0;
private int currentControllerType = NativeLibrary.EMULATED_CONTROLLER_TYPE_DISABLED;
public void setCurrentControllerType(int currentControllerType) {
this.currentControllerType = currentControllerType;
notifyDataSetChanged();
public EmulatedControllerTypeAdapter() {
choiceItems = Stream.of(NativeLibrary.EMULATED_CONTROLLER_TYPE_DISABLED,
NativeLibrary.EMULATED_CONTROLLER_TYPE_VPAD,
NativeLibrary.EMULATED_CONTROLLER_TYPE_PRO,
NativeLibrary.EMULATED_CONTROLLER_TYPE_CLASSIC,
NativeLibrary.EMULATED_CONTROLLER_TYPE_WIIMOTE)
.map(type -> new ChoiceItem<>(NativeLibrary.controllerTypeToResourceNameId(type), type))
.collect(Collectors.toList());
setSelectedValue(NativeLibrary.EMULATED_CONTROLLER_TYPE_DISABLED);
}
public void setControllerTypeCounts(int vpadCount, int wpadCount) {
@@ -28,7 +27,8 @@ public class EmulatedControllerTypeAdapter extends BaseAdapter {
@Override
public boolean isEnabled(int position) {
int type = controllerTypes.get(position);
int currentControllerType = getItem(selectedPosition);
int type = getItem(position);
if (type == NativeLibrary.EMULATED_CONTROLLER_TYPE_DISABLED)
return true;
if (type == NativeLibrary.EMULATED_CONTROLLER_TYPE_VPAD)
@@ -36,36 +36,4 @@ public class EmulatedControllerTypeAdapter extends BaseAdapter {
boolean isWPAD = currentControllerType != NativeLibrary.EMULATED_CONTROLLER_TYPE_VPAD && currentControllerType != NativeLibrary.EMULATED_CONTROLLER_TYPE_DISABLED;
return isWPAD || wpadCount < NativeLibrary.MAX_WPAD_CONTROLLERS;
}
@Override
public int getCount() {
return controllerTypes.size();
}
@Override
public Integer getItem(int position) {
return controllerTypes.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View view, ViewGroup viewGroup) {
int type = controllerTypes.get(position);
if (view == null)
view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_choice, viewGroup, false);
TextView textView = view.findViewById(R.id.textview_choice);
textView.setText(NativeLibrary.controllerTypeToResourceNameId(type));
float DISABLED_ALPHA = 0.6f;
if (!isEnabled(position))
view.setAlpha(DISABLED_ALPHA);
return view;
}
public int getPosition(int controllerType) {
return controllerTypes.indexOf(controllerType);
}
}
@@ -1,6 +1,8 @@
package info.cemu.Cemu;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -26,6 +28,11 @@ public class GamesFragment extends Fragment {
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentGamesBinding.inflate(inflater, container, false);
SharedPreferences cemuPreferences = requireActivity().getSharedPreferences(CemuPreferences.PREFERENCES_NAME, Context.MODE_PRIVATE);
String gamesPath = cemuPreferences.getString(CemuPreferences.GAMES_PATH_KEY, null);
if (gamesPath != null)
NativeLibrary.addGamePath(gamesPath);
RecyclerView recyclerView = binding.gamesRecyclerView;
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
View rootView = binding.getRoot();
@@ -0,0 +1,46 @@
package info.cemu.Cemu;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import info.cemu.Cemu.databinding.FragmentGraphicsSettingsBinding;
public class GraphicsSettingsFragment extends Fragment {
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
FragmentGraphicsSettingsBinding binding = FragmentGraphicsSettingsBinding.inflate(inflater, container, false);
GenericRecyclerViewAdapter genericRecyclerViewAdapter = new GenericRecyclerViewAdapter();
CheckboxRecyclerViewItem asyncShaderCheckbox = new CheckboxRecyclerViewItem(getString(R.string.async_shader_compile), getString(R.string.async_shader_compile_description), NativeLibrary.getAsyncShaderCompile(), NativeLibrary::setAsyncShaderCompile);
genericRecyclerViewAdapter.addRecyclerViewItem(asyncShaderCheckbox);
int vsyncMode = NativeLibrary.getVSyncMode();
var vsyncChoices = Stream.of(NativeLibrary.VSYNC_MODE_OFF, NativeLibrary.VSYNC_MODE_DOUBLE_BUFFERING, NativeLibrary.VSYNC_MODE_TRIPLE_BUFFERING)
.map(vsync -> new SelectionAdapter.ChoiceItem<>(NativeLibrary.vsyncModeToResourceNameId(vsync), vsync))
.collect(Collectors.toList());
SelectionAdapter<Integer> vsyncSelectionAdapter = new SelectionAdapter<>(vsyncChoices, vsyncMode);
SingleSelectionRecyclerViewItem<Integer> vsyncModeSelection = new SingleSelectionRecyclerViewItem<>(getString(R.string.vsync),
getString(NativeLibrary.vsyncModeToResourceNameId(vsyncMode)), vsyncSelectionAdapter,
(vsync, selectionRecyclerViewItem) -> {
NativeLibrary.setVSyncMode(vsync);
selectionRecyclerViewItem.setDescription(getString(NativeLibrary.vsyncModeToResourceNameId(vsync)));
});
genericRecyclerViewAdapter.addRecyclerViewItem(vsyncModeSelection);
CheckboxRecyclerViewItem accurateBarriersCheckbox = new CheckboxRecyclerViewItem(getString(R.string.accurate_barriers), getString(R.string.accurate_barriers_description), NativeLibrary.getAccurateBarriers(), NativeLibrary::setAccurateBarriers);
genericRecyclerViewAdapter.addRecyclerViewItem(accurateBarriersCheckbox);
binding.graphicsSettingsRecyclerView.setAdapter(genericRecyclerViewAdapter);
return binding.getRoot();
}
}
@@ -3,32 +3,17 @@ package info.cemu.Cemu;
import android.content.Intent;
import android.os.Bundle;
import com.google.android.material.snackbar.Snackbar;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import info.cemu.Cemu.databinding.ActivityMainBinding;
import android.view.Menu;
import android.view.MenuItem;
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
binding.fab.setOnClickListener(
view -> {
Intent intent = new Intent(this, SettingsActivity.class);
@@ -36,19 +21,4 @@ public class MainActivity extends AppCompatActivity {
}
);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
@@ -198,17 +198,14 @@ public class NativeLibrary {
static final int MAX_WPAD_CONTROLLERS = 7;
public static int controllerTypeToResourceNameId(int type) {
if (type == NativeLibrary.EMULATED_CONTROLLER_TYPE_DISABLED)
return R.string.disabled;
else if (type == NativeLibrary.EMULATED_CONTROLLER_TYPE_VPAD)
return R.string.vpad_controller;
else if (type == NativeLibrary.EMULATED_CONTROLLER_TYPE_PRO)
return R.string.pro_controller;
else if (type == NativeLibrary.EMULATED_CONTROLLER_TYPE_WIIMOTE)
return R.string.wiimote_controller;
else if (type == NativeLibrary.EMULATED_CONTROLLER_TYPE_CLASSIC)
return R.string.classic_controller;
throw new RuntimeException("Invalid controller type: " + type);
return switch (type) {
case EMULATED_CONTROLLER_TYPE_DISABLED -> R.string.disabled;
case EMULATED_CONTROLLER_TYPE_VPAD -> R.string.vpad_controller;
case EMULATED_CONTROLLER_TYPE_PRO -> R.string.pro_controller;
case EMULATED_CONTROLLER_TYPE_WIIMOTE -> R.string.wiimote_controller;
case EMULATED_CONTROLLER_TYPE_CLASSIC -> R.string.classic_controller;
default -> throw new RuntimeException("Invalid controller type: " + type);
};
}
public static native void setControllerType(int index, int emulatedControllerType);
@@ -232,4 +229,58 @@ public class NativeLibrary {
public static native void onKeyEvent(String deviceDescriptor, String deviceName, int keyCode, boolean isPressed);
public static native void onAxisEvent(String deviceDescriptor, String deviceName, int axisCode, float value);
public static native boolean getAsyncShaderCompile();
public static native void setAsyncShaderCompile(boolean enabled);
static final int VSYNC_MODE_OFF = 0;
static final int VSYNC_MODE_DOUBLE_BUFFERING = 1;
static final int VSYNC_MODE_TRIPLE_BUFFERING = 2;
public static int vsyncModeToResourceNameId(int vsyncMode) {
return switch (vsyncMode) {
case VSYNC_MODE_OFF -> R.string.off;
case VSYNC_MODE_DOUBLE_BUFFERING -> R.string.double_buffering;
case VSYNC_MODE_TRIPLE_BUFFERING -> R.string.triple_buffering;
default -> throw new RuntimeException("Invalid vsync mode: " + vsyncMode);
};
}
public static native int getVSyncMode();
public static native void setVSyncMode(int vsyncMode);
public static native boolean getAccurateBarriers();
public static native void setAccurateBarriers(boolean enabled);
public static native boolean getAudioDeviceEnabled(boolean tv);
public static native void setAudioDeviceEnabled(boolean enabled, boolean tv);
static final int AUDIO_CHANNELS_MONO = 0;
static final int AUDIO_CHANNELS_STEREO = 1;
static final int AUDIO_CHANNELS_SURROUND = 2;
public static int channelsToResourceNameId(int channels) {
return switch (channels) {
case AUDIO_CHANNELS_MONO -> R.string.mono;
case AUDIO_CHANNELS_STEREO -> R.string.stereo;
case AUDIO_CHANNELS_SURROUND -> R.string.surround;
default -> throw new RuntimeException("Invalid channels type: " + channels);
};
}
public static native void setAudioDeviceChannels(int channels, boolean tv);
public static native int getAudioDeviceChannels(boolean tv);
static final int AUDIO_MIN_VOLUME = 0;
static final int AUDIO_MAX_VOLUME = 100;
public static native void setAudioDeviceVolume(int volume, boolean tv);
public static native int getAudioDeviceVolume(boolean tv);
}
@@ -0,0 +1,58 @@
package info.cemu.Cemu;
import static android.app.Activity.RESULT_OK;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.documentfile.provider.DocumentFile;
import androidx.fragment.app.Fragment;
import androidx.navigation.fragment.NavHostFragment;
import info.cemu.Cemu.databinding.FragmentOptionsBinding;
public class OptionsFragment extends Fragment {
private ActivityResultLauncher<Intent> folderSelectionLauncher;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
FragmentOptionsBinding binding = FragmentOptionsBinding.inflate(inflater, container, false);
GenericRecyclerViewAdapter genericRecyclerViewAdapter = new GenericRecyclerViewAdapter();
folderSelectionLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (result.getResultCode() == RESULT_OK) {
Intent data = result.getData();
if (data != null) {
Uri uri = data.getData();
requireActivity().getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
DocumentFile documentFile = DocumentFile.fromTreeUri(requireContext(), uri);
if (documentFile == null) return;
String gamePath = documentFile.getUri().toString();
NativeLibrary.addGamePath(gamePath);
SharedPreferences.Editor preferencesEditor = requireActivity().getSharedPreferences(CemuPreferences.PREFERENCES_NAME, Context.MODE_PRIVATE).edit();
preferencesEditor.putString(CemuPreferences.GAMES_PATH_KEY, gamePath);
preferencesEditor.apply();
}
}
});
genericRecyclerViewAdapter.addRecyclerViewItem(new ButtonRecyclerViewItem(getString(R.string.games_folder_label), getString(R.string.games_folder_description), () -> {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
folderSelectionLauncher.launch(intent);
}));
genericRecyclerViewAdapter.addRecyclerViewItem(new SimpleButtonRecyclerViewItem(getString(R.string.input_settings), () -> NavHostFragment.findNavController(OptionsFragment.this).navigate(R.id.action_optionsFragment_to_inputSettingsFragment)));
genericRecyclerViewAdapter.addRecyclerViewItem(new SimpleButtonRecyclerViewItem(getString(R.string.graphics_settings), () -> NavHostFragment.findNavController(OptionsFragment.this).navigate(R.id.action_optionsFragment_to_graphicSettingsFragment)));
genericRecyclerViewAdapter.addRecyclerViewItem(new SimpleButtonRecyclerViewItem(getString(R.string.audio_settings), () -> NavHostFragment.findNavController(OptionsFragment.this).navigate(R.id.action_optionsFragment_to_audioSettingsFragment)));
binding.optionsRecyclerView.setAdapter(genericRecyclerViewAdapter);
return binding.getRoot();
}
}
@@ -0,0 +1,75 @@
package info.cemu.Cemu;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import com.google.android.material.radiobutton.MaterialRadioButton;
import java.util.ArrayList;
import java.util.List;
import java.util.OptionalInt;
import java.util.stream.IntStream;
public class SelectionAdapter<T> extends BaseAdapter {
public static class ChoiceItem<T> {
int choiceTextResourceId;
T choiceValue;
public ChoiceItem(int choiceTextResourceId, T choiceValue) {
this.choiceTextResourceId = choiceTextResourceId;
this.choiceValue = choiceValue;
}
}
protected List<ChoiceItem<T>> choiceItems = new ArrayList<>();
protected int selectedPosition = 0;
public SelectionAdapter() {
super();
}
public SelectionAdapter(List<ChoiceItem<T>> choiceItems, T selectedValue) {
super();
this.choiceItems = choiceItems;
setSelectedValue(selectedValue);
}
public void setSelectedValue(T selectedValue) {
selectedPosition = getPositionOf(selectedValue);
}
public int getPositionOf(T value) {
OptionalInt optionalInt = IntStream.range(0, choiceItems.size()).filter(position -> choiceItems.get(position).choiceValue.equals(value)).findFirst();
if (!optionalInt.isPresent())
throw new RuntimeException("value " + value + " was not found in the list of choiceItems");
return optionalInt.getAsInt();
}
@Override
public int getCount() {
return choiceItems.size();
}
@Override
public T getItem(int position) {
return choiceItems.get(position).choiceValue;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View view, ViewGroup viewGroup) {
if (view == null)
view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_single_selection_item, viewGroup, false);
MaterialRadioButton radioButton = view.findViewById(R.id.single_selection_item_radio_button);
radioButton.setEnabled(isEnabled(position));
radioButton.setText(choiceItems.get(position).choiceTextResourceId);
radioButton.setChecked(position == selectedPosition);
return view;
}
}
@@ -0,0 +1,46 @@
package info.cemu.Cemu;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
public class SimpleButtonRecyclerViewItem implements RecyclerViewItem {
public interface OnButtonClickListener {
void onButtonClick();
}
private static class SimpleButtonViewHolder extends RecyclerView.ViewHolder {
TextView text;
public SimpleButtonViewHolder(View itemView) {
super(itemView);
text = itemView.findViewById(R.id.simple_button_text);
}
}
private final SimpleButtonRecyclerViewItem.OnButtonClickListener onButtonClickListener;
private final String text;
public SimpleButtonRecyclerViewItem(String text, OnButtonClickListener onButtonClickListener) {
this.text = text;
this.onButtonClickListener = onButtonClickListener;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
return new SimpleButtonRecyclerViewItem.SimpleButtonViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_simple_button, parent, false));
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, RecyclerView.Adapter<RecyclerView.ViewHolder> adapter) {
SimpleButtonRecyclerViewItem.SimpleButtonViewHolder buttonViewHolder = (SimpleButtonRecyclerViewItem.SimpleButtonViewHolder) viewHolder;
buttonViewHolder.text.setText(text);
buttonViewHolder.itemView.setOnClickListener(view -> {
if (onButtonClickListener != null)
onButtonClickListener.onButtonClick();
});
}
}
@@ -0,0 +1,71 @@
package info.cemu.Cemu;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
public class SingleSelectionRecyclerViewItem<T> implements RecyclerViewItem {
private static class SingleSelectionViewHolder extends RecyclerView.ViewHolder {
TextView label;
TextView description;
public SingleSelectionViewHolder(View itemView) {
super(itemView);
label = itemView.findViewById(R.id.selection_label);
description = itemView.findViewById(R.id.selection_description);
}
}
public interface OnItemSelectedListener<T> {
void onItemSelected(T selectedValue, SingleSelectionRecyclerViewItem<T> selectionRecyclerViewItem);
}
private final SelectionAdapter<T> selectionAdapter;
private final String label;
private String description;
private final OnItemSelectedListener<T> onItemSelectedListener;
private SingleSelectionViewHolder singleSelectionViewHolder;
private RecyclerView.Adapter<RecyclerView.ViewHolder> recyclerViewAdapter;
public SingleSelectionRecyclerViewItem(String label, String description, SelectionAdapter<T> selectionAdapter, OnItemSelectedListener<T> onItemSelectedListener) {
this.label = label;
this.description = description;
this.selectionAdapter = selectionAdapter;
this.onItemSelectedListener = onItemSelectedListener;
}
public void setDescription(String description) {
this.description = description;
recyclerViewAdapter.notifyItemChanged(singleSelectionViewHolder.getAdapterPosition());
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
return new SingleSelectionViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_single_selection, parent, false));
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, RecyclerView.Adapter<RecyclerView.ViewHolder> recyclerViewAdapter) {
this.recyclerViewAdapter = recyclerViewAdapter;
singleSelectionViewHolder = (SingleSelectionViewHolder) viewHolder;
singleSelectionViewHolder.label.setText(label);
singleSelectionViewHolder.description.setText(description);
singleSelectionViewHolder.itemView.setOnClickListener(view -> {
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(viewHolder.itemView.getContext());
builder.setTitle(label).setAdapter(selectionAdapter, (dialogInterface, position) -> {
if (!selectionAdapter.isEnabled(position))
return;
T selectedValue = selectionAdapter.getItem(position);
selectionAdapter.setSelectedValue(selectedValue);
if (onItemSelectedListener != null)
onItemSelectedListener.onItemSelected(selectedValue, SingleSelectionRecyclerViewItem.this);
}).setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.dismiss()).show();
});
}
}
@@ -0,0 +1,73 @@
package info.cemu.Cemu;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.slider.LabelFormatter;
import com.google.android.material.slider.Slider;
public class SliderRecyclerViewItem implements RecyclerViewItem {
public interface OnChangeListener {
void onChange(float value);
}
private static class SliderViewHolder extends RecyclerView.ViewHolder {
TextView label;
Slider slider;
public SliderViewHolder(View itemView) {
super(itemView);
label = itemView.findViewById(R.id.slider_label);
slider = itemView.findViewById(R.id.slider);
}
}
private final String label;
private final float valueFrom;
private final float valueTo;
private float currentValue;
private final OnChangeListener onChangeListener;
private LabelFormatter labelFormatter;
public SliderRecyclerViewItem(String label, float valueFrom, float valueTo, float currentValue, OnChangeListener onChangeListener, LabelFormatter labelFormatter) {
this.label = label;
this.valueFrom = valueFrom;
this.valueTo = valueTo;
this.currentValue = currentValue;
this.onChangeListener = onChangeListener;
this.labelFormatter = labelFormatter;
}
public SliderRecyclerViewItem(String label, float valueFrom, float valueTo, float currentValue, OnChangeListener onChangeListener) {
this.label = label;
this.valueFrom = valueFrom;
this.valueTo = valueTo;
this.currentValue = currentValue;
this.onChangeListener = onChangeListener;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
return new SliderViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_slider, parent, false));
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, RecyclerView.Adapter<RecyclerView.ViewHolder> adapter) {
SliderViewHolder sliderViewHolder = (SliderViewHolder) viewHolder;
sliderViewHolder.label.setText(label);
if (labelFormatter != null) sliderViewHolder.slider.setLabelFormatter(labelFormatter);
sliderViewHolder.slider.setValueFrom(valueFrom);
sliderViewHolder.slider.setValueTo(valueTo);
float stepSize = 1.0f;
sliderViewHolder.slider.setStepSize(stepSize);
sliderViewHolder.slider.setValue(currentValue);
sliderViewHolder.slider.addOnChangeListener((slider, value, fromUser) -> {
currentValue = value;
if (onChangeListener != null) onChangeListener.onChange(currentValue);
});
}
}
@@ -1,65 +0,0 @@
package info.cemu.Cemu;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
public class SpinnerRecyclerViewItem implements RecyclerViewItem {
public interface OnItemSelectedListener {
void onItemSelected(int position);
}
private static class SpinnerViewHolder extends RecyclerView.ViewHolder {
TextView text;
Spinner spinner;
public SpinnerViewHolder(View itemView) {
super(itemView);
text = itemView.findViewById(R.id.spinner_label);
spinner = itemView.findViewById(R.id.spinner);
}
}
private final SpinnerAdapter spinnerAdapter;
private final int spinnerLabelResourceId;
private final int initialSelection;
private final OnItemSelectedListener onItemSelectedListener;
public SpinnerRecyclerViewItem(int spinnerLabelResourceId, SpinnerAdapter spinnerAdapter, int initialSelection, OnItemSelectedListener onItemSelectedListener) {
this.spinnerLabelResourceId = spinnerLabelResourceId;
this.spinnerAdapter = spinnerAdapter;
this.initialSelection = initialSelection;
this.onItemSelectedListener = onItemSelectedListener;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
return new SpinnerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_spinner, parent, false));
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, RecyclerView.Adapter<RecyclerView.ViewHolder> adapter) {
SpinnerViewHolder spinnerViewHolder = (SpinnerViewHolder) viewHolder;
spinnerViewHolder.text.setText(spinnerLabelResourceId);
spinnerViewHolder.spinner.setAdapter(spinnerAdapter);
spinnerViewHolder.spinner.setSelection(initialSelection);
spinnerViewHolder.spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long l) {
if (onItemSelectedListener != null)
onItemSelectedListener.onItemSelected(position);
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
}
}
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="match_parent"
tools:context=".AudioSettingsFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/audio_settings_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</FrameLayout>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="match_parent"
tools:context=".GraphicsSettingsFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/graphics_settings_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:orientation="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</FrameLayout>

Some files were not shown because too many files have changed in this diff Show More