diff options
Diffstat (limited to 'src/android/app')
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>  | 
