diff options
Diffstat (limited to 'src/android')
17 files changed, 343 insertions, 373 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index b222344c3..0da7562a6 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt @@ -8,15 +8,10 @@ import android.content.DialogInterface import android.content.Intent import android.graphics.Rect import android.os.Bundle -import android.view.MotionEvent import android.view.View import android.view.WindowManager -import androidx.activity.OnBackPressedCallback -import androidx.annotation.IntDef import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity -import androidx.fragment.app.FragmentManager import androidx.preference.PreferenceManager import com.google.android.material.dialog.MaterialAlertDialogBuilder 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.features.settings.model.Settings 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.SerializableHelper.parcelable import org.yuzu.yuzu_emu.utils.ThemeHelper import kotlin.math.roundToInt @@ -37,11 +33,11 @@ open class EmulationActivity : AppCompatActivity() { //private Intent foregroundService; var isActivityRecreated = false - private var selectedTitle: String? = null - private var path: String? = null private var menuVisible = false private var emulationFragment: EmulationFragment? = null + private lateinit var game: Game + override fun onDestroy() { // TODO(bunnei): Disable notifications until we support app suspension. //stopService(foregroundService); @@ -54,9 +50,7 @@ open class EmulationActivity : AppCompatActivity() { super.onCreate(savedInstanceState) if (savedInstanceState == null) { // Get params we were passed - val gameToEmulate = intent - path = gameToEmulate.getStringExtra(EXTRA_SELECTED_GAME) - selectedTitle = gameToEmulate.getStringExtra(EXTRA_SELECTED_TITLE) + game = intent.parcelable(EXTRA_SELECTED_GAME)!! isActivityRecreated = false } else { isActivityRecreated = true @@ -73,34 +67,26 @@ open class EmulationActivity : AppCompatActivity() { emulationFragment = supportFragmentManager.findFragmentById(R.id.frame_emulation_fragment) as EmulationFragment? if (emulationFragment == null) { - emulationFragment = EmulationFragment.newInstance(path) + emulationFragment = EmulationFragment.newInstance(game) supportFragmentManager.beginTransaction() .add(R.id.frame_emulation_fragment, emulationFragment!!) .commit() } - title = selectedTitle + title = game.title // Start a foreground service to prevent the app from getting killed in the background // TODO(bunnei): Disable notifications until we support app suspension. //foregroundService = new Intent(EmulationActivity.this, ForegroundService.class); //startForegroundService(foregroundService); - - onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - toggleMenu() - } - }) } override fun onSaveInstanceState(outState: Bundle) { - outState.putString(EXTRA_SELECTED_GAME, path) - outState.putString(EXTRA_SELECTED_TITLE, selectedTitle) + outState.putParcelable(EXTRA_SELECTED_GAME, game) super.onSaveInstanceState(outState) } private fun restoreState(savedInstanceState: Bundle) { - path = savedInstanceState.getString(EXTRA_SELECTED_GAME) - selectedTitle = savedInstanceState.getString(EXTRA_SELECTED_TITLE) + game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!! // If an alert prompt was in progress when state was restored, retry displaying it NativeLibrary.retryDisplayAlertPrompt() @@ -110,6 +96,8 @@ open class EmulationActivity : AppCompatActivity() { window.attributes.layoutInDisplayCutoutMode = 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. window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or @@ -119,15 +107,6 @@ open class EmulationActivity : AppCompatActivity() { View.SYSTEM_UI_FLAG_IMMERSIVE } - fun handleMenuAction(action: Int) { - when (action) { - MENU_ACTION_EXIT -> { - emulationFragment!!.stopEmulation() - finish() - } - } - } - private fun editControlsPlacement() { if (emulationFragment!!.isConfiguringControls) { emulationFragment!!.stopConfiguringControls() @@ -176,94 +155,14 @@ open class EmulationActivity : AppCompatActivity() { .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 { - private const val BACKSTACK_NAME_MENU = "menu" - private const val BACKSTACK_NAME_SUBMENU = "submenu" 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 @JvmStatic - fun launch(activity: FragmentActivity, path: String?, title: String?) { + fun launch(activity: FragmentActivity, game: Game) { val launcher = Intent(activity, EmulationActivity::class.java) - launcher.putExtra(EXTRA_SELECTED_GAME, path) - launcher.putExtra(EXTRA_SELECTED_TITLE, title) + launcher.putExtra(EXTRA_SELECTED_GAME, game) activity.startActivity(launcher) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt index e9f926d84..0295801ad 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt @@ -13,7 +13,6 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.FragmentActivity import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import coil.load @@ -23,8 +22,8 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.yuzu.yuzu_emu.NativeLibrary 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.activities.EmulationActivity import org.yuzu.yuzu_emu.model.Game import org.yuzu.yuzu_emu.model.GameDatabase import org.yuzu.yuzu_emu.utils.Log @@ -181,7 +180,7 @@ class GameAdapter(private val activity: AppCompatActivity) : RecyclerView.Adapte */ override fun onClick(view: View) { 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 { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 0889b6f7f..4ba283ddd 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -10,7 +10,14 @@ import android.graphics.Color import android.os.Bundle import android.os.Handler import android.view.* +import android.widget.TextView 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.localbroadcastmanager.content.LocalBroadcastManager 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.databinding.FragmentEmulationBinding 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.DirectoryInitializationState 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.SerializableHelper.parcelable class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.FrameCallback { private lateinit var preferences: SharedPreferences @@ -35,6 +47,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram private var _binding: FragmentEmulationBinding? = null private val binding get() = _binding!! + private lateinit var game: Game + override fun onAttach(context: Context) { super.onAttach(context) 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. retainInstance = true preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) - val gamePath = requireArguments().getString(KEY_GAMEPATH) - emulationState = EmulationState(gamePath) + game = requireArguments().parcelable(EmulationActivity.EXTRA_SELECTED_GAME)!! + emulationState = EmulationState(game.path) } /** @@ -78,6 +92,57 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram // Setup overlay. resetInputOverlay() 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() { @@ -202,8 +267,30 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram NativeLibrary.DoFrame() } - fun stopEmulation() { - emulationState.stop() + private fun showOverlayOptions() { + 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() { @@ -219,6 +306,27 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram val isConfiguringControls: Boolean 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 var state: State private var surface: Surface? = null @@ -340,12 +448,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram } companion object { - private const val KEY_GAMEPATH = "gamepath" private val perfStatsUpdateHandler = Handler() - fun newInstance(gamePath: String?): EmulationFragment { + fun newInstance(game: Game): EmulationFragment { val args = Bundle() - args.putString(KEY_GAMEPATH, gamePath) + args.putParcelable(EmulationActivity.EXTRA_SELECTED_GAME, game) val fragment = EmulationFragment() fragment.arguments = args return fragment diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MenuFragment.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MenuFragment.java deleted file mode 100644 index 5dc3f5545..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/MenuFragment.java +++ /dev/null @@ -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); - } -} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt index 37f08ac26..e7a04d917 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/InsetsHelper.kt @@ -1,7 +1,9 @@ package org.yuzu.yuzu_emu.utils import android.annotation.SuppressLint +import android.app.Activity import android.content.Context +import android.graphics.Rect import android.view.ViewGroup.MarginLayoutParams import androidx.core.graphics.Insets import com.google.android.material.appbar.AppBarLayout @@ -27,4 +29,10 @@ object InsetsHelper { resources.getInteger(resourceId) } else 0 } + + fun getBottomPaddingRequired(activity: Activity): Int { + val visibleFrame = Rect() + activity.window.decorView.getWindowVisibleDisplayFrame(visibleFrame) + return visibleFrame.bottom - visibleFrame.top - activity.resources.displayMetrics.heightPixels + } } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/SerializableHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/SerializableHelper.kt new file mode 100644 index 000000000..23ffbaf68 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/SerializableHelper.kt @@ -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 + } +} diff --git a/src/android/app/src/main/res/drawable/ic_controller.xml b/src/android/app/src/main/res/drawable/ic_controller.xml new file mode 100644 index 000000000..2359c35be --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_controller.xml @@ -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> diff --git a/src/android/app/src/main/res/drawable/ic_exit.xml b/src/android/app/src/main/res/drawable/ic_exit.xml new file mode 100644 index 000000000..a55a1d387 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_exit.xml @@ -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> diff --git a/src/android/app/src/main/res/drawable/ic_pause.xml b/src/android/app/src/main/res/drawable/ic_pause.xml new file mode 100644 index 000000000..adb3ababc --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_pause.xml @@ -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> diff --git a/src/android/app/src/main/res/drawable/ic_play.xml b/src/android/app/src/main/res/drawable/ic_play.xml new file mode 100644 index 000000000..7f01dc599 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_play.xml @@ -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> diff --git a/src/android/app/src/main/res/layout/activity_emulation.xml b/src/android/app/src/main/res/layout/activity_emulation.xml index debc26e6c..f6360a65b 100644 --- a/src/android/app/src/main/res/layout/activity_emulation.xml +++ b/src/android/app/src/main/res/layout/activity_emulation.xml @@ -1,32 +1,13 @@ -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:keepScreenOn="true" - xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/frame_content"> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/frame_content" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:keepScreenOn="true"> <FrameLayout android:id="@+id/frame_emulation_fragment" android:layout_width="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> + android:layout_height="match_parent" /> </FrameLayout> diff --git a/src/android/app/src/main/res/layout/fragment_emulation.xml b/src/android/app/src/main/res/layout/fragment_emulation.xml index a3e5707ef..be11f028f 100644 --- a/src/android/app/src/main/res/layout/fragment_emulation.xml +++ b/src/android/app/src/main/res/layout/fragment_emulation.xml @@ -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" + android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" 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 --> - <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" + <androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent" - android:layout_height="match_parent" - android:focusable="false" - android:focusableInTouchMode="false" /> + android:layout_height="match_parent"> - <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 the onscreen input overlay --> + <SurfaceView + android:id="@+id/surface_emulation" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:focusable="false" + android:focusableInTouchMode="false" /> + + <TextView + 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" /> - <TextView - android:id="@+id/show_fps_text" + <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_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" /> - -</FrameLayout> + android:layout_height="match_parent" + android:layout_gravity="start" + app:headerLayout="@layout/header_in_game" + app:menu="@menu/menu_in_game" /> + +</androidx.drawerlayout.widget.DrawerLayout> diff --git a/src/android/app/src/main/res/layout/fragment_ingame_menu.xml b/src/android/app/src/main/res/layout/fragment_ingame_menu.xml deleted file mode 100644 index ce618ef7b..000000000 --- a/src/android/app/src/main/res/layout/fragment_ingame_menu.xml +++ /dev/null @@ -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>
\ No newline at end of file diff --git a/src/android/app/src/main/res/layout/header_in_game.xml b/src/android/app/src/main/res/layout/header_in_game.xml new file mode 100644 index 000000000..135d429c5 --- /dev/null +++ b/src/android/app/src/main/res/layout/header_in_game.xml @@ -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> diff --git a/src/android/app/src/main/res/menu/menu_in_game.xml b/src/android/app/src/main/res/menu/menu_in_game.xml new file mode 100644 index 000000000..f68459640 --- /dev/null +++ b/src/android/app/src/main/res/menu/menu_in_game.xml @@ -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> diff --git a/src/android/app/src/main/res/menu/menu_overlay_options.xml b/src/android/app/src/main/res/menu/menu_overlay_options.xml new file mode 100644 index 000000000..75c84cdf3 --- /dev/null +++ b/src/android/app/src/main/res/menu/menu_overlay_options.xml @@ -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> diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 7da113728..c471425f2 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -87,6 +87,10 @@ <string name="emulation_toggle_controls">Toggle Controls</string> <string name="emulation_control_scale">Adjust Scale</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> |