mirror of
https://github.com/WinampDesktop/winamp.git
synced 2025-06-19 19:35:41 -04:00
Android: Add per-game settings and properties UI
This commit is contained in:
@ -43,6 +43,15 @@
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.github.stenzek.duckstation.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".GamePropertiesActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:label="@string/activity_game_properties"
|
||||
android:parentActivityName=".MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.github.stenzek.duckstation.MainActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
|
@ -87,6 +87,11 @@ public class AndroidHostInterface {
|
||||
|
||||
public native GameListEntry[] getGameListEntries();
|
||||
|
||||
public native GameListEntry getGameListEntry(String path);
|
||||
|
||||
public native String getGameSettingValue(String path, String key);
|
||||
public native void setGameSettingValue(String path, String key, String value);
|
||||
|
||||
public native void resetSystem();
|
||||
|
||||
public native void loadState(boolean global, int slot);
|
||||
|
@ -0,0 +1,245 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.util.Property;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.ListFragment;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class GamePropertiesActivity extends AppCompatActivity {
|
||||
PropertyListAdapter mPropertiesListAdapter;
|
||||
GameListEntry mGameListEntry;
|
||||
|
||||
public ListAdapter getPropertyListAdapter() {
|
||||
if (mPropertiesListAdapter != null)
|
||||
return mPropertiesListAdapter;
|
||||
|
||||
mPropertiesListAdapter = new PropertyListAdapter(this);
|
||||
mPropertiesListAdapter.addItem("title", "Title", mGameListEntry.getTitle());
|
||||
mPropertiesListAdapter.addItem("filetitle", "File Title", mGameListEntry.getFileTitle());
|
||||
mPropertiesListAdapter.addItem("serial", "Serial", mGameListEntry.getCode());
|
||||
mPropertiesListAdapter.addItem("type", "Type", mGameListEntry.getType().toString());
|
||||
mPropertiesListAdapter.addItem("path", "Path", mGameListEntry.getPath());
|
||||
mPropertiesListAdapter.addItem("region", "Region", mGameListEntry.getRegion().toString());
|
||||
mPropertiesListAdapter.addItem("compatibility", "Compatibility Rating", mGameListEntry.getCompatibilityRating().toString());
|
||||
return mPropertiesListAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
String path = getIntent().getStringExtra("path");
|
||||
if (path == null || path.isEmpty()) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
mGameListEntry = AndroidHostInterface.getInstance().getGameListEntry(path);
|
||||
if (mGameListEntry == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
setContentView(R.layout.settings_activity);
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.settings, new SettingsCollectionFragment(this))
|
||||
.commit();
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
setTitle(mGameListEntry.getTitle());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void displayError(String text) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(R.string.emulation_activity_error)
|
||||
.setMessage(text)
|
||||
.setNegativeButton(R.string.main_activity_ok, ((dialog, which) -> dialog.dismiss()))
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
private void createBooleanGameSetting(PreferenceScreen ps, String key, int titleId) {
|
||||
GameSettingPreference pref = new GameSettingPreference(ps.getContext(), mGameListEntry.getPath(), key, titleId);
|
||||
ps.addPreference(pref);
|
||||
}
|
||||
|
||||
private void createListGameSetting(PreferenceScreen ps, String key, int titleId, int entryId, int entryValuesId) {
|
||||
GameSettingPreference pref = new GameSettingPreference(ps.getContext(), mGameListEntry.getPath(), key, titleId, entryId, entryValuesId);
|
||||
ps.addPreference(pref);
|
||||
}
|
||||
|
||||
public static class GameSettingsFragment extends PreferenceFragmentCompat {
|
||||
private GamePropertiesActivity activity;
|
||||
|
||||
public GameSettingsFragment(GamePropertiesActivity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
final PreferenceScreen ps = getPreferenceManager().createPreferenceScreen(getContext());
|
||||
activity.createListGameSetting(ps, "CPUOverclock", R.string.settings_cpu_overclocking, R.array.settings_advanced_cpu_overclock_entries, R.array.settings_advanced_cpu_overclock_values);
|
||||
activity.createListGameSetting(ps,"CDROMReadSpeedup", R.string.settings_cdrom_read_speedup, R.array.settings_cdrom_read_speedup_entries, R.array.settings_cdrom_read_speedup_values);
|
||||
|
||||
activity.createListGameSetting(ps, "DisplayAspectRatio", R.string.settings_aspect_ratio, R.array.settings_display_aspect_ratio_names, R.array.settings_display_aspect_ratio_values);
|
||||
activity.createListGameSetting(ps, "DisplayCropMode", R.string.settings_crop_mode,R.array.settings_display_crop_mode_entries,R.array.settings_display_crop_mode_values);
|
||||
activity.createListGameSetting(ps,"GPUDownsampleMode", R.string.settings_downsample_mode, R.array.settings_downsample_mode_entries, R.array.settings_downsample_mode_values);
|
||||
activity.createBooleanGameSetting(ps, "DisplayLinearUpscaling", R.string.settings_linear_upscaling);
|
||||
activity.createBooleanGameSetting(ps,"DisplayIntegerUpscaling",R.string.settings_integer_upscaling);
|
||||
activity.createBooleanGameSetting(ps,"DisplayForce4_3For24Bit",R.string.settings_force_4_3_for_24bit);
|
||||
|
||||
activity.createListGameSetting(ps, "GPUResolutionScale", R.string.settings_gpu_resolution_scale, R.array.settings_gpu_resolution_scale_entries, R.array.settings_gpu_resolution_scale_values);
|
||||
activity.createListGameSetting(ps, "GPUMSAA", R.string.settings_msaa, R.array.settings_gpu_msaa_entries, R.array.settings_gpu_msaa_values);
|
||||
activity.createBooleanGameSetting(ps, "GPUTrueColor", R.string.settings_true_color);
|
||||
activity.createBooleanGameSetting(ps,"GPUScaledDithering",R.string.settings_scaled_dithering);
|
||||
activity.createListGameSetting(ps, "GPUTextureFilter", R.string.settings_texture_filtering, R.array.settings_gpu_texture_filter_names, R.array.settings_gpu_texture_filter_values);
|
||||
activity.createBooleanGameSetting(ps,"GPUForceNTSCTimings",R.string.settings_force_ntsc_timings);
|
||||
activity.createBooleanGameSetting(ps, "GPUWidescreenHack", R.string.settings_widescreen_hack);
|
||||
activity.createBooleanGameSetting(ps, "GPUPGXP", R.string.settings_pgxp_geometry_correction);
|
||||
activity.createBooleanGameSetting(ps, "GPUPGXPDepthBuffer", R.string.settings_pgxp_depth_buffer);
|
||||
|
||||
setPreferenceScreen(ps);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ControllerSettingsFragment extends PreferenceFragmentCompat {
|
||||
private GamePropertiesActivity activity;
|
||||
|
||||
public ControllerSettingsFragment(GamePropertiesActivity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
private void createInputProfileSetting(PreferenceScreen ps) {
|
||||
final GameSettingPreference pref = new GameSettingPreference(ps.getContext(), activity.mGameListEntry.getPath(), "InputProfileName", R.string.settings_input_profile);
|
||||
|
||||
final String[] inputProfileNames = AndroidHostInterface.getInstance().getInputProfileNames();
|
||||
pref.setEntries(inputProfileNames);
|
||||
pref.setEntryValues(inputProfileNames);
|
||||
ps.addPreference(pref);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
final PreferenceScreen ps = getPreferenceManager().createPreferenceScreen(getContext());
|
||||
|
||||
activity.createListGameSetting(ps, "Controller1Type", R.string.settings_controller_type, R.array.settings_controller_type_entries, R.array.settings_controller_type_values);
|
||||
activity.createListGameSetting(ps, "MemoryCard1Type", R.string.settings_memory_card_1_type, R.array.settings_memory_card_mode_entries, R.array.settings_memory_card_mode_values);
|
||||
activity.createListGameSetting(ps, "MemoryCard2Type", R.string.settings_memory_card_2_type, R.array.settings_memory_card_mode_entries, R.array.settings_memory_card_mode_values);
|
||||
createInputProfileSetting(ps);
|
||||
|
||||
setPreferenceScreen(ps);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SettingsCollectionFragment extends Fragment {
|
||||
private GamePropertiesActivity activity;
|
||||
private SettingsCollectionAdapter adapter;
|
||||
private ViewPager2 viewPager;
|
||||
|
||||
public SettingsCollectionFragment(GamePropertiesActivity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_controller_mapping, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
adapter = new SettingsCollectionAdapter(activity, this);
|
||||
viewPager = view.findViewById(R.id.view_pager);
|
||||
viewPager.setAdapter(adapter);
|
||||
|
||||
TabLayout tabLayout = view.findViewById(R.id.tab_layout);
|
||||
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> {
|
||||
switch (position)
|
||||
{
|
||||
case 0: tab.setText("Summary"); break;
|
||||
case 1: tab.setText("Game Settings"); break;
|
||||
case 2: tab.setText("Controller Settings"); break;
|
||||
}
|
||||
}).attach();
|
||||
}
|
||||
}
|
||||
|
||||
public static class SettingsCollectionAdapter extends FragmentStateAdapter {
|
||||
private GamePropertiesActivity activity;
|
||||
|
||||
public SettingsCollectionAdapter(@NonNull GamePropertiesActivity activity, @NonNull Fragment fragment) {
|
||||
super(fragment);
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment createFragment(int position) {
|
||||
switch (position)
|
||||
{
|
||||
case 0: { // Summary
|
||||
ListFragment lf = new ListFragment();
|
||||
lf.setListAdapter(activity.getPropertyListAdapter());
|
||||
return lf;
|
||||
}
|
||||
|
||||
case 1: { // Game Settings
|
||||
return new GameSettingsFragment(activity);
|
||||
}
|
||||
|
||||
case 2: { // Controller Settings
|
||||
return new ControllerSettingsFragment(activity);
|
||||
}
|
||||
|
||||
// TODO: Memory Card Editor
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.preference.ListPreference;
|
||||
|
||||
public class GameSettingPreference extends ListPreference {
|
||||
private String mGamePath;
|
||||
|
||||
/**
|
||||
* Creates a boolean game property preference.
|
||||
*/
|
||||
public GameSettingPreference(Context context, String gamePath, String settingKey, int titleId) {
|
||||
super(context);
|
||||
mGamePath = gamePath;
|
||||
setPersistent(false);
|
||||
setTitle(titleId);
|
||||
setKey(settingKey);
|
||||
setIconSpaceReserved(false);
|
||||
setSummaryProvider(SimpleSummaryProvider.getInstance());
|
||||
|
||||
setEntries(R.array.settings_boolean_entries);
|
||||
setEntryValues(R.array.settings_boolean_values);
|
||||
|
||||
updateValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list game property preference.
|
||||
*/
|
||||
public GameSettingPreference(Context context, String gamePath, String settingKey, int titleId, int entryArray, int entryValuesArray) {
|
||||
super(context);
|
||||
mGamePath = gamePath;
|
||||
setPersistent(false);
|
||||
setTitle(titleId);
|
||||
setKey(settingKey);
|
||||
setIconSpaceReserved(false);
|
||||
setSummaryProvider(SimpleSummaryProvider.getInstance());
|
||||
|
||||
setEntries(entryArray);
|
||||
setEntryValues(entryValuesArray);
|
||||
|
||||
updateValue();
|
||||
}
|
||||
|
||||
private void updateValue() {
|
||||
final String value = AndroidHostInterface.getInstance().getGameSettingValue(mGamePath, getKey());
|
||||
if (value == null)
|
||||
super.setValue("null");
|
||||
else
|
||||
super.setValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(String value) {
|
||||
super.setValue(value);
|
||||
if (value.equals("null"))
|
||||
AndroidHostInterface.getInstance().setGameSettingValue(mGamePath, getKey(), null);
|
||||
else
|
||||
AndroidHostInterface.getInstance().setGameSettingValue(mGamePath, getKey(), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntries(CharSequence[] entries) {
|
||||
final int length = (entries != null) ? entries.length : 0;
|
||||
CharSequence[] newEntries = new CharSequence[length + 1];
|
||||
newEntries[0] = getContext().getString(R.string.game_properties_preference_use_global_setting);
|
||||
if (entries != null)
|
||||
System.arraycopy(entries, 0, newEntries, 1, entries.length);
|
||||
super.setEntries(newEntries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEntryValues(CharSequence[] entryValues) {
|
||||
final int length = (entryValues != null) ? entryValues.length : 0;
|
||||
CharSequence[] newEntryValues = new CharSequence[length + 1];
|
||||
newEntryValues[0] = "null";
|
||||
if (entryValues != null)
|
||||
System.arraycopy(entryValues, 0, newEntryValues, 1, length);
|
||||
super.setEntryValues(newEntryValues);
|
||||
}
|
||||
}
|
@ -151,6 +151,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
} else if (id == R.id.game_list_entry_menu_resume_game) {
|
||||
startEmulation(mGameList.getEntry(position).getPath(), true);
|
||||
return true;
|
||||
} else if (id == R.id.game_list_entry_menu_properties) {
|
||||
openGameProperties(mGameList.getEntry(position).getPath());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -356,6 +359,13 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean openGameProperties(String path) {
|
||||
Intent intent = new Intent(this, GamePropertiesActivity.class);
|
||||
intent.putExtra("path", path);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean startEmulation(String bootPath, boolean resumeState) {
|
||||
if (!doBIOSCheck())
|
||||
return false;
|
||||
|
@ -0,0 +1,84 @@
|
||||
package com.github.stenzek.duckstation;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class PropertyListAdapter extends BaseAdapter {
|
||||
private class Item {
|
||||
public String key;
|
||||
public String title;
|
||||
public String value;
|
||||
|
||||
Item(String key, String title, String value) {
|
||||
this.key = key;
|
||||
this.title = title;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
private Context mContext;
|
||||
private ArrayList<Item> mItems = new ArrayList<>();
|
||||
|
||||
public PropertyListAdapter(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public Item getItemByKey(String key) {
|
||||
for (Item it : mItems) {
|
||||
if (it.key.equals(key))
|
||||
return it;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public int addItem(String key, String title, String value) {
|
||||
if (getItemByKey(key) != null)
|
||||
return -1;
|
||||
|
||||
Item it = new Item(key, title, value);
|
||||
int position = mItems.size();
|
||||
mItems.add(it);
|
||||
return position;
|
||||
}
|
||||
|
||||
public boolean removeItem(Item item) {
|
||||
return mItems.remove(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return mItems.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return mItems.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
if (convertView == null) {
|
||||
convertView = LayoutInflater.from(mContext)
|
||||
.inflate(R.layout.layout_game_property_entry, parent, false);
|
||||
}
|
||||
|
||||
TextView titleView = (TextView)convertView.findViewById(R.id.property_title);
|
||||
TextView valueView = (TextView)convertView.findViewById(R.id.property_value);
|
||||
Item prop = mItems.get(position);
|
||||
titleView.setText(prop.title);
|
||||
valueView.setText(prop.value);
|
||||
return convertView;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/property_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:text="TextView"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/property_value"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_below="@id/property_title"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:text="TextView" />
|
||||
</RelativeLayout>
|
@ -7,4 +7,7 @@
|
||||
<item
|
||||
android:id="@+id/game_list_entry_menu_resume_game"
|
||||
android:title="@string/menu_game_list_entry_resume_game" />
|
||||
<item
|
||||
android:id="@+id/game_list_entry_menu_properties"
|
||||
android:title="Game Properties" />
|
||||
</menu>
|
@ -460,4 +460,12 @@
|
||||
<item>Box</item>
|
||||
<item>Adaptive</item>
|
||||
</string-array>
|
||||
<string-array name="settings_boolean_entries">
|
||||
<item>Disabled</item>
|
||||
<item>Enabled</item>
|
||||
</string-array>
|
||||
<string-array name="settings_boolean_values">
|
||||
<item>false</item>
|
||||
<item>true</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
@ -183,4 +183,7 @@
|
||||
<string name="settings_disable_all_enhancements">Disable All Enhancements</string>
|
||||
<string name="settings_summary_disable_all_enhancements">Temporarily disables all enhancements, which can be useful when debugging issues.</string>
|
||||
<string name="settings_downsample_mode">Downsampling</string>
|
||||
<string name="activity_game_properties">Game Properties</string>
|
||||
<string name="game_properties_preference_use_global_setting">Use Global Setting</string>
|
||||
<string name="settings_input_profile">Input Profile</string>
|
||||
</resources>
|
||||
|
Reference in New Issue
Block a user