android: Use modal navigation drawer as in game menu

This commit is contained in:
Charles Lombardo 2023-03-21 01:58:25 -04:00 committed by bunnei
parent 1f3b41366c
commit 273e81bb94
17 changed files with 342 additions and 372 deletions

View file

@ -8,15 +8,10 @@ import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.graphics.Rect import android.graphics.Rect
import android.os.Bundle import android.os.Bundle
import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
import androidx.activity.OnBackPressedCallback
import androidx.annotation.IntDef
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider.OnChangeListener import com.google.android.material.slider.Slider.OnChangeListener
@ -25,8 +20,9 @@ import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogSliderBinding import org.yuzu.yuzu_emu.databinding.DialogSliderBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.fragments.EmulationFragment import org.yuzu.yuzu_emu.fragments.EmulationFragment
import org.yuzu.yuzu_emu.fragments.MenuFragment import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
import org.yuzu.yuzu_emu.utils.ThemeHelper import org.yuzu.yuzu_emu.utils.ThemeHelper
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -37,11 +33,11 @@ open class EmulationActivity : AppCompatActivity() {
//private Intent foregroundService; //private Intent foregroundService;
var isActivityRecreated = false var isActivityRecreated = false
private var selectedTitle: String? = null
private var path: String? = null
private var menuVisible = false private var menuVisible = false
private var emulationFragment: EmulationFragment? = null private var emulationFragment: EmulationFragment? = null
private lateinit var game: Game
override fun onDestroy() { override fun onDestroy() {
// TODO(bunnei): Disable notifications until we support app suspension. // TODO(bunnei): Disable notifications until we support app suspension.
//stopService(foregroundService); //stopService(foregroundService);
@ -54,9 +50,7 @@ open class EmulationActivity : AppCompatActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
if (savedInstanceState == null) { if (savedInstanceState == null) {
// Get params we were passed // Get params we were passed
val gameToEmulate = intent game = intent.parcelable(EXTRA_SELECTED_GAME)!!
path = gameToEmulate.getStringExtra(EXTRA_SELECTED_GAME)
selectedTitle = gameToEmulate.getStringExtra(EXTRA_SELECTED_TITLE)
isActivityRecreated = false isActivityRecreated = false
} else { } else {
isActivityRecreated = true isActivityRecreated = true
@ -73,34 +67,26 @@ open class EmulationActivity : AppCompatActivity() {
emulationFragment = emulationFragment =
supportFragmentManager.findFragmentById(R.id.frame_emulation_fragment) as EmulationFragment? supportFragmentManager.findFragmentById(R.id.frame_emulation_fragment) as EmulationFragment?
if (emulationFragment == null) { if (emulationFragment == null) {
emulationFragment = EmulationFragment.newInstance(path) emulationFragment = EmulationFragment.newInstance(game)
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.add(R.id.frame_emulation_fragment, emulationFragment!!) .add(R.id.frame_emulation_fragment, emulationFragment!!)
.commit() .commit()
} }
title = selectedTitle title = game.title
// Start a foreground service to prevent the app from getting killed in the background // Start a foreground service to prevent the app from getting killed in the background
// TODO(bunnei): Disable notifications until we support app suspension. // TODO(bunnei): Disable notifications until we support app suspension.
//foregroundService = new Intent(EmulationActivity.this, ForegroundService.class); //foregroundService = new Intent(EmulationActivity.this, ForegroundService.class);
//startForegroundService(foregroundService); //startForegroundService(foregroundService);
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
toggleMenu()
}
})
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
outState.putString(EXTRA_SELECTED_GAME, path) outState.putParcelable(EXTRA_SELECTED_GAME, game)
outState.putString(EXTRA_SELECTED_TITLE, selectedTitle)
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
} }
private fun restoreState(savedInstanceState: Bundle) { private fun restoreState(savedInstanceState: Bundle) {
path = savedInstanceState.getString(EXTRA_SELECTED_GAME) game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!!
selectedTitle = savedInstanceState.getString(EXTRA_SELECTED_TITLE)
// If an alert prompt was in progress when state was restored, retry displaying it // If an alert prompt was in progress when state was restored, retry displaying it
NativeLibrary.retryDisplayAlertPrompt() NativeLibrary.retryDisplayAlertPrompt()
@ -110,6 +96,8 @@ open class EmulationActivity : AppCompatActivity() {
window.attributes.layoutInDisplayCutoutMode = window.attributes.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)
// It would be nice to use IMMERSIVE_STICKY, but that doesn't show the toolbar. // It would be nice to use IMMERSIVE_STICKY, but that doesn't show the toolbar.
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
@ -119,15 +107,6 @@ open class EmulationActivity : AppCompatActivity() {
View.SYSTEM_UI_FLAG_IMMERSIVE View.SYSTEM_UI_FLAG_IMMERSIVE
} }
fun handleMenuAction(action: Int) {
when (action) {
MENU_ACTION_EXIT -> {
emulationFragment!!.stopEmulation()
finish()
}
}
}
private fun editControlsPlacement() { private fun editControlsPlacement() {
if (emulationFragment!!.isConfiguringControls) { if (emulationFragment!!.isConfiguringControls) {
emulationFragment!!.stopConfiguringControls() emulationFragment!!.stopConfiguringControls()
@ -176,94 +155,14 @@ open class EmulationActivity : AppCompatActivity() {
.show() .show()
} }
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
var anyMenuClosed = false
var submenu = supportFragmentManager.findFragmentById(R.id.frame_submenu)
if (submenu != null && areCoordinatesOutside(submenu.view, event.x, event.y)) {
closeSubmenu()
submenu = null
anyMenuClosed = true
}
if (submenu == null) {
val menu = supportFragmentManager.findFragmentById(R.id.frame_menu)
if (menu != null && areCoordinatesOutside(menu.view, event.x, event.y)) {
closeMenu()
anyMenuClosed = true
}
}
if (anyMenuClosed) {
return true
}
}
return super.dispatchTouchEvent(event)
}
@Retention(AnnotationRetention.SOURCE)
@IntDef(
MENU_ACTION_EDIT_CONTROLS_PLACEMENT,
MENU_ACTION_TOGGLE_CONTROLS,
MENU_ACTION_ADJUST_SCALE,
MENU_ACTION_EXIT,
MENU_ACTION_SHOW_FPS,
MENU_ACTION_RESET_OVERLAY,
MENU_ACTION_SHOW_OVERLAY,
MENU_ACTION_OPEN_SETTINGS
)
annotation class MenuAction
private fun closeSubmenu(): Boolean {
return supportFragmentManager.popBackStackImmediate(
BACKSTACK_NAME_SUBMENU,
FragmentManager.POP_BACK_STACK_INCLUSIVE
)
}
private fun closeMenu(): Boolean {
menuVisible = false
return supportFragmentManager.popBackStackImmediate(
BACKSTACK_NAME_MENU,
FragmentManager.POP_BACK_STACK_INCLUSIVE
)
}
private fun toggleMenu() {
if (!closeMenu()) {
val fragment: Fragment = MenuFragment.newInstance()
supportFragmentManager.beginTransaction()
.setCustomAnimations(
R.animator.menu_slide_in_from_start,
R.animator.menu_slide_out_to_start,
R.animator.menu_slide_in_from_start,
R.animator.menu_slide_out_to_start
)
.add(R.id.frame_menu, fragment)
.addToBackStack(BACKSTACK_NAME_MENU)
.commit()
menuVisible = true
}
}
companion object { companion object {
private const val BACKSTACK_NAME_MENU = "menu"
private const val BACKSTACK_NAME_SUBMENU = "submenu"
const val EXTRA_SELECTED_GAME = "SelectedGame" const val EXTRA_SELECTED_GAME = "SelectedGame"
const val EXTRA_SELECTED_TITLE = "SelectedTitle"
const val MENU_ACTION_EDIT_CONTROLS_PLACEMENT = 0
const val MENU_ACTION_TOGGLE_CONTROLS = 1
const val MENU_ACTION_ADJUST_SCALE = 2
const val MENU_ACTION_EXIT = 3
const val MENU_ACTION_SHOW_FPS = 4
const val MENU_ACTION_RESET_OVERLAY = 6
const val MENU_ACTION_SHOW_OVERLAY = 7
const val MENU_ACTION_OPEN_SETTINGS = 8
private const val EMULATION_RUNNING_NOTIFICATION = 0x1000 private const val EMULATION_RUNNING_NOTIFICATION = 0x1000
@JvmStatic @JvmStatic
fun launch(activity: FragmentActivity, path: String?, title: String?) { fun launch(activity: FragmentActivity, game: Game) {
val launcher = Intent(activity, EmulationActivity::class.java) val launcher = Intent(activity, EmulationActivity::class.java)
launcher.putExtra(EXTRA_SELECTED_GAME, path) launcher.putExtra(EXTRA_SELECTED_GAME, game)
launcher.putExtra(EXTRA_SELECTED_TITLE, title)
activity.startActivity(launcher) activity.startActivity(launcher)
} }

View file

@ -13,7 +13,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import coil.load import coil.load
@ -23,8 +22,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.activities.EmulationActivity.Companion.launch
import org.yuzu.yuzu_emu.databinding.CardGameBinding import org.yuzu.yuzu_emu.databinding.CardGameBinding
import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.model.GameDatabase import org.yuzu.yuzu_emu.model.GameDatabase
import org.yuzu.yuzu_emu.utils.Log import org.yuzu.yuzu_emu.utils.Log
@ -181,7 +180,7 @@ class GameAdapter(private val activity: AppCompatActivity) : RecyclerView.Adapte
*/ */
override fun onClick(view: View) { override fun onClick(view: View) {
val holder = view.tag as GameViewHolder val holder = view.tag as GameViewHolder
launch((view.context as FragmentActivity), holder.game.path, holder.game.title) EmulationActivity.launch((view.context as AppCompatActivity), holder.game)
} }
private fun isValidGame(path: String): Boolean { private fun isValidGame(path: String): Boolean {

View file

@ -10,7 +10,14 @@ import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.view.* import android.view.*
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.widget.PopupMenu
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
@ -20,10 +27,15 @@ import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.activities.EmulationActivity import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.utils.DirectoryInitialization import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.DirectoryInitialization.DirectoryInitializationState import org.yuzu.yuzu_emu.utils.DirectoryInitialization.DirectoryInitializationState
import org.yuzu.yuzu_emu.utils.DirectoryStateReceiver import org.yuzu.yuzu_emu.utils.DirectoryStateReceiver
import org.yuzu.yuzu_emu.utils.InsetsHelper
import org.yuzu.yuzu_emu.utils.Log import org.yuzu.yuzu_emu.utils.Log
import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.FrameCallback { class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.FrameCallback {
private lateinit var preferences: SharedPreferences private lateinit var preferences: SharedPreferences
@ -35,6 +47,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
private var _binding: FragmentEmulationBinding? = null private var _binding: FragmentEmulationBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
private lateinit var game: Game
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
super.onAttach(context) super.onAttach(context)
if (context is EmulationActivity) { if (context is EmulationActivity) {
@ -54,8 +68,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
// So this fragment doesn't restart on configuration changes; i.e. rotation. // So this fragment doesn't restart on configuration changes; i.e. rotation.
retainInstance = true retainInstance = true
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
val gamePath = requireArguments().getString(KEY_GAMEPATH) game = requireArguments().parcelable(EmulationActivity.EXTRA_SELECTED_GAME)!!
emulationState = EmulationState(gamePath) emulationState = EmulationState(game.path)
} }
/** /**
@ -78,6 +92,57 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
// Setup overlay. // Setup overlay.
resetInputOverlay() resetInputOverlay()
updateShowFpsOverlay() updateShowFpsOverlay()
binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text =
game.title
binding.inGameMenu.setNavigationItemSelectedListener {
when (it.itemId) {
R.id.menu_pause_emulation -> {
if (emulationState.isPaused) {
emulationState.run(false)
it.title = resources.getString(R.string.emulation_pause)
it.icon = ResourcesCompat.getDrawable(
resources,
R.drawable.ic_pause,
requireContext().theme
)
} else {
emulationState.pause()
it.title = resources.getString(R.string.emulation_unpause)
it.icon = ResourcesCompat.getDrawable(
resources,
R.drawable.ic_play,
requireContext().theme
)
}
true
}
R.id.menu_settings -> {
SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "")
true
}
R.id.menu_overlay_controls -> {
showOverlayOptions()
true
}
R.id.menu_exit -> {
requireActivity().finish()
emulationState.stop()
true
}
else -> true
}
}
setInsets()
requireActivity().onBackPressedDispatcher.addCallback(
requireActivity(),
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (binding.drawerLayout.isOpen) binding.drawerLayout.close() else binding.drawerLayout.open()
}
})
} }
override fun onResume() { override fun onResume() {
@ -202,8 +267,30 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
NativeLibrary.DoFrame() NativeLibrary.DoFrame()
} }
fun stopEmulation() { private fun showOverlayOptions() {
emulationState.stop() val anchor = binding.inGameMenu.findViewById<View>(R.id.menu_overlay_controls)
val popup = PopupMenu(requireContext(), anchor)
popup.menuInflater.inflate(R.menu.menu_overlay_options, popup.menu)
popup.setOnMenuItemClickListener {
when (it.itemId) {
R.id.menu_edit_overlay -> {
binding.drawerLayout.close()
binding.surfaceInputOverlay.requestFocus()
startConfiguringControls()
true
}
R.id.menu_reset_overlay -> {
binding.drawerLayout.close()
resetInputOverlay()
true
}
else -> true
}
}
popup.show()
} }
fun startConfiguringControls() { fun startConfiguringControls() {
@ -219,6 +306,27 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
val isConfiguringControls: Boolean val isConfiguringControls: Boolean
get() = binding.surfaceInputOverlay.isInEditMode get() = binding.surfaceInputOverlay.isInEditMode
private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener(binding.inGameMenu) { v: View, windowInsets: WindowInsetsCompat ->
val cutInsets: Insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
var left = 0
var right = 0
if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR) {
left = cutInsets.left
} else {
right = cutInsets.right
}
// Don't use padding if the navigation bar isn't in the way
if (InsetsHelper.getBottomPaddingRequired(requireActivity()) > 0) {
v.setPadding(left, cutInsets.top, right, 0)
} else {
v.setPadding(left, cutInsets.top, right, 0)
}
windowInsets
}
}
private class EmulationState(private val mGamePath: String?) { private class EmulationState(private val mGamePath: String?) {
private var state: State private var state: State
private var surface: Surface? = null private var surface: Surface? = null
@ -340,12 +448,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
} }
companion object { companion object {
private const val KEY_GAMEPATH = "gamepath"
private val perfStatsUpdateHandler = Handler() private val perfStatsUpdateHandler = Handler()
fun newInstance(gamePath: String?): EmulationFragment { fun newInstance(game: Game): EmulationFragment {
val args = Bundle() val args = Bundle()
args.putString(KEY_GAMEPATH, gamePath) args.putParcelable(EmulationActivity.EXTRA_SELECTED_GAME, game)
val fragment = EmulationFragment() val fragment = EmulationFragment()
fragment.arguments = args fragment.arguments = args
return fragment return fragment

View file

@ -1,129 +0,0 @@
package org.yuzu.yuzu_emu.fragments;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.Fragment;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.elevation.ElevationOverlayProvider;
import org.yuzu.yuzu_emu.R;
import org.yuzu.yuzu_emu.activities.EmulationActivity;
public final class MenuFragment extends Fragment implements View.OnClickListener
{
private static final String KEY_TITLE = "title";
private static final String KEY_WII = "wii";
private static SparseIntArray buttonsActionsMap = new SparseIntArray();
private int mCutInset = 0;
static
{
buttonsActionsMap.append(R.id.menu_exit, EmulationActivity.MENU_ACTION_EXIT);
}
public static MenuFragment newInstance()
{
MenuFragment fragment = new MenuFragment();
Bundle arguments = new Bundle();
fragment.setArguments(arguments);
return fragment;
}
// This is primarily intended to account for any navigation bar at the bottom of the screen
private int getBottomPaddingRequired()
{
Rect visibleFrame = new Rect();
requireActivity().getWindow().getDecorView().getWindowVisibleDisplayFrame(visibleFrame);
return visibleFrame.bottom - visibleFrame.top - getResources().getDisplayMetrics().heightPixels;
}
@NonNull
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View rootView = inflater.inflate(R.layout.fragment_ingame_menu, container, false);
LinearLayout options = rootView.findViewById(R.id.layout_options);
// mPauseEmulation = options.findViewById(R.id.menu_pause_emulation);
// mUnpauseEmulation = options.findViewById(R.id.menu_unpause_emulation);
//
// updatePauseUnpauseVisibility();
//
// if (!requireActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN))
// {
// options.findViewById(R.id.menu_overlay_controls).setVisibility(View.GONE);
// }
//
// if (!getArguments().getBoolean(KEY_WII, true))
// {
// options.findViewById(R.id.menu_refresh_wiimotes).setVisibility(View.GONE);
// }
int bottomPaddingRequired = getBottomPaddingRequired();
// Provide a safe zone between the navigation bar and Exit Emulation to avoid accidental touches
float density = getResources().getDisplayMetrics().density;
if (bottomPaddingRequired >= 32 * density)
{
bottomPaddingRequired += 32 * density;
}
if (bottomPaddingRequired > rootView.getPaddingBottom())
{
rootView.setPadding(rootView.getPaddingLeft(), rootView.getPaddingTop(),
rootView.getPaddingRight(), bottomPaddingRequired);
}
for (int childIndex = 0; childIndex < options.getChildCount(); childIndex++)
{
Button button = (Button) options.getChildAt(childIndex);
button.setOnClickListener(this);
}
rootView.findViewById(R.id.menu_exit).setOnClickListener(this);
// mTitleText = rootView.findViewById(R.id.text_game_title);
// String title = getArguments().getString(KEY_TITLE, null);
// if (title != null)
// {
// mTitleText.setText(title);
// }
if (getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR)
{
// rootView.post(() -> NativeLibrary.SetObscuredPixelsLeft(rootView.getWidth()));
}
return rootView;
}
@Override
public void onClick(View button)
{
int action = buttonsActionsMap.get(button.getId());
EmulationActivity activity = (EmulationActivity) requireActivity();
activity.handleMenuAction(action);
}
}

View file

@ -1,7 +1,9 @@
package org.yuzu.yuzu_emu.utils package org.yuzu.yuzu_emu.utils
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context import android.content.Context
import android.graphics.Rect
import android.view.ViewGroup.MarginLayoutParams import android.view.ViewGroup.MarginLayoutParams
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
@ -27,4 +29,10 @@ object InsetsHelper {
resources.getInteger(resourceId) resources.getInteger(resourceId)
} else 0 } else 0
} }
fun getBottomPaddingRequired(activity: Activity): Int {
val visibleFrame = Rect()
activity.window.decorView.getWindowVisibleDisplayFrame(visibleFrame)
return visibleFrame.bottom - visibleFrame.top - activity.resources.displayMetrics.heightPixels
}
} }

View file

@ -0,0 +1,37 @@
package org.yuzu.yuzu_emu.utils
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import java.io.Serializable
object SerializableHelper {
inline fun <reified T : Serializable> Bundle.serializable(key: String): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
getSerializable(key, T::class.java)
else
getSerializable(key) as? T
}
inline fun <reified T : Serializable> Intent.serializable(key: String): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
getSerializableExtra(key, T::class.java)
else
getSerializableExtra(key) as? T
}
inline fun <reified T : Parcelable> Bundle.parcelable(key: String): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
getParcelable(key, T::class.java)
else
getParcelable(key) as? T
}
inline fun <reified T : Parcelable> Intent.parcelable(key: String): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
getParcelableExtra(key, T::class.java)
else
getParcelableExtra(key) as? T
}
}

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M21,6L3,6c-1.1,0 -2,0.9 -2,2v8c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,8c0,-1.1 -0.9,-2 -2,-2zM11,13L8,13v3L6,16v-3L3,13v-2h3L6,8h2v3h3v2zM15.5,15c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM19.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S18.67,9 19.5,9s1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z" />
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M10.09,15.59L11.5,17l5,-5 -5,-5 -1.41,1.41L12.67,11H3v2h9.67l-2.58,2.59zM19,3H5c-1.11,0 -2,0.9 -2,2v4h2V5h14v14H5v-4H3v4c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z" />
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z" />
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M8,5v14l11,-7z" />
</vector>

View file

@ -1,32 +1,13 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout
android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent" android:id="@+id/frame_content"
android:keepScreenOn="true" android:layout_width="match_parent"
xmlns:tools="http://schemas.android.com/tools" android:layout_height="match_parent"
android:id="@+id/frame_content"> android:keepScreenOn="true">
<FrameLayout <FrameLayout
android:id="@+id/frame_emulation_fragment" android:id="@+id/frame_emulation_fragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="match_parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:baselineAligned="false">
<FrameLayout
android:id="@+id/frame_menu"
android:layout_width="@dimen/menu_width"
android:layout_height="match_parent"
tools:layout="@layout/fragment_ingame_menu"/>
<FrameLayout
android:id="@+id/frame_submenu"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</FrameLayout> </FrameLayout>

View file

@ -1,47 +1,63 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:keepScreenOn="true" android:keepScreenOn="true"
tools:context="org.yuzu.yuzu_emu.fragments.EmulationFragment"> tools:context="org.yuzu.yuzu_emu.fragments.EmulationFragment"
tools:openDrawer="start">
<!-- This is what everything is rendered to during emulation --> <androidx.coordinatorlayout.widget.CoordinatorLayout
<Button
android:id="@+id/done_control_config"
style="@style/Widget.Material3.Button.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="@dimen/spacing_small"
android:text="@string/emulation_done"
android:visibility="gone" />
<!-- This is the onscreen input overlay -->
<SurfaceView
android:id="@+id/surface_emulation"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:focusable="false"
android:focusableInTouchMode="false" />
<org.yuzu.yuzu_emu.overlay.InputOverlay <!-- This is the onscreen input overlay -->
android:id="@+id/surface_input_overlay" <SurfaceView
android:layout_width="match_parent" android:id="@+id/surface_emulation"
android:layout_height="match_parent" android:layout_width="match_parent"
android:focusable="true" android:layout_height="match_parent"
android:focusableInTouchMode="true" /> android:focusable="false"
android:focusableInTouchMode="false" />
<TextView <TextView
android:id="@+id/show_fps_text" android:id="@+id/show_fps_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginTop="2dp"
android:clickable="false"
android:linksClickable="false"
android:longClickable="false"
android:shadowColor="@android:color/black"
android:textColor="@android:color/white"
android:textSize="12sp" />
<org.yuzu.yuzu_emu.overlay.InputOverlay
android:id="@+id/surface_input_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:focusableInTouchMode="true" />
<!-- This is what everything is rendered to during emulation -->
<Button
style="@style/Widget.Material3.Button.ElevatedButton"
android:id="@+id/done_control_config"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/emulation_done"
android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/in_game_menu"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_marginStart="18dp" android:layout_gravity="start"
android:layout_marginTop="2dp" app:headerLayout="@layout/header_in_game"
android:clickable="false" app:menu="@menu/menu_in_game" />
android:linksClickable="false"
android:longClickable="false"
android:shadowColor="@android:color/black"
android:textColor="@android:color/white"
android:textSize="12sp" />
</FrameLayout> </androidx.drawerlayout.widget.DrawerLayout>

View file

@ -1,56 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorSurface"
android:elevation="3dp"
tools:layout_width="250dp">
<TextView
android:id="@+id/text_game_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:layout_marginVertical="24dp"
android:ellipsize="end"
android:letterSpacing="0"
android:maxLines="@integer/game_title_lines"
android:textSize="20sp"
android:textColor="?attr/colorOnSurface"
tools:text="The Legend of Zelda: Breath of the Wild" />
<com.google.android.material.divider.MaterialDivider
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scrollbarSize="4dp"
android:fadeScrollbars="false">
<LinearLayout
android:id="@+id/layout_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</ScrollView>
<com.google.android.material.divider.MaterialDivider
android:id="@+id/divider_2"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/menu_exit"
style="@style/InGameMenuOption"
android:layout_marginTop="@dimen/spacing_large"
android:text="@string/emulation_exit" />
</LinearLayout>

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_game_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="24dp"
android:textAppearance="?attr/textAppearanceHeadlineMedium"
android:textColor="?attr/colorOnSurface"
android:textAlignment="viewStart"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Super Mario Odyssey" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="">
<menu>
<item
android:id="@+id/menu_pause_emulation"
android:icon="@drawable/ic_pause"
android:title="@string/emulation_pause" />
<item
android:id="@+id/menu_settings"
android:icon="@drawable/ic_settings"
android:title="@string/preferences_settings" />
<item
android:id="@+id/menu_overlay_controls"
android:icon="@drawable/ic_controller"
android:title="@string/emulation_input_overlay" />
</menu>
</item>
<item
android:id="@+id/menu_exit"
android:icon="@drawable/ic_exit"
android:title="@string/emulation_exit" />
</menu>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_edit_overlay"
android:title="@string/emulation_touch_overlay_edit" />
<item
android:id="@+id/menu_reset_overlay"
android:title="@string/emulation_touch_overlay_reset" />
</menu>

View file

@ -87,6 +87,10 @@
<string name="emulation_toggle_controls">Toggle Controls</string> <string name="emulation_toggle_controls">Toggle Controls</string>
<string name="emulation_control_scale">Adjust Scale</string> <string name="emulation_control_scale">Adjust Scale</string>
<string name="emulation_touch_overlay_reset">Reset Overlay</string> <string name="emulation_touch_overlay_reset">Reset Overlay</string>
<string name="emulation_touch_overlay_edit">Edit Overlay</string>
<string name="emulation_pause">Pause Emulation</string>
<string name="emulation_unpause">Unpause Emulation</string>
<string name="emulation_input_overlay">Input Overlay</string>
<string name="load_settings">Loading Settings…</string> <string name="load_settings">Loading Settings…</string>