From 119b39ab19c2f62ad3ac606bb80bb4106aca6dc1 Mon Sep 17 00:00:00 2001 From: SSimco <37044560+SSimco@users.noreply.github.com> Date: Mon, 28 Apr 2025 08:36:44 +0300 Subject: [PATCH] refactor emulation activity --- .../cemu/emulation/CanvasOnTouchListener.kt | 39 ++ .../cemu/cemu/emulation/EmulationActivity.kt | 335 ++++++++++++++--- .../cemu/cemu/emulation/EmulationFragment.kt | 338 ------------------ .../main/res/layout/activity_emulation.xml | 81 ++++- .../main/res/layout/fragment_emulation.xml | 82 ----- 5 files changed, 408 insertions(+), 467 deletions(-) create mode 100644 src/android/app/src/main/java/info/cemu/cemu/emulation/CanvasOnTouchListener.kt delete mode 100644 src/android/app/src/main/java/info/cemu/cemu/emulation/EmulationFragment.kt delete mode 100644 src/android/app/src/main/res/layout/fragment_emulation.xml diff --git a/src/android/app/src/main/java/info/cemu/cemu/emulation/CanvasOnTouchListener.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/CanvasOnTouchListener.kt new file mode 100644 index 00000000..16545cec --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/CanvasOnTouchListener.kt @@ -0,0 +1,39 @@ +package info.cemu.cemu.emulation + +import android.annotation.SuppressLint +import android.view.MotionEvent +import android.view.View +import info.cemu.cemu.nativeinterface.NativeInput + +class CanvasOnTouchListener(val isTV: Boolean) : View.OnTouchListener { + private var currentPointerId: Int = -1 + + @SuppressLint("ClickableViewAccessibility") + override fun onTouch(v: View, event: MotionEvent): Boolean { + val pointerIndex = event.actionIndex + val pointerId = event.getPointerId(pointerIndex) + if (currentPointerId != -1 && pointerId != currentPointerId) { + return false + } + val x = event.getX(pointerIndex).toInt() + val y = event.getY(pointerIndex).toInt() + when (event.actionMasked) { + MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> { + NativeInput.onTouchDown(x, y, isTV) + return true + } + + MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> { + currentPointerId = -1 + NativeInput.onTouchUp(x, y, isTV) + return true + } + + MotionEvent.ACTION_MOVE -> { + NativeInput.onTouchMove(x, y, isTV) + return true + } + } + return false + } +} \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/emulation/EmulationActivity.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/EmulationActivity.kt index 774d813f..31ee6c8e 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/emulation/EmulationActivity.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/EmulationActivity.kt @@ -1,12 +1,22 @@ package info.cemu.cemu.emulation +import android.annotation.SuppressLint import android.content.DialogInterface +import android.graphics.SurfaceTexture import android.os.Bundle import android.text.InputFilter.LengthFilter import android.view.KeyEvent import android.view.MotionEvent +import android.view.Surface +import android.view.SurfaceHolder +import android.view.SurfaceView +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.Toast import androidx.activity.OnBackPressedCallback import androidx.annotation.Keep +import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat @@ -17,14 +27,62 @@ import com.google.android.material.textfield.TextInputLayout import info.cemu.cemu.BuildConfig import info.cemu.cemu.R import info.cemu.cemu.databinding.ActivityEmulationBinding -import info.cemu.cemu.emulation.EmulationFragment.OnEmulationErrorCallback +import info.cemu.cemu.databinding.LayoutSideMenuCheckboxItemBinding +import info.cemu.cemu.databinding.LayoutSideMenuEmulationBinding +import info.cemu.cemu.databinding.LayoutSideMenuTextItemBinding import info.cemu.cemu.input.InputManager +import info.cemu.cemu.input.SensorManager +import info.cemu.cemu.inputoverlay.InputOverlaySettingsManager +import info.cemu.cemu.inputoverlay.InputOverlaySurfaceView +import info.cemu.cemu.inputoverlay.OverlaySettings +import info.cemu.cemu.nativeinterface.NativeEmulation +import info.cemu.cemu.nativeinterface.NativeException import info.cemu.cemu.nativeinterface.NativeSwkbd.setCurrentInputText +import java.lang.ref.WeakReference import kotlin.system.exitProcess class EmulationActivity : AppCompatActivity() { + private inner class CanvasSurfaceHolderCallback(val isMainCanvas: Boolean) : + SurfaceHolder.Callback { + var surfaceSet: Boolean = false + + override fun surfaceCreated(surfaceHolder: SurfaceHolder) {} + + override fun surfaceChanged( + surfaceHolder: SurfaceHolder, + format: Int, + width: Int, + height: Int, + ) { + try { + NativeEmulation.setSurfaceSize(width, height, isMainCanvas) + if (surfaceSet) { + return + } + NativeEmulation.setSurface(surfaceHolder.surface, isMainCanvas) + surfaceSet = true + } catch (exception: NativeException) { + onEmulationError(getString(R.string.failed_create_surface_error, exception.message)) + } + } + + override fun surfaceDestroyed(surfaceHolder: SurfaceHolder) { + NativeEmulation.clearSurface(isMainCanvas) + surfaceSet = false + } + } + private val inputManager = InputManager() private var emulationTextInputDialog: AlertDialog? = null + private var isGameRunning = false + private var padCanvas: SurfaceView? = null + private var toast: Toast? = null + private lateinit var binding: ActivityEmulationBinding + private var isMotionEnabled = false + private lateinit var overlaySettings: OverlaySettings + private lateinit var inputOverlaySurfaceView: InputOverlaySurfaceView + private lateinit var sensorManager: SensorManager + private var hasEmulationError = false override fun onGenericMotionEvent(event: MotionEvent): Boolean { if (inputManager.onMotionEvent(event)) { @@ -40,16 +98,7 @@ class EmulationActivity : AppCompatActivity() { return super.dispatchKeyEvent(event) } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - emulationActivityInstance = this - onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - showExitConfirmationDialog() - } - }) - - val intent = intent + private fun getLaunchPath(): String { val extras = intent.extras val data = intent.data var launchPath: String? = null @@ -62,25 +111,231 @@ class EmulationActivity : AppCompatActivity() { if (launchPath == null) { throw RuntimeException("launchPath is null") } - setFullscreen() - val binding = ActivityEmulationBinding.inflate( - layoutInflater - ) + return launchPath + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + emulationActivityInstance = WeakReference(this) + + val inputOverlaySettingsManager = InputOverlaySettingsManager(this) + overlaySettings = inputOverlaySettingsManager.overlaySettings + sensorManager = SensorManager(this) + + onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + showExitConfirmationDialog() + } + }) + + + initializeView(getLaunchPath()) setContentView(binding.root) - var emulationFragment = - supportFragmentManager.findFragmentById(R.id.emulation_frame) as EmulationFragment? - if (emulationFragment == null) { - emulationFragment = EmulationFragment(launchPath) - emulationFragment.setOnEmulationErrorCallback(OnEmulationErrorCallback { emulationError: String? -> - this.onEmulationError( - emulationError - ) - }) - supportFragmentManager - .beginTransaction() - .add(R.id.emulation_frame, emulationFragment) - .commit() + } + + private fun destroyPadCanvas() { + if (padCanvas == null) { + return } + binding.canvasesLayout.removeView(padCanvas) + padCanvas = null + } + + private fun setPadViewVisibility(visible: Boolean) { + if (visible) { + createPadCanvas() + } else { + destroyPadCanvas() + } + } + + private fun setMotionEnabled(enabled: Boolean) { + isMotionEnabled = enabled + if (isMotionEnabled) { + sensorManager.startListening() + } else { + sensorManager.pauseListening() + } + } + + private fun LayoutSideMenuTextItemBinding.setEnabled(isEnabled: Boolean) { + textItem.isEnabled = isEnabled + textItem.alpha = if (isEnabled) 1f else 0.7f + } + + private fun LayoutSideMenuTextItemBinding.configure( + isEnabled: Boolean = true, + onClick: () -> Unit, + ) { + setEnabled(isEnabled) + textItem.setOnClickListener { + onClick() + binding.drawerLayout.close() + } + } + + private fun LayoutSideMenuCheckboxItemBinding.configure( + initialCheckedStatus: Boolean = false, + onCheckChanged: (Boolean) -> Unit, + ) { + checkbox.isChecked = initialCheckedStatus + checkboxItem.setOnClickListener { + checkbox.isChecked = !checkbox.isChecked + onCheckChanged(checkbox.isChecked) + binding.drawerLayout.close() + } + } + + private fun LayoutSideMenuEmulationBinding.configureSideMenu(isInputOverlayEnabled: Boolean) { + enableMotionCheckbox.configure(onCheckChanged = ::setMotionEnabled) + replaceTvWithPadCheckbox.configure(onCheckChanged = NativeEmulation::setReplaceTVWithPadView) + showPadCheckbox.configure(onCheckChanged = ::setPadViewVisibility) + showInputOverlayCheckbox.configure(initialCheckedStatus = isInputOverlayEnabled) { showInputOverlay -> + editInputsMenuItem.setEnabled(showInputOverlay) + resetInputOverlayMenuItem.setEnabled(showInputOverlay) + inputOverlaySurfaceView.setVisible(showInputOverlay) + } + editInputsMenuItem.configure(isEnabled = isInputOverlayEnabled) { + binding.editInputsLayout.visibility = View.VISIBLE + binding.finishEditInputsButton.visibility = View.VISIBLE + binding.moveInputsButton.performClick() + } + resetInputOverlayMenuItem.configure( + isEnabled = isInputOverlayEnabled, + onClick = inputOverlaySurfaceView::resetInputs + ) + exitMenuItem.configure(onClick = onBackPressedDispatcher::onBackPressed) + } + + @SuppressLint("ClickableViewAccessibility") + private fun initializeView(launchPath: String) { + setFullscreen() + + binding = ActivityEmulationBinding.inflate(layoutInflater) + inputOverlaySurfaceView = binding.inputOverlay + + binding.sideMenu.configureSideMenu(overlaySettings.isOverlayEnabled) + + binding.moveInputsButton.setOnClickListener { _ -> + if (inputOverlaySurfaceView.getInputMode() == InputOverlaySurfaceView.InputMode.EDIT_POSITION) { + return@setOnClickListener + } + binding.resizeInputsButton.alpha = 0.5f + binding.moveInputsButton.alpha = 1.0f + toastMessage(R.string.input_mode_edit_position) + inputOverlaySurfaceView.setInputMode(InputOverlaySurfaceView.InputMode.EDIT_POSITION) + } + binding.resizeInputsButton.setOnClickListener { _ -> + if (inputOverlaySurfaceView.getInputMode() == InputOverlaySurfaceView.InputMode.EDIT_SIZE) { + return@setOnClickListener + } + binding.moveInputsButton.alpha = 0.5f + binding.resizeInputsButton.alpha = 1.0f + toastMessage(R.string.input_mode_edit_size) + inputOverlaySurfaceView.setInputMode(InputOverlaySurfaceView.InputMode.EDIT_SIZE) + } + binding.finishEditInputsButton.setOnClickListener { _ -> + inputOverlaySurfaceView.setInputMode(InputOverlaySurfaceView.InputMode.DEFAULT) + binding.finishEditInputsButton.visibility = View.GONE + binding.editInputsLayout.visibility = View.GONE + toastMessage(R.string.input_mode_default) + } + binding.emulationSettingsButton.setOnClickListener { binding.drawerLayout.open() } + val mainCanvas = binding.mainCanvas + try { + val testSurfaceTexture = SurfaceTexture(0) + val testSurface = Surface(testSurfaceTexture) + NativeEmulation.initializeRenderer(testSurface) + testSurface.release() + testSurfaceTexture.release() + } catch (exception: NativeException) { + onEmulationError( + getString( + R.string.failed_initialize_renderer_error, + exception.message + ) + ) + return + } + + val mainCanvasHolder = mainCanvas.holder + mainCanvasHolder.addCallback(CanvasSurfaceHolderCallback(isMainCanvas = true)) + mainCanvasHolder.addCallback(object : SurfaceHolder.Callback { + override fun surfaceCreated(holder: SurfaceHolder) { + } + + override fun surfaceChanged( + holder: SurfaceHolder, + format: Int, + width: Int, + height: Int, + ) { + if (hasEmulationError) { + return + } + if (!isGameRunning) { + isGameRunning = true + startGame(launchPath) + } + } + + override fun surfaceDestroyed(holder: SurfaceHolder) { + } + }) + mainCanvas.setOnTouchListener(CanvasOnTouchListener(isTV = true)) + } + + private fun toastMessage(@StringRes toastTextResId: Int) { + toast?.cancel() + toast = Toast.makeText(this, toastTextResId, Toast.LENGTH_SHORT) + .also { it.show() } + } + + private fun startGame(launchPath: String) { + val result = NativeEmulation.startGame(launchPath) + if (result == NativeEmulation.START_GAME_SUCCESSFUL) { + return + } + val errorMessage = when (result) { + NativeEmulation.START_GAME_ERROR_GAME_BASE_FILES_NOT_FOUND -> getString(R.string.game_not_found) + NativeEmulation.START_GAME_ERROR_NO_DISC_KEY -> getString(R.string.no_disk_key) + NativeEmulation.START_GAME_ERROR_NO_TITLE_TIK -> getString(R.string.no_title_tik) + else -> getString(R.string.game_files_unknown_error, launchPath) + } + onEmulationError(errorMessage) + } + + override fun onPause() { + super.onPause() + sensorManager.pauseListening() + } + + override fun onResume() { + super.onResume() + if (isMotionEnabled) { + sensorManager.startListening() + } + } + + + override fun onDestroy() { + super.onDestroy() + sensorManager.pauseListening() + } + + @SuppressLint("ClickableViewAccessibility") + private fun createPadCanvas() { + if (padCanvas != null) { + return + } + val padCanvas = SurfaceView(this) + binding.canvasesLayout.addView( + padCanvas, + LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1.0f) + ) + padCanvas.holder.addCallback(CanvasSurfaceHolderCallback(false)) + padCanvas.setOnTouchListener(CanvasOnTouchListener(false)) + this.padCanvas = padCanvas } private fun showExitConfirmationDialog() { @@ -116,7 +371,8 @@ class EmulationActivity : AppCompatActivity() { companion object { const val EXTRA_LAUNCH_PATH: String = BuildConfig.APPLICATION_ID + ".LaunchPath" - private var emulationActivityInstance: EmulationActivity? = null + private var emulationActivityInstance: WeakReference = + WeakReference(null) /** * This method is called by swkbd using JNI. @@ -124,20 +380,21 @@ class EmulationActivity : AppCompatActivity() { @Keep @JvmStatic fun showEmulationTextInput(initialText: String?, maxLength: Int) { - if (emulationActivityInstance == null || emulationActivityInstance!!.emulationTextInputDialog != null) { + val emulationActivity = emulationActivityInstance.get() ?: return + if (emulationActivity.emulationTextInputDialog != null) { return } setCurrentInputText(initialText) - emulationActivityInstance!!.runOnUiThread { + emulationActivity.runOnUiThread { val inputEditTextLayout = - emulationActivityInstance!!.layoutInflater.inflate( + emulationActivity.layoutInflater.inflate( R.layout.layout_emulation_input, null ) val inputEditText = inputEditTextLayout.requireViewById(R.id.emulation_input_text) inputEditText.updateText(initialText) - val dialog = MaterialAlertDialogBuilder(emulationActivityInstance!!) + val dialog = MaterialAlertDialogBuilder(emulationActivity) .setView(inputEditTextLayout) .setCancelable(false) .setPositiveButton(R.string.done) { _, _ -> }.show() @@ -156,7 +413,7 @@ class EmulationActivity : AppCompatActivity() { } else { parentTextInputLayout.isCounterEnabled = false } - emulationActivityInstance!!.emulationTextInputDialog = dialog + emulationActivity.emulationTextInputDialog = dialog } } @@ -166,12 +423,10 @@ class EmulationActivity : AppCompatActivity() { @Keep @JvmStatic fun hideEmulationTextInput() { - if (emulationActivityInstance?.emulationTextInputDialog == null) { - return - } - val textInputDialog = emulationActivityInstance!!.emulationTextInputDialog!! - emulationActivityInstance!!.emulationTextInputDialog = null - emulationActivityInstance!!.runOnUiThread { textInputDialog.dismiss() } + val emulationActivity = emulationActivityInstance.get() ?: return + val textInputDialog = emulationActivity.emulationTextInputDialog ?: return + emulationActivity.emulationTextInputDialog = null + emulationActivity.runOnUiThread { textInputDialog.dismiss() } } } } \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/emulation/EmulationFragment.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/EmulationFragment.kt deleted file mode 100644 index 5133e7f2..00000000 --- a/src/android/app/src/main/java/info/cemu/cemu/emulation/EmulationFragment.kt +++ /dev/null @@ -1,338 +0,0 @@ -package info.cemu.cemu.emulation - -import android.annotation.SuppressLint -import android.content.res.Configuration -import android.graphics.SurfaceTexture -import android.os.Bundle -import android.view.LayoutInflater -import android.view.MotionEvent -import android.view.Surface -import android.view.SurfaceHolder -import android.view.SurfaceView -import android.view.View -import android.view.View.OnTouchListener -import android.view.ViewGroup -import android.widget.LinearLayout -import android.widget.Toast -import androidx.annotation.StringRes -import androidx.fragment.app.Fragment -import info.cemu.cemu.R -import info.cemu.cemu.databinding.FragmentEmulationBinding -import info.cemu.cemu.databinding.LayoutSideMenuCheckboxItemBinding -import info.cemu.cemu.databinding.LayoutSideMenuEmulationBinding -import info.cemu.cemu.databinding.LayoutSideMenuTextItemBinding -import info.cemu.cemu.input.SensorManager -import info.cemu.cemu.inputoverlay.InputOverlaySettingsManager -import info.cemu.cemu.inputoverlay.InputOverlaySurfaceView -import info.cemu.cemu.inputoverlay.OverlaySettings -import info.cemu.cemu.nativeinterface.NativeEmulation -import info.cemu.cemu.nativeinterface.NativeException -import info.cemu.cemu.nativeinterface.NativeInput - -private class CanvasOnTouchListener(val isTV: Boolean) : OnTouchListener { - var currentPointerId: Int = -1 - - @SuppressLint("ClickableViewAccessibility") - override fun onTouch(v: View, event: MotionEvent): Boolean { - val pointerIndex = event.actionIndex - val pointerId = event.getPointerId(pointerIndex) - if (currentPointerId != -1 && pointerId != currentPointerId) { - return false - } - val x = event.getX(pointerIndex).toInt() - val y = event.getY(pointerIndex).toInt() - when (event.actionMasked) { - MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> { - NativeInput.onTouchDown(x, y, isTV) - return true - } - - MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> { - currentPointerId = -1 - NativeInput.onTouchUp(x, y, isTV) - return true - } - - MotionEvent.ACTION_MOVE -> { - NativeInput.onTouchMove(x, y, isTV) - return true - } - } - return false - } -} - -class EmulationFragment(private val launchPath: String) : Fragment() { - private inner class CanvasSurfaceHolderCallback(val isMainCanvas: Boolean) : - SurfaceHolder.Callback { - var surfaceSet: Boolean = false - - override fun surfaceCreated(surfaceHolder: SurfaceHolder) {} - - override fun surfaceChanged( - surfaceHolder: SurfaceHolder, - format: Int, - width: Int, - height: Int, - ) { - try { - NativeEmulation.setSurfaceSize(width, height, isMainCanvas) - if (surfaceSet) { - return - } - NativeEmulation.setSurface(surfaceHolder.surface, isMainCanvas) - surfaceSet = true - } catch (exception: NativeException) { - onEmulationError(getString(R.string.failed_create_surface_error, exception.message)) - } - } - - override fun surfaceDestroyed(surfaceHolder: SurfaceHolder) { - NativeEmulation.clearSurface(isMainCanvas) - surfaceSet = false - } - } - - fun interface OnEmulationErrorCallback { - fun onEmulationError(errorMessage: String) - } - - private var isGameRunning = false - private var padCanvas: SurfaceView? = null - private var toast: Toast? = null - private lateinit var binding: FragmentEmulationBinding - private var isMotionEnabled = false - private lateinit var overlaySettings: OverlaySettings - private lateinit var inputOverlaySurfaceView: InputOverlaySurfaceView - private lateinit var sensorManager: SensorManager - private var onEmulationErrorCallback: OnEmulationErrorCallback? = null - private var hasEmulationError = false - - fun setOnEmulationErrorCallback(onEmulationErrorCallback: OnEmulationErrorCallback) { - this.onEmulationErrorCallback = onEmulationErrorCallback - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val inputOverlaySettingsManager = InputOverlaySettingsManager(requireContext()) - overlaySettings = inputOverlaySettingsManager.overlaySettings - sensorManager = SensorManager(requireContext()) - sensorManager.setIsLandscape(resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) - } - - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - sensorManager.setIsLandscape(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) - } - - override fun onPause() { - super.onPause() - sensorManager.pauseListening() - } - - override fun onResume() { - super.onResume() - if (isMotionEnabled) { - sensorManager.startListening() - } - } - - - override fun onDestroy() { - super.onDestroy() - sensorManager.pauseListening() - } - - @SuppressLint("ClickableViewAccessibility") - private fun createPadCanvas() { - if (padCanvas != null) { - return - } - padCanvas = SurfaceView(requireContext()) - binding.canvasesLayout.addView( - padCanvas, - LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1.0f) - ) - padCanvas!!.holder.addCallback(CanvasSurfaceHolderCallback(false)) - padCanvas!!.setOnTouchListener(CanvasOnTouchListener(false)) - } - - private fun toastMessage(@StringRes toastTextResId: Int) { - toast?.cancel() - toast = Toast.makeText(requireContext(), toastTextResId, Toast.LENGTH_SHORT) - .also { it.show() } - } - - private fun destroyPadCanvas() { - if (padCanvas == null) { - return - } - binding.canvasesLayout.removeView(padCanvas) - padCanvas = null - } - - private fun setPadViewVisibility(visible: Boolean) { - if (visible) { - createPadCanvas() - } else { - destroyPadCanvas() - } - } - - private fun setMotionEnabled(enabled: Boolean) { - isMotionEnabled = enabled - if (isMotionEnabled) { - sensorManager.startListening() - } else { - sensorManager.pauseListening() - } - } - - private fun LayoutSideMenuTextItemBinding.setEnabled(isEnabled: Boolean) { - textItem.isEnabled = isEnabled - textItem.alpha = if (isEnabled) 1f else 0.7f - } - - private fun LayoutSideMenuTextItemBinding.configure( - isEnabled: Boolean = true, - onClick: () -> Unit, - ) { - setEnabled(isEnabled) - textItem.setOnClickListener { - onClick() - binding.drawerLayout.close() - } - } - - private fun LayoutSideMenuCheckboxItemBinding.configure( - initialCheckedStatus: Boolean = false, - onCheckChanged: (Boolean) -> Unit, - ) { - checkbox.isChecked = initialCheckedStatus - checkboxItem.setOnClickListener { - checkbox.isChecked = !checkbox.isChecked - onCheckChanged(checkbox.isChecked) - binding.drawerLayout.close() - } - } - - private fun LayoutSideMenuEmulationBinding.configureSideMenu(isInputOverlayEnabled: Boolean) { - enableMotionCheckbox.configure(onCheckChanged = ::setMotionEnabled) - replaceTvWithPadCheckbox.configure(onCheckChanged = NativeEmulation::setReplaceTVWithPadView) - showPadCheckbox.configure(onCheckChanged = ::setPadViewVisibility) - showInputOverlayCheckbox.configure(initialCheckedStatus = isInputOverlayEnabled) { showInputOverlay -> - editInputsMenuItem.setEnabled(showInputOverlay) - resetInputOverlayMenuItem.setEnabled(showInputOverlay) - inputOverlaySurfaceView.setVisible(showInputOverlay) - this@EmulationFragment.view?.invalidate() - } - editInputsMenuItem.configure(isEnabled = isInputOverlayEnabled) { - binding.editInputsLayout.visibility = View.VISIBLE - binding.finishEditInputsButton.visibility = View.VISIBLE - binding.moveInputsButton.performClick() - } - resetInputOverlayMenuItem.configure( - isEnabled = isInputOverlayEnabled, - onClick = inputOverlaySurfaceView::resetInputs - ) - exitMenuItem.configure { requireActivity().onBackPressedDispatcher.onBackPressed() } - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View { - binding = FragmentEmulationBinding.inflate(inflater, container, false) - inputOverlaySurfaceView = binding.inputOverlay - binding.sideMenu.configureSideMenu(isInputOverlayEnabled = overlaySettings.isOverlayEnabled) - inputOverlaySurfaceView.setVisible(overlaySettings.isOverlayEnabled) - binding.moveInputsButton.setOnClickListener { _ -> - if (inputOverlaySurfaceView.getInputMode() == InputOverlaySurfaceView.InputMode.EDIT_POSITION) { - return@setOnClickListener - } - binding.resizeInputsButton.alpha = 0.5f - binding.moveInputsButton.alpha = 1.0f - toastMessage(R.string.input_mode_edit_position) - inputOverlaySurfaceView.setInputMode(InputOverlaySurfaceView.InputMode.EDIT_POSITION) - } - binding.resizeInputsButton.setOnClickListener { _ -> - if (inputOverlaySurfaceView.getInputMode() == InputOverlaySurfaceView.InputMode.EDIT_SIZE) { - return@setOnClickListener - } - binding.moveInputsButton.alpha = 0.5f - binding.resizeInputsButton.alpha = 1.0f - toastMessage(R.string.input_mode_edit_size) - inputOverlaySurfaceView.setInputMode(InputOverlaySurfaceView.InputMode.EDIT_SIZE) - } - binding.finishEditInputsButton.setOnClickListener { _ -> - inputOverlaySurfaceView.setInputMode(InputOverlaySurfaceView.InputMode.DEFAULT) - binding.finishEditInputsButton.visibility = View.GONE - binding.editInputsLayout.visibility = View.GONE - toastMessage(R.string.input_mode_default) - } - binding.emulationSettingsButton.setOnClickListener { binding.drawerLayout.open() } - val mainCanvas = binding.mainCanvas - try { - val testSurfaceTexture = SurfaceTexture(0) - val testSurface = Surface(testSurfaceTexture) - NativeEmulation.initializeRenderer(testSurface) - testSurface.release() - testSurfaceTexture.release() - } catch (exception: NativeException) { - onEmulationError( - getString( - R.string.failed_initialize_renderer_error, - exception.message - ) - ) - return binding.root - } - - val mainCanvasHolder = mainCanvas.holder - mainCanvasHolder.addCallback(CanvasSurfaceHolderCallback(isMainCanvas = true)) - mainCanvasHolder.addCallback(object : SurfaceHolder.Callback { - override fun surfaceCreated(holder: SurfaceHolder) { - } - - override fun surfaceChanged( - holder: SurfaceHolder, - format: Int, - width: Int, - height: Int, - ) { - if (hasEmulationError) { - return - } - if (!isGameRunning) { - isGameRunning = true - startGame() - } - } - - override fun surfaceDestroyed(holder: SurfaceHolder) { - } - }) - mainCanvas.setOnTouchListener(CanvasOnTouchListener(isTV = true)) - return binding.root - } - - private fun startGame() { - val result = NativeEmulation.startGame(launchPath) - if (result == NativeEmulation.START_GAME_SUCCESSFUL) { - return - } - val errorMessage = when (result) { - NativeEmulation.START_GAME_ERROR_GAME_BASE_FILES_NOT_FOUND -> getString(R.string.game_not_found) - NativeEmulation.START_GAME_ERROR_NO_DISC_KEY -> getString(R.string.no_disk_key) - NativeEmulation.START_GAME_ERROR_NO_TITLE_TIK -> getString(R.string.no_title_tik) - else -> getString(R.string.game_files_unknown_error, launchPath) - } - onEmulationError(errorMessage) - } - - private fun onEmulationError(errorMessage: String) { - hasEmulationError = true - onEmulationErrorCallback?.onEmulationError(errorMessage) - } -} \ No newline at end of file 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 072d690d..cd247697 100644 --- a/src/android/app/src/main/res/layout/activity_emulation.xml +++ b/src/android/app/src/main/res/layout/activity_emulation.xml @@ -1,14 +1,81 @@ - + android:touchscreenBlocksFocus="true" + tools:openDrawer="start"> - + android:layout_height="match_parent"> - + + + + + + + +