diff --git a/src/android/app/src/main/cpp/native-lib.cpp b/src/android/app/src/main/cpp/native-lib.cpp index 53b28452..6abbc4c0 100644 --- a/src/android/app/src/main/cpp/native-lib.cpp +++ b/src/android/app/src/main/cpp/native-lib.cpp @@ -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(&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(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(); +} \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/Cemu/AudioSettingsFragment.java b/src/android/app/src/main/java/info/cemu/Cemu/AudioSettingsFragment.java new file mode 100644 index 00000000..c4e5bd03 --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/Cemu/AudioSettingsFragment.java @@ -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 tvChannelsSelectionAdapter = new SelectionAdapter<>(tvChannelsChoices, tvChannels); + SingleSelectionRecyclerViewItem 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 gamepadChannelsSelectionAdapter = new SelectionAdapter<>(gamepadChannelsChoices, gamepadChannels); + SingleSelectionRecyclerViewItem 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(); + } +} \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/Cemu/ButtonRecyclerViewItem.java b/src/android/app/src/main/java/info/cemu/Cemu/ButtonRecyclerViewItem.java index 3115c434..4ccb0345 100644 --- a/src/android/app/src/main/java/info/cemu/Cemu/ButtonRecyclerViewItem.java +++ b/src/android/app/src/main/java/info/cemu/Cemu/ButtonRecyclerViewItem.java @@ -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; diff --git a/src/android/app/src/main/java/info/cemu/Cemu/CemuApplication.java b/src/android/app/src/main/java/info/cemu/Cemu/CemuApplication.java index b94d7070..efa63959 100644 --- a/src/android/app/src/main/java/info/cemu/Cemu/CemuApplication.java +++ b/src/android/app/src/main/java/info/cemu/Cemu/CemuApplication.java @@ -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() { diff --git a/src/android/app/src/main/java/info/cemu/Cemu/CemuPreferences.java b/src/android/app/src/main/java/info/cemu/Cemu/CemuPreferences.java new file mode 100644 index 00000000..1167ff0f --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/Cemu/CemuPreferences.java @@ -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"; +} diff --git a/src/android/app/src/main/java/info/cemu/Cemu/CheckboxRecyclerViewItem.java b/src/android/app/src/main/java/info/cemu/Cemu/CheckboxRecyclerViewItem.java new file mode 100644 index 00000000..4747f296 --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/Cemu/CheckboxRecyclerViewItem.java @@ -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 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); + }); + } +} diff --git a/src/android/app/src/main/java/info/cemu/Cemu/ControllerInputsFragment.java b/src/android/app/src/main/java/info/cemu/Cemu/ControllerInputsFragment.java index 34a597ef..d7a53292 100644 --- a/src/android/app/src/main/java/info/cemu/Cemu/ControllerInputsFragment.java +++ b/src/android/app/src/main/java/info/cemu/Cemu/ControllerInputsFragment.java @@ -35,10 +35,15 @@ public class ControllerInputsFragment extends Fragment { } private void setControllerInputs(Map 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 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) { diff --git a/src/android/app/src/main/java/info/cemu/Cemu/EmulatedControllerTypeAdapter.java b/src/android/app/src/main/java/info/cemu/Cemu/EmulatedControllerTypeAdapter.java index e20cc034..3ede0c71 100644 --- a/src/android/app/src/main/java/info/cemu/Cemu/EmulatedControllerTypeAdapter.java +++ b/src/android/app/src/main/java/info/cemu/Cemu/EmulatedControllerTypeAdapter.java @@ -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 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 { 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); - } } diff --git a/src/android/app/src/main/java/info/cemu/Cemu/GamesFragment.java b/src/android/app/src/main/java/info/cemu/Cemu/GamesFragment.java index 29caedd1..8d3bb56e 100644 --- a/src/android/app/src/main/java/info/cemu/Cemu/GamesFragment.java +++ b/src/android/app/src/main/java/info/cemu/Cemu/GamesFragment.java @@ -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(); diff --git a/src/android/app/src/main/java/info/cemu/Cemu/GraphicsSettingsFragment.java b/src/android/app/src/main/java/info/cemu/Cemu/GraphicsSettingsFragment.java new file mode 100644 index 00000000..5ce7b96e --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/Cemu/GraphicsSettingsFragment.java @@ -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 vsyncSelectionAdapter = new SelectionAdapter<>(vsyncChoices, vsyncMode); + SingleSelectionRecyclerViewItem 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(); + } +} \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/Cemu/MainActivity.java b/src/android/app/src/main/java/info/cemu/Cemu/MainActivity.java index 37f6ef1c..8b5ae856 100644 --- a/src/android/app/src/main/java/info/cemu/Cemu/MainActivity.java +++ b/src/android/app/src/main/java/info/cemu/Cemu/MainActivity.java @@ -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); - } } \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/Cemu/NativeLibrary.java b/src/android/app/src/main/java/info/cemu/Cemu/NativeLibrary.java index 849f7a9e..18da9436 100644 --- a/src/android/app/src/main/java/info/cemu/Cemu/NativeLibrary.java +++ b/src/android/app/src/main/java/info/cemu/Cemu/NativeLibrary.java @@ -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); + } diff --git a/src/android/app/src/main/java/info/cemu/Cemu/OptionsFragment.java b/src/android/app/src/main/java/info/cemu/Cemu/OptionsFragment.java new file mode 100644 index 00000000..1f83f181 --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/Cemu/OptionsFragment.java @@ -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 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(); + } +} \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/Cemu/SelectionAdapter.java b/src/android/app/src/main/java/info/cemu/Cemu/SelectionAdapter.java new file mode 100644 index 00000000..20cb21a0 --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/Cemu/SelectionAdapter.java @@ -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 extends BaseAdapter { + public static class ChoiceItem { + int choiceTextResourceId; + T choiceValue; + + public ChoiceItem(int choiceTextResourceId, T choiceValue) { + this.choiceTextResourceId = choiceTextResourceId; + this.choiceValue = choiceValue; + } + } + + protected List> choiceItems = new ArrayList<>(); + protected int selectedPosition = 0; + + public SelectionAdapter() { + super(); + } + + public SelectionAdapter(List> 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; + } +} diff --git a/src/android/app/src/main/java/info/cemu/Cemu/SimpleButtonRecyclerViewItem.java b/src/android/app/src/main/java/info/cemu/Cemu/SimpleButtonRecyclerViewItem.java new file mode 100644 index 00000000..16fb8607 --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/Cemu/SimpleButtonRecyclerViewItem.java @@ -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 adapter) { + SimpleButtonRecyclerViewItem.SimpleButtonViewHolder buttonViewHolder = (SimpleButtonRecyclerViewItem.SimpleButtonViewHolder) viewHolder; + buttonViewHolder.text.setText(text); + buttonViewHolder.itemView.setOnClickListener(view -> { + if (onButtonClickListener != null) + onButtonClickListener.onButtonClick(); + }); + } +} diff --git a/src/android/app/src/main/java/info/cemu/Cemu/SingleSelectionRecyclerViewItem.java b/src/android/app/src/main/java/info/cemu/Cemu/SingleSelectionRecyclerViewItem.java new file mode 100644 index 00000000..32ca3b17 --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/Cemu/SingleSelectionRecyclerViewItem.java @@ -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 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 { + void onItemSelected(T selectedValue, SingleSelectionRecyclerViewItem selectionRecyclerViewItem); + } + + private final SelectionAdapter selectionAdapter; + private final String label; + private String description; + private final OnItemSelectedListener onItemSelectedListener; + private SingleSelectionViewHolder singleSelectionViewHolder; + private RecyclerView.Adapter recyclerViewAdapter; + + public SingleSelectionRecyclerViewItem(String label, String description, SelectionAdapter selectionAdapter, OnItemSelectedListener 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 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(); + }); + } +} diff --git a/src/android/app/src/main/java/info/cemu/Cemu/SliderRecyclerViewItem.java b/src/android/app/src/main/java/info/cemu/Cemu/SliderRecyclerViewItem.java new file mode 100644 index 00000000..3d3703ef --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/Cemu/SliderRecyclerViewItem.java @@ -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 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); + }); + } +} diff --git a/src/android/app/src/main/java/info/cemu/Cemu/SpinnerRecyclerViewItem.java b/src/android/app/src/main/java/info/cemu/Cemu/SpinnerRecyclerViewItem.java deleted file mode 100644 index 5fd7178e..00000000 --- a/src/android/app/src/main/java/info/cemu/Cemu/SpinnerRecyclerViewItem.java +++ /dev/null @@ -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 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) { - - } - }); - } -} diff --git a/src/android/app/src/main/res/layout/fragment_audio_settings.xml b/src/android/app/src/main/res/layout/fragment_audio_settings.xml new file mode 100644 index 00000000..6acf8b84 --- /dev/null +++ b/src/android/app/src/main/res/layout/fragment_audio_settings.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/src/android/app/src/main/res/layout/fragment_graphics_settings.xml b/src/android/app/src/main/res/layout/fragment_graphics_settings.xml new file mode 100644 index 00000000..877deeae --- /dev/null +++ b/src/android/app/src/main/res/layout/fragment_graphics_settings.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/src/android/app/src/main/res/layout/fragment_options.xml b/src/android/app/src/main/res/layout/fragment_options.xml new file mode 100644 index 00000000..5929f81d --- /dev/null +++ b/src/android/app/src/main/res/layout/fragment_options.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/src/android/app/src/main/res/layout/settings_checkbox.xml b/src/android/app/src/main/res/layout/layout_checkbox.xml similarity index 50% rename from src/android/app/src/main/res/layout/settings_checkbox.xml rename to src/android/app/src/main/res/layout/layout_checkbox.xml index a3fe1c3a..ca21b29c 100644 --- a/src/android/app/src/main/res/layout/settings_checkbox.xml +++ b/src/android/app/src/main/res/layout/layout_checkbox.xml @@ -1,35 +1,47 @@ + android:layout_marginHorizontal="8dp" + android:layout_marginTop="8dp" + android:layout_toStartOf="@+id/checkbox" + android:text="Checkbox label" + android:textAppearance="?attr/textAppearanceHeadline6" + tools:ignore="HardcodedText" /> + android:text="Checkbox description" + android:textAlignment="textStart" + android:textAppearance="?attr/textAppearanceSubtitle1" + tools:ignore="HardcodedText" /> - - \ No newline at end of file + diff --git a/src/android/app/src/main/res/layout/layout_simple_button.xml b/src/android/app/src/main/res/layout/layout_simple_button.xml new file mode 100644 index 00000000..e0f23783 --- /dev/null +++ b/src/android/app/src/main/res/layout/layout_simple_button.xml @@ -0,0 +1,20 @@ + + + + + diff --git a/src/android/app/src/main/res/layout/layout_single_selection.xml b/src/android/app/src/main/res/layout/layout_single_selection.xml new file mode 100644 index 00000000..9a61b79a --- /dev/null +++ b/src/android/app/src/main/res/layout/layout_single_selection.xml @@ -0,0 +1,26 @@ + + + + + + + diff --git a/src/android/app/src/main/res/layout/layout_single_selection_item.xml b/src/android/app/src/main/res/layout/layout_single_selection_item.xml new file mode 100644 index 00000000..d117a543 --- /dev/null +++ b/src/android/app/src/main/res/layout/layout_single_selection_item.xml @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/src/android/app/src/main/res/layout/layout_slider.xml b/src/android/app/src/main/res/layout/layout_slider.xml new file mode 100644 index 00000000..cbf47ceb --- /dev/null +++ b/src/android/app/src/main/res/layout/layout_slider.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/src/android/app/src/main/res/layout/layout_spinner.xml b/src/android/app/src/main/res/layout/layout_spinner.xml deleted file mode 100644 index 54235f90..00000000 --- a/src/android/app/src/main/res/layout/layout_spinner.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - diff --git a/src/android/app/src/main/res/layout/settings_header.xml b/src/android/app/src/main/res/layout/settings_header.xml deleted file mode 100644 index 4799ea07..00000000 --- a/src/android/app/src/main/res/layout/settings_header.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - diff --git a/src/android/app/src/main/res/navigation/nav_settings_graph.xml b/src/android/app/src/main/res/navigation/nav_settings_graph.xml index 5028a870..6565d182 100644 --- a/src/android/app/src/main/res/navigation/nav_settings_graph.xml +++ b/src/android/app/src/main/res/navigation/nav_settings_graph.xml @@ -3,7 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/nav_settings_graph" - app:startDestination="@id/inputSettingsFragment"> + app:startDestination="@id/optionsFragment"> + + + + + + + \ No newline at end of file diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 619d76d9..e0963e2a 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -53,7 +53,7 @@ Cancel Controller %1d Input Settings - Emulated controller: + Emulated controller Emulated controller: %1s Are you sure you want to exit? Exit Confirmation @@ -65,4 +65,30 @@ Right Axis Extra Nunchuck + Add the games path to scan for games displayed in the game list + No games folder selected + Set games folder + Options + Selected controller: %1s + Async shader compile + Enables async shader and pipeline compilation. Reduces stutter at the cost of objects not rendering for a short time + Graphics settings + Off + Double buffering + Triple buffering + VSync + Accurate barriers + Disabling the accurate barriers option will lead to flickering graphics but may improve performance. It is highly recommended to leave it turned on + TV + Enable audio output for the Wii U TV + Gamepad + Enable audio output for for the Wii U Gamepad + Audio settings + Mono + Stereo + Surround + TV channels + Gamepad channels + TV volume + Gamepad volume \ No newline at end of file diff --git a/src/gui/AndroidGui/AndroidAudio.cpp b/src/gui/AndroidGui/AndroidAudio.cpp index eaa8a9c8..f409fc82 100644 --- a/src/gui/AndroidGui/AndroidAudio.cpp +++ b/src/gui/AndroidGui/AndroidAudio.cpp @@ -15,6 +15,18 @@ void createAudioDevice(IAudioAPI::AudioAPI audioApi, sint32 channels, sint32 vol static constexpr int AX_FRAMES_PER_GROUP = 4; std::unique_lock lock(g_audioMutex); auto& audioDevice = isTV ? g_tvAudio : g_padAudio; + switch (channels) + { + case 0: + channels = 1; + break; + case 2: + channels = 6; + break; + default: // stereo + channels = 2; + break; + } switch (audioApi) { #if HAS_CUBEB