refactor emulation activity

This commit is contained in:
SSimco
2025-04-28 08:36:44 +03:00
parent 09eb9e5b48
commit 119b39ab19
5 changed files with 408 additions and 467 deletions
@@ -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
}
}
@@ -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<EmulationActivity?> =
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<EmulationTextInputEditText>(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() }
}
}
}
@@ -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)
}
}
@@ -1,14 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<info.cemu.cemu.guicore.components.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=".emulation.EmulationActivity">
android:touchscreenBlocksFocus="true"
tools:openDrawer="start">
<FrameLayout
android:id="@+id/emulation_frame"
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent">
</FrameLayout>
<LinearLayout
android:id="@+id/canvases_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<SurfaceView
android:id="@+id/main_canvas"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
<info.cemu.cemu.inputoverlay.InputOverlaySurfaceView
android:id="@+id/input_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="@+id/finish_edit_inputs_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/input_finish_edit_label"
android:visibility="gone" />
<LinearLayout
android:id="@+id/edit_inputs_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_margin="8dp"
android:gravity="center"
android:visibility="gone">
<Button
android:id="@+id/move_inputs_button"
style="?attr/materialIconButtonFilledStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:contentDescription="@string/move_inputs_description"
app:icon="@drawable/ic_move" />
<Button
android:id="@+id/resize_inputs_button"
style="?attr/materialIconButtonFilledStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/resize_inputs_description"
app:icon="@drawable/ic_resize" />
</LinearLayout>
<Button
android:id="@+id/emulation_settings_button"
style="?attr/materialIconButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/input_mode_button_description"
app:icon="@drawable/ic_settings" />
</RelativeLayout>
<include
android:id="@+id/side_menu"
layout="@layout/layout_side_menu_emulation" />
</info.cemu.cemu.guicore.components.DrawerLayout>
@@ -1,82 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<info.cemu.cemu.guicore.components.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:touchscreenBlocksFocus="true"
tools:context=".emulation.EmulationFragment"
tools:openDrawer="start">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/canvases_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<SurfaceView
android:id="@+id/main_canvas"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
<info.cemu.cemu.inputoverlay.InputOverlaySurfaceView
android:id="@+id/input_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<Button
android:id="@+id/finish_edit_inputs_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="@string/input_finish_edit_label"
android:visibility="gone" />
<LinearLayout
android:id="@+id/edit_inputs_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_margin="8dp"
android:gravity="center"
android:visibility="gone">
<Button
android:id="@+id/move_inputs_button"
style="?attr/materialIconButtonFilledStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:contentDescription="@string/move_inputs_description"
app:icon="@drawable/ic_move" />
<Button
android:id="@+id/resize_inputs_button"
style="?attr/materialIconButtonFilledStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/resize_inputs_description"
app:icon="@drawable/ic_resize" />
</LinearLayout>
<Button
android:id="@+id/emulation_settings_button"
style="?attr/materialIconButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/input_mode_button_description"
app:icon="@drawable/ic_settings" />
</RelativeLayout>
<include
android:id="@+id/side_menu"
layout="@layout/layout_side_menu_emulation" />
</info.cemu.cemu.guicore.components.DrawerLayout>