forked from MeloNX/MeloNX
android - add physical controller support
add performance hints expand full screen to behind cutouts fix touch, add toggle for virtual gamepad remove safe area margins
This commit is contained in:
parent
c57f6a7fe3
commit
fcb511bbca
@ -36,6 +36,12 @@ namespace LibRyujinx
|
|||||||
[DllImport("libryujinxjni")]
|
[DllImport("libryujinxjni")]
|
||||||
private extern static JStringLocalRef createString(JEnvRef jEnv, IntPtr ch);
|
private extern static JStringLocalRef createString(JEnvRef jEnv, IntPtr ch);
|
||||||
|
|
||||||
|
[DllImport("libryujinxjni")]
|
||||||
|
internal extern static void setRenderingThread();
|
||||||
|
|
||||||
|
[DllImport("libryujinxjni")]
|
||||||
|
internal extern static void onFrameEnd(double time);
|
||||||
|
|
||||||
public delegate IntPtr JniCreateSurface(IntPtr native_surface, IntPtr instance);
|
public delegate IntPtr JniCreateSurface(IntPtr native_surface, IntPtr instance);
|
||||||
|
|
||||||
[UnmanagedCallersOnly(EntryPoint = "JNI_OnLoad")]
|
[UnmanagedCallersOnly(EntryPoint = "JNI_OnLoad")]
|
||||||
@ -279,6 +285,11 @@ namespace LibRyujinx
|
|||||||
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererRunLoop")]
|
[UnmanagedCallersOnly(EntryPoint = "Java_org_ryujinx_android_RyujinxNative_graphicsRendererRunLoop")]
|
||||||
public static void JniRunLoopNative(JEnvRef jEnv, JObjectLocalRef jObj)
|
public static void JniRunLoopNative(JEnvRef jEnv, JObjectLocalRef jObj)
|
||||||
{
|
{
|
||||||
|
SetSwapBuffersCallback(() =>
|
||||||
|
{
|
||||||
|
var time = SwitchDevice.EmulationContext.Statistics.GetGameFrameTime();
|
||||||
|
onFrameEnd(time);
|
||||||
|
});
|
||||||
RunLoop();
|
RunLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +112,11 @@ namespace LibRyujinx
|
|||||||
|
|
||||||
_isActive = true;
|
_isActive = true;
|
||||||
|
|
||||||
|
if (Ryujinx.Common.SystemInfo.SystemInfo.IsBionic)
|
||||||
|
{
|
||||||
|
setRenderingThread();
|
||||||
|
}
|
||||||
|
|
||||||
while (_isActive)
|
while (_isActive)
|
||||||
{
|
{
|
||||||
if (_isStopped)
|
if (_isStopped)
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
|
android:appCategory="game"
|
||||||
android:theme="@style/Theme.RyujinxAndroid"
|
android:theme="@style/Theme.RyujinxAndroid"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<activity
|
<activity
|
||||||
|
@ -36,4 +36,10 @@ void* _ryujinxNative = NULL;
|
|||||||
// Ryujinx imported functions
|
// Ryujinx imported functions
|
||||||
bool (*initialize)(char*) = NULL;
|
bool (*initialize)(char*) = NULL;
|
||||||
|
|
||||||
|
long _renderingThreadId = 0;
|
||||||
|
long _currentRenderingThreadId = 0;
|
||||||
|
JavaVM* _vm = nullptr;
|
||||||
|
jobject _mainActivity = nullptr;
|
||||||
|
jclass _mainActivityClass = nullptr;
|
||||||
|
|
||||||
#endif //RYUJINXNATIVE_RYUIJNX_H
|
#endif //RYUJINXNATIVE_RYUIJNX_H
|
||||||
|
@ -17,6 +17,31 @@
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
#include "ryuijnx.h"
|
#include "ryuijnx.h"
|
||||||
|
#include "pthread.h"
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
jmethodID _updateFrameTime;
|
||||||
|
JNIEnv* _rendererEnv = nullptr;
|
||||||
|
|
||||||
|
std::chrono::time_point<std::chrono::steady_clock, std::chrono::nanoseconds> _currentTimePoint;
|
||||||
|
|
||||||
|
JNIEnv* getEnv(bool isRenderer){
|
||||||
|
JNIEnv* env;
|
||||||
|
if(isRenderer){
|
||||||
|
env = _rendererEnv;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(env != nullptr)
|
||||||
|
return env;
|
||||||
|
|
||||||
|
auto result = _vm->AttachCurrentThread(&env, NULL);
|
||||||
|
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
void detachEnv(){
|
||||||
|
auto result = _vm->DetachCurrentThread();
|
||||||
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
{
|
{
|
||||||
@ -129,3 +154,38 @@ jstring createString(
|
|||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT jlong JNICALL
|
||||||
|
Java_org_ryujinx_android_MainActivity_getRenderingThreadId(JNIEnv *env, jobject thiz) {
|
||||||
|
return _currentRenderingThreadId;
|
||||||
|
}
|
||||||
|
extern "C"
|
||||||
|
void setRenderingThread(){
|
||||||
|
auto currentId = pthread_self();
|
||||||
|
|
||||||
|
_currentRenderingThreadId = currentId;
|
||||||
|
_renderingThreadId = currentId;
|
||||||
|
|
||||||
|
_currentTimePoint = std::chrono::high_resolution_clock::now();
|
||||||
|
}
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_org_ryujinx_android_MainActivity_initVm(JNIEnv *env, jobject thiz) {
|
||||||
|
JavaVM* vm = nullptr;
|
||||||
|
auto success = env->GetJavaVM(&vm);
|
||||||
|
_vm = vm;
|
||||||
|
_mainActivity = thiz;
|
||||||
|
_mainActivityClass = env->GetObjectClass(thiz);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
void onFrameEnd(double time){
|
||||||
|
auto env = getEnv(true);
|
||||||
|
auto cl = env->FindClass("org/ryujinx/android/MainActivity");
|
||||||
|
_updateFrameTime = env->GetStaticMethodID( cl , "updateRenderSessionPerformance", "(J)V");
|
||||||
|
|
||||||
|
auto now = std::chrono::high_resolution_clock::now();
|
||||||
|
auto nano = std::chrono::duration_cast<std::chrono::nanoseconds>(now-_currentTimePoint).count();
|
||||||
|
env->CallStaticVoidMethod(cl, _updateFrameTime,
|
||||||
|
nano);
|
||||||
|
}
|
@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.GraphicsLayerScope
|
import androidx.compose.ui.graphics.GraphicsLayerScope
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleCoroutineScope
|
import androidx.lifecycle.LifecycleCoroutineScope
|
||||||
import androidx.lifecycle.flowWithLifecycle
|
import androidx.lifecycle.flowWithLifecycle
|
||||||
@ -45,9 +46,18 @@ typealias GamePad = RadialGamePad
|
|||||||
typealias GamePadConfig = RadialGamePadConfig
|
typealias GamePadConfig = RadialGamePadConfig
|
||||||
|
|
||||||
class GameController(var activity: Activity, var ryujinxNative: RyujinxNative = RyujinxNative()) {
|
class GameController(var activity: Activity, var ryujinxNative: RyujinxNative = RyujinxNative()) {
|
||||||
|
private var controllerView: View? = null
|
||||||
var leftGamePad: GamePad
|
var leftGamePad: GamePad
|
||||||
var rightGamePad: GamePad
|
var rightGamePad: GamePad
|
||||||
var controllerId: Int = -1
|
var controllerId: Int = -1
|
||||||
|
val isVisible : Boolean
|
||||||
|
get() {
|
||||||
|
controllerView?.apply {
|
||||||
|
return this.isVisible
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
leftGamePad = GamePad(generateConfig(true), 16f, activity)
|
leftGamePad = GamePad(generateConfig(true), 16f, activity)
|
||||||
@ -65,7 +75,6 @@ class GameController(var activity: Activity, var ryujinxNative: RyujinxNative =
|
|||||||
@Composable
|
@Composable
|
||||||
fun Compose(lifecycleScope: LifecycleCoroutineScope, lifecycle:Lifecycle) : Unit
|
fun Compose(lifecycleScope: LifecycleCoroutineScope, lifecycle:Lifecycle) : Unit
|
||||||
{
|
{
|
||||||
|
|
||||||
AndroidView(
|
AndroidView(
|
||||||
modifier = Modifier.fillMaxSize(), factory = { context -> Create(context)})
|
modifier = Modifier.fillMaxSize(), factory = { context -> Create(context)})
|
||||||
|
|
||||||
@ -88,7 +97,18 @@ class GameController(var activity: Activity, var ryujinxNative: RyujinxNative =
|
|||||||
view.findViewById<FrameLayout>(R.id.leftcontainer)!!.addView(leftGamePad);
|
view.findViewById<FrameLayout>(R.id.leftcontainer)!!.addView(leftGamePad);
|
||||||
view.findViewById<FrameLayout>(R.id.rightcontainer)!!.addView(rightGamePad);
|
view.findViewById<FrameLayout>(R.id.rightcontainer)!!.addView(rightGamePad);
|
||||||
|
|
||||||
return view as View
|
controllerView = view
|
||||||
|
|
||||||
|
return controllerView as View
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setVisible(isVisible: Boolean){
|
||||||
|
controllerView?.apply {
|
||||||
|
this.isVisible = isVisible
|
||||||
|
|
||||||
|
if(isVisible)
|
||||||
|
connect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun connect(){
|
fun connect(){
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
package org.ryujinx.android
|
package org.ryujinx.android
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.Build
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.SurfaceHolder
|
import android.view.SurfaceHolder
|
||||||
import android.view.SurfaceView
|
import android.view.SurfaceView
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import org.ryujinx.android.viewmodels.GameModel
|
import org.ryujinx.android.viewmodels.GameModel
|
||||||
import org.ryujinx.android.viewmodels.MainViewModel
|
import org.ryujinx.android.viewmodels.MainViewModel
|
||||||
import org.ryujinx.android.viewmodels.QuickSettings
|
import org.ryujinx.android.viewmodels.QuickSettings
|
||||||
@ -15,6 +12,7 @@ import kotlin.concurrent.thread
|
|||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class GameHost(context: Context?, val controller: GameController, val mainViewModel: MainViewModel) : SurfaceView(context), SurfaceHolder.Callback {
|
class GameHost(context: Context?, val controller: GameController, val mainViewModel: MainViewModel) : SurfaceView(context), SurfaceHolder.Callback {
|
||||||
|
private var _renderingThreadWatcher: Thread? = null
|
||||||
private var _height: Int = 0
|
private var _height: Int = 0
|
||||||
private var _width: Int = 0
|
private var _width: Int = 0
|
||||||
private var _updateThread: Thread? = null
|
private var _updateThread: Thread? = null
|
||||||
@ -35,26 +33,6 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo
|
|||||||
holder.addCallback(this)
|
holder.addCallback(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
|
||||||
if (_isStarted)
|
|
||||||
return when (event!!.actionMasked) {
|
|
||||||
MotionEvent.ACTION_MOVE -> {
|
|
||||||
_nativeRyujinx.inputSetTouchPoint(event.x.roundToInt(), event.y.roundToInt())
|
|
||||||
true
|
|
||||||
}
|
|
||||||
MotionEvent.ACTION_DOWN -> {
|
|
||||||
_nativeRyujinx.inputSetTouchPoint(event.x.roundToInt(), event.y.roundToInt())
|
|
||||||
true
|
|
||||||
}
|
|
||||||
MotionEvent.ACTION_UP -> {
|
|
||||||
_nativeRyujinx.inputReleaseTouchPoint()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> super.onTouchEvent(event)
|
|
||||||
}
|
|
||||||
return super.onTouchEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun surfaceCreated(holder: SurfaceHolder) {
|
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +110,16 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo
|
|||||||
|
|
||||||
_nativeRyujinx.inputInitialize(width, height)
|
_nativeRyujinx.inputInitialize(width, height)
|
||||||
|
|
||||||
|
if(!settings.useVirtualController){
|
||||||
|
controller.setVisible(false)
|
||||||
|
}
|
||||||
|
else{
|
||||||
controller.connect()
|
controller.connect()
|
||||||
|
}
|
||||||
|
|
||||||
|
mainViewModel.activity.physicalControllerManager.connect()
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
_nativeRyujinx.graphicsRendererSetSize(
|
_nativeRyujinx.graphicsRendererSetSize(
|
||||||
surfaceHolder.surfaceFrame.width(),
|
surfaceHolder.surfaceFrame.width(),
|
||||||
@ -159,7 +146,25 @@ class GameHost(context: Context?, val controller: GameController, val mainViewMo
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun runGame() : Unit{
|
private fun runGame() : Unit{
|
||||||
|
// RenderingThreadWatcher
|
||||||
|
_renderingThreadWatcher = thread(start = true) {
|
||||||
|
var threadId = 0L;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
mainViewModel.performanceManager?.enable()
|
||||||
|
while (_isStarted) {
|
||||||
|
Thread.sleep(1000)
|
||||||
|
var newthreadId = mainViewModel.activity.getRenderingThreadId()
|
||||||
|
|
||||||
|
if (threadId != newthreadId) {
|
||||||
|
mainViewModel.performanceManager?.closeCurrentRenderingSession()
|
||||||
|
}
|
||||||
|
threadId = newthreadId;
|
||||||
|
if (threadId != 0L) {
|
||||||
|
mainViewModel.performanceManager?.initializeRenderingSession(threadId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_nativeRyujinx.graphicsRendererRunLoop()
|
_nativeRyujinx.graphicsRendererRunLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package org.ryujinx.android
|
package org.ryujinx.android
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
@ -8,6 +9,8 @@ import android.media.AudioManager
|
|||||||
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.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
@ -29,28 +32,43 @@ import org.ryujinx.android.views.MainView
|
|||||||
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private var mainViewModel: MainViewModel? = null
|
var physicalControllerManager: PhysicalControllerManager
|
||||||
private var _isInit: Boolean = false
|
private var _isInit: Boolean = false
|
||||||
var storageHelper: SimpleStorageHelper? = null
|
var storageHelper: SimpleStorageHelper? = null
|
||||||
companion object {
|
companion object {
|
||||||
|
var mainViewModel: MainViewModel? = null
|
||||||
var AppPath : String?
|
var AppPath : String?
|
||||||
var StorageHelper: SimpleStorageHelper? = null
|
var StorageHelper: SimpleStorageHelper? = null
|
||||||
init {
|
init {
|
||||||
AppPath = ""
|
AppPath = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun updateRenderSessionPerformance(gameTime : Long)
|
||||||
|
{
|
||||||
|
if(gameTime <= 0)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
mainViewModel?.performanceManager?.updateRenderingSessionTime(gameTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
physicalControllerManager = PhysicalControllerManager(this)
|
||||||
storageHelper = SimpleStorageHelper(this)
|
storageHelper = SimpleStorageHelper(this)
|
||||||
StorageHelper = storageHelper
|
StorageHelper = storageHelper
|
||||||
|
System.loadLibrary("ryujinxjni")
|
||||||
|
initVm()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
external fun getRenderingThreadId() : Long
|
||||||
|
external fun initVm()
|
||||||
|
|
||||||
fun setFullScreen() :Unit {
|
fun setFullScreen() :Unit {
|
||||||
requestedOrientation =
|
requestedOrientation =
|
||||||
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
|
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||||
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
|
||||||
|
|
||||||
WindowCompat.setDecorFitsSystemWindows(window,false)
|
|
||||||
|
|
||||||
var insets = WindowCompat.getInsetsController(window, window.decorView)
|
var insets = WindowCompat.getInsetsController(window, window.decorView)
|
||||||
|
|
||||||
@ -79,6 +97,21 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
|
||||||
|
event?.apply {
|
||||||
|
return physicalControllerManager.onKeyEvent(this)
|
||||||
|
}
|
||||||
|
return super.dispatchKeyEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispatchGenericMotionEvent(ev: MotionEvent?): Boolean {
|
||||||
|
ev?.apply {
|
||||||
|
physicalControllerManager.onMotionEvent(this)
|
||||||
|
}
|
||||||
|
return super.dispatchGenericMotionEvent(ev)
|
||||||
|
}
|
||||||
|
|
||||||
private fun initialize() : Unit
|
private fun initialize() : Unit
|
||||||
{
|
{
|
||||||
if(_isInit)
|
if(_isInit)
|
||||||
@ -94,6 +127,9 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
initialize()
|
initialize()
|
||||||
|
|
||||||
|
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
||||||
|
WindowCompat.setDecorFitsSystemWindows(window,false)
|
||||||
|
|
||||||
if(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
!Environment.isExternalStorageManager()
|
!Environment.isExternalStorageManager()
|
||||||
} else {
|
} else {
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
package org.ryujinx.android
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.PerformanceHintManager
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
|
||||||
|
class PerformanceManager(val performanceHintManager: PerformanceHintManager) {
|
||||||
|
private var _isEnabled: Boolean = false
|
||||||
|
private var renderingSession: PerformanceHintManager.Session? = null
|
||||||
|
val DEFAULT_TARGET_NS = 16666666L
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.S)
|
||||||
|
fun initializeRenderingSession(threadId : Long){
|
||||||
|
if(!_isEnabled || renderingSession != null)
|
||||||
|
return
|
||||||
|
|
||||||
|
var threads = IntArray(1)
|
||||||
|
threads[0] = threadId.toInt()
|
||||||
|
renderingSession = performanceHintManager.createHintSession(threads, DEFAULT_TARGET_NS)
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.S)
|
||||||
|
fun closeCurrentRenderingSession() {
|
||||||
|
if (_isEnabled)
|
||||||
|
renderingSession?.apply {
|
||||||
|
renderingSession = null
|
||||||
|
this.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun enable(){
|
||||||
|
_isEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.S)
|
||||||
|
fun updateRenderingSessionTime(newTime : Long){
|
||||||
|
if(!_isEnabled)
|
||||||
|
return
|
||||||
|
|
||||||
|
var effectiveTime = newTime
|
||||||
|
|
||||||
|
if(newTime < DEFAULT_TARGET_NS)
|
||||||
|
effectiveTime = DEFAULT_TARGET_NS
|
||||||
|
|
||||||
|
renderingSession?.apply {
|
||||||
|
this.reportActualWorkDuration(effectiveTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
package org.ryujinx.android
|
||||||
|
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import android.view.MotionEvent
|
||||||
|
|
||||||
|
class PhysicalControllerManager(val activity: MainActivity) {
|
||||||
|
private var controllerId: Int = -1
|
||||||
|
private var ryujinxNative: RyujinxNative = RyujinxNative()
|
||||||
|
|
||||||
|
fun onKeyEvent(event: KeyEvent) : Boolean{
|
||||||
|
if(controllerId != -1) {
|
||||||
|
var id = GetGamePadButtonInputId(event.keyCode)
|
||||||
|
|
||||||
|
if(id != GamePadButtonInputId.None) {
|
||||||
|
when (event.action) {
|
||||||
|
KeyEvent.ACTION_UP -> {
|
||||||
|
ryujinxNative.inputSetButtonReleased(id.ordinal, controllerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyEvent.ACTION_DOWN -> {
|
||||||
|
ryujinxNative.inputSetButtonPressed(id.ordinal, controllerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onMotionEvent(ev: MotionEvent) {
|
||||||
|
if(controllerId != -1) {
|
||||||
|
if(ev.action == MotionEvent.ACTION_MOVE) {
|
||||||
|
var leftStickX = ev.getAxisValue(MotionEvent.AXIS_X);
|
||||||
|
var leftStickY = ev.getAxisValue(MotionEvent.AXIS_Y);
|
||||||
|
var rightStickX = ev.getAxisValue(MotionEvent.AXIS_Z);
|
||||||
|
var rightStickY = ev.getAxisValue(MotionEvent.AXIS_RZ);
|
||||||
|
ryujinxNative.inputSetStickAxis(1, leftStickX, -leftStickY ,controllerId)
|
||||||
|
ryujinxNative.inputSetStickAxis(2, rightStickX, -rightStickY ,controllerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun connect(){
|
||||||
|
controllerId = ryujinxNative.inputConnectGamepad(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun GetGamePadButtonInputId(keycode: Int): GamePadButtonInputId {
|
||||||
|
return when (keycode) {
|
||||||
|
KeyEvent.KEYCODE_BUTTON_A -> GamePadButtonInputId.B
|
||||||
|
KeyEvent.KEYCODE_BUTTON_B -> GamePadButtonInputId.A
|
||||||
|
KeyEvent.KEYCODE_BUTTON_X -> GamePadButtonInputId.X
|
||||||
|
KeyEvent.KEYCODE_BUTTON_Y -> GamePadButtonInputId.Y
|
||||||
|
KeyEvent.KEYCODE_BUTTON_L1 -> GamePadButtonInputId.LeftShoulder
|
||||||
|
KeyEvent.KEYCODE_BUTTON_L2 -> GamePadButtonInputId.LeftTrigger
|
||||||
|
KeyEvent.KEYCODE_BUTTON_R1 -> GamePadButtonInputId.RightShoulder
|
||||||
|
KeyEvent.KEYCODE_BUTTON_R2 -> GamePadButtonInputId.RightTrigger
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBL -> GamePadButtonInputId.LeftStick
|
||||||
|
KeyEvent.KEYCODE_BUTTON_THUMBR -> GamePadButtonInputId.RightStick
|
||||||
|
KeyEvent.KEYCODE_DPAD_UP -> GamePadButtonInputId.DpadUp
|
||||||
|
KeyEvent.KEYCODE_DPAD_DOWN -> GamePadButtonInputId.DpadDown
|
||||||
|
KeyEvent.KEYCODE_DPAD_LEFT -> GamePadButtonInputId.DpadLeft
|
||||||
|
KeyEvent.KEYCODE_DPAD_RIGHT -> GamePadButtonInputId.DpadRight
|
||||||
|
KeyEvent.KEYCODE_BUTTON_START -> GamePadButtonInputId.Plus
|
||||||
|
KeyEvent.KEYCODE_BUTTON_SELECT -> GamePadButtonInputId.Minus
|
||||||
|
else -> GamePadButtonInputId.None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,33 @@
|
|||||||
package org.ryujinx.android.viewmodels
|
package org.ryujinx.android.viewmodels
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.PerformanceHintManager
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import org.ryujinx.android.GameHost
|
import org.ryujinx.android.GameHost
|
||||||
import org.ryujinx.android.MainActivity
|
import org.ryujinx.android.MainActivity
|
||||||
|
import org.ryujinx.android.PerformanceManager
|
||||||
|
|
||||||
|
@SuppressLint("WrongConstant")
|
||||||
class MainViewModel(val activity: MainActivity) {
|
class MainViewModel(val activity: MainActivity) {
|
||||||
|
var performanceManager: PerformanceManager? = null
|
||||||
var selected: GameModel? = null
|
var selected: GameModel? = null
|
||||||
private var gameTimeState: MutableState<Double>? = null
|
private var gameTimeState: MutableState<Double>? = null
|
||||||
private var gameFpsState: MutableState<Double>? = null
|
private var gameFpsState: MutableState<Double>? = null
|
||||||
private var fifoState: MutableState<Double>? = null
|
private var fifoState: MutableState<Double>? = null
|
||||||
private var navController : NavHostController? = null
|
private var navController : NavHostController? = null
|
||||||
|
|
||||||
var homeViewModel: HomeViewModel = HomeViewModel(activity, this,)
|
var homeViewModel: HomeViewModel = HomeViewModel(activity, this)
|
||||||
|
|
||||||
|
init {
|
||||||
|
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
var hintService =
|
||||||
|
activity.getSystemService(Context.PERFORMANCE_HINT_SERVICE) as PerformanceHintManager
|
||||||
|
performanceManager = PerformanceManager(hintService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun loadGame(game:GameModel) {
|
fun loadGame(game:GameModel) {
|
||||||
var controller = navController?: return;
|
var controller = navController?: return;
|
||||||
|
@ -10,6 +10,7 @@ class QuickSettings(val activity: MainActivity) {
|
|||||||
var enableDocked: Boolean
|
var enableDocked: Boolean
|
||||||
var enableVsync: Boolean
|
var enableVsync: Boolean
|
||||||
var useNce: Boolean
|
var useNce: Boolean
|
||||||
|
var useVirtualController: Boolean
|
||||||
var isHostMapped: Boolean
|
var isHostMapped: Boolean
|
||||||
var enableShaderCache: Boolean
|
var enableShaderCache: Boolean
|
||||||
var enableTextureRecompression: Boolean
|
var enableTextureRecompression: Boolean
|
||||||
@ -27,5 +28,6 @@ class QuickSettings(val activity: MainActivity) {
|
|||||||
enableShaderCache = sharedPref.getBoolean("enableShaderCache", true)
|
enableShaderCache = sharedPref.getBoolean("enableShaderCache", true)
|
||||||
enableTextureRecompression = sharedPref.getBoolean("enableTextureRecompression", false)
|
enableTextureRecompression = sharedPref.getBoolean("enableTextureRecompression", false)
|
||||||
resScale = sharedPref.getFloat("resScale", 1f)
|
resScale = sharedPref.getFloat("resScale", 1f)
|
||||||
|
useVirtualController = sharedPref.getBoolean("useVirtualController", true)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,7 +26,8 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
|
|||||||
ignoreMissingServices: MutableState<Boolean>,
|
ignoreMissingServices: MutableState<Boolean>,
|
||||||
enableShaderCache: MutableState<Boolean>,
|
enableShaderCache: MutableState<Boolean>,
|
||||||
enableTextureRecompression: MutableState<Boolean>,
|
enableTextureRecompression: MutableState<Boolean>,
|
||||||
resScale: MutableState<Float>
|
resScale: MutableState<Float>,
|
||||||
|
useVirtualController: MutableState<Boolean>
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -39,6 +40,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
|
|||||||
enableShaderCache.value = sharedPref.getBoolean("enableShaderCache", true)
|
enableShaderCache.value = sharedPref.getBoolean("enableShaderCache", true)
|
||||||
enableTextureRecompression.value = sharedPref.getBoolean("enableTextureRecompression", false)
|
enableTextureRecompression.value = sharedPref.getBoolean("enableTextureRecompression", false)
|
||||||
resScale.value = sharedPref.getFloat("resScale", 1f)
|
resScale.value = sharedPref.getFloat("resScale", 1f)
|
||||||
|
useVirtualController.value = sharedPref.getBoolean("useVirtualController", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun save(
|
fun save(
|
||||||
@ -50,7 +52,8 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
|
|||||||
ignoreMissingServices: MutableState<Boolean>,
|
ignoreMissingServices: MutableState<Boolean>,
|
||||||
enableShaderCache: MutableState<Boolean>,
|
enableShaderCache: MutableState<Boolean>,
|
||||||
enableTextureRecompression: MutableState<Boolean>,
|
enableTextureRecompression: MutableState<Boolean>,
|
||||||
resScale: MutableState<Float>
|
resScale: MutableState<Float>,
|
||||||
|
useVirtualController: MutableState<Boolean>
|
||||||
){
|
){
|
||||||
var editor = sharedPref.edit()
|
var editor = sharedPref.edit()
|
||||||
|
|
||||||
@ -63,6 +66,7 @@ class SettingsViewModel(var navController: NavHostController, val activity: Main
|
|||||||
editor.putBoolean("enableShaderCache", enableShaderCache?.value ?: true)
|
editor.putBoolean("enableShaderCache", enableShaderCache?.value ?: true)
|
||||||
editor.putBoolean("enableTextureRecompression", enableTextureRecompression?.value ?: false)
|
editor.putBoolean("enableTextureRecompression", enableTextureRecompression?.value ?: false)
|
||||||
editor.putFloat("resScale", resScale?.value ?: 1f)
|
editor.putFloat("resScale", resScale?.value ?: 1f)
|
||||||
|
editor.putBoolean("useVirtualController", useVirtualController?.value ?: true)
|
||||||
|
|
||||||
editor.apply()
|
editor.apply()
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
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.layout.safeContentPadding
|
||||||
|
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.layout.wrapContentWidth
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
|
@ -1,16 +1,30 @@
|
|||||||
package org.ryujinx.android.views
|
package org.ryujinx.android.views
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.PathFillType
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
|
import androidx.compose.ui.graphics.StrokeJoin
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.graphics.vector.path
|
||||||
|
import androidx.compose.ui.input.pointer.PointerEventType
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
@ -19,8 +33,10 @@ import androidx.navigation.compose.composable
|
|||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import org.ryujinx.android.GameController
|
import org.ryujinx.android.GameController
|
||||||
import org.ryujinx.android.GameHost
|
import org.ryujinx.android.GameHost
|
||||||
|
import org.ryujinx.android.RyujinxNative
|
||||||
import org.ryujinx.android.viewmodels.MainViewModel
|
import org.ryujinx.android.viewmodels.MainViewModel
|
||||||
import org.ryujinx.android.viewmodels.SettingsViewModel
|
import org.ryujinx.android.viewmodels.SettingsViewModel
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class MainView {
|
class MainView {
|
||||||
companion object {
|
companion object {
|
||||||
@ -38,16 +54,171 @@ class MainView {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GameView(mainViewModel: MainViewModel){
|
fun GameView(mainViewModel: MainViewModel){
|
||||||
Box {
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
var controller = GameController(mainViewModel.activity)
|
val controller = remember {
|
||||||
|
GameController(mainViewModel.activity)
|
||||||
|
}
|
||||||
AndroidView(
|
AndroidView(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
factory = { context ->
|
factory = { context ->
|
||||||
GameHost(context, controller, mainViewModel)
|
GameHost(context, controller, mainViewModel)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
GameOverlay(mainViewModel, controller)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GameOverlay(mainViewModel: MainViewModel, controller: GameController){
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
GameStats(mainViewModel)
|
GameStats(mainViewModel)
|
||||||
|
|
||||||
|
var ryujinxNative = RyujinxNative()
|
||||||
|
|
||||||
|
// touch surface
|
||||||
|
Surface(color = Color.Transparent, modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(0.dp)
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
awaitPointerEventScope {
|
||||||
|
while (true) {
|
||||||
|
Thread.sleep(2);
|
||||||
|
val event = awaitPointerEvent()
|
||||||
|
|
||||||
|
if(controller.isVisible)
|
||||||
|
continue
|
||||||
|
|
||||||
|
var change = event
|
||||||
|
.component1()
|
||||||
|
.firstOrNull()
|
||||||
|
change?.apply {
|
||||||
|
var position = this.position
|
||||||
|
|
||||||
|
if (event.type == PointerEventType.Press) {
|
||||||
|
ryujinxNative.inputSetTouchPoint(
|
||||||
|
position.x.roundToInt(),
|
||||||
|
position.y.roundToInt()
|
||||||
|
)
|
||||||
|
} else if (event.type == PointerEventType.Release) {
|
||||||
|
ryujinxNative.inputReleaseTouchPoint()
|
||||||
|
|
||||||
|
} else if (event.type == PointerEventType.Move) {
|
||||||
|
ryujinxNative.inputSetTouchPoint(
|
||||||
|
position.x.roundToInt(),
|
||||||
|
position.y.roundToInt()
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
}
|
||||||
controller.Compose(mainViewModel.activity.lifecycleScope, mainViewModel.activity.lifecycle)
|
controller.Compose(mainViewModel.activity.lifecycleScope, mainViewModel.activity.lifecycle)
|
||||||
|
Row(modifier = Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.padding(8.dp)) {
|
||||||
|
IconButton(modifier = Modifier.padding(4.dp),onClick = {
|
||||||
|
controller.setVisible(!controller.isVisible)
|
||||||
|
}) {
|
||||||
|
Icon(imageVector = rememberVideogameAsset(), contentDescription = "Toggle Virtual Pad")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Composable
|
||||||
|
fun rememberVideogameAsset(): ImageVector {
|
||||||
|
var primaryColor = MaterialTheme.colorScheme.primary
|
||||||
|
return remember {
|
||||||
|
ImageVector.Builder(
|
||||||
|
name = "videogame_asset",
|
||||||
|
defaultWidth = 40.0.dp,
|
||||||
|
defaultHeight = 40.0.dp,
|
||||||
|
viewportWidth = 40.0f,
|
||||||
|
viewportHeight = 40.0f
|
||||||
|
).apply {
|
||||||
|
path(
|
||||||
|
fill = SolidColor(Color.Black.copy(alpha = 0.5f)),
|
||||||
|
fillAlpha = 1f,
|
||||||
|
stroke = SolidColor(primaryColor),
|
||||||
|
strokeAlpha = 1f,
|
||||||
|
strokeLineWidth = 1.0f,
|
||||||
|
strokeLineCap = StrokeCap.Butt,
|
||||||
|
strokeLineJoin = StrokeJoin.Miter,
|
||||||
|
strokeLineMiter = 1f,
|
||||||
|
pathFillType = PathFillType.NonZero
|
||||||
|
) {
|
||||||
|
moveTo(6.25f, 29.792f)
|
||||||
|
quadToRelative(-1.083f, 0f, -1.854f, -0.792f)
|
||||||
|
quadToRelative(-0.771f, -0.792f, -0.771f, -1.833f)
|
||||||
|
verticalLineTo(12.833f)
|
||||||
|
quadToRelative(0f, -1.083f, 0.771f, -1.854f)
|
||||||
|
quadToRelative(0.771f, -0.771f, 1.854f, -0.771f)
|
||||||
|
horizontalLineToRelative(27.5f)
|
||||||
|
quadToRelative(1.083f, 0f, 1.854f, 0.771f)
|
||||||
|
quadToRelative(0.771f, 0.771f, 0.771f, 1.854f)
|
||||||
|
verticalLineToRelative(14.334f)
|
||||||
|
quadToRelative(0f, 1.041f, -0.771f, 1.833f)
|
||||||
|
reflectiveQuadToRelative(-1.854f, 0.792f)
|
||||||
|
close()
|
||||||
|
moveToRelative(0f, -2.625f)
|
||||||
|
horizontalLineToRelative(27.5f)
|
||||||
|
verticalLineTo(12.833f)
|
||||||
|
horizontalLineTo(6.25f)
|
||||||
|
verticalLineToRelative(14.334f)
|
||||||
|
close()
|
||||||
|
moveToRelative(7.167f, -1.792f)
|
||||||
|
quadToRelative(0.541f, 0f, 0.916f, -0.375f)
|
||||||
|
reflectiveQuadToRelative(0.375f, -0.917f)
|
||||||
|
verticalLineToRelative(-2.791f)
|
||||||
|
horizontalLineToRelative(2.75f)
|
||||||
|
quadToRelative(0.584f, 0f, 0.959f, -0.375f)
|
||||||
|
reflectiveQuadToRelative(0.375f, -0.917f)
|
||||||
|
quadToRelative(0f, -0.542f, -0.375f, -0.938f)
|
||||||
|
quadToRelative(-0.375f, -0.395f, -0.959f, -0.395f)
|
||||||
|
horizontalLineToRelative(-2.75f)
|
||||||
|
verticalLineToRelative(-2.75f)
|
||||||
|
quadToRelative(0f, -0.542f, -0.375f, -0.938f)
|
||||||
|
quadToRelative(-0.375f, -0.396f, -0.916f, -0.396f)
|
||||||
|
quadToRelative(-0.584f, 0f, -0.959f, 0.396f)
|
||||||
|
reflectiveQuadToRelative(-0.375f, 0.938f)
|
||||||
|
verticalLineToRelative(2.75f)
|
||||||
|
horizontalLineToRelative(-2.75f)
|
||||||
|
quadToRelative(-0.541f, 0f, -0.937f, 0.395f)
|
||||||
|
quadTo(8f, 19.458f, 8f, 20f)
|
||||||
|
quadToRelative(0f, 0.542f, 0.396f, 0.917f)
|
||||||
|
reflectiveQuadToRelative(0.937f, 0.375f)
|
||||||
|
horizontalLineToRelative(2.75f)
|
||||||
|
verticalLineToRelative(2.791f)
|
||||||
|
quadToRelative(0f, 0.542f, 0.396f, 0.917f)
|
||||||
|
reflectiveQuadToRelative(0.938f, 0.375f)
|
||||||
|
close()
|
||||||
|
moveToRelative(11.125f, -0.5f)
|
||||||
|
quadToRelative(0.791f, 0f, 1.396f, -0.583f)
|
||||||
|
quadToRelative(0.604f, -0.584f, 0.604f, -1.375f)
|
||||||
|
quadToRelative(0f, -0.834f, -0.604f, -1.417f)
|
||||||
|
quadToRelative(-0.605f, -0.583f, -1.396f, -0.583f)
|
||||||
|
quadToRelative(-0.834f, 0f, -1.417f, 0.583f)
|
||||||
|
quadToRelative(-0.583f, 0.583f, -0.583f, 1.375f)
|
||||||
|
quadToRelative(0f, 0.833f, 0.583f, 1.417f)
|
||||||
|
quadToRelative(0.583f, 0.583f, 1.417f, 0.583f)
|
||||||
|
close()
|
||||||
|
moveToRelative(3.916f, -5.833f)
|
||||||
|
quadToRelative(0.834f, 0f, 1.417f, -0.584f)
|
||||||
|
quadToRelative(0.583f, -0.583f, 0.583f, -1.416f)
|
||||||
|
quadToRelative(0f, -0.792f, -0.583f, -1.375f)
|
||||||
|
quadToRelative(-0.583f, -0.584f, -1.417f, -0.584f)
|
||||||
|
quadToRelative(-0.791f, 0f, -1.375f, 0.584f)
|
||||||
|
quadToRelative(-0.583f, 0.583f, -0.583f, 1.375f)
|
||||||
|
quadToRelative(0f, 0.833f, 0.583f, 1.416f)
|
||||||
|
quadToRelative(0.584f, 0.584f, 1.375f, 0.584f)
|
||||||
|
close()
|
||||||
|
moveTo(6.25f, 27.167f)
|
||||||
|
verticalLineTo(12.833f)
|
||||||
|
verticalLineToRelative(14.334f)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,8 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
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.layout.safeContentPadding
|
||||||
|
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.layout.wrapContentWidth
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@ -92,6 +94,9 @@ class SettingViews {
|
|||||||
var resScale = remember {
|
var resScale = remember {
|
||||||
mutableStateOf(1f)
|
mutableStateOf(1f)
|
||||||
}
|
}
|
||||||
|
var useVirtualController = remember {
|
||||||
|
mutableStateOf(true)
|
||||||
|
}
|
||||||
|
|
||||||
if (!loaded.value) {
|
if (!loaded.value) {
|
||||||
settingsViewModel.initializeState(
|
settingsViewModel.initializeState(
|
||||||
@ -100,7 +105,8 @@ class SettingViews {
|
|||||||
enableVsync, enableDocked, enablePtc, ignoreMissingServices,
|
enableVsync, enableDocked, enablePtc, ignoreMissingServices,
|
||||||
enableShaderCache,
|
enableShaderCache,
|
||||||
enableTextureRecompression,
|
enableTextureRecompression,
|
||||||
resScale
|
resScale,
|
||||||
|
useVirtualController
|
||||||
)
|
)
|
||||||
loaded.value = true
|
loaded.value = true
|
||||||
}
|
}
|
||||||
@ -121,7 +127,8 @@ class SettingViews {
|
|||||||
ignoreMissingServices,
|
ignoreMissingServices,
|
||||||
enableShaderCache,
|
enableShaderCache,
|
||||||
enableTextureRecompression,
|
enableTextureRecompression,
|
||||||
resScale
|
resScale,
|
||||||
|
useVirtualController
|
||||||
)
|
)
|
||||||
settingsViewModel.navController.popBackStack()
|
settingsViewModel.navController.popBackStack()
|
||||||
}) {
|
}) {
|
||||||
@ -136,7 +143,8 @@ class SettingViews {
|
|||||||
useNce, enableVsync, enableDocked, enablePtc, ignoreMissingServices,
|
useNce, enableVsync, enableDocked, enablePtc, ignoreMissingServices,
|
||||||
enableShaderCache,
|
enableShaderCache,
|
||||||
enableTextureRecompression,
|
enableTextureRecompression,
|
||||||
resScale
|
resScale,
|
||||||
|
useVirtualController
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ExpandableView(onCardArrowClick = { }, title = "System") {
|
ExpandableView(onCardArrowClick = { }, title = "System") {
|
||||||
@ -431,6 +439,25 @@ class SettingViews {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ExpandableView(onCardArrowClick = { }, title = "Input") {
|
||||||
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Show virtual controller",
|
||||||
|
modifier = Modifier.align(Alignment.CenterVertically)
|
||||||
|
)
|
||||||
|
Switch(checked = useVirtualController.value, onCheckedChange = {
|
||||||
|
useVirtualController.value = !useVirtualController.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user