lots of ui menu changes

This commit is contained in:
izzy2lost
2025-09-11 23:14:41 -04:00
parent 1c676f1250
commit d8fd73eb96
25 changed files with 1323 additions and 607 deletions
@@ -114,6 +114,13 @@ public class GameSettingsDialogFragment extends DialogFragment {
serialView.setVisibility(View.GONE);
}
// Aspect Ratio Spinner
Spinner spAspectRatio = view.findViewById(R.id.sp_aspect_ratio);
ArrayAdapter<CharSequence> aspectAdapter = ArrayAdapter.createFromResource(ctx,
R.array.aspect_ratio_entries, android.R.layout.simple_spinner_item);
aspectAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spAspectRatio.setAdapter(aspectAdapter);
// Blending Accuracy Spinner
Spinner spBlendingAccuracy = view.findViewById(R.id.sp_blending_accuracy);
ArrayAdapter<CharSequence> blendingAdapter = ArrayAdapter.createFromResource(ctx,
@@ -147,6 +154,7 @@ public class GameSettingsDialogFragment extends DialogFragment {
android.content.SharedPreferences gp = ctx.getSharedPreferences("app_prefs", Context.MODE_PRIVATE);
int gRenderer = gp.getInt("renderer", -1);
float gScale = gp.getFloat("upscale_multiplier", 1.0f);
int gAspect = gp.getInt("aspect_ratio", 1);
int gBlend = gp.getInt("blending_accuracy", 1);
boolean gWide = gp.getBoolean("widescreen_patches", true);
boolean gNoInt = gp.getBoolean("no_interlacing_patches", true);
@@ -157,6 +165,7 @@ public class GameSettingsDialogFragment extends DialogFragment {
// Map to indices
int defaultRendererIdx = (gRenderer == -1 ? 0 : (gRenderer == 14 ? 1 : (gRenderer == 12 ? 2 : 3)));
int defaultScaleIdx = Math.max(0, Math.min(7, Math.round(gScale) - 1));
spAspectRatio.setSelection(Math.max(0, Math.min(4, gAspect)));
spRenderer.setSelection(defaultRendererIdx);
spResolution.setSelection(defaultScaleIdx);
spBlendingAccuracy.setSelection(Math.max(0, Math.min(5, gBlend)));
@@ -206,6 +215,22 @@ public class GameSettingsDialogFragment extends DialogFragment {
if (m.find()) {
try { float mult = Float.parseFloat(m.group(1)); int sel = Math.max(0, Math.min(7, Math.round(mult - 1))); spResolution.setSelection(sel); } catch (Exception ignored) {}
}
m = java.util.regex.Pattern.compile("(?m)^AspectRatio=\\s*(.+)$").matcher(content);
if (m.find()) {
String av = m.group(1).trim();
int idx = 1; // default Auto 4:3/3:2
try {
int num = Integer.parseInt(av);
if (num >= 0 && num <= 4) idx = num;
} catch (Exception e) {
if ("Stretch".equalsIgnoreCase(av)) idx = 0;
else if ("Auto".equalsIgnoreCase(av)) idx = 1;
else if ("4:3".equalsIgnoreCase(av)) idx = 2;
else if ("16:9".equalsIgnoreCase(av)) idx = 3;
else if ("10:7".equalsIgnoreCase(av)) idx = 4;
}
spAspectRatio.setSelection(idx);
}
m = java.util.regex.Pattern.compile("(?m)^accurate_blending_unit=\\s*(.+)$").matcher(content);
if (m.find()) {
String bv = m.group(1).trim();
@@ -255,6 +280,7 @@ public class GameSettingsDialogFragment extends DialogFragment {
}
} catch (Throwable ignored) {
// Fallback to defaults if loading fails
spAspectRatio.setSelection(1);
spBlendingAccuracy.setSelection(1);
// Mirror global default when error
android.content.SharedPreferences prefs = ctx.getSharedPreferences("app_prefs", Context.MODE_PRIVATE);
@@ -276,6 +302,7 @@ public class GameSettingsDialogFragment extends DialogFragment {
.setView(view)
.setNegativeButton("Cancel", (d, w) -> d.dismiss())
.setPositiveButton("Save", (d, w) -> {
final int aspectIdx = spAspectRatio.getSelectedItemPosition();
final int blendLevel = spBlendingAccuracy.getSelectedItemPosition();
final int rendererIdx = spRenderer.getSelectedItemPosition();
final int resIdx = spResolution.getSelectedItemPosition();
@@ -288,7 +315,7 @@ public class GameSettingsDialogFragment extends DialogFragment {
// Persist per-game INI explicitly (supports Auto as well)
writeGameSettingsIni(ctx, gameSerial, gameCrc,
blendLevel, rendererIdx, resIdx, wide, noInt,
aspectIdx, blendLevel, rendererIdx, resIdx, wide, noInt,
/*enablePatches*/ enablePatches, enableCheats);
// Live-apply per-game settings in one batch
@@ -301,6 +328,7 @@ public class GameSettingsDialogFragment extends DialogFragment {
else renderer = 13;
float scale = Math.max(1, Math.min(8, resIdx + 1));
NativeApp.setAspectRatio(aspectIdx);
NativeApp.setLoadTextures(loadTex);
NativeApp.setAsyncTextureLoading(asyncTex);
NativeApp.applyPerGameSettingsBatch(renderer, scale, blendLevel, wide, noInt, enablePatches, enableCheats);
@@ -385,6 +413,7 @@ public class GameSettingsDialogFragment extends DialogFragment {
private static void writeGameSettingsIni(Context ctx,
String gameSerial,
String gameCrc,
int aspectRatioIdx,
int blendingAccuracyIdx,
int rendererIdx,
int resolutionIdx,
@@ -403,11 +432,13 @@ public class GameSettingsDialogFragment extends DialogFragment {
String rendererName = (rendererIdx == 0) ? "Auto" : (rendererIdx == 1 ? "Vulkan" : (rendererIdx == 2 ? "OpenGL" : "Software"));
float upscale = Math.max(1, Math.min(8, resolutionIdx + 1));
int abl = Math.max(0, Math.min(5, blendingAccuracyIdx));
int aspectRatio = Math.max(0, Math.min(4, aspectRatioIdx));
StringBuilder sb = new StringBuilder();
sb.append("[EmuCore/GS]\n");
sb.append("Renderer=").append(rendererName).append('\n');
sb.append("upscale_multiplier=").append((int) upscale).append('\n');
sb.append("AspectRatio=").append(aspectRatio).append('\n');
sb.append("accurate_blending_unit=").append(abl).append('\n');
sb.append('\n');
sb.append("[EmuCore]\n");
File diff suppressed because it is too large Load Diff
@@ -1,69 +0,0 @@
package com.izzy2lost.psx2;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class LettersAdapter extends RecyclerView.Adapter<LettersAdapter.VH> {
public interface OnClick {
void onClick(char letter);
}
private final List<Character> letters;
private final OnClick onClick;
public LettersAdapter(List<Character> letters, OnClick onClick) {
this.letters = letters;
this.onClick = onClick;
setHasStableIds(true);
}
@Override
public long getItemId(int position) {
// Ensure uniqueness across the infinite range by including position
return position;
}
@NonNull
@Override
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_letter, parent, false);
return new VH(v);
}
@Override
public void onBindViewHolder(@NonNull VH holder, int position) {
int n = letters != null ? letters.size() : 0;
if (n == 0) return;
int real = position % n;
char ch = letters.get(real);
holder.text.setText(String.valueOf(ch));
holder.itemView.setOnClickListener(v -> {
android.util.Log.d("LettersAdapter", "Letter clicked: " + ch + " at position " + position);
if (onClick != null) {
onClick.onClick(ch);
} else {
android.util.Log.d("LettersAdapter", "onClick is null!");
}
});
}
@Override
public int getItemCount() {
return (letters != null && !letters.isEmpty()) ? Integer.MAX_VALUE : 0;
}
static class VH extends RecyclerView.ViewHolder {
final TextView text;
VH(@NonNull View itemView) {
super(itemView);
text = itemView.findViewById(R.id.text_letter);
}
}
}
@@ -18,6 +18,8 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.view.ViewGroup;
import android.content.res.ColorStateList;
import android.graphics.Color;
@@ -74,6 +76,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
private boolean joyRightPressed = false;
private boolean controllerUiApplied = false;
private AlertDialog mBiosPromptDialog = null;
private boolean mControllerHintShowing = false;
private boolean isThread() {
if (mEmulationThread != null) {
@@ -223,6 +226,15 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
w.setAttributes(lp);
}
// Common flags for legacy immersive (works as belt-and-suspenders on newer API too)
final int legacyFlags = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Tell Window to lay out edge-to-edge and hide all system bars
w.setDecorFitsSystemWindows(false);
@@ -231,15 +243,22 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
controller.hide(WindowInsets.Type.systemBars());
controller.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
}
// Also apply legacy flags to decor; helps on 3-button nav devices
View decor = w.getDecorView();
decor.setSystemUiVisibility(legacyFlags);
decor.setOnSystemUiVisibilityChangeListener(vis -> {
if ((vis & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
decor.setSystemUiVisibility(legacyFlags);
}
});
} else {
View decor = w.getDecorView();
int flags = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
decor.setSystemUiVisibility(flags);
decor.setSystemUiVisibility(legacyFlags);
decor.setOnSystemUiVisibilityChangeListener(vis -> {
if ((vis & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
decor.setSystemUiVisibility(legacyFlags);
}
});
}
}
@@ -314,7 +333,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
}
private static final String[] GAME_EXTS = new String[]{
".iso", ".bin", ".img", ".mdf", ".nrg", ".chd"
".iso", ".bin", ".img", ".mdf", ".nrg", ".chd", ".cso", ".zso", ".gz"
};
private static boolean hasGameExt(String name) {
@@ -483,11 +502,23 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
if (btn_settings != null) {
btn_settings.setOnClickListener(v -> {
try {
// Refresh drawer settings before opening
refreshDrawerSettings();
DrawerLayout drawer = findViewById(R.id.drawer_layout);
if (drawer != null) drawer.openDrawer(androidx.core.view.GravityCompat.START);
} catch (Throwable ignored) {}
});
}
// Pause/Play button toggles emulation pause state
MaterialButton btn_pause_play = findViewById(R.id.btn_pause_play);
if (btn_pause_play != null) {
btn_pause_play.setOnClickListener(v -> {
try {
togglePauseState();
} catch (Throwable ignored) {}
});
}
// Wire left drawer header controls (title/power/reboot/renderer)
try {
NavigationView nav = findViewById(R.id.nav_view);
@@ -542,6 +573,18 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
} catch (Throwable ignored) {}
});
}
View btnGames = header.findViewById(R.id.drawer_btn_games);
if (btnGames != null) {
btnGames.setOnClickListener(v -> {
try {
// Close drawer first
DrawerLayout drawer = findViewById(R.id.drawer_layout);
if (drawer != null) drawer.closeDrawer(androidx.core.view.GravityCompat.START);
// Open games dialog
openGamesDialog();
} catch (Throwable ignored) {}
});
}
if (btnGameState != null) {
btnGameState.setOnClickListener(v -> {
try {
@@ -550,6 +593,9 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
} catch (Throwable ignored) {}
});
}
// Setup drawer settings controls to mirror quick actions
setupDrawerSettings(header);
}
}
} catch (Throwable ignored) {}
@@ -809,6 +855,11 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
// Hide on-screen touch controls when a physical controller is connected
setControlsVisible(!hasController);
// Show controller hint when no controller is connected (only once)
if (!hasController) {
showControllerHintIfNeeded();
}
}
private int getCurrentRendererPref() {
@@ -1333,7 +1384,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
SDLControllerManager.initialize();
// Load and apply saved settings
SettingsDialogFragment.loadAndApplySettings(this);
loadAndApplyStoredSettings();
mHIDDeviceManager = HIDDeviceManager.acquire(this);
// Initialize HID device manager for USB and Bluetooth controllers
@@ -1360,6 +1411,14 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
if(!isThread()) {
mEmulationThread = new Thread(() -> NativeApp.runVMThread(m_szGamefile));
mEmulationThread.start();
// Show pause button when game starts (game is always running initially)
runOnUiThread(() -> {
MaterialButton btn_pause_play = findViewById(R.id.btn_pause_play);
if (btn_pause_play != null) {
btn_pause_play.setVisibility(View.VISIBLE);
btn_pause_play.setIcon(ContextCompat.getDrawable(this, R.drawable.pause_circle_24px));
}
});
}
}
@@ -1395,6 +1454,7 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
} else {
// No game loaded; just shutdown for safety
NativeApp.shutdown();
updatePausePlayButton();
}
}
@@ -1704,10 +1764,320 @@ public class MainActivity extends AppCompatActivity implements GamesCoverDialogF
break;
}
}
private void setupDrawerSettings(View header) {
if (header == null) return;
SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
// Aspect Ratio spinner - setup like QuickActions
Spinner spAspect = header.findViewById(R.id.drawer_sp_aspect_ratio);
if (spAspect != null) {
if (spAspect.getAdapter() == null) {
ArrayAdapter<CharSequence> aspectAdapter = ArrayAdapter.createFromResource(this,
R.array.aspect_ratio_entries, android.R.layout.simple_spinner_item);
aspectAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spAspect.setAdapter(aspectAdapter);
spAspect.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() {
@Override public void onItemSelected(android.widget.AdapterView<?> parent, View view, int position, long id) {
prefs.edit().putInt("aspect_ratio", position).apply();
try { NativeApp.setAspectRatio(position); } catch (Throwable ignored) {}
}
@Override public void onNothingSelected(android.widget.AdapterView<?> parent) {}
});
}
ArrayAdapter<?> adapter = (ArrayAdapter<?>) spAspect.getAdapter();
if (adapter != null) {
int savedAspect = prefs.getInt("aspect_ratio", 1);
if (savedAspect < 0 || savedAspect >= adapter.getCount()) savedAspect = 1;
spAspect.setSelection(savedAspect);
}
}
// Resolution Scale spinner
Spinner spScale = header.findViewById(R.id.drawer_sp_scale);
if (spScale != null) {
if (spScale.getAdapter() == null) {
ArrayAdapter<CharSequence> scaleAdapter = ArrayAdapter.createFromResource(this,
R.array.scale_entries, android.R.layout.simple_spinner_item);
scaleAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spScale.setAdapter(scaleAdapter);
spScale.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() {
@Override public void onItemSelected(android.widget.AdapterView<?> parent, View view, int position, long id) {
float scale = Math.max(1, Math.min(8, position + 1));
prefs.edit().putFloat("upscale_multiplier", scale).apply();
try { NativeApp.renderUpscalemultiplier(scale); } catch (Throwable ignored) {}
}
@Override public void onNothingSelected(android.widget.AdapterView<?> parent) {}
});
}
ArrayAdapter<?> adapter = (ArrayAdapter<?>) spScale.getAdapter();
if (adapter != null) {
float savedScale = prefs.getFloat("upscale_multiplier", 1.0f);
int scaleIndex = Math.max(0, Math.min(adapter.getCount() - 1, Math.round(savedScale) - 1));
spScale.setSelection(scaleIndex);
}
}
// Blending Accuracy spinner - setup like QuickActions
Spinner spBlending = header.findViewById(R.id.drawer_sp_blending_accuracy);
if (spBlending != null) {
if (spBlending.getAdapter() == null) {
ArrayAdapter<CharSequence> blendAdapter = ArrayAdapter.createFromResource(this,
R.array.blending_accuracy_entries, android.R.layout.simple_spinner_item);
blendAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spBlending.setAdapter(blendAdapter);
spBlending.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() {
@Override public void onItemSelected(android.widget.AdapterView<?> parent, View view, int position, long id) {
prefs.edit().putInt("blending_accuracy", position).apply();
try { NativeApp.setBlendingAccuracy(position); } catch (Throwable ignored) {}
}
@Override public void onNothingSelected(android.widget.AdapterView<?> parent) {}
});
}
ArrayAdapter<?> adapter = (ArrayAdapter<?>) spBlending.getAdapter();
if (adapter != null) {
int savedBlend = prefs.getInt("blending_accuracy", 1);
if (savedBlend < 0 || savedBlend >= adapter.getCount()) savedBlend = 1;
spBlending.setSelection(savedBlend);
}
}
// Setup switch listeners only once
setupDrawerSwitchListeners(header, prefs);
}
private void setupDrawerSwitchListeners(View header, SharedPreferences prefs) {
// Widescreen Patches switch
com.google.android.material.materialswitch.MaterialSwitch swWide = header.findViewById(R.id.drawer_sw_widescreen);
if (swWide != null && swWide.getTag() == null) {
swWide.setTag("setup"); // Mark as setup to avoid duplicate listeners
swWide.setOnCheckedChangeListener((buttonView, isChecked) -> {
prefs.edit().putBoolean("widescreen_patches", isChecked).apply();
try { NativeApp.setWidescreenPatches(isChecked); } catch (Throwable ignored) {}
});
}
// No Interlacing switch
com.google.android.material.materialswitch.MaterialSwitch swNoInt = header.findViewById(R.id.drawer_sw_no_interlacing);
if (swNoInt != null && swNoInt.getTag() == null) {
swNoInt.setTag("setup");
swNoInt.setOnCheckedChangeListener((buttonView, isChecked) -> {
prefs.edit().putBoolean("no_interlacing_patches", isChecked).apply();
try { NativeApp.setNoInterlacingPatches(isChecked); } catch (Throwable ignored) {}
});
}
// Load Textures switch
com.google.android.material.materialswitch.MaterialSwitch swLoadTex = header.findViewById(R.id.drawer_sw_load_textures);
if (swLoadTex != null && swLoadTex.getTag() == null) {
swLoadTex.setTag("setup");
swLoadTex.setOnCheckedChangeListener((buttonView, isChecked) -> {
prefs.edit().putBoolean("load_textures", isChecked).apply();
try { NativeApp.setLoadTextures(isChecked); } catch (Throwable ignored) {}
});
}
// Async Textures switch
com.google.android.material.materialswitch.MaterialSwitch swAsyncTex = header.findViewById(R.id.drawer_sw_async_textures);
if (swAsyncTex != null && swAsyncTex.getTag() == null) {
swAsyncTex.setTag("setup");
swAsyncTex.setOnCheckedChangeListener((buttonView, isChecked) -> {
prefs.edit().putBoolean("async_texture_loading", isChecked).apply();
try { NativeApp.setAsyncTextureLoading(isChecked); } catch (Throwable ignored) {}
});
}
// Precache Textures switch
com.google.android.material.materialswitch.MaterialSwitch swPrecache = header.findViewById(R.id.drawer_sw_precache_textures);
if (swPrecache != null && swPrecache.getTag() == null) {
swPrecache.setTag("setup");
swPrecache.setOnCheckedChangeListener((buttonView, isChecked) -> {
prefs.edit().putBoolean("precache_textures", isChecked).apply();
try { NativeApp.setPrecacheTextureReplacements(isChecked); } catch (Throwable ignored) {}
});
}
// HUD Developer switch
com.google.android.material.materialswitch.MaterialSwitch swDevHud = header.findViewById(R.id.drawer_sw_dev_hud);
if (swDevHud != null && swDevHud.getTag() == null) {
swDevHud.setTag("setup");
swDevHud.setOnCheckedChangeListener((buttonView, isChecked) -> {
prefs.edit().putBoolean("hud_visible", isChecked).apply();
try { NativeApp.setHudVisible(isChecked); } catch (Throwable ignored) {}
});
}
}
private void refreshDrawerSettings() {
try {
NavigationView nav = findViewById(R.id.nav_view);
if (nav != null && nav.getHeaderCount() > 0) {
View header = nav.getHeaderView(0);
if (header != null) {
SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
// Refresh switch states only (spinners are set up once in setupDrawerSettings)
com.google.android.material.materialswitch.MaterialSwitch swWide = header.findViewById(R.id.drawer_sw_widescreen);
if (swWide != null) {
swWide.setChecked(prefs.getBoolean("widescreen_patches", true));
}
com.google.android.material.materialswitch.MaterialSwitch swNoInt = header.findViewById(R.id.drawer_sw_no_interlacing);
if (swNoInt != null) {
swNoInt.setChecked(prefs.getBoolean("no_interlacing_patches", true));
}
com.google.android.material.materialswitch.MaterialSwitch swLoadTex = header.findViewById(R.id.drawer_sw_load_textures);
if (swLoadTex != null) {
swLoadTex.setChecked(prefs.getBoolean("load_textures", false));
}
com.google.android.material.materialswitch.MaterialSwitch swAsyncTex = header.findViewById(R.id.drawer_sw_async_textures);
if (swAsyncTex != null) {
swAsyncTex.setChecked(prefs.getBoolean("async_texture_loading", true));
}
com.google.android.material.materialswitch.MaterialSwitch swPrecache = header.findViewById(R.id.drawer_sw_precache_textures);
if (swPrecache != null) {
swPrecache.setChecked(prefs.getBoolean("precache_textures", false));
}
com.google.android.material.materialswitch.MaterialSwitch swDevHud = header.findViewById(R.id.drawer_sw_dev_hud);
if (swDevHud != null) {
swDevHud.setChecked(prefs.getBoolean("hud_visible", false));
}
}
}
} catch (Throwable ignored) {}
}
private void loadAndApplyStoredSettings() {
try {
SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
// Apply aspect ratio setting
int aspectRatio = prefs.getInt("aspect_ratio", 1);
NativeApp.setAspectRatio(aspectRatio);
// Apply blending accuracy setting
int blendingAccuracy = prefs.getInt("blending_accuracy", 1);
NativeApp.setBlendingAccuracy(blendingAccuracy);
// Apply other settings
boolean widescreenPatches = prefs.getBoolean("widescreen_patches", true);
NativeApp.setWidescreenPatches(widescreenPatches);
boolean noInterlacing = prefs.getBoolean("no_interlacing_patches", true);
NativeApp.setNoInterlacingPatches(noInterlacing);
boolean loadTextures = prefs.getBoolean("load_textures", false);
NativeApp.setLoadTextures(loadTextures);
boolean asyncTextures = prefs.getBoolean("async_texture_loading", true);
NativeApp.setAsyncTextureLoading(asyncTextures);
boolean precacheTextures = prefs.getBoolean("precache_textures", false);
NativeApp.setPrecacheTextureReplacements(precacheTextures);
// Apply renderer setting
int renderer = prefs.getInt("renderer", -1);
NativeApp.renderGpu(renderer);
} catch (Throwable ignored) {}
}
private void showControllerHintIfNeeded() {
// Prevent multiple dialogs from showing
if (mControllerHintShowing) return;
SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
boolean hasShownHint = prefs.getBoolean("controller_hint_shown", false);
// Only show the hint once, and only if setup is complete
if (!hasShownHint && prefs.getBoolean("first_run_done", false)) {
mControllerHintShowing = true;
// Small delay to avoid showing immediately on startup
findViewById(android.R.id.content).postDelayed(() -> {
if (!isFinishing() && !mSetupWizardActive && mControllerHintShowing) {
showControllerHintDialog();
}
}, 2000); // 2 second delay
}
}
private void showControllerHintDialog() {
// Double-check to prevent multiple dialogs
if (!mControllerHintShowing) return;
View dialogView = getLayoutInflater().inflate(R.layout.dialog_controller_hint, null);
com.google.android.material.materialswitch.MaterialSwitch swDontShow = dialogView.findViewById(R.id.sw_dont_show_again);
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this,
com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog)
.setCustomTitle(UiUtils.centeredDialogTitle(this, "Controller Tip"))
.setView(dialogView)
.setPositiveButton("Got it!", (dialog, which) -> {
SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
if (swDontShow != null && swDontShow.isChecked()) {
prefs.edit().putBoolean("controller_hint_shown", true).apply();
}
mControllerHintShowing = false;
dialog.dismiss();
})
.setCancelable(true)
.setOnDismissListener(dialog -> {
mControllerHintShowing = false;
});
AlertDialog dialog = builder.create();
dialog.show();
}
// Pause/Play toggle functionality
public void togglePauseState() {
try {
boolean isPaused = NativeApp.isPaused();
if (isPaused) {
NativeApp.resume();
} else {
NativeApp.pause();
}
updatePausePlayButton();
} catch (Throwable ignored) {}
}
private void updatePausePlayButton() {
MaterialButton btn_pause_play = findViewById(R.id.btn_pause_play);
if (btn_pause_play != null) {
try {
boolean isPaused = NativeApp.isPaused();
boolean hasGame = hasSelectedGame() && isThread();
// Show button only when a game is running
btn_pause_play.setVisibility(hasGame ? View.VISIBLE : View.GONE);
if (hasGame) {
// Update icon to show the action it will perform
if (isPaused) {
// Game is paused, show pause icon (will pause more/stay paused)
btn_pause_play.setIcon(ContextCompat.getDrawable(this, R.drawable.pause_circle_24px));
} else {
// Game is running, show play icon (will keep playing)
btn_pause_play.setIcon(ContextCompat.getDrawable(this, R.drawable.play_circle_24px));
}
}
} catch (Throwable ignored) {}
}
}
// Call this method when game starts/stops to update button visibility
public void updateGameState() {
updatePausePlayButton();
}
}
@@ -159,6 +159,7 @@ public class NativeApp {
public static native void pause();
public static native void resume();
public static native boolean isPaused();
public static native void shutdown();
public static native boolean saveStateToSlot(int slot);
@@ -10,6 +10,7 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.materialswitch.MaterialSwitch;
import android.widget.Spinner;
@@ -22,13 +23,14 @@ public class QuickActionsDialogFragment extends DialogFragment {
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
View view = getLayoutInflater().inflate(R.layout.dialog_quick_actions, null, false);
MaterialButton btnPower = view.findViewById(R.id.btn_quick_power);
MaterialButton btnReboot = view.findViewById(R.id.btn_quick_reboot);
FloatingActionButton btnPower = view.findViewById(R.id.btn_quick_power);
FloatingActionButton btnReboot = view.findViewById(R.id.btn_quick_reboot);
com.google.android.material.button.MaterialButtonToggleGroup tgRenderer = view.findViewById(R.id.qa_tg_renderer);
View tbAt = view.findViewById(R.id.qa_tb_at);
View tbVk = view.findViewById(R.id.qa_tb_vk);
View tbGl = view.findViewById(R.id.qa_tb_gl);
View tbSw = view.findViewById(R.id.qa_tb_sw);
FloatingActionButton btnPausePlay = view.findViewById(R.id.btn_quick_pause_play);
MaterialButton btnGames = view.findViewById(R.id.btn_quick_games);
MaterialButton btnSaves = view.findViewById(R.id.btn_quick_saves);
MaterialButton btnCancel = view.findViewById(R.id.btn_cancel);
@@ -51,6 +53,26 @@ public class QuickActionsDialogFragment extends DialogFragment {
.show());
}
// Pause/Play toggle
if (btnPausePlay != null) {
try {
boolean isPaused = NativeApp.isPaused();
btnPausePlay.setImageResource(R.drawable.play_pause_24px);
} catch (Throwable ignored) {}
btnPausePlay.setOnClickListener(v -> {
try {
if (requireActivity() instanceof MainActivity) {
((MainActivity) requireActivity()).togglePauseState();
} else {
boolean paused = NativeApp.isPaused();
if (paused) NativeApp.resume(); else NativeApp.pause();
}
boolean isPausedNow = NativeApp.isPaused();
btnPausePlay.setImageResource(R.drawable.play_pause_24px);
} catch (Throwable ignored) {}
});
}
// Reboot game
if (btnReboot != null) {
btnReboot.setOnClickListener(v -> new MaterialAlertDialogBuilder(requireContext(),
@@ -208,3 +230,5 @@ public class QuickActionsDialogFragment extends DialogFragment {
System.exit(0);
}
}
@@ -56,12 +56,16 @@ public class SetupWizardDialogFragment extends DialogFragment {
int pad = (int)(24 * getResources().getDisplayMetrics().density);
root.setPadding(pad, pad, pad, pad);
root.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
// Use theme background
root.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.md_theme_surface));
titleView = new TextView(requireContext());
titleView.setText("Welcome! Let's set up PSX2");
titleView.setTextSize(22f);
titleView.setTextSize(26f);
titleView.setGravity(Gravity.CENTER_HORIZONTAL);
titleView.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
titleView.setTextColor(ContextCompat.getColor(requireContext(), R.color.md_theme_tertiaryFixedDim));
titleView.setTypeface(titleView.getTypeface(), android.graphics.Typeface.BOLD);
root.addView(titleView);
// Subtitle removed; using inline hint near the Done button instead.
@@ -144,7 +148,8 @@ public class SetupWizardDialogFragment extends DialogFragment {
hintView.setTextSize(14f);
hintView.setGravity(Gravity.CENTER_HORIZONTAL);
hintView.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
hintView.setAlpha(0.8f);
hintView.setTextColor(ContextCompat.getColor(requireContext(), R.color.brand_primary));
hintView.setAlpha(0.9f);
LinearLayout.LayoutParams hintLp = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
hintLp.topMargin = (int)(8 * getResources().getDisplayMetrics().density);
hintView.setLayoutParams(hintLp);
@@ -199,11 +204,37 @@ public class SetupWizardDialogFragment extends DialogFragment {
btnGames.setIcon(step2 ? ContextCompat.getDrawable(requireContext(), R.drawable.check_circle_24px) : null);
btnBios.setIcon(step3 ? ContextCompat.getDrawable(requireContext(), R.drawable.check_circle_24px) : null);
// Add theme accent to completed buttons
int themeAccent = ContextCompat.getColor(requireContext(), R.color.md_theme_tertiary);
int defaultColor = ContextCompat.getColor(requireContext(), R.color.md_theme_onSurface);
btnData.setIconTint(step1 ? android.content.res.ColorStateList.valueOf(themeAccent) : null);
btnGames.setIconTint(step2 ? android.content.res.ColorStateList.valueOf(themeAccent) : null);
btnBios.setIconTint(step3 ? android.content.res.ColorStateList.valueOf(themeAccent) : null);
btnGames.setEnabled(step1);
btnBios.setEnabled(step1 && step2);
boolean doneEnabled = (step1 && step2 && step3);
btnDone.setEnabled(doneEnabled);
if (hintView != null) hintView.setVisibility(doneEnabled ? View.GONE : View.VISIBLE);
// Style the Done button when ready
if (doneEnabled) {
btnDone.setBackgroundTintList(android.content.res.ColorStateList.valueOf(themeAccent));
btnDone.setTextColor(ContextCompat.getColor(requireContext(), R.color.md_theme_onTertiary));
if (hintView != null) {
hintView.setText("🎉 Ready to go! Tap Done to start.");
hintView.setTextColor(ContextCompat.getColor(requireContext(), R.color.md_theme_tertiaryFixedDim));
}
} else {
btnDone.setBackgroundTintList(null);
btnDone.setTextColor(defaultColor);
if (hintView != null) {
hintView.setText("Complete all steps to finish.");
hintView.setTextColor(ContextCompat.getColor(requireContext(), R.color.brand_primary));
}
}
if (hintView != null) hintView.setVisibility(View.VISIBLE);
}
}