forked from MeloNX/MeloNX
android - drop game activity, replace with compose view
This commit is contained in:
parent
f6850bcc1a
commit
fdb7320031
@ -27,14 +27,10 @@
|
|||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.RyujinxAndroid"
|
android:theme="@style/Theme.RyujinxAndroid"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
|
||||||
android:name=".GameActivity"
|
|
||||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:theme="@style/Theme.RyujinxAndroid" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:theme="@style/Theme.RyujinxAndroid">
|
android:theme="@style/Theme.RyujinxAndroid">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
@ -1,407 +0,0 @@
|
|||||||
package org.ryujinx.android
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.ActivityInfo
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.KeyEvent
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import androidx.activity.compose.BackHandler
|
|
||||||
import androidx.activity.compose.setContent
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
|
||||||
import androidx.compose.foundation.layout.wrapContentWidth
|
|
||||||
import androidx.compose.material3.AlertDialog
|
|
||||||
import androidx.compose.material3.AlertDialogDefaults
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.Card
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.input.pointer.PointerEventType
|
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
|
||||||
import androidx.compose.ui.window.Popup
|
|
||||||
import androidx.core.view.WindowCompat
|
|
||||||
import androidx.core.view.WindowInsetsCompat
|
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
|
||||||
import compose.icons.CssGgIcons
|
|
||||||
import compose.icons.cssggicons.ToolbarBottom
|
|
||||||
import org.ryujinx.android.ui.theme.RyujinxAndroidTheme
|
|
||||||
import org.ryujinx.android.viewmodels.MainViewModel
|
|
||||||
import org.ryujinx.android.viewmodels.QuickSettings
|
|
||||||
import kotlin.math.abs
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
class GameActivity : BaseActivity() {
|
|
||||||
private var physicalControllerManager: PhysicalControllerManager =
|
|
||||||
PhysicalControllerManager(this)
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
|
|
||||||
MainActivity.mainViewModel!!.physicalControllerManager = physicalControllerManager
|
|
||||||
setContent {
|
|
||||||
RyujinxAndroidTheme {
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
color = MaterialTheme.colorScheme.background
|
|
||||||
) {
|
|
||||||
GameView(mainViewModel = MainActivity.mainViewModel!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
|
||||||
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
|
|
||||||
event?.apply {
|
|
||||||
if (physicalControllerManager.onKeyEvent(this))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return super.dispatchKeyEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun dispatchGenericMotionEvent(ev: MotionEvent?): Boolean {
|
|
||||||
ev?.apply {
|
|
||||||
physicalControllerManager.onMotionEvent(this)
|
|
||||||
}
|
|
||||||
return super.dispatchGenericMotionEvent(ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
super.onStop()
|
|
||||||
|
|
||||||
NativeHelpers().setTurboMode(false)
|
|
||||||
force60HzRefreshRate(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
|
|
||||||
setFullScreen(true)
|
|
||||||
NativeHelpers().setTurboMode(true)
|
|
||||||
force60HzRefreshRate(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
super.onPause()
|
|
||||||
|
|
||||||
NativeHelpers().setTurboMode(false)
|
|
||||||
force60HzRefreshRate(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun force60HzRefreshRate(enable: Boolean) {
|
|
||||||
// Hack for MIUI devices since they don't support the standard Android APIs
|
|
||||||
try {
|
|
||||||
val setFpsIntent = Intent("com.miui.powerkeeper.SET_ACTIVITY_FPS")
|
|
||||||
setFpsIntent.putExtra("package_name", "org.ryujinx.android")
|
|
||||||
setFpsIntent.putExtra("isEnter", enable)
|
|
||||||
sendBroadcast(setFpsIntent)
|
|
||||||
} catch (_: Exception) {
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enable)
|
|
||||||
display?.supportedModes?.minByOrNull { abs(it.refreshRate - 60f) }
|
|
||||||
?.let { window.attributes.preferredDisplayModeId = it.modeId }
|
|
||||||
else
|
|
||||||
display?.supportedModes?.maxByOrNull { it.refreshRate }
|
|
||||||
?.let { window.attributes.preferredDisplayModeId = it.modeId }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setFullScreen(fullscreen: Boolean) {
|
|
||||||
requestedOrientation =
|
|
||||||
if (fullscreen) ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE else ActivityInfo.SCREEN_ORIENTATION_FULL_USER
|
|
||||||
|
|
||||||
val insets = WindowCompat.getInsetsController(window, window.decorView)
|
|
||||||
|
|
||||||
insets.apply {
|
|
||||||
if (fullscreen) {
|
|
||||||
insets.hide(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
|
|
||||||
insets.systemBarsBehavior =
|
|
||||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
||||||
} else {
|
|
||||||
insets.show(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
|
|
||||||
insets.systemBarsBehavior =
|
|
||||||
WindowInsetsControllerCompat.BEHAVIOR_DEFAULT
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun GameView(mainViewModel: MainViewModel) {
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
AndroidView(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
factory = { context ->
|
|
||||||
GameHost(context, mainViewModel)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
GameOverlay(mainViewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun GameOverlay(mainViewModel: MainViewModel) {
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
GameStats(mainViewModel)
|
|
||||||
|
|
||||||
val ryujinxNative = RyujinxNative()
|
|
||||||
|
|
||||||
val showController = remember {
|
|
||||||
mutableStateOf(QuickSettings(this@GameActivity).useVirtualController)
|
|
||||||
}
|
|
||||||
val enableVsync = remember {
|
|
||||||
mutableStateOf(QuickSettings(this@GameActivity).enableVsync)
|
|
||||||
}
|
|
||||||
val showMore = remember {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
val showLoading = remember {
|
|
||||||
mutableStateOf(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
val progressValue = remember {
|
|
||||||
mutableStateOf(0.0f)
|
|
||||||
}
|
|
||||||
|
|
||||||
val progress = remember {
|
|
||||||
mutableStateOf("Loading")
|
|
||||||
}
|
|
||||||
|
|
||||||
mainViewModel.setProgressStates(showLoading, progressValue, progress)
|
|
||||||
|
|
||||||
// touch surface
|
|
||||||
Surface(color = Color.Transparent, modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(0.dp)
|
|
||||||
.pointerInput(Unit) {
|
|
||||||
awaitPointerEventScope {
|
|
||||||
while (true) {
|
|
||||||
val event = awaitPointerEvent()
|
|
||||||
if (showController.value)
|
|
||||||
continue
|
|
||||||
|
|
||||||
val change = event
|
|
||||||
.component1()
|
|
||||||
.firstOrNull()
|
|
||||||
change?.apply {
|
|
||||||
val position = this.position
|
|
||||||
|
|
||||||
when (event.type) {
|
|
||||||
PointerEventType.Press -> {
|
|
||||||
ryujinxNative.inputSetTouchPoint(
|
|
||||||
position.x.roundToInt(),
|
|
||||||
position.y.roundToInt()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
PointerEventType.Release -> {
|
|
||||||
ryujinxNative.inputReleaseTouchPoint()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
PointerEventType.Move -> {
|
|
||||||
ryujinxNative.inputSetTouchPoint(
|
|
||||||
position.x.roundToInt(),
|
|
||||||
position.y.roundToInt()
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
}
|
|
||||||
if (!showLoading.value) {
|
|
||||||
GameController.Compose(mainViewModel)
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.BottomCenter)
|
|
||||||
.padding(8.dp)
|
|
||||||
) {
|
|
||||||
IconButton(modifier = Modifier.padding(4.dp), onClick = {
|
|
||||||
showMore.value = true
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = CssGgIcons.ToolbarBottom,
|
|
||||||
contentDescription = "Open Panel"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showMore.value) {
|
|
||||||
Popup(
|
|
||||||
alignment = Alignment.BottomCenter,
|
|
||||||
onDismissRequest = { showMore.value = false }) {
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier.padding(16.dp),
|
|
||||||
shape = MaterialTheme.shapes.medium
|
|
||||||
) {
|
|
||||||
Row(modifier = Modifier.padding(8.dp)) {
|
|
||||||
IconButton(modifier = Modifier.padding(4.dp), onClick = {
|
|
||||||
showMore.value = false
|
|
||||||
showController.value = !showController.value
|
|
||||||
mainViewModel.controller?.setVisible(showController.value)
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.videoGame(),
|
|
||||||
contentDescription = "Toggle Virtual Pad"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
IconButton(modifier = Modifier.padding(4.dp), onClick = {
|
|
||||||
showMore.value = false
|
|
||||||
enableVsync.value = !enableVsync.value
|
|
||||||
RyujinxNative().graphicsRendererSetVsync(enableVsync.value)
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.vSync(),
|
|
||||||
tint = if (enableVsync.value) Color.Green else Color.Red,
|
|
||||||
contentDescription = "Toggle VSync"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val showBackNotice = remember {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
BackHandler {
|
|
||||||
showBackNotice.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showLoading.value) {
|
|
||||||
Card(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(16.dp)
|
|
||||||
.fillMaxWidth(0.5f)
|
|
||||||
.align(Alignment.Center),
|
|
||||||
shape = MaterialTheme.shapes.medium
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(16.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Text(text = progress.value)
|
|
||||||
|
|
||||||
if (progressValue.value > -1)
|
|
||||||
LinearProgressIndicator(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(top = 16.dp),
|
|
||||||
progress = progressValue.value
|
|
||||||
)
|
|
||||||
else
|
|
||||||
LinearProgressIndicator(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(top = 16.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showBackNotice.value) {
|
|
||||||
AlertDialog(onDismissRequest = { showBackNotice.value = false }) {
|
|
||||||
Column {
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier
|
|
||||||
.wrapContentWidth()
|
|
||||||
.wrapContentHeight(),
|
|
||||||
shape = MaterialTheme.shapes.large,
|
|
||||||
tonalElevation = AlertDialogDefaults.TonalElevation
|
|
||||||
) {
|
|
||||||
Column {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
Text(text = "Are you sure you want to exit the game?")
|
|
||||||
Text(text = "All unsaved data will be lost!")
|
|
||||||
}
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.End,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
Button(onClick = {
|
|
||||||
showBackNotice.value = false
|
|
||||||
mainViewModel.closeGame()
|
|
||||||
setFullScreen(false)
|
|
||||||
finishActivity(0)
|
|
||||||
}, modifier = Modifier.padding(16.dp)) {
|
|
||||||
Text(text = "Exit Game")
|
|
||||||
}
|
|
||||||
|
|
||||||
Button(onClick = {
|
|
||||||
showBackNotice.value = false
|
|
||||||
}, modifier = Modifier.padding(16.dp)) {
|
|
||||||
Text(text = "Dismiss")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun GameStats(mainViewModel: MainViewModel) {
|
|
||||||
val fifo = remember {
|
|
||||||
mutableStateOf(0.0)
|
|
||||||
}
|
|
||||||
val gameFps = remember {
|
|
||||||
mutableStateOf(0.0)
|
|
||||||
}
|
|
||||||
val gameTime = remember {
|
|
||||||
mutableStateOf(0.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier.padding(16.dp),
|
|
||||||
color = MaterialTheme.colorScheme.surface.copy(0.4f)
|
|
||||||
) {
|
|
||||||
Column {
|
|
||||||
var gameTimeVal = 0.0
|
|
||||||
if (!gameTime.value.isInfinite())
|
|
||||||
gameTimeVal = gameTime.value
|
|
||||||
Text(text = "${String.format("%.3f", fifo.value)} %")
|
|
||||||
Text(text = "${String.format("%.3f", gameFps.value)} FPS")
|
|
||||||
Text(text = "${String.format("%.3f", gameTimeVal)} ms")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mainViewModel.setStatStates(fifo, gameFps, gameTime)
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,7 +12,8 @@ import org.ryujinx.android.viewmodels.QuickSettings
|
|||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
@SuppressLint("ViewConstructor")
|
@SuppressLint("ViewConstructor")
|
||||||
class GameHost(context: Context?, private val mainViewModel: MainViewModel) : SurfaceView(context), SurfaceHolder.Callback {
|
class GameHost(context: Context?, private val mainViewModel: MainViewModel) : SurfaceView(context),
|
||||||
|
SurfaceHolder.Callback {
|
||||||
private var isProgressHidden: Boolean = false
|
private var isProgressHidden: Boolean = false
|
||||||
private var progress: MutableState<String>? = null
|
private var progress: MutableState<String>? = null
|
||||||
private var progressValue: MutableState<Float>? = null
|
private var progressValue: MutableState<Float>? = null
|
||||||
@ -26,7 +27,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
private var _guestThread: Thread? = null
|
private var _guestThread: Thread? = null
|
||||||
private var _isInit: Boolean = false
|
private var _isInit: Boolean = false
|
||||||
private var _isStarted: Boolean = false
|
private var _isStarted: Boolean = false
|
||||||
private val nativeWindow : NativeWindow
|
private val nativeWindow: NativeWindow
|
||||||
|
|
||||||
private var _nativeRyujinx: RyujinxNative = RyujinxNative()
|
private var _nativeRyujinx: RyujinxNative = RyujinxNative()
|
||||||
|
|
||||||
@ -42,12 +43,11 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
|
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
|
||||||
if(_isClosed)
|
if (_isClosed)
|
||||||
return
|
return
|
||||||
start(holder)
|
start(holder)
|
||||||
|
|
||||||
if(_width != width || _height != height)
|
if (_width != width || _height != height) {
|
||||||
{
|
|
||||||
val window = nativeWindow.requeryWindowHandle()
|
val window = nativeWindow.requeryWindowHandle()
|
||||||
_nativeRyujinx.graphicsSetSurface(window, nativeWindow.nativePointer)
|
_nativeRyujinx.graphicsSetSurface(window, nativeWindow.nativePointer)
|
||||||
|
|
||||||
@ -62,8 +62,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
height
|
height
|
||||||
)
|
)
|
||||||
|
|
||||||
if(_isStarted)
|
if (_isStarted) {
|
||||||
{
|
|
||||||
_nativeRyujinx.inputSetClientSize(width, height)
|
_nativeRyujinx.inputSetClientSize(width, height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,7 +71,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun close(){
|
fun close() {
|
||||||
_isClosed = true
|
_isClosed = true
|
||||||
_isInit = false
|
_isInit = false
|
||||||
_isStarted = false
|
_isStarted = false
|
||||||
@ -82,7 +81,7 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun start(surfaceHolder: SurfaceHolder) {
|
private fun start(surfaceHolder: SurfaceHolder) {
|
||||||
if(_isStarted)
|
if (_isStarted)
|
||||||
return
|
return
|
||||||
|
|
||||||
game = mainViewModel.gameModel
|
game = mainViewModel.gameModel
|
||||||
@ -91,10 +90,9 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
|
|
||||||
val settings = QuickSettings(mainViewModel.activity)
|
val settings = QuickSettings(mainViewModel.activity)
|
||||||
|
|
||||||
if(!settings.useVirtualController){
|
if (!settings.useVirtualController) {
|
||||||
mainViewModel.controller?.setVisible(false)
|
mainViewModel.controller?.setVisible(false)
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
mainViewModel.controller?.connect()
|
mainViewModel.controller?.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,13 +116,13 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
Thread.sleep(1)
|
Thread.sleep(1)
|
||||||
|
|
||||||
showLoading?.apply {
|
showLoading?.apply {
|
||||||
if(value){
|
if (value) {
|
||||||
var value = helper.getProgressValue()
|
var value = helper.getProgressValue()
|
||||||
|
|
||||||
if(value != -1f)
|
if (value != -1f)
|
||||||
progress?.apply {
|
progress?.apply {
|
||||||
this.value = helper.getProgressInfo()
|
this.value = helper.getProgressInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
progressValue?.apply {
|
progressValue?.apply {
|
||||||
this.value = value
|
this.value = value
|
||||||
@ -133,12 +131,16 @@ class GameHost(context: Context?, private val mainViewModel: MainViewModel) : Su
|
|||||||
}
|
}
|
||||||
c++
|
c++
|
||||||
if (c >= 1000) {
|
if (c >= 1000) {
|
||||||
if(helper.getProgressValue() == -1f)
|
if (helper.getProgressValue() == -1f)
|
||||||
progress?.apply {
|
progress?.apply {
|
||||||
this.value = "Loading ${game!!.titleName}"
|
this.value = "Loading ${game!!.titleName}"
|
||||||
}
|
}
|
||||||
c = 0
|
c = 0
|
||||||
mainViewModel.updateStats(_nativeRyujinx.deviceGetGameFifo(), _nativeRyujinx.deviceGetGameFrameRate(), _nativeRyujinx.deviceGetGameFrameTime())
|
mainViewModel.updateStats(
|
||||||
|
_nativeRyujinx.deviceGetGameFifo(),
|
||||||
|
_nativeRyujinx.deviceGetGameFrameRate(),
|
||||||
|
_nativeRyujinx.deviceGetGameFrameTime()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
package org.ryujinx.android
|
package org.ryujinx.android
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.ActivityInfo
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import android.view.MotionEvent
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
@ -10,14 +15,20 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
import com.anggrayudi.storage.SimpleStorageHelper
|
import com.anggrayudi.storage.SimpleStorageHelper
|
||||||
import org.ryujinx.android.ui.theme.RyujinxAndroidTheme
|
import org.ryujinx.android.ui.theme.RyujinxAndroidTheme
|
||||||
import org.ryujinx.android.viewmodels.MainViewModel
|
import org.ryujinx.android.viewmodels.MainViewModel
|
||||||
import org.ryujinx.android.views.MainView
|
import org.ryujinx.android.views.MainView
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : BaseActivity() {
|
class MainActivity : BaseActivity() {
|
||||||
|
private var physicalControllerManager: PhysicalControllerManager =
|
||||||
|
PhysicalControllerManager(this)
|
||||||
private var _isInit: Boolean = false
|
private var _isInit: Boolean = false
|
||||||
|
var isGameRunning = false
|
||||||
var storageHelper: SimpleStorageHelper? = null
|
var storageHelper: SimpleStorageHelper? = null
|
||||||
companion object {
|
companion object {
|
||||||
var mainViewModel: MainViewModel? = null
|
var mainViewModel: MainViewModel? = null
|
||||||
@ -67,12 +78,14 @@ class MainActivity : BaseActivity() {
|
|||||||
|
|
||||||
AppPath = this.getExternalFilesDir(null)!!.absolutePath
|
AppPath = this.getExternalFilesDir(null)!!.absolutePath
|
||||||
|
|
||||||
|
|
||||||
initialize()
|
initialize()
|
||||||
|
|
||||||
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
||||||
WindowCompat.setDecorFitsSystemWindows(window,false)
|
WindowCompat.setDecorFitsSystemWindows(window,false)
|
||||||
|
|
||||||
mainViewModel = MainViewModel(this)
|
mainViewModel = MainViewModel(this)
|
||||||
|
mainViewModel!!.physicalControllerManager = physicalControllerManager
|
||||||
|
|
||||||
mainViewModel?.apply {
|
mainViewModel?.apply {
|
||||||
setContent {
|
setContent {
|
||||||
@ -98,4 +111,87 @@ class MainActivity : BaseActivity() {
|
|||||||
super.onRestoreInstanceState(savedInstanceState)
|
super.onRestoreInstanceState(savedInstanceState)
|
||||||
storageHelper?.onRestoreInstanceState(savedInstanceState)
|
storageHelper?.onRestoreInstanceState(savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Game Stuff
|
||||||
|
private fun force60HzRefreshRate(enable: Boolean) {
|
||||||
|
// Hack for MIUI devices since they don't support the standard Android APIs
|
||||||
|
try {
|
||||||
|
val setFpsIntent = Intent("com.miui.powerkeeper.SET_ACTIVITY_FPS")
|
||||||
|
setFpsIntent.putExtra("package_name", "org.ryujinx.android")
|
||||||
|
setFpsIntent.putExtra("isEnter", enable)
|
||||||
|
sendBroadcast(setFpsIntent)
|
||||||
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enable)
|
||||||
|
display?.supportedModes?.minByOrNull { abs(it.refreshRate - 60f) }
|
||||||
|
?.let { window.attributes.preferredDisplayModeId = it.modeId }
|
||||||
|
else
|
||||||
|
display?.supportedModes?.maxByOrNull { it.refreshRate }
|
||||||
|
?.let { window.attributes.preferredDisplayModeId = it.modeId }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setFullScreen(fullscreen: Boolean) {
|
||||||
|
requestedOrientation =
|
||||||
|
if (fullscreen) ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE else ActivityInfo.SCREEN_ORIENTATION_FULL_USER
|
||||||
|
|
||||||
|
val insets = WindowCompat.getInsetsController(window, window.decorView)
|
||||||
|
|
||||||
|
insets.apply {
|
||||||
|
if (fullscreen) {
|
||||||
|
insets.hide(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
|
||||||
|
insets.systemBarsBehavior =
|
||||||
|
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
|
} else {
|
||||||
|
insets.show(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
|
||||||
|
insets.systemBarsBehavior =
|
||||||
|
WindowInsetsControllerCompat.BEHAVIOR_DEFAULT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
|
||||||
|
event?.apply {
|
||||||
|
if (physicalControllerManager.onKeyEvent(this))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return super.dispatchKeyEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispatchGenericMotionEvent(ev: MotionEvent?): Boolean {
|
||||||
|
ev?.apply {
|
||||||
|
physicalControllerManager.onMotionEvent(this)
|
||||||
|
}
|
||||||
|
return super.dispatchGenericMotionEvent(ev)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
|
||||||
|
if(isGameRunning) {
|
||||||
|
NativeHelpers().setTurboMode(false)
|
||||||
|
force60HzRefreshRate(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
if(isGameRunning) {
|
||||||
|
setFullScreen(true)
|
||||||
|
NativeHelpers().setTurboMode(true)
|
||||||
|
force60HzRefreshRate(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
|
||||||
|
if(isGameRunning) {
|
||||||
|
NativeHelpers().setTurboMode(false)
|
||||||
|
force60HzRefreshRate(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ package org.ryujinx.android
|
|||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
|
|
||||||
class PhysicalControllerManager(val activity: GameActivity) {
|
class PhysicalControllerManager(val activity: MainActivity) {
|
||||||
private var controllerId: Int = -1
|
private var controllerId: Int = -1
|
||||||
private var ryujinxNative: RyujinxNative = RyujinxNative()
|
private var ryujinxNative: RyujinxNative = RyujinxNative()
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package org.ryujinx.android.viewmodels
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.PerformanceHintManager
|
import android.os.PerformanceHintManager
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
@ -10,7 +9,6 @@ import androidx.navigation.NavHostController
|
|||||||
import com.anggrayudi.storage.extension.launchOnUiThread
|
import com.anggrayudi.storage.extension.launchOnUiThread
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
import org.ryujinx.android.GameActivity
|
|
||||||
import org.ryujinx.android.GameController
|
import org.ryujinx.android.GameController
|
||||||
import org.ryujinx.android.GameHost
|
import org.ryujinx.android.GameHost
|
||||||
import org.ryujinx.android.GraphicsConfiguration
|
import org.ryujinx.android.GraphicsConfiguration
|
||||||
@ -241,8 +239,9 @@ class MainViewModel(val activity: MainActivity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun navigateToGame() {
|
fun navigateToGame() {
|
||||||
val intent = Intent(activity, GameActivity::class.java)
|
activity.setFullScreen(true)
|
||||||
activity.startActivity(intent)
|
navController?.navigate("game")
|
||||||
|
activity.isGameRunning = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setProgressStates(
|
fun setProgressStates(
|
||||||
|
@ -0,0 +1,319 @@
|
|||||||
|
package org.ryujinx.android.views
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.AlertDialogDefaults
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.input.pointer.PointerEventType
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import androidx.compose.ui.window.Popup
|
||||||
|
import compose.icons.CssGgIcons
|
||||||
|
import compose.icons.cssggicons.ToolbarBottom
|
||||||
|
import org.ryujinx.android.GameController
|
||||||
|
import org.ryujinx.android.GameHost
|
||||||
|
import org.ryujinx.android.Icons
|
||||||
|
import org.ryujinx.android.MainActivity
|
||||||
|
import org.ryujinx.android.RyujinxNative
|
||||||
|
import org.ryujinx.android.viewmodels.MainViewModel
|
||||||
|
import org.ryujinx.android.viewmodels.QuickSettings
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
class GameViews {
|
||||||
|
companion object {
|
||||||
|
@Composable
|
||||||
|
fun Main() {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
color = MaterialTheme.colorScheme.background
|
||||||
|
) {
|
||||||
|
GameView(mainViewModel = MainActivity.mainViewModel!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GameView(mainViewModel: MainViewModel) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
AndroidView(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
factory = { context ->
|
||||||
|
GameHost(context, mainViewModel)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
GameOverlay(mainViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun GameOverlay(mainViewModel: MainViewModel) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
GameStats(mainViewModel)
|
||||||
|
|
||||||
|
val ryujinxNative = RyujinxNative()
|
||||||
|
|
||||||
|
val showController = remember {
|
||||||
|
mutableStateOf(QuickSettings(mainViewModel.activity).useVirtualController)
|
||||||
|
}
|
||||||
|
val enableVsync = remember {
|
||||||
|
mutableStateOf(QuickSettings(mainViewModel.activity).enableVsync)
|
||||||
|
}
|
||||||
|
val showMore = remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
val showLoading = remember {
|
||||||
|
mutableStateOf(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
val progressValue = remember {
|
||||||
|
mutableStateOf(0.0f)
|
||||||
|
}
|
||||||
|
|
||||||
|
val progress = remember {
|
||||||
|
mutableStateOf("Loading")
|
||||||
|
}
|
||||||
|
|
||||||
|
mainViewModel.setProgressStates(showLoading, progressValue, progress)
|
||||||
|
|
||||||
|
// touch surface
|
||||||
|
Surface(color = Color.Transparent, modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(0.dp)
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
awaitPointerEventScope {
|
||||||
|
while (true) {
|
||||||
|
val event = awaitPointerEvent()
|
||||||
|
if (showController.value)
|
||||||
|
continue
|
||||||
|
|
||||||
|
val change = event
|
||||||
|
.component1()
|
||||||
|
.firstOrNull()
|
||||||
|
change?.apply {
|
||||||
|
val position = this.position
|
||||||
|
|
||||||
|
when (event.type) {
|
||||||
|
PointerEventType.Press -> {
|
||||||
|
ryujinxNative.inputSetTouchPoint(
|
||||||
|
position.x.roundToInt(),
|
||||||
|
position.y.roundToInt()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
PointerEventType.Release -> {
|
||||||
|
ryujinxNative.inputReleaseTouchPoint()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
PointerEventType.Move -> {
|
||||||
|
ryujinxNative.inputSetTouchPoint(
|
||||||
|
position.x.roundToInt(),
|
||||||
|
position.y.roundToInt()
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
}
|
||||||
|
if (!showLoading.value) {
|
||||||
|
GameController.Compose(mainViewModel)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
IconButton(modifier = Modifier.padding(4.dp), onClick = {
|
||||||
|
showMore.value = true
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = CssGgIcons.ToolbarBottom,
|
||||||
|
contentDescription = "Open Panel"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showMore.value) {
|
||||||
|
Popup(
|
||||||
|
alignment = Alignment.BottomCenter,
|
||||||
|
onDismissRequest = { showMore.value = false }) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
shape = MaterialTheme.shapes.medium
|
||||||
|
) {
|
||||||
|
Row(modifier = Modifier.padding(8.dp)) {
|
||||||
|
IconButton(modifier = Modifier.padding(4.dp), onClick = {
|
||||||
|
showMore.value = false
|
||||||
|
showController.value = !showController.value
|
||||||
|
mainViewModel.controller?.setVisible(showController.value)
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.videoGame(),
|
||||||
|
contentDescription = "Toggle Virtual Pad"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(modifier = Modifier.padding(4.dp), onClick = {
|
||||||
|
showMore.value = false
|
||||||
|
enableVsync.value = !enableVsync.value
|
||||||
|
RyujinxNative().graphicsRendererSetVsync(enableVsync.value)
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.vSync(),
|
||||||
|
tint = if (enableVsync.value) Color.Green else Color.Red,
|
||||||
|
contentDescription = "Toggle VSync"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val showBackNotice = remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
BackHandler {
|
||||||
|
showBackNotice.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showLoading.value) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(0.5f)
|
||||||
|
.align(Alignment.Center),
|
||||||
|
shape = MaterialTheme.shapes.medium
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(text = progress.value)
|
||||||
|
|
||||||
|
if (progressValue.value > -1)
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 16.dp),
|
||||||
|
progress = progressValue.value
|
||||||
|
)
|
||||||
|
else
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showBackNotice.value) {
|
||||||
|
AlertDialog(onDismissRequest = { showBackNotice.value = false }) {
|
||||||
|
Column {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentWidth()
|
||||||
|
.wrapContentHeight(),
|
||||||
|
shape = MaterialTheme.shapes.large,
|
||||||
|
tonalElevation = AlertDialogDefaults.TonalElevation
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(text = "Are you sure you want to exit the game?")
|
||||||
|
Text(text = "All unsaved data will be lost!")
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.End,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Button(onClick = {
|
||||||
|
showBackNotice.value = false
|
||||||
|
mainViewModel.closeGame()
|
||||||
|
mainViewModel.activity.setFullScreen(false)
|
||||||
|
mainViewModel.navController?.popBackStack()
|
||||||
|
mainViewModel.activity.isGameRunning = false
|
||||||
|
}, modifier = Modifier.padding(16.dp)) {
|
||||||
|
Text(text = "Exit Game")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(onClick = {
|
||||||
|
showBackNotice.value = false
|
||||||
|
}, modifier = Modifier.padding(16.dp)) {
|
||||||
|
Text(text = "Dismiss")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GameStats(mainViewModel: MainViewModel) {
|
||||||
|
val fifo = remember {
|
||||||
|
mutableStateOf(0.0)
|
||||||
|
}
|
||||||
|
val gameFps = remember {
|
||||||
|
mutableStateOf(0.0)
|
||||||
|
}
|
||||||
|
val gameTime = remember {
|
||||||
|
mutableStateOf(0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
color = MaterialTheme.colorScheme.surface.copy(0.4f)
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
var gameTimeVal = 0.0
|
||||||
|
if (!gameTime.value.isInfinite())
|
||||||
|
gameTimeVal = gameTime.value
|
||||||
|
Text(text = "${String.format("%.3f", fifo.value)} %")
|
||||||
|
Text(text = "${String.format("%.3f", gameFps.value)} FPS")
|
||||||
|
Text(text = "${String.format("%.3f", gameTimeVal)} ms")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mainViewModel.setStatStates(fifo, gameFps, gameTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@ class MainView {
|
|||||||
NavHost(navController = navController, startDestination = "home") {
|
NavHost(navController = navController, startDestination = "home") {
|
||||||
composable("home") { HomeViews.Home(mainViewModel.homeViewModel, navController) }
|
composable("home") { HomeViews.Home(mainViewModel.homeViewModel, navController) }
|
||||||
composable("user") { UserViews.Main(mainViewModel, navController) }
|
composable("user") { UserViews.Main(mainViewModel, navController) }
|
||||||
|
composable("game") { GameViews.Main() }
|
||||||
composable("settings") {
|
composable("settings") {
|
||||||
SettingViews.Main(
|
SettingViews.Main(
|
||||||
SettingsViewModel(
|
SettingsViewModel(
|
||||||
|
@ -461,7 +461,8 @@ class SettingViews {
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(300.dp)
|
.height(350.dp)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -494,7 +495,7 @@ class SettingViews {
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(8.dp),
|
.padding(4.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
RadioButton(
|
RadioButton(
|
||||||
|
@ -6,6 +6,8 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
@ -62,6 +64,7 @@ class TitleUpdateViews {
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(250.dp)
|
.height(250.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
Row(modifier = Modifier.padding(8.dp)) {
|
Row(modifier = Modifier.padding(8.dp)) {
|
||||||
RadioButton(
|
RadioButton(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user