diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ec6a57e..270b4bd4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -254,6 +254,7 @@ if(ANDROID) add_subdirectory("dependencies/libadrenotools" EXCLUDE_FROM_ALL) endif() add_subdirectory("dependencies/libucontext" EXCLUDE_FROM_ALL) + add_subdirectory("dependencies/stb" EXCLUDE_FROM_ALL) endif() add_subdirectory(src) \ No newline at end of file diff --git a/dependencies/libucontext/CMakeLists.txt b/dependencies/libucontext/CMakeLists.txt index 5c898462..6a62f5bb 100644 --- a/dependencies/libucontext/CMakeLists.txt +++ b/dependencies/libucontext/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.21.1) +cmake_minimum_required(VERSION 3.25) project(libucontext LANGUAGES C ASM) diff --git a/dependencies/stb/CMakeLists.txt b/dependencies/stb/CMakeLists.txt new file mode 100644 index 00000000..61e95b95 --- /dev/null +++ b/dependencies/stb/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.25) + +project(stb LANGUAGES C) + +add_library(stb INTERFACE) + +target_include_directories(stb INTERFACE include) diff --git a/src/android/app/src/main/cpp/stb_image.h b/dependencies/stb/include/stb_image.h similarity index 100% rename from src/android/app/src/main/cpp/stb_image.h rename to dependencies/stb/include/stb_image.h diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index c1de952d..d59ae272 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -46,7 +46,7 @@ val cemuDataFilesFolder = "../../../bin" android { namespace = "info.cemu.cemu" - compileSdk = 35 + compileSdk = 36 ndkVersion = "26.3.11579264" defaultConfig { applicationId = "info.cemu.cemu" @@ -231,6 +231,7 @@ dependencies { implementation(libs.androidx.ui.graphics) implementation(libs.androidx.compose.material3) testImplementation(libs.junit) + testImplementation(libs.archunit.junit4) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(platform(libs.androidx.compose.bom)) diff --git a/src/android/app/src/androidTest/java/info/cemu/cemu/PlaceholderInstrumentedTest.kt b/src/android/app/src/androidTest/java/info/cemu/cemu/tests/PlaceholderInstrumentedTest.kt similarity index 90% rename from src/android/app/src/androidTest/java/info/cemu/cemu/PlaceholderInstrumentedTest.kt rename to src/android/app/src/androidTest/java/info/cemu/cemu/tests/PlaceholderInstrumentedTest.kt index 92e34f83..fc9361b1 100644 --- a/src/android/app/src/androidTest/java/info/cemu/cemu/PlaceholderInstrumentedTest.kt +++ b/src/android/app/src/androidTest/java/info/cemu/cemu/tests/PlaceholderInstrumentedTest.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu +package info.cemu.cemu.tests import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Assert.* diff --git a/src/android/app/src/main/cpp/AndroidFilesystemCallbacks.h b/src/android/app/src/main/cpp/AndroidFilesystemCallbacks.h index 4f26dd5c..a54a8561 100644 --- a/src/android/app/src/main/cpp/AndroidFilesystemCallbacks.h +++ b/src/android/app/src/main/cpp/AndroidFilesystemCallbacks.h @@ -26,7 +26,7 @@ class AndroidFilesystemCallbacks : public FilesystemAndroid::FilesystemCallbacks AndroidFilesystemCallbacks() { JNIUtils::ScopedJNIENV env; - m_fileUtilClass = JNIUtils::Scopedjclass("info/cemu/cemu/nativeinterface/NativeFilesKt"); + m_fileUtilClass = JNIUtils::Scopedjclass("info/cemu/cemu/nativeinterface/NativeFiles"); m_openContentUriMid = env->GetStaticMethodID(*m_fileUtilClass, "openContentUri", "(Ljava/lang/String;)I"); m_listFilesMid = env->GetStaticMethodID(*m_fileUtilClass, "listFiles", "(Ljava/lang/String;)[Ljava/lang/String;"); m_isDirectoryMid = env->GetStaticMethodID(*m_fileUtilClass, "isDirectory", "(Ljava/lang/String;)Z"); diff --git a/src/android/app/src/main/cpp/CMakeLists.txt b/src/android/app/src/main/cpp/CMakeLists.txt index 90da504d..a0144ea2 100644 --- a/src/android/app/src/main/cpp/CMakeLists.txt +++ b/src/android/app/src/main/cpp/CMakeLists.txt @@ -29,4 +29,5 @@ target_link_libraries(CemuAndroid PRIVATE CemuBin CemuGui ZArchive::zarchive + stb ) diff --git a/src/android/app/src/main/cpp/Image.cpp b/src/android/app/src/main/cpp/Image.cpp index 64b2f0f4..2269dcb4 100644 --- a/src/android/app/src/main/cpp/Image.cpp +++ b/src/android/app/src/main/cpp/Image.cpp @@ -3,7 +3,7 @@ #define STB_IMAGE_IMPLEMENTATION #define STBI_ONLY_TGA -#include "stb_image.h" +#include Image::Image(Image&& image) { @@ -21,14 +21,8 @@ Image::Image(const std::vector& imageBytes) return; for (size_t i = 0; i < m_width * m_height * 4; i += 4) { - uint8 r = stbImage[i]; - uint8 g = stbImage[i + 1]; - uint8 b = stbImage[i + 2]; - uint8 a = stbImage[i + 3]; - stbImage[i] = b; - stbImage[i + 1] = g; - stbImage[i + 2] = r; - stbImage[i + 3] = a; + // RGBA -> BGRA + std::swap(stbImage[i + 0], stbImage[i + 2]); } m_colors = reinterpret_cast(stbImage); } diff --git a/src/android/app/src/main/java/info/cemu/cemu/CemuApplication.kt b/src/android/app/src/main/java/info/cemu/cemu/CemuApplication.kt index b25808d0..71369a1c 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/CemuApplication.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/CemuApplication.kt @@ -1,21 +1,19 @@ package info.cemu.cemu import android.app.Application -import android.content.res.Resources -import android.util.Log -import info.cemu.cemu.core.translation.setLanguage -import info.cemu.cemu.core.translation.setTranslations +import info.cemu.cemu.common.context.internalFolder +import info.cemu.cemu.common.translation.setLanguage +import info.cemu.cemu.common.translation.setTranslations import info.cemu.cemu.nativeinterface.NativeActiveSettings.initializeActiveSettings import info.cemu.cemu.nativeinterface.NativeActiveSettings.setInternalDir import info.cemu.cemu.nativeinterface.NativeActiveSettings.setNativeLibDir import info.cemu.cemu.nativeinterface.NativeEmulation.initializeEmulation import info.cemu.cemu.nativeinterface.NativeEmulation.setDPI import info.cemu.cemu.nativeinterface.NativeGraphicPacks.refreshGraphicPacks -import info.cemu.cemu.nativeinterface.NativeLocalization import info.cemu.cemu.nativeinterface.NativeLogging.crashLog import info.cemu.cemu.nativeinterface.NativeSwkbd.initializeSwkbd -import info.cemu.cemu.settings.SettingsManager -import info.cemu.cemu.utils.parsePoFile +import info.cemu.cemu.common.settings.SettingsManager +import info.cemu.cemu.nativeinterface.NativeFiles import java.io.File import java.io.IOException import java.io.PrintWriter @@ -23,30 +21,25 @@ import java.io.StringWriter import java.util.regex.Pattern class CemuApplication : Application() { - init { - Application = this - } - - val internalFolder: File - get() { - val externalFilesDir = getExternalFilesDir(null) - if (externalFilesDir != null) { - return externalFilesDir - } - return filesDir - } - override fun onCreate() { super.onCreate() + configureExceptionHandler() + + SettingsManager.initialize(this) + + NativeFiles.initialize(contentResolver) + initializeTranslations() + initializeCemu() + saveDataFiles() } private fun initializeTranslations() { setTranslations(this) - setLanguage(SettingsManager(this).guiSettings.language, this) + setLanguage(SettingsManager.guiSettings.language, this) } private fun saveDataFiles() { @@ -143,10 +136,10 @@ class CemuApplication : Application() { } private val internalCemuDataFolder: String - get() = internalFolder.resolve("data").toString() + get() = internalFolder().resolve("data").toString() private val internalCemuUserFolder: String - get() = internalFolder.toString() + get() = internalFolder().toString() companion object { init { @@ -154,9 +147,5 @@ class CemuApplication : Application() { } private var DefaultUncaughtExceptionHandler: Thread.UncaughtExceptionHandler? = null - - @JvmStatic - lateinit var Application: CemuApplication - private set } } diff --git a/src/android/app/src/main/java/info/cemu/cemu/MainActivity.kt b/src/android/app/src/main/java/info/cemu/cemu/MainActivity.kt index 0ba6e1fe..263637f5 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/MainActivity.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/MainActivity.kt @@ -1,8 +1,10 @@ package info.cemu.cemu -import android.content.ActivityNotFoundException +import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.content.pm.ShortcutInfo +import android.content.pm.ShortcutManager import android.os.Bundle import android.provider.DocumentsContract import android.widget.Toast @@ -24,6 +26,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.documentfile.provider.DocumentFile @@ -31,9 +34,9 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.rememberNavController import info.cemu.cemu.about.AboutCemuRoute import info.cemu.cemu.about.aboutCemuNavigation -import info.cemu.cemu.core.components.ActivityContent -import info.cemu.cemu.core.translation.TranslatableContent -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.components.ActivityContent +import info.cemu.cemu.common.translation.TranslatableContent +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.emulation.EmulationActivity import info.cemu.cemu.gamelist.GameListRoute import info.cemu.cemu.gamelist.gameListNavigation @@ -49,6 +52,9 @@ import info.cemu.cemu.titlemanager.TitleManagerRoute import info.cemu.cemu.titlemanager.titleManagerNavigation import java.io.File +import android.graphics.drawable.Icon as AndroidIcon + + class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -72,16 +78,6 @@ class MainActivity : ComponentActivity() { } } -private fun startGame(context: Context, game: Game) { - Intent( - context, - EmulationActivity::class.java - ).apply { - putExtra(EmulationActivity.EXTRA_LAUNCH_PATH, game.path) - context.startActivity(this) - } -} - @Composable private fun MainNav() { val navController = rememberNavController() @@ -93,7 +89,11 @@ private fun MainNav() { enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None } ) { - gameListNavigation(navController, startGame = { startGame(context, it) }) { + gameListNavigation( + navController = navController, + startGame = { startGame(context, it) }, + createShortcut = { createShortcutForGame(context, it) } + ) { GameListToolBarActionsMenu( goToSettings = { navController.navigate(SettingsRoute) }, goToTitleManager = { navController.navigate(TitleManagerRoute) }, @@ -168,6 +168,16 @@ private fun GameListToolBarActionsMenu( } } +private fun startGame(context: Context, game: Game) { + Intent( + context, + EmulationActivity::class.java + ).apply { + putExtra(EmulationActivity.EXTRA_LAUNCH_PATH, game.path) + context.startActivity(this) + } +} + private fun shareLogFile(context: Context) { val logFileName = "log.txt" val logFile = File(NativeActiveSettings.getUserDataPath()).resolve(logFileName) @@ -207,7 +217,59 @@ private fun openCemuFolder(context: Context) { DocumentsProvider.ROOT_ID ) context.startActivity(intent) - } catch (activityNotFoundException: ActivityNotFoundException) { + } catch (_: Exception) { Toast.makeText(context, tr("Could not open Cemu folder"), Toast.LENGTH_LONG).show() } } + + +private fun createShortcutForGame( + context: Context, + game: Game, +) { + fun onFailedToCreateShortcut() { + Toast.makeText(context, tr("Couldn't create shortcut for game"), Toast.LENGTH_LONG).show() + } + + try { + val shortcutManager = context.getSystemService( + ShortcutManager::class.java + ) + if (!shortcutManager.isRequestPinShortcutSupported) { + onFailedToCreateShortcut() + return + } + + val icon = game.icon?.asAndroidBitmap().let { + if (it != null) AndroidIcon.createWithBitmap(it) + else AndroidIcon.createWithResource(context, R.mipmap.ic_launcher) + } + + val intent = Intent( + context, + EmulationActivity::class.java + ) + intent.action = Intent.ACTION_VIEW + intent.putExtra(EmulationActivity.EXTRA_LAUNCH_PATH, game.path) + + val pinShortcutInfo = ShortcutInfo.Builder(context, game.titleId.toString()) + .setShortLabel(game.name!!) + .setIntent(intent) + .setIcon(icon) + .build() + + val pinnedShortcutCallbackIntent = + shortcutManager.createShortcutResultIntent(pinShortcutInfo) + + val successCallback = PendingIntent.getBroadcast( + context, + 0, + pinnedShortcutCallbackIntent, + PendingIntent.FLAG_IMMUTABLE + ) + + shortcutManager.requestPinShortcut(pinShortcutInfo, successCallback.intentSender) + } catch (_: Exception) { + onFailedToCreateShortcut() + } +} diff --git a/src/android/app/src/main/java/info/cemu/cemu/about/AboutCemuScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/about/AboutCemuScreen.kt index 4b917e7f..8b5eb7f0 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/about/AboutCemuScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/about/AboutCemuScreen.kt @@ -39,8 +39,8 @@ import com.mikepenz.aboutlibraries.ui.compose.m3.libraryColors import com.mikepenz.aboutlibraries.ui.compose.util.author import info.cemu.cemu.BuildConfig import info.cemu.cemu.R -import info.cemu.cemu.core.components.ScreenContentLazy -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.components.ScreenContentLazy +import info.cemu.cemu.common.translation.tr import kotlinx.collections.immutable.persistentListOf typealias AboutLibrary = com.mikepenz.aboutlibraries.entity.Library diff --git a/src/android/app/src/main/java/info/cemu/cemu/utils/Canvas.kt b/src/android/app/src/main/java/info/cemu/cemu/common/canvas/CanvasExtensions.kt similarity index 96% rename from src/android/app/src/main/java/info/cemu/cemu/utils/Canvas.kt rename to src/android/app/src/main/java/info/cemu/cemu/common/canvas/CanvasExtensions.kt index de92d901..2409a83c 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/utils/Canvas.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/common/canvas/CanvasExtensions.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.utils +package info.cemu.cemu.common.canvas import android.graphics.Canvas import android.graphics.Paint diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/components/ActivityContent.kt b/src/android/app/src/main/java/info/cemu/cemu/common/components/ActivityContent.kt similarity index 84% rename from src/android/app/src/main/java/info/cemu/cemu/core/components/ActivityContent.kt rename to src/android/app/src/main/java/info/cemu/cemu/common/components/ActivityContent.kt index 4a7fc42c..76902bdf 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/core/components/ActivityContent.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/common/components/ActivityContent.kt @@ -1,11 +1,11 @@ -package info.cemu.cemu.core.components +package info.cemu.cemu.common.components import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import info.cemu.cemu.core.theme.CemuTheme +import info.cemu.cemu.common.theme.CemuTheme @Composable fun ActivityContent(content: @Composable () -> Unit) { diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/components/Button.kt b/src/android/app/src/main/java/info/cemu/cemu/common/components/Button.kt similarity index 97% rename from src/android/app/src/main/java/info/cemu/cemu/core/components/Button.kt rename to src/android/app/src/main/java/info/cemu/cemu/common/components/Button.kt index 9aa9f4bb..f51b6422 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/core/components/Button.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/common/components/Button.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.core.components +package info.cemu.cemu.common.components import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/components/DrawerLayout.kt b/src/android/app/src/main/java/info/cemu/cemu/common/components/DrawerLayout.kt similarity index 96% rename from src/android/app/src/main/java/info/cemu/cemu/core/components/DrawerLayout.kt rename to src/android/app/src/main/java/info/cemu/cemu/common/components/DrawerLayout.kt index 3035cb37..6d3359f3 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/core/components/DrawerLayout.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/common/components/DrawerLayout.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.core.components +package info.cemu.cemu.common.components import android.content.Context import android.util.AttributeSet diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/components/FilledSearchToolbar.kt b/src/android/app/src/main/java/info/cemu/cemu/common/components/FilledSearchToolbar.kt similarity index 99% rename from src/android/app/src/main/java/info/cemu/cemu/core/components/FilledSearchToolbar.kt rename to src/android/app/src/main/java/info/cemu/cemu/common/components/FilledSearchToolbar.kt index f0ef82c5..36329b37 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/core/components/FilledSearchToolbar.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/common/components/FilledSearchToolbar.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.core.components +package info.cemu.cemu.common.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.IntrinsicSize diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/components/Header.kt b/src/android/app/src/main/java/info/cemu/cemu/common/components/Header.kt similarity index 92% rename from src/android/app/src/main/java/info/cemu/cemu/core/components/Header.kt rename to src/android/app/src/main/java/info/cemu/cemu/common/components/Header.kt index 25d7065d..6da3638d 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/core/components/Header.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/common/components/Header.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.core.components +package info.cemu.cemu.common.components import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/components/ScreenContent.kt b/src/android/app/src/main/java/info/cemu/cemu/common/components/ScreenContent.kt similarity index 97% rename from src/android/app/src/main/java/info/cemu/cemu/core/components/ScreenContent.kt rename to src/android/app/src/main/java/info/cemu/cemu/common/components/ScreenContent.kt index a061f296..c42a7caa 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/core/components/ScreenContent.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/common/components/ScreenContent.kt @@ -1,6 +1,6 @@ @file:OptIn(ExperimentalMaterial3Api::class) -package info.cemu.cemu.core.components +package info.cemu.cemu.common.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -24,12 +24,10 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.dropUnlessResumed -import info.cemu.cemu.R @Composable fun ScreenContent( diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/components/SingleSelection.kt b/src/android/app/src/main/java/info/cemu/cemu/common/components/SingleSelection.kt similarity index 98% rename from src/android/app/src/main/java/info/cemu/cemu/core/components/SingleSelection.kt rename to src/android/app/src/main/java/info/cemu/cemu/common/components/SingleSelection.kt index 8119d3a2..d5645dd9 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/core/components/SingleSelection.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/common/components/SingleSelection.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.core.components +package info.cemu.cemu.common.components import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -30,7 +30,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.translation.tr @Composable fun SingleSelection( diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/components/Slider.kt b/src/android/app/src/main/java/info/cemu/cemu/common/components/Slider.kt similarity index 97% rename from src/android/app/src/main/java/info/cemu/cemu/core/components/Slider.kt rename to src/android/app/src/main/java/info/cemu/cemu/common/components/Slider.kt index d8e0ffa5..9f494e70 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/core/components/Slider.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/common/components/Slider.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.core.components +package info.cemu.cemu.common.components import androidx.annotation.IntRange import androidx.compose.foundation.layout.Column diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/components/Toggle.kt b/src/android/app/src/main/java/info/cemu/cemu/common/components/Toggle.kt similarity index 98% rename from src/android/app/src/main/java/info/cemu/cemu/core/components/Toggle.kt rename to src/android/app/src/main/java/info/cemu/cemu/common/components/Toggle.kt index eeb84ed8..ba89850c 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/core/components/Toggle.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/common/components/Toggle.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.core.components +package info.cemu.cemu.common.components import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement diff --git a/src/android/app/src/main/java/info/cemu/cemu/common/context/ContextExtensions.kt b/src/android/app/src/main/java/info/cemu/cemu/common/context/ContextExtensions.kt new file mode 100644 index 00000000..21d95bc0 --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/cemu/common/context/ContextExtensions.kt @@ -0,0 +1,12 @@ +package info.cemu.cemu.common.context + +import android.content.Context +import java.io.File + +fun Context.internalFolder(): File { + val externalFilesDir = getExternalFilesDir(null) + if (externalFilesDir != null) { + return externalFilesDir + } + return filesDir +} \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/format/Formatters.kt b/src/android/app/src/main/java/info/cemu/cemu/common/format/Formatters.kt similarity index 90% rename from src/android/app/src/main/java/info/cemu/cemu/core/format/Formatters.kt rename to src/android/app/src/main/java/info/cemu/cemu/common/format/Formatters.kt index aecc140b..0f2f4c7d 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/core/format/Formatters.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/common/format/Formatters.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.core.format +package info.cemu.cemu.common.format import android.text.format.Formatter import androidx.compose.runtime.Composable diff --git a/src/android/app/src/main/java/info/cemu/cemu/common/inputdevice/InputDeviceExtensions.kt b/src/android/app/src/main/java/info/cemu/cemu/common/inputdevice/InputDeviceExtensions.kt new file mode 100644 index 00000000..b8968649 --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/cemu/common/inputdevice/InputDeviceExtensions.kt @@ -0,0 +1,11 @@ +package info.cemu.cemu.common.inputdevice + +import android.view.InputDevice + +fun InputDevice.isGameController(): Boolean { + return !isVirtual && ( + sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD + || sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK + || sources and InputDevice.SOURCE_DPAD == InputDevice.SOURCE_DPAD + ) +} \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/utils/IO.kt b/src/android/app/src/main/java/info/cemu/cemu/common/io/IOUtils.kt similarity index 91% rename from src/android/app/src/main/java/info/cemu/cemu/utils/IO.kt rename to src/android/app/src/main/java/info/cemu/cemu/common/io/IOUtils.kt index a6eb922b..654f8911 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/utils/IO.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/common/io/IOUtils.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.utils +package info.cemu.cemu.common.io import java.io.InputStream import java.nio.file.Path diff --git a/src/android/app/src/main/java/info/cemu/cemu/utils/Json.kt b/src/android/app/src/main/java/info/cemu/cemu/common/io/JsonUtils.kt similarity index 92% rename from src/android/app/src/main/java/info/cemu/cemu/utils/Json.kt rename to src/android/app/src/main/java/info/cemu/cemu/common/io/JsonUtils.kt index 3b964153..7bacaa3e 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/utils/Json.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/common/io/JsonUtils.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.utils +package info.cemu.cemu.common.io import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json diff --git a/src/android/app/src/main/java/info/cemu/cemu/utils/Zip.kt b/src/android/app/src/main/java/info/cemu/cemu/common/io/ZipUtils.kt similarity index 97% rename from src/android/app/src/main/java/info/cemu/cemu/utils/Zip.kt rename to src/android/app/src/main/java/info/cemu/cemu/common/io/ZipUtils.kt index 2916bbef..cb304f29 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/utils/Zip.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/common/io/ZipUtils.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.utils +package info.cemu.cemu.common.io import java.io.FileOutputStream import java.io.InputStream diff --git a/src/android/app/src/main/java/info/cemu/cemu/common/motionevent/MotionEventExtensions.kt b/src/android/app/src/main/java/info/cemu/cemu/common/motionevent/MotionEventExtensions.kt new file mode 100644 index 00000000..1fc9417f --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/cemu/common/motionevent/MotionEventExtensions.kt @@ -0,0 +1,9 @@ +package info.cemu.cemu.common.motionevent + +import android.view.InputDevice +import android.view.MotionEvent + +fun MotionEvent.isMotionEventFromJoystickOrGamepad(): Boolean { + return (source and InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK + || (source and InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD +} \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/nativeenummapper/GameTitlesEnums.kt b/src/android/app/src/main/java/info/cemu/cemu/common/nativeenummapper/GameTitlesEnums.kt similarity index 83% rename from src/android/app/src/main/java/info/cemu/cemu/core/nativeenummapper/GameTitlesEnums.kt rename to src/android/app/src/main/java/info/cemu/cemu/common/nativeenummapper/GameTitlesEnums.kt index 3d498c84..b3b5626f 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/core/nativeenummapper/GameTitlesEnums.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/common/nativeenummapper/GameTitlesEnums.kt @@ -1,6 +1,6 @@ -package info.cemu.cemu.core.nativeenummapper +package info.cemu.cemu.common.nativeenummapper -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.nativeinterface.NativeGameTitles.ConsoleRegion fun regionToString(region: Int): String = when (region) { diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/nativeenummapper/InputEnums.kt b/src/android/app/src/main/java/info/cemu/cemu/common/nativeenummapper/InputEnums.kt similarity index 84% rename from src/android/app/src/main/java/info/cemu/cemu/core/nativeenummapper/InputEnums.kt rename to src/android/app/src/main/java/info/cemu/cemu/common/nativeenummapper/InputEnums.kt index d643ef0d..4ac2ad80 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/core/nativeenummapper/InputEnums.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/common/nativeenummapper/InputEnums.kt @@ -1,6 +1,6 @@ -package info.cemu.cemu.core.nativeenummapper +package info.cemu.cemu.common.nativeenummapper -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.nativeinterface.NativeInput.EmulatedControllerType fun controllerTypeToString(type: Int) = when (type) { diff --git a/src/android/app/src/main/java/info/cemu/cemu/common/settings/PreferenceDelegate.kt b/src/android/app/src/main/java/info/cemu/cemu/common/settings/PreferenceDelegate.kt new file mode 100644 index 00000000..d4a68c92 --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/cemu/common/settings/PreferenceDelegate.kt @@ -0,0 +1,47 @@ +package info.cemu.cemu.common.settings + +import android.content.SharedPreferences +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty +import androidx.core.content.edit + +class PreferenceDelegate( + private val sharedPreferences: SharedPreferences, + private val defaultValue: T, + private val getter: SharedPreferences.(String, T) -> T, + private val setter: SharedPreferences.Editor.(String, T) -> SharedPreferences.Editor +) : ReadWriteProperty { + + override fun getValue(thisRef: Any, property: KProperty<*>): T { + val key = "${thisRef::class.simpleName}_${property.name}".uppercase() + return sharedPreferences.getter(key, defaultValue) + } + + override fun setValue(thisRef: Any, property: KProperty<*>, value: T) { + val key = "${thisRef::class.simpleName}_${property.name}".uppercase() + sharedPreferences.edit { setter(key, value) } + } +} + +fun SharedPreferences.booleanPref(default: Boolean) = + PreferenceDelegate( + this, + default, + SharedPreferences::getBoolean, + SharedPreferences.Editor::putBoolean + ) + +fun SharedPreferences.stringPref(default: String) = + PreferenceDelegate( + this, default, + { key, def -> getString(key, def) ?: def }, + SharedPreferences.Editor::putString + ) + +fun SharedPreferences.intPref(default: Int) = + PreferenceDelegate( + this, + default, + SharedPreferences::getInt, + SharedPreferences.Editor::putInt + ) \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/common/settings/SettingsManager.kt b/src/android/app/src/main/java/info/cemu/cemu/common/settings/SettingsManager.kt new file mode 100644 index 00000000..241e1a7d --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/cemu/common/settings/SettingsManager.kt @@ -0,0 +1,38 @@ +package info.cemu.cemu.common.settings + +import android.content.Context +import android.content.SharedPreferences +import info.cemu.cemu.common.translation.DEFAULT_LANGUAGE +import kotlin.getValue + +class EmulationScreenSettings(sharedPreferences: SharedPreferences) { + var isDrawerButtonVisible by sharedPreferences.booleanPref(false) +} + +class GuiSettings(sharedPreferences: SharedPreferences) { + var language by sharedPreferences.stringPref(DEFAULT_LANGUAGE) +} + +class InputOverlaySettings(sharedPreferences: SharedPreferences) { + var isVibrateOnTouchEnabled by sharedPreferences.booleanPref(false) + var isOverlayEnabled by sharedPreferences.booleanPref(false) + var controllerIndex by sharedPreferences.intPref(0) + var alpha by sharedPreferences.intPref(64) +} + +object SettingsManager { + fun initialize(context: Context) { + sharedPreferences = + context.getSharedPreferences(SETTINGS_NAME, Context.MODE_PRIVATE) + } + + private lateinit var sharedPreferences: SharedPreferences + + val emulationScreenSettings by lazy { EmulationScreenSettings(sharedPreferences) } + + val guiSettings by lazy { GuiSettings(sharedPreferences) } + + val inputOverlaySettings by lazy { InputOverlaySettings(sharedPreferences) } + + private const val SETTINGS_NAME = "SETTINGS" +} diff --git a/src/android/app/src/main/java/info/cemu/cemu/utils/String.kt b/src/android/app/src/main/java/info/cemu/cemu/common/string/StringExtensions.kt similarity index 90% rename from src/android/app/src/main/java/info/cemu/cemu/utils/String.kt rename to src/android/app/src/main/java/info/cemu/cemu/common/string/StringExtensions.kt index 1116a547..9bf41710 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/utils/String.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/common/string/StringExtensions.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.utils +package info.cemu.cemu.common.string import java.net.URLDecoder diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/theme/Theme.kt b/src/android/app/src/main/java/info/cemu/cemu/common/theme/Theme.kt similarity index 96% rename from src/android/app/src/main/java/info/cemu/cemu/core/theme/Theme.kt rename to src/android/app/src/main/java/info/cemu/cemu/common/theme/Theme.kt index 55127365..e3b28e5a 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/core/theme/Theme.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/common/theme/Theme.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.core.theme +package info.cemu.cemu.common.theme import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme diff --git a/src/android/app/src/main/java/info/cemu/cemu/utils/PoFile.kt b/src/android/app/src/main/java/info/cemu/cemu/common/translation/PoFile.kt similarity index 97% rename from src/android/app/src/main/java/info/cemu/cemu/utils/PoFile.kt rename to src/android/app/src/main/java/info/cemu/cemu/common/translation/PoFile.kt index a38e7bfe..509f2fd6 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/utils/PoFile.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/common/translation/PoFile.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.utils +package info.cemu.cemu.common.translation import java.io.InputStream diff --git a/src/android/app/src/main/java/info/cemu/cemu/core/translation/Translation.kt b/src/android/app/src/main/java/info/cemu/cemu/common/translation/Translation.kt similarity index 97% rename from src/android/app/src/main/java/info/cemu/cemu/core/translation/Translation.kt rename to src/android/app/src/main/java/info/cemu/cemu/common/translation/Translation.kt index f95a0bf2..02b0109a 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/core/translation/Translation.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/common/translation/Translation.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.core.translation +package info.cemu.cemu.common.translation import android.content.Context import android.util.Log @@ -9,7 +9,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.runtime.staticCompositionLocalOf import info.cemu.cemu.nativeinterface.NativeLocalization -import info.cemu.cemu.utils.parsePoFile import name.kropp.kotlinx.gettext.Gettext import name.kropp.kotlinx.gettext.Locale import name.kropp.kotlinx.gettext.load diff --git a/src/android/app/src/main/java/info/cemu/cemu/emulation/EmulationActivity.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/EmulationActivity.kt index 80bb4fe1..e3736567 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/emulation/EmulationActivity.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/EmulationActivity.kt @@ -1,10 +1,8 @@ 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 @@ -23,24 +21,18 @@ import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.google.android.material.textfield.TextInputLayout import info.cemu.cemu.BuildConfig -import info.cemu.cemu.R -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.databinding.ActivityEmulationBinding 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 info.cemu.cemu.settings.EmulationScreenSettings -import info.cemu.cemu.settings.SettingsManager +import info.cemu.cemu.common.settings.EmulationScreenSettings +import info.cemu.cemu.common.settings.InputOverlaySettings +import info.cemu.cemu.common.settings.SettingsManager +import info.cemu.cemu.emulation.inputoverlay.InputOverlaySurfaceView import java.lang.ref.WeakReference import kotlin.system.exitProcess @@ -75,28 +67,27 @@ class EmulationActivity : AppCompatActivity() { } } - 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 inputOverlaySettings: InputOverlaySettings private lateinit var emulationScreenSettings: EmulationScreenSettings private lateinit var inputOverlaySurfaceView: InputOverlaySurfaceView private lateinit var sensorManager: SensorManager private var hasEmulationError = false override fun onGenericMotionEvent(event: MotionEvent): Boolean { - if (inputManager.onMotionEvent(event)) { + if (InputHandler.onMotionEvent(event)) { return true } return super.onGenericMotionEvent(event) } override fun dispatchKeyEvent(event: KeyEvent): Boolean { - if (inputManager.onKeyEvent(event)) { + if (InputHandler.onKeyEvent(event)) { return true } return super.dispatchKeyEvent(event) @@ -125,8 +116,8 @@ class EmulationActivity : AppCompatActivity() { emulationActivityInstance = WeakReference(this) - overlaySettings = InputOverlaySettingsManager(this).overlaySettings - emulationScreenSettings = SettingsManager(this).emulationScreenSettings + inputOverlaySettings = SettingsManager.inputOverlaySettings + emulationScreenSettings = SettingsManager.emulationScreenSettings sensorManager = SensorManager(this) sensorManager.setDeviceRotationProvider(deviceRotationProvider = { display.rotation }) @@ -198,7 +189,7 @@ class EmulationActivity : AppCompatActivity() { } private fun LayoutSideMenuEmulationBinding.configureSideMenu() { - val isInputOverlayEnabled = overlaySettings.isOverlayEnabled + val isInputOverlayEnabled = inputOverlaySettings.isOverlayEnabled enableMotionCheckbox.configure( label = tr("Enable motion"), onCheckChanged = ::setMotionEnabled @@ -310,7 +301,7 @@ class EmulationActivity : AppCompatActivity() { private fun initializeInputOverlay() { inputOverlaySurfaceView = binding.inputOverlay - inputOverlaySurfaceView.setVisible(overlaySettings.isOverlayEnabled) + inputOverlaySurfaceView.setVisible(inputOverlaySettings.isOverlayEnabled) } private fun toastMessage(text: String) { @@ -368,8 +359,8 @@ class EmulationActivity : AppCompatActivity() { } private fun showExitConfirmationDialog() { - val builder = MaterialAlertDialogBuilder(this) - builder.setTitle(tr("Exit confirmation")) + MaterialAlertDialogBuilder(this) + .setTitle(tr("Exit confirmation")) .setMessage(tr("Are you sure you want to exit?")) .setPositiveButton(tr("Yes")) { _, _ -> quit() } .setNegativeButton(tr("No")) { _, _ -> } @@ -377,8 +368,8 @@ class EmulationActivity : AppCompatActivity() { } private fun onEmulationError(emulationError: String?) { - val builder = MaterialAlertDialogBuilder(this) - builder.setTitle(tr("Error")) + MaterialAlertDialogBuilder(this) + .setTitle(tr("Error")) .setMessage(emulationError) .setNeutralButton(tr("Quit")) { _, _ -> } .setOnDismissListener { _ -> quit() } @@ -413,38 +404,14 @@ class EmulationActivity : AppCompatActivity() { if (emulationActivity.emulationTextInputDialog != null) { return } - setCurrentInputText(initialText) + emulationActivity.runOnUiThread { - val inputEditTextLayout = - emulationActivity.layoutInflater.inflate( - R.layout.layout_emulation_input, - null - ) - val inputEditText = - inputEditTextLayout.requireViewById(R.id.emulation_input_text) - inputEditText.updateText(initialText) - val dialog = MaterialAlertDialogBuilder(emulationActivity) - .setView(inputEditTextLayout) - .setCancelable(false) - .setPositiveButton(tr("Done")) { _, _ -> }.show() - val doneButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE)!! - doneButton.isEnabled = false - doneButton.setOnClickListener { _ -> inputEditText.onFinishedEdit() } - inputEditText.setOnTextChangedListener { - doneButton.isEnabled = it.isNotEmpty() - } - val parentTextInputLayout = - inputEditTextLayout.requireViewById(R.id.emulation_input_layout) - - if (maxLength > 0) { - parentTextInputLayout.isCounterEnabled = true - parentTextInputLayout.counterMaxLength = maxLength - inputEditText.appendFilter(LengthFilter(maxLength)) - } else { - parentTextInputLayout.isCounterEnabled = false - } - - emulationActivity.emulationTextInputDialog = dialog + emulationActivity.emulationTextInputDialog = showEmulationTextInputDialog( + initialText, + maxLength, + emulationActivity, + emulationActivity.layoutInflater + ) } } diff --git a/src/android/app/src/main/java/info/cemu/cemu/emulation/EmulationTextInputEditText.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/EmulationTextInputEditText.kt index ffa8dcd7..8882025f 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/emulation/EmulationTextInputEditText.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/EmulationTextInputEditText.kt @@ -1,14 +1,20 @@ package info.cemu.cemu.emulation import android.content.Context +import android.content.DialogInterface import android.text.Editable import android.text.InputFilter +import android.text.InputFilter.LengthFilter import android.text.TextWatcher import android.util.AttributeSet +import android.view.LayoutInflater import android.view.inputmethod.EditorInfo +import androidx.appcompat.app.AlertDialog import com.google.android.material.R +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.textfield.TextInputEditText -import info.cemu.cemu.core.translation.tr +import com.google.android.material.textfield.TextInputLayout +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.nativeinterface.NativeSwkbd import java.util.regex.Pattern @@ -75,3 +81,52 @@ class EmulationTextInputEditText @JvmOverloads constructor( Pattern.compile("^[\\da-zA-Z \\-/;:',.?!#\\[\\]$%^&*()_@\\\\<>+=]+$") } } + +fun showEmulationTextInputDialog( + initialText: String?, + maxLength: Int, + context: Context, + layoutInflater: LayoutInflater +): AlertDialog { + NativeSwkbd.setCurrentInputText(initialText) + + val inputEditTextLayout = + layoutInflater.inflate( + info.cemu.cemu.R.layout.layout_emulation_input, + null + ) + + val inputEditText = + inputEditTextLayout.requireViewById(info.cemu.cemu.R.id.emulation_input_text) + + inputEditText.updateText(initialText) + + val dialog = MaterialAlertDialogBuilder(context) + .setView(inputEditTextLayout) + .setCancelable(false) + .setPositiveButton(tr("Done")) { _, _ -> } + .show() + + val doneButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE)!! + + doneButton.isEnabled = false + + doneButton.setOnClickListener { _ -> inputEditText.onFinishedEdit() } + + inputEditText.setOnTextChangedListener { + doneButton.isEnabled = it.isNotEmpty() + } + + val parentTextInputLayout = + inputEditTextLayout.requireViewById(info.cemu.cemu.R.id.emulation_input_layout) + + if (maxLength > 0) { + parentTextInputLayout.isCounterEnabled = true + parentTextInputLayout.counterMaxLength = maxLength + inputEditText.appendFilter(LengthFilter(maxLength)) + } else { + parentTextInputLayout.isCounterEnabled = false + } + + return dialog +} \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/emulation/InputHandler.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/InputHandler.kt new file mode 100644 index 00000000..5e3373a5 --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/InputHandler.kt @@ -0,0 +1,52 @@ +package info.cemu.cemu.emulation + +import android.view.KeyEvent +import android.view.MotionEvent +import info.cemu.cemu.common.inputdevice.isGameController +import info.cemu.cemu.common.motionevent.isMotionEventFromJoystickOrGamepad +import info.cemu.cemu.nativeinterface.NativeInput.onNativeAxis +import info.cemu.cemu.nativeinterface.NativeInput.onNativeKey + +private fun KeyEvent.isSpecialKey(): Boolean { + return keyCode == KeyEvent.KEYCODE_VOLUME_DOWN + || keyCode == KeyEvent.KEYCODE_VOLUME_UP + || keyCode == KeyEvent.KEYCODE_CAMERA + || keyCode == KeyEvent.KEYCODE_ZOOM_IN + || keyCode == KeyEvent.KEYCODE_ZOOM_OUT +} + +object InputHandler { + fun onKeyEvent(event: KeyEvent): Boolean { + if (event.isSpecialKey()) { + return false + } + if (event.deviceId < 0) { + return false + } + val device = event.device + if (!device.isGameController()) { + return false + } + onNativeKey( + device.descriptor, + device.name, + event.keyCode, + event.action == KeyEvent.ACTION_DOWN + ) + return true + } + + fun onMotionEvent(event: MotionEvent): Boolean { + if (!event.isMotionEventFromJoystickOrGamepad()) { + return false + } + val device = event.device + val actionPointerIndex = event.actionIndex + for (motionRange in device.motionRanges) { + val axisValue = event.getAxisValue(motionRange.axis, actionPointerIndex) + val axis = motionRange.axis + onNativeAxis(device.descriptor, device.name, axis, axisValue) + } + return true + } +} \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/input/SensorManager.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/SensorManager.kt similarity index 90% rename from src/android/app/src/main/java/info/cemu/cemu/input/SensorManager.kt rename to src/android/app/src/main/java/info/cemu/cemu/emulation/SensorManager.kt index e24c89d2..6ac97b5c 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/input/SensorManager.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/SensorManager.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.input +package info.cemu.cemu.emulation import android.content.Context import android.hardware.Sensor @@ -6,8 +6,7 @@ import android.hardware.SensorEvent import android.hardware.SensorEventListener import android.hardware.SensorManager import android.view.Surface -import info.cemu.cemu.nativeinterface.NativeInput.onMotion -import info.cemu.cemu.nativeinterface.NativeInput.setMotionEnabled +import info.cemu.cemu.nativeinterface.NativeInput class SensorManager(context: Context) : SensorEventListener { private val sensorManager = @@ -29,7 +28,7 @@ class SensorManager(context: Context) : SensorEventListener { return } isListening = true - setMotionEnabled(true) + NativeInput.setMotionEnabled(true) sensorManager.registerListener(this, gyroscope, SensorManager.SENSOR_DELAY_GAME) sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_GAME) } @@ -43,7 +42,7 @@ class SensorManager(context: Context) : SensorEventListener { return } isListening = false - setMotionEnabled(false) + NativeInput.setMotionEnabled(false) sensorManager.unregisterListener(this) } @@ -61,7 +60,7 @@ class SensorManager(context: Context) : SensorEventListener { return } val (accelX, accelY, accelZ) = getSensorEventValues(values) - onMotion(event.timestamp, gyroX, gyroY, gyroZ, accelX, accelZ, -accelY) + NativeInput.onMotion(event.timestamp, gyroX, gyroY, gyroZ, accelX, accelZ, -accelY) } override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) { @@ -95,4 +94,4 @@ class SensorManager(context: Context) : SensorEventListener { } return Triple(x, y, z) } -} +} \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/Colors.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/Colors.kt new file mode 100644 index 00000000..c5b892bd --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/Colors.kt @@ -0,0 +1,14 @@ +package info.cemu.cemu.emulation.inputoverlay + +import android.graphics.Color + +object Colors { + fun activeFill(alpha: Int) = Color.argb(alpha, 255, 255, 255) + fun activeStroke(alpha: Int) = Color.argb(alpha, 0, 0, 0) + + fun inactiveFill(alpha: Int) = Color.argb(alpha, 0, 0, 0) + fun inactiveStroke(alpha: Int) = Color.argb(alpha, 255, 255, 255) + + fun backgroundFill(alpha: Int) = Color.argb(alpha, 128, 128, 128) + fun backgroundStroke(alpha: Int) = Color.argb(alpha, 200, 200, 200) +} diff --git a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/InputOverlayDefaultConfigParser.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/InputOverlayDefaultConfigParser.kt similarity index 97% rename from src/android/app/src/main/java/info/cemu/cemu/inputoverlay/InputOverlayDefaultConfigParser.kt rename to src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/InputOverlayDefaultConfigParser.kt index cf313859..58c2d0cb 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/InputOverlayDefaultConfigParser.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/InputOverlayDefaultConfigParser.kt @@ -1,6 +1,6 @@ -package info.cemu.cemu.inputoverlay +package info.cemu.cemu.emulation.inputoverlay -import info.cemu.cemu.utils.toIntOrZero +import info.cemu.cemu.common.string.toIntOrZero import org.xmlpull.v1.XmlPullParser data class InputConfig( diff --git a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/InputOverlaySettingsManager.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/InputOverlayInputsSettingsManager.kt similarity index 69% rename from src/android/app/src/main/java/info/cemu/cemu/inputoverlay/InputOverlaySettingsManager.kt rename to src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/InputOverlayInputsSettingsManager.kt index c47bce98..7822a568 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/InputOverlaySettingsManager.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/InputOverlayInputsSettingsManager.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.inputoverlay +package info.cemu.cemu.emulation.inputoverlay import android.content.Context import android.content.SharedPreferences @@ -8,51 +8,12 @@ import info.cemu.cemu.R import kotlin.math.max import kotlin.math.min -class OverlaySettings( - var isVibrateOnTouchEnabled: Boolean, - var isOverlayEnabled: Boolean, - var controllerIndex: Int, - var alpha: Int, -) { - companion object { - private const val DEFAULT_ALPHA: Int = 64 - private const val IS_VIBRATE_ON_TOUCH_ENABLED_KEY = "IS_VIBRATE_ON_TOUCH_ENABLED" - private const val IS_OVERLAY_ENABLED_KEY = "IS_OVERLAY_ENABLED" - private const val CONTROLLER_INDEX_KEY = "CONTROLLER_INDEX" - private const val ALPHA_KEY = "ALPHA" - } - - constructor(sharedPreferences: SharedPreferences) : this( - isVibrateOnTouchEnabled = sharedPreferences.getBoolean( - IS_VIBRATE_ON_TOUCH_ENABLED_KEY, - false - ), - isOverlayEnabled = sharedPreferences.getBoolean(IS_OVERLAY_ENABLED_KEY, false), - controllerIndex = sharedPreferences.getInt(CONTROLLER_INDEX_KEY, 0), - alpha = sharedPreferences.getInt(ALPHA_KEY, DEFAULT_ALPHA), - ) - - fun save(sharedPreferences: SharedPreferences) { - sharedPreferences.edit().apply { - putBoolean(IS_VIBRATE_ON_TOUCH_ENABLED_KEY, isVibrateOnTouchEnabled) - putBoolean(IS_OVERLAY_ENABLED_KEY, isOverlayEnabled) - putInt(CONTROLLER_INDEX_KEY, controllerIndex) - putInt(ALPHA_KEY, alpha) - apply() - } - } -} - -class InputOverlaySettingsManager(context: Context) { +class InputOverlayInputsSettingsManager(context: Context) { private val defaultInputConfigs = parseDefaultInputConfigs(context.resources.getXml(R.xml.input_overlay_default_configs)) private val sharedPreferences: SharedPreferences = context.getSharedPreferences(INPUT_OVERLAY_SETTINGS_NAME, Context.MODE_PRIVATE) - var overlaySettings: OverlaySettings - get() = OverlaySettings(sharedPreferences) - set(value) = value.save(sharedPreferences) - fun getInputOverlayRectangle( input: OverlayInput, width: Int, diff --git a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/InputOverlaySurfaceView.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/InputOverlaySurfaceView.kt similarity index 92% rename from src/android/app/src/main/java/info/cemu/cemu/inputoverlay/InputOverlaySurfaceView.kt rename to src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/InputOverlaySurfaceView.kt index c5726d79..55400cc4 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/InputOverlaySurfaceView.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/InputOverlaySurfaceView.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.inputoverlay +package info.cemu.cemu.emulation.inputoverlay import android.content.Context import android.graphics.Canvas @@ -13,15 +13,17 @@ import android.view.SurfaceView import android.view.View import android.view.View.OnTouchListener import info.cemu.cemu.R -import info.cemu.cemu.inputoverlay.inputs.DPadInput -import info.cemu.cemu.inputoverlay.inputs.Input -import info.cemu.cemu.inputoverlay.inputs.Joystick -import info.cemu.cemu.inputoverlay.inputs.RectangleButton -import info.cemu.cemu.inputoverlay.inputs.RoundButton -import info.cemu.cemu.inputoverlay.inputs.innerdrawing.ButtonInnerDrawing -import info.cemu.cemu.inputoverlay.inputs.innerdrawing.HomeButtonInnerDrawing -import info.cemu.cemu.inputoverlay.inputs.innerdrawing.StickClickInnerDrawing -import info.cemu.cemu.inputoverlay.inputs.innerdrawing.TextButtonInnerDrawing +import info.cemu.cemu.common.settings.SettingsManager +import info.cemu.cemu.emulation.inputoverlay.inputs.DPadInput +import info.cemu.cemu.emulation.inputoverlay.inputs.Input +import info.cemu.cemu.emulation.inputoverlay.inputs.Joystick +import info.cemu.cemu.emulation.inputoverlay.inputs.RectangleButton +import info.cemu.cemu.emulation.inputoverlay.inputs.RoundButton +import info.cemu.cemu.emulation.inputoverlay.inputs.innerdrawing.BlowButtonInnerDrawing +import info.cemu.cemu.emulation.inputoverlay.inputs.innerdrawing.ButtonInnerDrawing +import info.cemu.cemu.emulation.inputoverlay.inputs.innerdrawing.HomeButtonInnerDrawing +import info.cemu.cemu.emulation.inputoverlay.inputs.innerdrawing.StickClickInnerDrawing +import info.cemu.cemu.emulation.inputoverlay.inputs.innerdrawing.TextButtonInnerDrawing import info.cemu.cemu.nativeinterface.NativeInput import info.cemu.cemu.nativeinterface.NativeInput.getControllerType import info.cemu.cemu.nativeinterface.NativeInput.isControllerDisabled @@ -48,7 +50,7 @@ class InputOverlaySurfaceView(context: Context, attrs: AttributeSet?) : { _, _, _, _, _ -> } private var overlyButtonToNativeButton: (OverlayInput) -> Int = { _ -> -1 } private var inputs: MutableList>? = null - private val settingsProvider: InputOverlaySettingsManager + private val inputOverlayInputsSettingsManager: InputOverlayInputsSettingsManager private val vibrator: Vibrator? private val buttonTouchVibrationEffect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK) @@ -61,8 +63,8 @@ class InputOverlaySurfaceView(context: Context, attrs: AttributeSet?) : (INPUTS_MIN_WIDTH_HEIGHT_DP * (context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)).roundToInt() vibrator = getVibrator(context) setOnTouchListener(this) - settingsProvider = InputOverlaySettingsManager(context) - val overlaySettings = settingsProvider.overlaySettings + inputOverlayInputsSettingsManager = InputOverlayInputsSettingsManager(context) + val overlaySettings = SettingsManager.inputOverlaySettings controllerIndex = overlaySettings.controllerIndex currentAlpha = overlaySettings.alpha vibrateOnTouch = vibrator.hasVibrator() && overlaySettings.isVibrateOnTouchEnabled @@ -78,7 +80,7 @@ class InputOverlaySurfaceView(context: Context, attrs: AttributeSet?) : return } for (input in OverlayInputList) { - settingsProvider.clearSavedRectangle(input) + inputOverlayInputsSettingsManager.clearSavedRectangle(input) } inputs!!.clear() inputs = null @@ -95,7 +97,7 @@ class InputOverlaySurfaceView(context: Context, attrs: AttributeSet?) : return } for ((overlayInput, input) in inputs!!) { - settingsProvider.saveRectangle(overlayInput, input.getBoundingRectangle()) + inputOverlayInputsSettingsManager.saveRectangle(overlayInput, input.getBoundingRectangle()) } } @@ -127,6 +129,7 @@ class InputOverlaySurfaceView(context: Context, attrs: AttributeSet?) : OverlayButton.R -> NativeInput.VPADButton.R OverlayButton.ZR -> NativeInput.VPADButton.ZR OverlayButton.ZL -> NativeInput.VPADButton.ZL + OverlayButton.BLOW_MIC -> NativeInput.VPADButton.MIC else -> -1 } } @@ -289,7 +292,7 @@ class InputOverlaySurfaceView(context: Context, attrs: AttributeSet?) : } private fun getBoundingRectangleForInput(input: OverlayInput): Rect { - return settingsProvider.getInputOverlayRectangle(input, width, height, pixelDensity) + return inputOverlayInputsSettingsManager.getInputOverlayRectangle(input, width, height, pixelDensity) } private fun MutableList>.addRoundButton( @@ -399,6 +402,10 @@ class InputOverlaySurfaceView(context: Context, attrs: AttributeSet?) : addRoundButton(OverlayButton.L_STICK_CLICK, StickClickInnerDrawing()) addRoundButton(OverlayButton.R_STICK_CLICK, StickClickInnerDrawing()) } + + if (nativeControllerType == NativeInput.EmulatedControllerType.VPAD) { + addRoundButton(OverlayButton.BLOW_MIC, BlowButtonInnerDrawing()) + } } } @@ -417,7 +424,6 @@ class InputOverlaySurfaceView(context: Context, attrs: AttributeSet?) : } } - private fun onEditPosition(event: MotionEvent): Boolean { val configuredInput = currentConfiguredInput if (event.actionMasked == MotionEvent.ACTION_DOWN) { diff --git a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/OverlayInput.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/OverlayInput.kt similarity index 90% rename from src/android/app/src/main/java/info/cemu/cemu/inputoverlay/OverlayInput.kt rename to src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/OverlayInput.kt index c684ca11..7ec1fd17 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/OverlayInput.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/OverlayInput.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.inputoverlay +package info.cemu.cemu.emulation.inputoverlay sealed interface OverlayInput { val configName: String @@ -28,7 +28,9 @@ enum class OverlayButton : OverlayInput { X, Y, ZL, - ZR; + ZR, + BLOW_MIC, + ; override val configName = "BUTTON_$name" } diff --git a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/Button.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/Button.kt similarity index 96% rename from src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/Button.kt rename to src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/Button.kt index 5e166fbb..938dd63e 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/Button.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/Button.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.inputoverlay.inputs +package info.cemu.cemu.emulation.inputoverlay.inputs import android.graphics.Rect import android.view.MotionEvent diff --git a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/DPadInput.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/DPadInput.kt similarity index 94% rename from src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/DPadInput.kt rename to src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/DPadInput.kt index 28d28254..c97b21f5 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/DPadInput.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/DPadInput.kt @@ -1,13 +1,13 @@ -package info.cemu.cemu.inputoverlay.inputs +package info.cemu.cemu.emulation.inputoverlay.inputs import android.graphics.Canvas -import android.graphics.Color import android.graphics.Rect import android.graphics.RectF import android.view.MotionEvent -import info.cemu.cemu.inputoverlay.OverlayDpad -import info.cemu.cemu.utils.fillCircleWithStroke -import info.cemu.cemu.utils.fillRoundRectangleWithStroke +import info.cemu.cemu.common.canvas.fillCircleWithStroke +import info.cemu.cemu.common.canvas.fillRoundRectangleWithStroke +import info.cemu.cemu.emulation.inputoverlay.Colors +import info.cemu.cemu.emulation.inputoverlay.OverlayDpad import kotlin.math.atan2 import kotlin.math.min @@ -33,8 +33,8 @@ class DPadInput( private var currentPointerId: Int = -1 override fun configure() { - backgroundFillColor = Color.argb(alpha, 128, 128, 128) - backgroundStrokeColor = Color.argb(alpha, 200, 200, 200) + backgroundFillColor = Colors.backgroundFill(alpha) + backgroundStrokeColor = Colors.backgroundStroke(alpha) configureColors(alpha) centerX = rect.exactCenterX() diff --git a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/Input.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/Input.kt similarity index 88% rename from src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/Input.kt rename to src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/Input.kt index 95d2c835..47497a9f 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/Input.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/Input.kt @@ -1,11 +1,11 @@ -package info.cemu.cemu.inputoverlay.inputs +package info.cemu.cemu.emulation.inputoverlay.inputs import android.graphics.Canvas -import android.graphics.Color import android.graphics.Paint import android.graphics.Rect import android.view.MotionEvent import androidx.annotation.ColorInt +import info.cemu.cemu.emulation.inputoverlay.Colors import kotlin.math.max import kotlin.math.min @@ -74,10 +74,10 @@ abstract class Input protected constructor( protected abstract fun configure() protected fun configureColors(alpha: Int) { - activeFillColor = Color.argb(alpha, 255, 255, 255) - activeStrokeColor = Color.argb(alpha, 0, 0, 0) - inactiveFillColor = activeStrokeColor - inactiveStrokeColor = activeFillColor + activeFillColor = Colors.activeFill(alpha) + activeStrokeColor = Colors.activeStroke(alpha) + inactiveFillColor = Colors.inactiveFill(alpha) + inactiveStrokeColor = Colors.inactiveStroke(alpha) } protected abstract fun drawInput(canvas: Canvas) diff --git a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/Joystick.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/Joystick.kt similarity index 94% rename from src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/Joystick.kt rename to src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/Joystick.kt index fde7b512..22aeb59c 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/Joystick.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/Joystick.kt @@ -1,11 +1,11 @@ -package info.cemu.cemu.inputoverlay.inputs +package info.cemu.cemu.emulation.inputoverlay.inputs import android.graphics.Canvas -import android.graphics.Color import android.graphics.Paint import android.graphics.Rect import android.view.MotionEvent -import info.cemu.cemu.utils.fillCircleWithStroke +import info.cemu.cemu.emulation.inputoverlay.Colors +import info.cemu.cemu.common.canvas.fillCircleWithStroke import kotlin.math.min import kotlin.math.sqrt @@ -38,7 +38,7 @@ class Joystick( } override fun configure() { - backgroundFillColor = Color.argb(alpha, 128, 128, 128) + backgroundFillColor = Colors.backgroundFill(alpha) configureColors(alpha) centerX = rect.exactCenterX() diff --git a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/RectangleButton.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/RectangleButton.kt similarity index 87% rename from src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/RectangleButton.kt rename to src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/RectangleButton.kt index 75c7e0c5..7b7aa7e5 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/RectangleButton.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/RectangleButton.kt @@ -1,9 +1,9 @@ -package info.cemu.cemu.inputoverlay.inputs +package info.cemu.cemu.emulation.inputoverlay.inputs import android.graphics.Canvas import android.graphics.Rect -import info.cemu.cemu.inputoverlay.inputs.innerdrawing.ButtonInnerDrawing -import info.cemu.cemu.utils.fillRoundRectangleWithStroke +import info.cemu.cemu.emulation.inputoverlay.inputs.innerdrawing.ButtonInnerDrawing +import info.cemu.cemu.common.canvas.fillRoundRectangleWithStroke class RectangleButton( private val innerDrawing: ButtonInnerDrawing, diff --git a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/RoundButton.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/RoundButton.kt similarity index 81% rename from src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/RoundButton.kt rename to src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/RoundButton.kt index 03fff045..760fd0d6 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/RoundButton.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/RoundButton.kt @@ -1,10 +1,9 @@ -package info.cemu.cemu.inputoverlay.inputs +package info.cemu.cemu.emulation.inputoverlay.inputs import android.graphics.Canvas -import android.graphics.Paint import android.graphics.Rect -import info.cemu.cemu.inputoverlay.inputs.innerdrawing.ButtonInnerDrawing -import info.cemu.cemu.utils.fillCircleWithStroke +import info.cemu.cemu.emulation.inputoverlay.inputs.innerdrawing.ButtonInnerDrawing +import info.cemu.cemu.common.canvas.fillCircleWithStroke import kotlin.math.min class RoundButton( @@ -15,7 +14,6 @@ class RoundButton( ) : Button(onButtonStateChange, rect) { private var centerX: Float = 0f private var centerY: Float = 0f - private var radius2: Float = 0f private var radius: Float = 0f init { @@ -26,7 +24,6 @@ class RoundButton( centerX = rect.centerX().toFloat() centerY = rect.centerY().toFloat() radius = min(rect.width().toFloat(), rect.height().toFloat()) / 2f - radius2 = radius * radius innerDrawing.configure(rect, alpha) configureColors(alpha) } @@ -46,6 +43,6 @@ class RoundButton( } override fun isInside(x: Float, y: Float): Boolean { - return (x - centerX) * (x - centerX) + (y - centerY) * (y - centerY) <= radius2 + return (x - centerX) * (x - centerX) + (y - centerY) * (y - centerY) <= radius * radius } } diff --git a/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/innerdrawing/BlowButtonInnerDrawing.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/innerdrawing/BlowButtonInnerDrawing.kt new file mode 100644 index 00000000..557e89fb --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/innerdrawing/BlowButtonInnerDrawing.kt @@ -0,0 +1,17 @@ +package info.cemu.cemu.emulation.inputoverlay.inputs.innerdrawing + +import android.graphics.Path +import androidx.core.graphics.PathParser + +class BlowButtonInnerDrawing : PathInnerDrawing() { + override val canvasSize: Float = CANVAS_SIZE + override val originalPath: Path + get() = PATH + + companion object { + private const val CANVAS_SIZE = 960f + private const val PATH_DATA = + "M460-160q-50 0-85-35t-35-85h80q0 17 11.5 28.5T460-240q17 0 28.5-11.5T500-280q0-17-11.5-28.5T460-320H80v-80h380q50 0 85 35t35 85q0 50-35 85t-85 35ZM80-560v-80h540q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43h-80q0-59 40.5-99.5T620-840q59 0 99.5 40.5T760-700q0 59-40.5 99.5T620-560H80Zm660 320v-80q26 0 43-17t17-43q0-26-17-43t-43-17H80v-80h660q59 0 99.5 40.5T880-380q0 59-40.5 99.5T740-240Z" + private val PATH by lazy { PathParser.createPathFromPathData(PATH_DATA) } + } +} \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/innerdrawing/ButtonInnerDrawing.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/innerdrawing/ButtonInnerDrawing.kt similarity index 74% rename from src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/innerdrawing/ButtonInnerDrawing.kt rename to src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/innerdrawing/ButtonInnerDrawing.kt index 815d3d32..e5a3ee69 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/innerdrawing/ButtonInnerDrawing.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/innerdrawing/ButtonInnerDrawing.kt @@ -1,4 +1,4 @@ -package info.cemu.cemu.inputoverlay.inputs.innerdrawing +package info.cemu.cemu.emulation.inputoverlay.inputs.innerdrawing import android.graphics.Canvas import android.graphics.Rect diff --git a/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/innerdrawing/HomeButtonInnerDrawing.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/innerdrawing/HomeButtonInnerDrawing.kt new file mode 100644 index 00000000..18bce4f2 --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/innerdrawing/HomeButtonInnerDrawing.kt @@ -0,0 +1,17 @@ +package info.cemu.cemu.emulation.inputoverlay.inputs.innerdrawing + +import android.graphics.Path +import androidx.core.graphics.PathParser + +class HomeButtonInnerDrawing : PathInnerDrawing() { + override val canvasSize: Float = CANVAS_SIZE + override val originalPath: Path + get() = PATH + + companion object { + private const val CANVAS_SIZE = 960f + private const val PATH_DATA = + "M240-200h120v-240h240v240h120v-360L480-740 240-560v360Zm-80 80v-480l320-240 320 240v480H520v-240h-80v240H160Zm320-350Z" + private val PATH by lazy { PathParser.createPathFromPathData(PATH_DATA) } + } +} \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/innerdrawing/PathInnerDrawing.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/innerdrawing/PathInnerDrawing.kt new file mode 100644 index 00000000..52145344 --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/innerdrawing/PathInnerDrawing.kt @@ -0,0 +1,41 @@ +package info.cemu.cemu.emulation.inputoverlay.inputs.innerdrawing + +import android.graphics.Canvas +import android.graphics.Matrix +import android.graphics.Paint +import android.graphics.Path +import android.graphics.Rect +import info.cemu.cemu.emulation.inputoverlay.Colors +import kotlin.math.min + +abstract class PathInnerDrawing : ButtonInnerDrawing { + private var activeColor = 0 + private var inactiveColor = 0 + private val paint = Paint() + private var path = Path() + override fun draw(canvas: Canvas, state: Boolean) { + paint.color = if (state) activeColor else inactiveColor + canvas.drawPath(path, paint) + } + + protected abstract val canvasSize: Float + protected abstract val originalPath: Path + + override fun configure(boundingRect: Rect, alpha: Int) { + activeColor = Colors.activeStroke(alpha) + inactiveColor = Colors.inactiveStroke(alpha) + + path = Path(originalPath) + + val transformMatrix = Matrix() + val rectSize = min(boundingRect.width(), boundingRect.height()) * 0.85f + val scale = rectSize / canvasSize + transformMatrix.setScale(scale, scale) + transformMatrix.postTranslate( + boundingRect.exactCenterX() - rectSize * 0.5f, + boundingRect.exactCenterY() + rectSize * 0.5f + ) + + path.transform(transformMatrix) + } +} \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/innerdrawing/StickClickInnerDrawing.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/innerdrawing/StickClickInnerDrawing.kt similarity index 80% rename from src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/innerdrawing/StickClickInnerDrawing.kt rename to src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/innerdrawing/StickClickInnerDrawing.kt index df8a58d3..b8f1710f 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/innerdrawing/StickClickInnerDrawing.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/innerdrawing/StickClickInnerDrawing.kt @@ -1,16 +1,16 @@ -package info.cemu.cemu.inputoverlay.inputs.innerdrawing +package info.cemu.cemu.emulation.inputoverlay.inputs.innerdrawing import android.graphics.Canvas -import android.graphics.Color import android.graphics.Matrix import android.graphics.Paint import android.graphics.Path import android.graphics.Rect +import info.cemu.cemu.emulation.inputoverlay.Colors import kotlin.math.min class StickClickInnerDrawing : ButtonInnerDrawing { - private var activeFillColor = 0 - private var inactiveFillColor = 0 + private var activeColor = 0 + private var inactiveColor = 0 private val paint = Paint().apply { strokeWidth = 5f style = Paint.Style.STROKE @@ -18,14 +18,13 @@ class StickClickInnerDrawing : ButtonInnerDrawing { private var path = Path() override fun draw(canvas: Canvas, state: Boolean) { - paint.color = if (state) activeFillColor else inactiveFillColor + paint.color = if (state) activeColor else inactiveColor canvas.drawPath(path, paint) } override fun configure(boundingRect: Rect, alpha: Int) { - activeFillColor = Color.argb(alpha, 0, 0, 0) - inactiveFillColor = Color.argb(alpha, 255, 255, 255) - + activeColor = Colors.activeStroke(alpha) + inactiveColor = Colors.inactiveStroke(alpha) val transformMatrix = Matrix() path = createArrowPath() diff --git a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/innerdrawing/TextButtonInnerDrawing.kt b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/innerdrawing/TextButtonInnerDrawing.kt similarity index 77% rename from src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/innerdrawing/TextButtonInnerDrawing.kt rename to src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/innerdrawing/TextButtonInnerDrawing.kt index 64aec2b8..791cda11 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/innerdrawing/TextButtonInnerDrawing.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/emulation/inputoverlay/inputs/innerdrawing/TextButtonInnerDrawing.kt @@ -1,11 +1,11 @@ -package info.cemu.cemu.inputoverlay.inputs.innerdrawing +package info.cemu.cemu.emulation.inputoverlay.inputs.innerdrawing import android.graphics.Canvas -import android.graphics.Color import android.graphics.Rect import android.text.StaticLayout import android.text.TextPaint import androidx.core.graphics.withTranslation +import info.cemu.cemu.emulation.inputoverlay.Colors import kotlin.math.min class TextButtonInnerDrawing(private val text: String) : ButtonInnerDrawing { @@ -13,11 +13,11 @@ class TextButtonInnerDrawing(private val text: String) : ButtonInnerDrawing { private var staticLayout = createStaticLayout() private var textXCoordinate = 0f private var textYCoordinate = 0f - private var activeFillColor = 0 - private var inactiveFillColor = 0 + private var activeColor = 0 + private var inactiveColor = 0 override fun draw(canvas: Canvas, state: Boolean) { - textPaint.color = if (state) activeFillColor else inactiveFillColor + textPaint.color = if (state) activeColor else inactiveColor canvas.withTranslation(textXCoordinate, textYCoordinate) { staticLayout.draw(canvas) } @@ -25,8 +25,8 @@ class TextButtonInnerDrawing(private val text: String) : ButtonInnerDrawing { override fun configure(boundingRect: Rect, alpha: Int) { textPaint.textSize = min(boundingRect.width(), boundingRect.height()) * 0.75f - activeFillColor = Color.argb(alpha, 0, 0, 0) - inactiveFillColor = Color.argb(alpha, 255, 255, 255) + activeColor = Colors.activeStroke(alpha) + inactiveColor = Colors.inactiveStroke(alpha) staticLayout = createStaticLayout() textXCoordinate = boundingRect.exactCenterX() - staticLayout.width * 0.5f textYCoordinate = boundingRect.exactCenterY() - staticLayout.height * 0.5f diff --git a/src/android/app/src/main/java/info/cemu/cemu/gamelist/GameDetailsScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/gamelist/GameDetailsScreen.kt index f53cfb22..4c33b2a4 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/gamelist/GameDetailsScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/gamelist/GameDetailsScreen.kt @@ -10,10 +10,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import info.cemu.cemu.core.components.ScreenContent -import info.cemu.cemu.core.nativeenummapper.regionToString -import info.cemu.cemu.core.translation.tr -import info.cemu.cemu.nativeinterface.NativeGameTitles +import info.cemu.cemu.common.components.ScreenContent +import info.cemu.cemu.common.nativeenummapper.regionToString +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.nativeinterface.NativeGameTitles.Game import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/src/android/app/src/main/java/info/cemu/cemu/gamelist/GameListNavigation.kt b/src/android/app/src/main/java/info/cemu/cemu/gamelist/GameListNavigation.kt index 101fc52d..2593973d 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/gamelist/GameListNavigation.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/gamelist/GameListNavigation.kt @@ -42,6 +42,7 @@ private inline fun NavGraphBuilder.composableGameScreen( fun NavGraphBuilder.gameListNavigation( navController: NavHostController, startGame: (NativeGameTitles.Game) -> Unit, + createShortcut: (NativeGameTitles.Game) -> Unit, gameListToolBarActions: @Composable (RowScope.() -> Unit), ) { navigation(startDestination = GameListRoutes.GamesRoute) { @@ -49,6 +50,7 @@ fun NavGraphBuilder.gameListNavigation( val gameViewModel: GameViewModel = viewModel(backStackEntry) GamesListScreen( startGame = startGame, + createShortcut = createShortcut, goToGameEditProfile = { game -> gameViewModel.game = game navController.navigate(GameListRoutes.GameProfileEditRoute) diff --git a/src/android/app/src/main/java/info/cemu/cemu/gamelist/GameProfileEditScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/gamelist/GameProfileEditScreen.kt index 0dc2f6b9..3309d61a 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/gamelist/GameProfileEditScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/gamelist/GameProfileEditScreen.kt @@ -5,11 +5,11 @@ import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import info.cemu.cemu.core.components.Header -import info.cemu.cemu.core.components.ScreenContent -import info.cemu.cemu.core.components.SingleSelection -import info.cemu.cemu.core.components.Toggle -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.components.Header +import info.cemu.cemu.common.components.ScreenContent +import info.cemu.cemu.common.components.SingleSelection +import info.cemu.cemu.common.components.Toggle +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.nativeinterface.NativeGameTitles @Composable diff --git a/src/android/app/src/main/java/info/cemu/cemu/gamelist/GamesListScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/gamelist/GamesListScreen.kt index 946682fd..1e0064e5 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/gamelist/GamesListScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/gamelist/GamesListScreen.kt @@ -4,11 +4,7 @@ package info.cemu.cemu.gamelist -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.content.pm.ShortcutInfo -import android.content.pm.ShortcutManager + import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable @@ -51,8 +47,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.asAndroidBitmap -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -62,14 +56,12 @@ import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import info.cemu.cemu.R -import info.cemu.cemu.core.components.FilledSearchToolbar -import info.cemu.cemu.core.translation.tr -import info.cemu.cemu.emulation.EmulationActivity +import info.cemu.cemu.common.components.FilledSearchToolbar +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.nativeinterface.NativeGameTitles import info.cemu.cemu.nativeinterface.NativeGameTitles.Game import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import android.graphics.drawable.Icon as ShortcutIcon @Composable fun GamesListScreen( @@ -77,9 +69,9 @@ fun GamesListScreen( goToGameDetails: (Game) -> Unit, goToGameEditProfile: (Game) -> Unit, startGame: (Game) -> Unit, + createShortcut: (Game) -> Unit, toolbarActions: @Composable RowScope.() -> Unit, ) { - val context = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current val lifecycleState by lifecycleOwner.lifecycle.currentStateFlow.collectAsState() val coroutineScope = rememberCoroutineScope() @@ -138,15 +130,7 @@ fun GamesListScreen( startGame = startGame, goToGameDetails = goToGameDetails, goToGameEditProfile = goToGameEditProfile, - createShortcut = { game -> - createShortcutForGame( - context, - game, - onFailedToCreateShortCut = { - coroutineScope.launch { snackbarHostState.showSnackbar(tr("Device doesn't support creating shortcuts")) } - } - ) - }, + createShortcut = createShortcut, ) PullToRefreshDefaults.Indicator( modifier = Modifier.align(Alignment.TopCenter), @@ -371,45 +355,3 @@ fun GameContextMenu( } } - -private fun createShortcutForGame( - context: Context, - game: Game, - onFailedToCreateShortCut: () -> Unit, -) { - try { - val shortcutManager = context.getSystemService( - ShortcutManager::class.java - ) - if (!shortcutManager.isRequestPinShortcutSupported) { - onFailedToCreateShortCut() - return - } - val icon = game.icon?.asAndroidBitmap().let { - if (it != null) ShortcutIcon.createWithBitmap(it) - else ShortcutIcon.createWithResource(context, R.mipmap.ic_launcher) - } - val intent = Intent( - context, - EmulationActivity::class.java - ) - intent.setAction(Intent.ACTION_VIEW) - intent.putExtra(EmulationActivity.EXTRA_LAUNCH_PATH, game.path) - val pinShortcutInfo = ShortcutInfo.Builder(context, game.titleId.toString()) - .setShortLabel(game.name!!) - .setIntent(intent) - .setIcon(icon) - .build() - val pinnedShortcutCallbackIntent = - shortcutManager.createShortcutResultIntent(pinShortcutInfo) - val successCallback = PendingIntent.getBroadcast( - context, - 0, - pinnedShortcutCallbackIntent, - PendingIntent.FLAG_IMMUTABLE - ) - shortcutManager.requestPinShortcut(pinShortcutInfo, successCallback.intentSender) - } catch (_: Exception) { - onFailedToCreateShortCut() - } -} diff --git a/src/android/app/src/main/java/info/cemu/cemu/graphicpacks/GraphicPacksDownloader.kt b/src/android/app/src/main/java/info/cemu/cemu/graphicpacks/GraphicPacksDownloader.kt index d77bd619..0677475d 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/graphicpacks/GraphicPacksDownloader.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/graphicpacks/GraphicPacksDownloader.kt @@ -3,7 +3,7 @@ package info.cemu.cemu.graphicpacks import android.content.Context import info.cemu.cemu.BuildConfig import info.cemu.cemu.nativeinterface.NativeGraphicPacks -import info.cemu.cemu.utils.unzip +import info.cemu.cemu.common.io.unzip import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okhttp3.OkHttpClient diff --git a/src/android/app/src/main/java/info/cemu/cemu/graphicpacks/GraphicPacksScreens.kt b/src/android/app/src/main/java/info/cemu/cemu/graphicpacks/GraphicPacksScreens.kt index 382eff78..a54164f6 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/graphicpacks/GraphicPacksScreens.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/graphicpacks/GraphicPacksScreens.kt @@ -55,11 +55,11 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.dropUnlessResumed import androidx.lifecycle.viewmodel.compose.viewModel import info.cemu.cemu.R -import info.cemu.cemu.core.components.DefaultAppBarTitle -import info.cemu.cemu.core.components.ScreenContentLazy -import info.cemu.cemu.core.components.SearchToolbarInput -import info.cemu.cemu.core.components.SingleSelection -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.components.DefaultAppBarTitle +import info.cemu.cemu.common.components.ScreenContentLazy +import info.cemu.cemu.common.components.SearchToolbarInput +import info.cemu.cemu.common.components.SingleSelection +import info.cemu.cemu.common.translation.tr import kotlinx.coroutines.launch diff --git a/src/android/app/src/main/java/info/cemu/cemu/input/EmulatedInputs.kt b/src/android/app/src/main/java/info/cemu/cemu/input/EmulatedInputs.kt deleted file mode 100644 index fc744123..00000000 --- a/src/android/app/src/main/java/info/cemu/cemu/input/EmulatedInputs.kt +++ /dev/null @@ -1,122 +0,0 @@ -package info.cemu.cemu.input - -import info.cemu.cemu.nativeinterface.NativeInput -import info.cemu.cemu.nativeinterface.NativeInput.EmulatedControllerType - -sealed interface NativeInputButton { - val nativeKeyCode: Int -} - -enum class VPadButtons(override val nativeKeyCode: Int) : NativeInputButton { - A(NativeInput.VPADButton.A), - B(NativeInput.VPADButton.B), - X(NativeInput.VPADButton.X), - Y(NativeInput.VPADButton.Y), - L(NativeInput.VPADButton.L), - R(NativeInput.VPADButton.R), - ZL(NativeInput.VPADButton.ZL), - ZR(NativeInput.VPADButton.ZR), - PLUS(NativeInput.VPADButton.PLUS), - MINUS(NativeInput.VPADButton.MINUS), - UP(NativeInput.VPADButton.UP), - DOWN(NativeInput.VPADButton.DOWN), - LEFT(NativeInput.VPADButton.LEFT), - RIGHT(NativeInput.VPADButton.RIGHT), - STICKL(NativeInput.VPADButton.STICKL), - STICKR(NativeInput.VPADButton.STICKR), - STICKL_UP(NativeInput.VPADButton.STICKL_UP), - STICKL_DOWN(NativeInput.VPADButton.STICKL_DOWN), - STICKL_LEFT(NativeInput.VPADButton.STICKL_LEFT), - STICKL_RIGHT(NativeInput.VPADButton.STICKL_RIGHT), - STICKR_UP(NativeInput.VPADButton.STICKR_UP), - STICKR_DOWN(NativeInput.VPADButton.STICKR_DOWN), - STICKR_LEFT(NativeInput.VPADButton.STICKR_LEFT), - STICKR_RIGHT(NativeInput.VPADButton.STICKR_RIGHT), - MIC(NativeInput.VPADButton.MIC), - SCREEN(NativeInput.VPADButton.SCREEN), - HOME(NativeInput.VPADButton.HOME), -} - -enum class ProControllerButtons(override val nativeKeyCode: Int) : NativeInputButton { - A(NativeInput.ProButton.A), - B(NativeInput.ProButton.B), - X(NativeInput.ProButton.X), - Y(NativeInput.ProButton.Y), - L(NativeInput.ProButton.L), - R(NativeInput.ProButton.R), - ZL(NativeInput.ProButton.ZL), - ZR(NativeInput.ProButton.ZR), - PLUS(NativeInput.ProButton.PLUS), - MINUS(NativeInput.ProButton.MINUS), - HOME(NativeInput.ProButton.HOME), - UP(NativeInput.ProButton.UP), - DOWN(NativeInput.ProButton.DOWN), - LEFT(NativeInput.ProButton.LEFT), - RIGHT(NativeInput.ProButton.RIGHT), - STICKL(NativeInput.ProButton.STICKL), - STICKR(NativeInput.ProButton.STICKR), - STICKL_UP(NativeInput.ProButton.STICKL_UP), - STICKL_DOWN(NativeInput.ProButton.STICKL_DOWN), - STICKL_LEFT(NativeInput.ProButton.STICKL_LEFT), - STICKL_RIGHT(NativeInput.ProButton.STICKL_RIGHT), - STICKR_UP(NativeInput.ProButton.STICKR_UP), - STICKR_DOWN(NativeInput.ProButton.STICKR_DOWN), - STICKR_LEFT(NativeInput.ProButton.STICKR_LEFT), - STICKR_RIGHT(NativeInput.ProButton.STICKR_RIGHT), -} - -enum class ClassicControllerButtons(override val nativeKeyCode: Int) : NativeInputButton { - A(NativeInput.ClassicButton.A), - B(NativeInput.ClassicButton.B), - X(NativeInput.ClassicButton.X), - Y(NativeInput.ClassicButton.Y), - L(NativeInput.ClassicButton.L), - R(NativeInput.ClassicButton.R), - ZL(NativeInput.ClassicButton.ZL), - ZR(NativeInput.ClassicButton.ZR), - PLUS(NativeInput.ClassicButton.PLUS), - MINUS(NativeInput.ClassicButton.MINUS), - HOME(NativeInput.ClassicButton.HOME), - UP(NativeInput.ClassicButton.UP), - DOWN(NativeInput.ClassicButton.DOWN), - LEFT(NativeInput.ClassicButton.LEFT), - RIGHT(NativeInput.ClassicButton.RIGHT), - STICKL_UP(NativeInput.ClassicButton.STICKL_UP), - STICKL_DOWN(NativeInput.ClassicButton.STICKL_DOWN), - STICKL_LEFT(NativeInput.ClassicButton.STICKL_LEFT), - STICKL_RIGHT(NativeInput.ClassicButton.STICKL_RIGHT), - STICKR_UP(NativeInput.ClassicButton.STICKR_UP), - STICKR_DOWN(NativeInput.ClassicButton.STICKR_DOWN), - STICKR_LEFT(NativeInput.ClassicButton.STICKR_LEFT), - STICKR_RIGHT(NativeInput.ClassicButton.STICKR_RIGHT), -} - -enum class WiimoteButtons(override val nativeKeyCode: Int) : NativeInputButton { - A(NativeInput.WiimoteButton.A), - B(NativeInput.WiimoteButton.B), - ONE(NativeInput.WiimoteButton.ONE), - TWO(NativeInput.WiimoteButton.TWO), - NUNCHUCK_Z(NativeInput.WiimoteButton.NUNCHUCK_Z), - NUNCHUCK_C(NativeInput.WiimoteButton.NUNCHUCK_C), - PLUS(NativeInput.WiimoteButton.PLUS), - MINUS(NativeInput.WiimoteButton.MINUS), - UP(NativeInput.WiimoteButton.UP), - DOWN(NativeInput.WiimoteButton.DOWN), - LEFT(NativeInput.WiimoteButton.LEFT), - RIGHT(NativeInput.WiimoteButton.RIGHT), - NUNCHUCK_UP(NativeInput.WiimoteButton.NUNCHUCK_UP), - NUNCHUCK_DOWN(NativeInput.WiimoteButton.NUNCHUCK_DOWN), - NUNCHUCK_LEFT(NativeInput.WiimoteButton.NUNCHUCK_LEFT), - NUNCHUCK_RIGHT(NativeInput.WiimoteButton.NUNCHUCK_RIGHT), - HOME(NativeInput.WiimoteButton.HOME), -} - -fun getNativeButtonsForControllerType(controllerType: Int): Array { - return when (controllerType) { - EmulatedControllerType.VPAD -> VPadButtons.entries.toTypedArray() - EmulatedControllerType.PRO -> ProControllerButtons.entries.toTypedArray() - EmulatedControllerType.CLASSIC -> ClassicControllerButtons.entries.toTypedArray() - EmulatedControllerType.WIIMOTE -> WiimoteButtons.entries.toTypedArray() - else -> arrayOf() - } -} diff --git a/src/android/app/src/main/java/info/cemu/cemu/input/InputMappings.kt b/src/android/app/src/main/java/info/cemu/cemu/input/InputMappings.kt deleted file mode 100644 index ee68c7b4..00000000 --- a/src/android/app/src/main/java/info/cemu/cemu/input/InputMappings.kt +++ /dev/null @@ -1,84 +0,0 @@ -package info.cemu.cemu.input - -import android.view.KeyEvent -import android.view.MotionEvent - -sealed interface InputMapping - -enum class ButtonInputMapping(val keyCode: Int) : InputMapping { - BUTTON_1(KeyEvent.KEYCODE_BUTTON_1), - BUTTON_2(KeyEvent.KEYCODE_BUTTON_2), - BUTTON_3(KeyEvent.KEYCODE_BUTTON_3), - BUTTON_4(KeyEvent.KEYCODE_BUTTON_4), - BUTTON_5(KeyEvent.KEYCODE_BUTTON_5), - BUTTON_6(KeyEvent.KEYCODE_BUTTON_6), - BUTTON_7(KeyEvent.KEYCODE_BUTTON_7), - BUTTON_8(KeyEvent.KEYCODE_BUTTON_8), - BUTTON_9(KeyEvent.KEYCODE_BUTTON_9), - BUTTON_10(KeyEvent.KEYCODE_BUTTON_10), - BUTTON_11(KeyEvent.KEYCODE_BUTTON_11), - BUTTON_12(KeyEvent.KEYCODE_BUTTON_12), - BUTTON_13(KeyEvent.KEYCODE_BUTTON_13), - BUTTON_14(KeyEvent.KEYCODE_BUTTON_14), - BUTTON_15(KeyEvent.KEYCODE_BUTTON_15), - BUTTON_16(KeyEvent.KEYCODE_BUTTON_16), - BUTTON_A(KeyEvent.KEYCODE_BUTTON_A), - BUTTON_B(KeyEvent.KEYCODE_BUTTON_B), - BUTTON_C(KeyEvent.KEYCODE_BUTTON_C), - BUTTON_L1(KeyEvent.KEYCODE_BUTTON_L1), - BUTTON_L2(KeyEvent.KEYCODE_BUTTON_L2), - BUTTON_MODE(KeyEvent.KEYCODE_BUTTON_MODE), - BUTTON_R1(KeyEvent.KEYCODE_BUTTON_R1), - BUTTON_R2(KeyEvent.KEYCODE_BUTTON_R2), - BUTTON_SELECT(KeyEvent.KEYCODE_BUTTON_SELECT), - BUTTON_START(KeyEvent.KEYCODE_BUTTON_START), - BUTTON_THUMBL(KeyEvent.KEYCODE_BUTTON_THUMBL), - BUTTON_THUMBR(KeyEvent.KEYCODE_BUTTON_THUMBR), - BUTTON_X(KeyEvent.KEYCODE_BUTTON_X), - BUTTON_Y(KeyEvent.KEYCODE_BUTTON_Y), - BUTTON_Z(KeyEvent.KEYCODE_BUTTON_Z), - DPAD_DOWN(KeyEvent.KEYCODE_DPAD_DOWN), - DPAD_LEFT(KeyEvent.KEYCODE_DPAD_LEFT), - DPAD_RIGHT(KeyEvent.KEYCODE_DPAD_RIGHT), - DPAD_UP(KeyEvent.KEYCODE_DPAD_UP), -} - -val FALLBACK_BUTTONS = arrayOf( - ButtonInputMapping.BUTTON_1, - ButtonInputMapping.BUTTON_2, - ButtonInputMapping.BUTTON_3, - ButtonInputMapping.BUTTON_4, - ButtonInputMapping.BUTTON_5, - ButtonInputMapping.BUTTON_6, - ButtonInputMapping.BUTTON_7, - ButtonInputMapping.BUTTON_8, - ButtonInputMapping.BUTTON_9, - ButtonInputMapping.BUTTON_10, - ButtonInputMapping.BUTTON_11, - ButtonInputMapping.BUTTON_12, - ButtonInputMapping.BUTTON_13, - ButtonInputMapping.BUTTON_14, - ButtonInputMapping.BUTTON_15, - ButtonInputMapping.BUTTON_16, -) - -enum class AxisInputMapping(val axisCode: Int, val isPositive: Boolean) : InputMapping { - HAT_X_POS(MotionEvent.AXIS_HAT_X, true), - HAT_X_NEG(MotionEvent.AXIS_HAT_X, false), - HAT_Y_POS(MotionEvent.AXIS_HAT_Y, true), - HAT_Y_NEG(MotionEvent.AXIS_HAT_Y, false), - RX_POS(MotionEvent.AXIS_RX, true), - RX_NEG(MotionEvent.AXIS_RX, false), - RY_POS(MotionEvent.AXIS_RY, true), - RY_NEG(MotionEvent.AXIS_RY, false), - RZ_POS(MotionEvent.AXIS_RZ, true), - RZ_NEG(MotionEvent.AXIS_RZ, false), - LTRIGGER(MotionEvent.AXIS_LTRIGGER, true), - RTRIGGER(MotionEvent.AXIS_RTRIGGER, true), - X_POS(MotionEvent.AXIS_X, true), - X_NEG(MotionEvent.AXIS_X, false), - Y_POS(MotionEvent.AXIS_Y, true), - Y_NEG(MotionEvent.AXIS_Y, false), - Z_POS(MotionEvent.AXIS_Z, true), - Z_NEG(MotionEvent.AXIS_Z, false), -} diff --git a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/innerdrawing/HomeButtonInnerDrawing.kt b/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/innerdrawing/HomeButtonInnerDrawing.kt deleted file mode 100644 index d7b63e98..00000000 --- a/src/android/app/src/main/java/info/cemu/cemu/inputoverlay/inputs/innerdrawing/HomeButtonInnerDrawing.kt +++ /dev/null @@ -1,55 +0,0 @@ -package info.cemu.cemu.inputoverlay.inputs.innerdrawing - -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Matrix -import android.graphics.Paint -import android.graphics.Path -import android.graphics.Rect -import kotlin.math.min - -class HomeButtonInnerDrawing : ButtonInnerDrawing { - private var activeFillColor = 0 - private var inactiveFillColor = 0 - private val paint = Paint() - private var path = Path() - override fun draw(canvas: Canvas, state: Boolean) { - paint.color = if (state) activeFillColor else inactiveFillColor - canvas.drawPath(path, paint) - } - - override fun configure(boundingRect: Rect, alpha: Int) { - activeFillColor = Color.argb(alpha, 0, 0, 0) - inactiveFillColor = Color.argb(alpha, 255, 255, 255) - - path = Path() - val firstPoint = IconPathPoints[0] - path.moveTo(firstPoint.first, firstPoint.second) - for (i in 1.. { -//// Log.i("NATIVE_FILES", "listFiles $uri") - - val files = ArrayList() - val directoryUri = uri.fromNativePath() - val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree( - directoryUri, - DocumentsContract.getDocumentId(directoryUri) - ) - try { - ContentResolver.query( - childrenUri, - arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID), - null, - null, - null - ).use { cursor -> - while (cursor != null && cursor.moveToNext()) { - val documentId = cursor.getString(0) - val documentUri = - DocumentsContract.buildDocumentUriUsingTree(directoryUri, documentId) - files.add(documentUri.toNativePath()) + @Keep + @JvmStatic + fun openContentUri(uri: String): Int { + try { + val parcelFileDescriptor = + contentResolver.openFileDescriptor( + uri.fromNativePath(), MODE + ) + if (parcelFileDescriptor != null) { + val fd = parcelFileDescriptor.detachFd() + parcelFileDescriptor.close() + return fd } + } catch (e: Exception) { + Log.e("NativeFiles", "Cannot open content uri, error: ${e.message}") } - } catch (e: Exception) { - Log.e("NativeFiles", "Cannot list files: ${e.message}") + return -1 } - var filesArray = arrayOfNulls(files.size) - filesArray = files.toArray(filesArray) - return filesArray -} -@Keep -fun isDirectory(uri: String): Boolean { -// Log.i("NATIVE_FILES", "isDirectory $uri") + @Keep + @JvmStatic + fun listFiles(uri: String): Array { + val files = ArrayList() + val directoryUri = uri.fromNativePath() + val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree( + directoryUri, + DocumentsContract.getDocumentId(directoryUri) + ) + try { + contentResolver.query( + childrenUri, + arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID), + null, + null, + null + ).use { cursor -> + while (cursor != null && cursor.moveToNext()) { + val documentId = cursor.getString(0) + val documentUri = + DocumentsContract.buildDocumentUriUsingTree(directoryUri, documentId) + files.add(documentUri.toNativePath()) + } + } + } catch (e: Exception) { + Log.e("NativeFiles", "Cannot list files: ${e.message}") + } + var filesArray = arrayOfNulls(files.size) + filesArray = files.toArray(filesArray) + return filesArray + } - val mimeType = ContentResolver.getType( - uri.fromNativePath() - ) - return DocumentsContract.Document.MIME_TYPE_DIR == mimeType -} + @Keep + @JvmStatic + fun isDirectory(uri: String): Boolean { + val mimeType = contentResolver.getType( + uri.fromNativePath() + ) + return DocumentsContract.Document.MIME_TYPE_DIR == mimeType + } -@Keep -fun isFile(uri: String): Boolean { - return !isDirectory(uri) -} + @Keep + @JvmStatic + fun isFile(uri: String): Boolean { + return !isDirectory(uri) + } -@Keep -fun exists(uri: String): Boolean { -// Log.i("NATIVE_FILES", "exists $uri") - - try { - ContentResolver.query(uri.fromNativePath(), null, null, null, null).use { cursor -> - return cursor != null && cursor.moveToFirst() + @Keep + @JvmStatic + fun exists(uri: String): Boolean { + try { + contentResolver.query(uri.fromNativePath(), null, null, null, null).use { cursor -> + return cursor != null && cursor.moveToFirst() + } + } catch (e: Exception) { + Log.e("NativeFiles", "Failed checking if file exists: ${e.message}") + return false } - } catch (e: Exception) { - Log.e("NativeFiles", "Failed checking if file exists: ${e.message}") - return false } } diff --git a/src/android/app/src/main/java/info/cemu/cemu/provider/DocumentsProvider.kt b/src/android/app/src/main/java/info/cemu/cemu/provider/DocumentsProvider.kt index 4bd89d68..b2ce3a6b 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/provider/DocumentsProvider.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/provider/DocumentsProvider.kt @@ -12,8 +12,8 @@ import android.provider.DocumentsContract import android.provider.DocumentsProvider import android.webkit.MimeTypeMap import info.cemu.cemu.BuildConfig -import info.cemu.cemu.CemuApplication import info.cemu.cemu.R +import info.cemu.cemu.common.context.internalFolder import java.io.File import java.io.FileInputStream import java.io.FileNotFoundException @@ -22,22 +22,14 @@ import java.io.IOException import java.util.Objects class DocumentsProvider : DocumentsProvider() { - private var _baseDirectory: File? = null - private val baseDirectory: File - get() { - if (_baseDirectory == null) { - try { - _baseDirectory = CemuApplication.Application.internalFolder.canonicalFile - } catch (e: IOException) { - throw RuntimeException(e) - } - } - return _baseDirectory!! - } + private val baseDirectory: File by lazy { + requireContext().internalFolder() + } - private val applicationName = - CemuApplication.Application.applicationInfo.loadLabel(CemuApplication.Application.packageManager) - .toString() + private val applicationName: String by lazy { + var context = requireContext().applicationContext + context.applicationInfo.loadLabel(context.packageManager).toString() + } override fun onCreate(): Boolean { return true diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/SettingsHomeScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/SettingsHomeScreen.kt index e2d70315..4fce2cdc 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/SettingsHomeScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/SettingsHomeScreen.kt @@ -2,9 +2,9 @@ package info.cemu.cemu.settings import androidx.compose.runtime.Composable import androidx.lifecycle.compose.dropUnlessResumed -import info.cemu.cemu.core.components.Button -import info.cemu.cemu.core.components.ScreenContent -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.components.Button +import info.cemu.cemu.common.components.ScreenContent +import info.cemu.cemu.common.translation.tr data class SettingsHomeScreenActions( val goToGeneralSettings: () -> Unit, diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/SettingsManager.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/SettingsManager.kt deleted file mode 100644 index 82dd66b0..00000000 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/SettingsManager.kt +++ /dev/null @@ -1,64 +0,0 @@ -package info.cemu.cemu.settings - -import android.content.Context -import android.content.SharedPreferences -import info.cemu.cemu.core.translation.DEFAULT_LANGUAGE - -class EmulationScreenSettings( - var isDrawerButtonVisible: Boolean, -) { - companion object { - private const val IS_BUTTON_VISIBLE_KEY = "IS_BUTTON_VISIBLE" - } - - constructor(sharedPreferences: SharedPreferences) : this( - isDrawerButtonVisible = sharedPreferences.getBoolean( - IS_BUTTON_VISIBLE_KEY, - false - ), - ) - - fun save(sharedPreferences: SharedPreferences) { - sharedPreferences.edit().apply { - putBoolean(IS_BUTTON_VISIBLE_KEY, isDrawerButtonVisible) - apply() - } - } -} - -class GuiSettings(var language: String) { - companion object { - private const val LANGUAGE_KEY = "LANGUAGE" - } - - constructor(sharedPreferences: SharedPreferences) : this( - language = sharedPreferences.getString( - LANGUAGE_KEY, - null - ) ?: DEFAULT_LANGUAGE, - ) - - fun save(sharedPreferences: SharedPreferences) { - sharedPreferences.edit().apply { - putString(LANGUAGE_KEY, language) - apply() - } - } -} - -class SettingsManager(context: Context) { - private val sharedPreferences: SharedPreferences = - context.getSharedPreferences(SETTINGS_NAME, Context.MODE_PRIVATE) - - var emulationScreenSettings: EmulationScreenSettings - get() = EmulationScreenSettings(sharedPreferences) - set(value) = value.save(sharedPreferences) - - var guiSettings: GuiSettings - get() = GuiSettings(sharedPreferences) - set(value) = value.save(sharedPreferences) - - companion object { - private const val SETTINGS_NAME = "SETTINGS" - } -} diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/account/AccountsScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/account/AccountsScreen.kt index 45d1324f..3c7bcc75 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/account/AccountsScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/account/AccountsScreen.kt @@ -47,11 +47,11 @@ import androidx.compose.ui.text.withLink import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import info.cemu.cemu.R -import info.cemu.cemu.core.components.Header -import info.cemu.cemu.core.components.ScreenContent -import info.cemu.cemu.core.components.SingleSelection -import info.cemu.cemu.core.translation.getCurrentLocale -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.components.Header +import info.cemu.cemu.common.components.ScreenContent +import info.cemu.cemu.common.components.SingleSelection +import info.cemu.cemu.common.translation.getCurrentLocale +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.nativeinterface.NativeAccount import info.cemu.cemu.nativeinterface.NativeAccount.AccountGender import info.cemu.cemu.nativeinterface.NativeAccount.DEFAULT_MII_NAME @@ -59,7 +59,7 @@ import info.cemu.cemu.nativeinterface.NativeAccount.MAX_ACCOUNT_COUNT import info.cemu.cemu.nativeinterface.NativeAccount.MIN_ACCOUNT_COUNT import info.cemu.cemu.nativeinterface.NativeSettings import info.cemu.cemu.nativeinterface.NativeSettings.NetworkService -import info.cemu.cemu.utils.parseHexOrNull +import info.cemu.cemu.common.string.parseHexOrNull import java.text.SimpleDateFormat import java.util.Date diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/audio/AudoSettingsScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/audio/AudoSettingsScreen.kt index cd0745ad..99557a22 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/audio/AudoSettingsScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/audio/AudoSettingsScreen.kt @@ -1,13 +1,11 @@ package info.cemu.cemu.settings.audio import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import info.cemu.cemu.R -import info.cemu.cemu.core.components.ScreenContent -import info.cemu.cemu.core.components.SingleSelection -import info.cemu.cemu.core.components.Slider -import info.cemu.cemu.core.components.Toggle -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.components.ScreenContent +import info.cemu.cemu.common.components.SingleSelection +import info.cemu.cemu.common.components.Slider +import info.cemu.cemu.common.components.Toggle +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.nativeinterface.NativeSettings private const val AUDIO_LATENCY_STEPS = 22 diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/customdrivers/CustomDriversScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/customdrivers/CustomDriversScreen.kt index 40f566b8..e0783533 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/customdrivers/CustomDriversScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/customdrivers/CustomDriversScreen.kt @@ -36,15 +36,13 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel -import info.cemu.cemu.R -import info.cemu.cemu.core.components.ScreenContentLazy -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.components.ScreenContentLazy +import info.cemu.cemu.common.translation.tr import kotlinx.coroutines.launch @Composable diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/customdrivers/CustomDriversViewModel.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/customdrivers/CustomDriversViewModel.kt index 1862b9a5..95ce88ff 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/customdrivers/CustomDriversViewModel.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/customdrivers/CustomDriversViewModel.kt @@ -9,8 +9,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import info.cemu.cemu.nativeinterface.NativeActiveSettings import info.cemu.cemu.nativeinterface.NativeSettings -import info.cemu.cemu.utils.decodeJsonFromFile -import info.cemu.cemu.utils.unzip +import info.cemu.cemu.common.io.decodeJsonFromFile +import info.cemu.cemu.common.io.unzip import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/gamespath/GamePathsScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/gamespath/GamePathsScreen.kt index 82ae0fce..7a59f60a 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/gamespath/GamePathsScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/gamespath/GamePathsScreen.kt @@ -28,8 +28,8 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.viewmodel.compose.viewModel -import info.cemu.cemu.core.components.ScreenContentLazy -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.components.ScreenContentLazy +import info.cemu.cemu.common.translation.tr import kotlinx.coroutines.launch @Composable diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/general/GeneralSettingsScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/general/GeneralSettingsScreen.kt index 4f44e0fc..03c4dc4a 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/general/GeneralSettingsScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/general/GeneralSettingsScreen.kt @@ -4,18 +4,18 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.compose.dropUnlessResumed import androidx.lifecycle.viewmodel.compose.viewModel -import info.cemu.cemu.core.components.Button -import info.cemu.cemu.core.components.ScreenContent -import info.cemu.cemu.core.components.SingleSelection -import info.cemu.cemu.core.components.Toggle -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.components.Button +import info.cemu.cemu.common.components.ScreenContent +import info.cemu.cemu.common.components.SingleSelection +import info.cemu.cemu.common.components.Toggle +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.nativeinterface.NativeSettings @Composable fun GeneralSettingsScreen( navigateBack: () -> Unit, goToGamePathsSettings: () -> Unit, - generalSettingsViewModel: GeneralSettingsViewModel = viewModel(factory = GeneralSettingsViewModel.Factory), + generalSettingsViewModel: GeneralSettingsViewModel = viewModel(), ) { val context = LocalContext.current diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/general/GeneralSettingsViewModel.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/general/GeneralSettingsViewModel.kt index cab4614c..4356f3e9 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/general/GeneralSettingsViewModel.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/general/GeneralSettingsViewModel.kt @@ -2,47 +2,25 @@ package info.cemu.cemu.settings.general import android.content.Context import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewmodel.initializer -import androidx.lifecycle.viewmodel.viewModelFactory -import info.cemu.cemu.CemuApplication -import info.cemu.cemu.core.translation.getAvailableLanguages -import info.cemu.cemu.settings.EmulationScreenSettings -import info.cemu.cemu.settings.GuiSettings -import info.cemu.cemu.settings.SettingsManager +import info.cemu.cemu.common.settings.EmulationScreenSettings +import info.cemu.cemu.common.settings.GuiSettings +import info.cemu.cemu.common.settings.SettingsManager +import info.cemu.cemu.common.translation.getAvailableLanguages -class GeneralSettingsViewModel(private val settingsManager: SettingsManager) : ViewModel() { +class GeneralSettingsViewModel : ViewModel() { val languages: List val languageToDisplayNameMap: Map - val emulationScreenSettings: EmulationScreenSettings - val guiSettings: GuiSettings + val emulationScreenSettings: EmulationScreenSettings = SettingsManager.emulationScreenSettings + val guiSettings: GuiSettings = SettingsManager.guiSettings init { - this.emulationScreenSettings = settingsManager.emulationScreenSettings - this.guiSettings = settingsManager.guiSettings val availableLanguages = getAvailableLanguages() languages = availableLanguages.map { it.code } languageToDisplayNameMap = availableLanguages.associateBy({ it.code }, { it.displayName }) } fun setLanguage(language: String, context: Context) { - info.cemu.cemu.core.translation.setLanguage(language, context) + info.cemu.cemu.common.translation.setLanguage(language, context) guiSettings.language = language } - - override fun onCleared() { - super.onCleared() - settingsManager.emulationScreenSettings = emulationScreenSettings - settingsManager.guiSettings = guiSettings - } - - companion object { - val Factory: ViewModelProvider.Factory = viewModelFactory { - initializer { - GeneralSettingsViewModel( - SettingsManager(CemuApplication.Application) - ) - } - } - } } \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/graphics/GraphicsSettingsScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/graphics/GraphicsSettingsScreen.kt index a181f63a..2e7a4102 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/graphics/GraphicsSettingsScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/graphics/GraphicsSettingsScreen.kt @@ -2,11 +2,11 @@ package info.cemu.cemu.settings.graphics import androidx.compose.runtime.Composable import androidx.compose.runtime.saveable.rememberSaveable -import info.cemu.cemu.core.components.Button -import info.cemu.cemu.core.components.ScreenContent -import info.cemu.cemu.core.components.SingleSelection -import info.cemu.cemu.core.components.Toggle -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.components.Button +import info.cemu.cemu.common.components.ScreenContent +import info.cemu.cemu.common.components.SingleSelection +import info.cemu.cemu.common.components.Toggle +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.nativeinterface.NativeEmulation import info.cemu.cemu.nativeinterface.NativeSettings diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/input/ClassicControllerInputs.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/input/ClassicControllerInputs.kt index 65d28c11..5c425f44 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/input/ClassicControllerInputs.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/input/ClassicControllerInputs.kt @@ -1,7 +1,7 @@ package info.cemu.cemu.settings.input import androidx.compose.runtime.Composable -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.nativeinterface.NativeInput.ClassicButton @Composable diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/input/ControllerInputSettingsScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/input/ControllerInputSettingsScreen.kt index 81ae06e3..a3ed29fe 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/input/ControllerInputSettingsScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/input/ControllerInputSettingsScreen.kt @@ -34,10 +34,10 @@ import androidx.compose.ui.window.Dialog import androidx.lifecycle.viewmodel.MutableCreationExtras import androidx.lifecycle.viewmodel.compose.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder -import info.cemu.cemu.core.components.ScreenContent -import info.cemu.cemu.core.components.SingleSelection -import info.cemu.cemu.core.nativeenummapper.controllerTypeToString -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.components.ScreenContent +import info.cemu.cemu.common.components.SingleSelection +import info.cemu.cemu.common.nativeenummapper.controllerTypeToString +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.nativeinterface.NativeInput import kotlinx.coroutines.launch import androidx.compose.material3.Button as MaterialButton diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/input/ControllersViewModel.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/input/ControllersViewModel.kt index 5ffe34e0..31937bcc 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/input/ControllersViewModel.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/input/ControllersViewModel.kt @@ -7,14 +7,11 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewmodel.CreationExtras import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory -import info.cemu.cemu.input.InputManager -import info.cemu.cemu.input.getNativeButtonsForControllerType import info.cemu.cemu.nativeinterface.NativeInput import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow class ControllersViewModel(val controllerIndex: Int) : ViewModel() { - private val inputManager = InputManager() private var _controllerType = MutableStateFlow( if (NativeInput.isControllerDisabled(controllerIndex)) NativeInput.EmulatedControllerType.DISABLED else NativeInput.getControllerType(controllerIndex) @@ -42,12 +39,12 @@ class ControllersViewModel(val controllerIndex: Int) : ViewModel() { } fun mapKeyEvent(keyEvent: KeyEvent, buttonId: Int) { - inputManager.mapKeyEventToMappingId(controllerIndex, buttonId, keyEvent) + InputMapper.mapKeyEventToMappingId(controllerIndex, buttonId, keyEvent) _controls.value += getControllerMapping(buttonId) } fun refreshAvailableControllers(onNoControllersAvailable: () -> Unit) { - val newControllers = inputManager.getGameControllers() + val newControllers = InputMapper.getGameControllers() if (newControllers.isEmpty()) { _controllers.value = null onNoControllersAvailable() @@ -65,14 +62,14 @@ class ControllersViewModel(val controllerIndex: Int) : ViewModel() { _controls.value = emptyMap() oldControls.keys.forEach { NativeInput.clearControllerMapping(controllerIndex, it) } - inputManager.mapAllInputs(deviceId, controllerIndex) + InputMapper.mapAllInputs(deviceId, controllerIndex) - val buttons = getNativeButtonsForControllerType (controllerType.value) + val buttons = getNativeButtonsForControllerType(controllerType.value) buttons.forEach { _controls.value += getControllerMapping(it.nativeKeyCode) } } fun tryMapMotionEvent(motionEvent: MotionEvent, buttonId: Int): Boolean { - if (inputManager.tryMapMotionEventToMappingId(controllerIndex, buttonId, motionEvent)) { + if (InputMapper.tryMapMotionEventToMappingId(controllerIndex, buttonId, motionEvent)) { _controls.value += getControllerMapping(buttonId) return true } diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/input/InputItemGroup.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/input/InputItemGroup.kt index ad68cd79..8426ce9f 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/input/InputItemGroup.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/input/InputItemGroup.kt @@ -10,7 +10,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import info.cemu.cemu.core.components.Header +import info.cemu.cemu.common.components.Header @Composable fun InputItemsGroup( diff --git a/src/android/app/src/main/java/info/cemu/cemu/input/InputManager.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/input/InputMapper.kt similarity index 51% rename from src/android/app/src/main/java/info/cemu/cemu/input/InputManager.kt rename to src/android/app/src/main/java/info/cemu/cemu/settings/input/InputMapper.kt index b0b80ceb..0b167f59 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/input/InputManager.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/input/InputMapper.kt @@ -1,56 +1,52 @@ -package info.cemu.cemu.input +package info.cemu.cemu.settings.input import android.view.InputDevice import android.view.KeyEvent import android.view.MotionEvent +import info.cemu.cemu.common.inputdevice.isGameController +import info.cemu.cemu.common.motionevent.isMotionEventFromJoystickOrGamepad import info.cemu.cemu.nativeinterface.NativeInput -import info.cemu.cemu.nativeinterface.NativeInput.onNativeAxis -import info.cemu.cemu.nativeinterface.NativeInput.onNativeKey import info.cemu.cemu.nativeinterface.NativeInput.setControllerMapping - import kotlin.math.abs -class InputManager { - private class InvalidAxisException(axis: Int) : Exception("Invalid axis $axis") +private class InvalidAxisException(axis: Int) : Exception("Invalid axis $axis") - @Throws(InvalidAxisException::class) - private fun getNativeAxisKey(axis: Int, isPositive: Boolean): Int { - return if (isPositive) { - when (axis) { - MotionEvent.AXIS_X -> NativeInput.Axis.X_POS - MotionEvent.AXIS_Y -> NativeInput.Axis.Y_POS - MotionEvent.AXIS_RX, MotionEvent.AXIS_Z -> NativeInput.Axis.ROTATION_X_POS - MotionEvent.AXIS_RY, MotionEvent.AXIS_RZ -> NativeInput.Axis.ROTATION_Y_POS - MotionEvent.AXIS_LTRIGGER -> NativeInput.Axis.TRIGGER_X_POS - MotionEvent.AXIS_RTRIGGER -> NativeInput.Axis.TRIGGER_Y_POS - MotionEvent.AXIS_HAT_X -> NativeInput.Axis.DPAD_RIGHT - MotionEvent.AXIS_HAT_Y -> NativeInput.Axis.DPAD_DOWN - else -> throw InvalidAxisException(axis) - } - } else { - when (axis) { - MotionEvent.AXIS_X -> NativeInput.Axis.X_NEG - MotionEvent.AXIS_Y -> NativeInput.Axis.Y_NEG - MotionEvent.AXIS_RX, MotionEvent.AXIS_Z -> NativeInput.Axis.ROTATION_X_NEG - MotionEvent.AXIS_RY, MotionEvent.AXIS_RZ -> NativeInput.Axis.ROTATION_Y_NEG - MotionEvent.AXIS_HAT_X -> NativeInput.Axis.DPAD_LEFT - MotionEvent.AXIS_HAT_Y -> NativeInput.Axis.DPAD_UP - else -> throw InvalidAxisException(axis) - } +@Throws(InvalidAxisException::class) +private fun getNativeAxisKey(axis: Int, isPositive: Boolean): Int { + return if (isPositive) { + when (axis) { + MotionEvent.AXIS_X -> NativeInput.Axis.X_POS + MotionEvent.AXIS_Y -> NativeInput.Axis.Y_POS + MotionEvent.AXIS_RX, MotionEvent.AXIS_Z -> NativeInput.Axis.ROTATION_X_POS + MotionEvent.AXIS_RY, MotionEvent.AXIS_RZ -> NativeInput.Axis.ROTATION_Y_POS + MotionEvent.AXIS_LTRIGGER -> NativeInput.Axis.TRIGGER_X_POS + MotionEvent.AXIS_RTRIGGER -> NativeInput.Axis.TRIGGER_Y_POS + MotionEvent.AXIS_HAT_X -> NativeInput.Axis.DPAD_RIGHT + MotionEvent.AXIS_HAT_Y -> NativeInput.Axis.DPAD_DOWN + else -> throw InvalidAxisException(axis) + } + } else { + when (axis) { + MotionEvent.AXIS_X -> NativeInput.Axis.X_NEG + MotionEvent.AXIS_Y -> NativeInput.Axis.Y_NEG + MotionEvent.AXIS_RX, MotionEvent.AXIS_Z -> NativeInput.Axis.ROTATION_X_NEG + MotionEvent.AXIS_RY, MotionEvent.AXIS_RZ -> NativeInput.Axis.ROTATION_Y_NEG + MotionEvent.AXIS_HAT_X -> NativeInput.Axis.DPAD_LEFT + MotionEvent.AXIS_HAT_Y -> NativeInput.Axis.DPAD_UP + else -> throw InvalidAxisException(axis) } } +} - private fun isMotionEventFromJoystickOrGamepad(event: MotionEvent): Boolean { - return (event.source and InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK - || (event.source and InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD - } +private const val MIN_ABS_AXIS_VALUE = 0.33f +object InputMapper { fun tryMapMotionEventToMappingId( controllerIndex: Int, mappingId: Int, event: MotionEvent, ): Boolean { - if (!isMotionEventFromJoystickOrGamepad(event)) { + if (!event.isMotionEventFromJoystickOrGamepad()) { return false } val device = event.device @@ -83,51 +79,6 @@ class InputManager { return false } - private fun isSpecialKey(event: KeyEvent): Boolean { - val keyCode = event.keyCode - return keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_CAMERA || keyCode == KeyEvent.KEYCODE_ZOOM_IN || keyCode == KeyEvent.KEYCODE_ZOOM_OUT - } - - private fun isController(inputDevice: InputDevice): Boolean { - val sources = inputDevice.sources - return (sources and InputDevice.SOURCE_CLASS_JOYSTICK) != 0 || - ((sources and InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD) || - ((sources and InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) - } - - fun onKeyEvent(event: KeyEvent): Boolean { - if (isSpecialKey(event)) { - return false - } - if (event.deviceId < 0) { - return false - } - val device = event.device - if (!isController(device)) { - return false - } - onNativeKey( - device.descriptor, - device.name, - event.keyCode, - event.action == KeyEvent.ACTION_DOWN - ) - return true - } - - fun onMotionEvent(event: MotionEvent): Boolean { - if (!isMotionEventFromJoystickOrGamepad(event)) { - return false - } - val device = event.device - val actionPointerIndex = event.actionIndex - for (motionRange in device.motionRanges) { - val axisValue = event.getAxisValue(motionRange.axis, actionPointerIndex) - val axis = motionRange.axis - onNativeAxis(device.descriptor, device.name, axis, axisValue) - } - return true - } fun mapKeyEventToMappingId(controllerIndex: Int, mappingId: Int, event: KeyEvent) { val device = event.device @@ -173,14 +124,6 @@ class InputManager { ) } - private fun InputDevice.isGameController(): Boolean { - return !isVirtual && ( - sources and InputDevice.SOURCE_GAMEPAD == InputDevice.SOURCE_GAMEPAD - || sources and InputDevice.SOURCE_JOYSTICK == InputDevice.SOURCE_JOYSTICK - || sources and InputDevice.SOURCE_DPAD == InputDevice.SOURCE_DPAD - ) - } - fun getGameControllers(): List> { val gameControllers = mutableListOf>() @@ -396,8 +339,203 @@ class InputManager { else -> arrayOf() } } +} - companion object { - private const val MIN_ABS_AXIS_VALUE = 0.33f + +private sealed interface InputMapping + +private enum class ButtonInputMapping(val keyCode: Int) : InputMapping { + BUTTON_1(KeyEvent.KEYCODE_BUTTON_1), + BUTTON_2(KeyEvent.KEYCODE_BUTTON_2), + BUTTON_3(KeyEvent.KEYCODE_BUTTON_3), + BUTTON_4(KeyEvent.KEYCODE_BUTTON_4), + BUTTON_5(KeyEvent.KEYCODE_BUTTON_5), + BUTTON_6(KeyEvent.KEYCODE_BUTTON_6), + BUTTON_7(KeyEvent.KEYCODE_BUTTON_7), + BUTTON_8(KeyEvent.KEYCODE_BUTTON_8), + BUTTON_9(KeyEvent.KEYCODE_BUTTON_9), + BUTTON_10(KeyEvent.KEYCODE_BUTTON_10), + BUTTON_11(KeyEvent.KEYCODE_BUTTON_11), + BUTTON_12(KeyEvent.KEYCODE_BUTTON_12), + BUTTON_13(KeyEvent.KEYCODE_BUTTON_13), + BUTTON_14(KeyEvent.KEYCODE_BUTTON_14), + BUTTON_15(KeyEvent.KEYCODE_BUTTON_15), + BUTTON_16(KeyEvent.KEYCODE_BUTTON_16), + BUTTON_A(KeyEvent.KEYCODE_BUTTON_A), + BUTTON_B(KeyEvent.KEYCODE_BUTTON_B), + BUTTON_C(KeyEvent.KEYCODE_BUTTON_C), + BUTTON_L1(KeyEvent.KEYCODE_BUTTON_L1), + BUTTON_L2(KeyEvent.KEYCODE_BUTTON_L2), + BUTTON_MODE(KeyEvent.KEYCODE_BUTTON_MODE), + BUTTON_R1(KeyEvent.KEYCODE_BUTTON_R1), + BUTTON_R2(KeyEvent.KEYCODE_BUTTON_R2), + BUTTON_SELECT(KeyEvent.KEYCODE_BUTTON_SELECT), + BUTTON_START(KeyEvent.KEYCODE_BUTTON_START), + BUTTON_THUMBL(KeyEvent.KEYCODE_BUTTON_THUMBL), + BUTTON_THUMBR(KeyEvent.KEYCODE_BUTTON_THUMBR), + BUTTON_X(KeyEvent.KEYCODE_BUTTON_X), + BUTTON_Y(KeyEvent.KEYCODE_BUTTON_Y), + BUTTON_Z(KeyEvent.KEYCODE_BUTTON_Z), + DPAD_DOWN(KeyEvent.KEYCODE_DPAD_DOWN), + DPAD_LEFT(KeyEvent.KEYCODE_DPAD_LEFT), + DPAD_RIGHT(KeyEvent.KEYCODE_DPAD_RIGHT), + DPAD_UP(KeyEvent.KEYCODE_DPAD_UP), +} + +private val FALLBACK_BUTTONS = arrayOf( + ButtonInputMapping.BUTTON_1, + ButtonInputMapping.BUTTON_2, + ButtonInputMapping.BUTTON_3, + ButtonInputMapping.BUTTON_4, + ButtonInputMapping.BUTTON_5, + ButtonInputMapping.BUTTON_6, + ButtonInputMapping.BUTTON_7, + ButtonInputMapping.BUTTON_8, + ButtonInputMapping.BUTTON_9, + ButtonInputMapping.BUTTON_10, + ButtonInputMapping.BUTTON_11, + ButtonInputMapping.BUTTON_12, + ButtonInputMapping.BUTTON_13, + ButtonInputMapping.BUTTON_14, + ButtonInputMapping.BUTTON_15, + ButtonInputMapping.BUTTON_16, +) + +private enum class AxisInputMapping(val axisCode: Int, val isPositive: Boolean) : InputMapping { + HAT_X_POS(MotionEvent.AXIS_HAT_X, true), + HAT_X_NEG(MotionEvent.AXIS_HAT_X, false), + HAT_Y_POS(MotionEvent.AXIS_HAT_Y, true), + HAT_Y_NEG(MotionEvent.AXIS_HAT_Y, false), + RX_POS(MotionEvent.AXIS_RX, true), + RX_NEG(MotionEvent.AXIS_RX, false), + RY_POS(MotionEvent.AXIS_RY, true), + RY_NEG(MotionEvent.AXIS_RY, false), + RZ_POS(MotionEvent.AXIS_RZ, true), + RZ_NEG(MotionEvent.AXIS_RZ, false), + LTRIGGER(MotionEvent.AXIS_LTRIGGER, true), + RTRIGGER(MotionEvent.AXIS_RTRIGGER, true), + X_POS(MotionEvent.AXIS_X, true), + X_NEG(MotionEvent.AXIS_X, false), + Y_POS(MotionEvent.AXIS_Y, true), + Y_NEG(MotionEvent.AXIS_Y, false), + Z_POS(MotionEvent.AXIS_Z, true), + Z_NEG(MotionEvent.AXIS_Z, false), +} + +sealed interface NativeInputButton { + val nativeKeyCode: Int +} + +enum class VPadButtons(override val nativeKeyCode: Int) : NativeInputButton { + A(NativeInput.VPADButton.A), + B(NativeInput.VPADButton.B), + X(NativeInput.VPADButton.X), + Y(NativeInput.VPADButton.Y), + L(NativeInput.VPADButton.L), + R(NativeInput.VPADButton.R), + ZL(NativeInput.VPADButton.ZL), + ZR(NativeInput.VPADButton.ZR), + PLUS(NativeInput.VPADButton.PLUS), + MINUS(NativeInput.VPADButton.MINUS), + UP(NativeInput.VPADButton.UP), + DOWN(NativeInput.VPADButton.DOWN), + LEFT(NativeInput.VPADButton.LEFT), + RIGHT(NativeInput.VPADButton.RIGHT), + STICKL(NativeInput.VPADButton.STICKL), + STICKR(NativeInput.VPADButton.STICKR), + STICKL_UP(NativeInput.VPADButton.STICKL_UP), + STICKL_DOWN(NativeInput.VPADButton.STICKL_DOWN), + STICKL_LEFT(NativeInput.VPADButton.STICKL_LEFT), + STICKL_RIGHT(NativeInput.VPADButton.STICKL_RIGHT), + STICKR_UP(NativeInput.VPADButton.STICKR_UP), + STICKR_DOWN(NativeInput.VPADButton.STICKR_DOWN), + STICKR_LEFT(NativeInput.VPADButton.STICKR_LEFT), + STICKR_RIGHT(NativeInput.VPADButton.STICKR_RIGHT), + MIC(NativeInput.VPADButton.MIC), + SCREEN(NativeInput.VPADButton.SCREEN), + HOME(NativeInput.VPADButton.HOME), +} + +enum class ProControllerButtons(override val nativeKeyCode: Int) : NativeInputButton { + A(NativeInput.ProButton.A), + B(NativeInput.ProButton.B), + X(NativeInput.ProButton.X), + Y(NativeInput.ProButton.Y), + L(NativeInput.ProButton.L), + R(NativeInput.ProButton.R), + ZL(NativeInput.ProButton.ZL), + ZR(NativeInput.ProButton.ZR), + PLUS(NativeInput.ProButton.PLUS), + MINUS(NativeInput.ProButton.MINUS), + HOME(NativeInput.ProButton.HOME), + UP(NativeInput.ProButton.UP), + DOWN(NativeInput.ProButton.DOWN), + LEFT(NativeInput.ProButton.LEFT), + RIGHT(NativeInput.ProButton.RIGHT), + STICKL(NativeInput.ProButton.STICKL), + STICKR(NativeInput.ProButton.STICKR), + STICKL_UP(NativeInput.ProButton.STICKL_UP), + STICKL_DOWN(NativeInput.ProButton.STICKL_DOWN), + STICKL_LEFT(NativeInput.ProButton.STICKL_LEFT), + STICKL_RIGHT(NativeInput.ProButton.STICKL_RIGHT), + STICKR_UP(NativeInput.ProButton.STICKR_UP), + STICKR_DOWN(NativeInput.ProButton.STICKR_DOWN), + STICKR_LEFT(NativeInput.ProButton.STICKR_LEFT), + STICKR_RIGHT(NativeInput.ProButton.STICKR_RIGHT), +} + +enum class ClassicControllerButtons(override val nativeKeyCode: Int) : NativeInputButton { + A(NativeInput.ClassicButton.A), + B(NativeInput.ClassicButton.B), + X(NativeInput.ClassicButton.X), + Y(NativeInput.ClassicButton.Y), + L(NativeInput.ClassicButton.L), + R(NativeInput.ClassicButton.R), + ZL(NativeInput.ClassicButton.ZL), + ZR(NativeInput.ClassicButton.ZR), + PLUS(NativeInput.ClassicButton.PLUS), + MINUS(NativeInput.ClassicButton.MINUS), + HOME(NativeInput.ClassicButton.HOME), + UP(NativeInput.ClassicButton.UP), + DOWN(NativeInput.ClassicButton.DOWN), + LEFT(NativeInput.ClassicButton.LEFT), + RIGHT(NativeInput.ClassicButton.RIGHT), + STICKL_UP(NativeInput.ClassicButton.STICKL_UP), + STICKL_DOWN(NativeInput.ClassicButton.STICKL_DOWN), + STICKL_LEFT(NativeInput.ClassicButton.STICKL_LEFT), + STICKL_RIGHT(NativeInput.ClassicButton.STICKL_RIGHT), + STICKR_UP(NativeInput.ClassicButton.STICKR_UP), + STICKR_DOWN(NativeInput.ClassicButton.STICKR_DOWN), + STICKR_LEFT(NativeInput.ClassicButton.STICKR_LEFT), + STICKR_RIGHT(NativeInput.ClassicButton.STICKR_RIGHT), +} + +enum class WiimoteButtons(override val nativeKeyCode: Int) : NativeInputButton { + A(NativeInput.WiimoteButton.A), + B(NativeInput.WiimoteButton.B), + ONE(NativeInput.WiimoteButton.ONE), + TWO(NativeInput.WiimoteButton.TWO), + NUNCHUCK_Z(NativeInput.WiimoteButton.NUNCHUCK_Z), + NUNCHUCK_C(NativeInput.WiimoteButton.NUNCHUCK_C), + PLUS(NativeInput.WiimoteButton.PLUS), + MINUS(NativeInput.WiimoteButton.MINUS), + UP(NativeInput.WiimoteButton.UP), + DOWN(NativeInput.WiimoteButton.DOWN), + LEFT(NativeInput.WiimoteButton.LEFT), + RIGHT(NativeInput.WiimoteButton.RIGHT), + NUNCHUCK_UP(NativeInput.WiimoteButton.NUNCHUCK_UP), + NUNCHUCK_DOWN(NativeInput.WiimoteButton.NUNCHUCK_DOWN), + NUNCHUCK_LEFT(NativeInput.WiimoteButton.NUNCHUCK_LEFT), + NUNCHUCK_RIGHT(NativeInput.WiimoteButton.NUNCHUCK_RIGHT), + HOME(NativeInput.WiimoteButton.HOME), +} + +fun getNativeButtonsForControllerType(controllerType: Int): Array { + return when (controllerType) { + NativeInput.EmulatedControllerType.VPAD -> VPadButtons.entries.toTypedArray() + NativeInput.EmulatedControllerType.PRO -> ProControllerButtons.entries.toTypedArray() + NativeInput.EmulatedControllerType.CLASSIC -> ClassicControllerButtons.entries.toTypedArray() + NativeInput.EmulatedControllerType.WIIMOTE -> WiimoteButtons.entries.toTypedArray() + else -> arrayOf() } } diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/input/InputSettingsScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/input/InputSettingsScreen.kt index a5850a3b..d11dceca 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/input/InputSettingsScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/input/InputSettingsScreen.kt @@ -3,10 +3,10 @@ package info.cemu.cemu.settings.input import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.lifecycle.compose.dropUnlessResumed -import info.cemu.cemu.core.components.Button -import info.cemu.cemu.core.components.ScreenContent -import info.cemu.cemu.core.nativeenummapper.controllerTypeToString -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.components.Button +import info.cemu.cemu.common.components.ScreenContent +import info.cemu.cemu.common.nativeenummapper.controllerTypeToString +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.nativeinterface.NativeInput data class InputSettingsScreenActions( diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/input/ProControllerInputs.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/input/ProControllerInputs.kt index 7045c4a7..ee552906 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/input/ProControllerInputs.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/input/ProControllerInputs.kt @@ -1,7 +1,7 @@ package info.cemu.cemu.settings.input import androidx.compose.runtime.Composable -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.nativeinterface.NativeInput.ProButton @Composable diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/input/VPADInputs.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/input/VPADInputs.kt index c924e60a..aadf3ed9 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/input/VPADInputs.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/input/VPADInputs.kt @@ -1,8 +1,8 @@ package info.cemu.cemu.settings.input import androidx.compose.runtime.Composable -import info.cemu.cemu.core.components.Toggle -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.components.Toggle +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.nativeinterface.NativeInput import info.cemu.cemu.nativeinterface.NativeInput.VPADButton diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/input/WiimoteControllerInputs.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/input/WiimoteControllerInputs.kt index 017bda4c..25068971 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/input/WiimoteControllerInputs.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/input/WiimoteControllerInputs.kt @@ -1,7 +1,7 @@ package info.cemu.cemu.settings.input import androidx.compose.runtime.Composable -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.nativeinterface.NativeInput.WiimoteButton @Composable diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/inputoverlay/InputOverlaySettingsScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/inputoverlay/InputOverlaySettingsScreen.kt index be2ef960..9dcdf21a 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/inputoverlay/InputOverlaySettingsScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/inputoverlay/InputOverlaySettingsScreen.kt @@ -2,18 +2,18 @@ package info.cemu.cemu.settings.inputoverlay import androidx.compose.runtime.Composable import androidx.lifecycle.viewmodel.compose.viewModel -import info.cemu.cemu.core.components.ScreenContent -import info.cemu.cemu.core.components.SingleSelection -import info.cemu.cemu.core.components.Slider -import info.cemu.cemu.core.components.Toggle -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.components.ScreenContent +import info.cemu.cemu.common.components.SingleSelection +import info.cemu.cemu.common.components.Slider +import info.cemu.cemu.common.components.Toggle +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.nativeinterface.NativeInput private val ControllerIndexChoices = (0.. Unit, ) { val overlaySettings = inputOverlaySettingsViewModel.overlaySettings diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/inputoverlay/InputOverlaySettingsViewModel.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/inputoverlay/InputOverlaySettingsViewModel.kt index ab296f3f..6c72c57c 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/inputoverlay/InputOverlaySettingsViewModel.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/inputoverlay/InputOverlaySettingsViewModel.kt @@ -1,29 +1,8 @@ package info.cemu.cemu.settings.inputoverlay import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewmodel.initializer -import androidx.lifecycle.viewmodel.viewModelFactory -import info.cemu.cemu.CemuApplication -import info.cemu.cemu.inputoverlay.InputOverlaySettingsManager +import info.cemu.cemu.common.settings.SettingsManager -class InputOverlaySettingsViewModel( - private val inputOverlaySettingsManager: InputOverlaySettingsManager -) : ViewModel() { - val overlaySettings = inputOverlaySettingsManager.overlaySettings - - override fun onCleared() { - super.onCleared() - inputOverlaySettingsManager.overlaySettings = overlaySettings - } - - companion object { - val Factory: ViewModelProvider.Factory = viewModelFactory { - initializer { - InputOverlaySettingsViewModel( - InputOverlaySettingsManager(CemuApplication.Application) - ) - } - } - } +class InputOverlaySettingsViewModel : ViewModel() { + val overlaySettings = SettingsManager.inputOverlaySettings } \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/cemu/settings/overlay/OverlaySettingsScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/settings/overlay/OverlaySettingsScreen.kt index 1f290f82..e86d441d 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/settings/overlay/OverlaySettingsScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/settings/overlay/OverlaySettingsScreen.kt @@ -5,12 +5,12 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import info.cemu.cemu.core.components.Header -import info.cemu.cemu.core.components.ScreenContent -import info.cemu.cemu.core.components.SingleSelection -import info.cemu.cemu.core.components.Slider -import info.cemu.cemu.core.components.Toggle -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.components.Header +import info.cemu.cemu.common.components.ScreenContent +import info.cemu.cemu.common.components.SingleSelection +import info.cemu.cemu.common.components.Slider +import info.cemu.cemu.common.components.Toggle +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.nativeinterface.NativeSettings private val OverlayPositionChoices = listOf( diff --git a/src/android/app/src/main/java/info/cemu/cemu/titlemanager/TitleInstaller.kt b/src/android/app/src/main/java/info/cemu/cemu/titlemanager/TitleInstaller.kt index 877d34f2..f56d3e50 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/titlemanager/TitleInstaller.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/titlemanager/TitleInstaller.kt @@ -1,15 +1,2 @@ package info.cemu.cemu.titlemanager -import android.content.ContentResolver -import android.content.Context -import android.net.Uri -import android.provider.DocumentsContract -import androidx.documentfile.provider.DocumentFile -import info.cemu.cemu.utils.copyInputStreamToFile -import info.cemu.cemu.utils.urlDecode -import java.nio.file.Path -import java.util.LinkedList -import kotlin.io.path.Path -import kotlin.io.path.createDirectories -import kotlin.math.max - diff --git a/src/android/app/src/main/java/info/cemu/cemu/titlemanager/TitleListViewModel.kt b/src/android/app/src/main/java/info/cemu/cemu/titlemanager/TitleListViewModel.kt index 8b196dc4..a996ade6 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/titlemanager/TitleListViewModel.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/titlemanager/TitleListViewModel.kt @@ -12,9 +12,9 @@ import info.cemu.cemu.nativeinterface.NativeActiveSettings import info.cemu.cemu.nativeinterface.NativeGameTitles import info.cemu.cemu.nativeinterface.NativeGameTitles.TitleIdToTitlesCallback.Title import info.cemu.cemu.nativeinterface.fromNativePath -import info.cemu.cemu.utils.copyInputStreamToFile -import info.cemu.cemu.utils.isContentUri -import info.cemu.cemu.utils.urlDecode +import info.cemu.cemu.common.io.copyInputStreamToFile +import info.cemu.cemu.common.string.isContentUri +import info.cemu.cemu.common.string.urlDecode import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.cancelAndJoin diff --git a/src/android/app/src/main/java/info/cemu/cemu/titlemanager/TitleManagerScreen.kt b/src/android/app/src/main/java/info/cemu/cemu/titlemanager/TitleManagerScreen.kt index 4e809d98..4c4a2421 100644 --- a/src/android/app/src/main/java/info/cemu/cemu/titlemanager/TitleManagerScreen.kt +++ b/src/android/app/src/main/java/info/cemu/cemu/titlemanager/TitleManagerScreen.kt @@ -65,10 +65,10 @@ import androidx.compose.ui.unit.sp import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.viewmodel.compose.viewModel import info.cemu.cemu.R -import info.cemu.cemu.core.components.ScreenContentLazy -import info.cemu.cemu.core.format.formatBytes -import info.cemu.cemu.core.nativeenummapper.regionToString -import info.cemu.cemu.core.translation.tr +import info.cemu.cemu.common.components.ScreenContentLazy +import info.cemu.cemu.common.format.formatBytes +import info.cemu.cemu.common.nativeenummapper.regionToString +import info.cemu.cemu.common.translation.tr import info.cemu.cemu.nativeinterface.NativeGameTitles import kotlinx.coroutines.launch import java.text.MessageFormat @@ -360,7 +360,7 @@ private fun TitleInstallProgressDialog( LinearProgressIndicator(modifier = progressModifiers) Text( modifier = Modifier.padding(8.dp), - text = tr("Parsing title content…") + text = tr("Parsing title content...") ) } else { val (bytesWritten, maxBytes) = progress diff --git a/src/android/app/src/main/java/info/cemu/cemu/utils/Drawables.kt b/src/android/app/src/main/java/info/cemu/cemu/utils/Drawables.kt deleted file mode 100644 index 3358da70..00000000 --- a/src/android/app/src/main/java/info/cemu/cemu/utils/Drawables.kt +++ /dev/null @@ -1,25 +0,0 @@ -package info.cemu.cemu.utils - -import android.content.res.Resources -import android.graphics.ColorMatrix -import android.graphics.ColorMatrixColorFilter -import android.graphics.drawable.Drawable - -private val INVERTED_COLOR_MATRIX = ColorMatrix( - floatArrayOf( - -1f, 0f, 0f, 0f, 255f, - 0f, -1f, 0f, 0f, 255f, - 0f, 0f, -1f, 0f, 255f, - 0f, 0f, 0f, 1f, 0f - ) -) - -fun Drawable.getInvertedDrawable(resources: Resources?): Drawable { - val newDrawable = constantState!!.newDrawable(resources) - return newDrawable.applyInvertedColorTransform() -} - -fun Drawable.applyInvertedColorTransform(): Drawable { - mutate().colorFilter = ColorMatrixColorFilter(INVERTED_COLOR_MATRIX) - return this -} diff --git a/src/android/app/src/main/res/layout/activity_emulation.xml b/src/android/app/src/main/res/layout/activity_emulation.xml index c0aa9473..7a4dd01a 100644 --- a/src/android/app/src/main/res/layout/activity_emulation.xml +++ b/src/android/app/src/main/res/layout/activity_emulation.xml @@ -1,5 +1,5 @@ - - @@ -74,4 +74,4 @@ android:id="@+id/side_menu" layout="@layout/layout_side_menu_emulation" /> - + diff --git a/src/android/app/src/main/res/xml/input_overlay_default_configs.xml b/src/android/app/src/main/res/xml/input_overlay_default_configs.xml index d01370b5..a7f56316 100644 --- a/src/android/app/src/main/res/xml/input_overlay_default_configs.xml +++ b/src/android/app/src/main/res/xml/input_overlay_default_configs.xml @@ -153,4 +153,12 @@ 8 8 + + BUTTON_BLOW_MIC + true + true + 8 + 8 + 40 + \ No newline at end of file diff --git a/src/android/app/src/test/java/info/cemu/cemu/PlaceholderUnitTest.kt b/src/android/app/src/test/java/info/cemu/cemu/PlaceholderUnitTest.kt deleted file mode 100644 index 70b69334..00000000 --- a/src/android/app/src/test/java/info/cemu/cemu/PlaceholderUnitTest.kt +++ /dev/null @@ -1,12 +0,0 @@ -package info.cemu.cemu - -import org.junit.Test - -import org.junit.Assert.* - -class PlaceholderUnitTest { - @Test - fun test() { - assertTrue(true) - } -} \ No newline at end of file diff --git a/src/android/app/src/test/java/info/cemu/cemu/tests/ArchitectureTests.kt b/src/android/app/src/test/java/info/cemu/cemu/tests/ArchitectureTests.kt new file mode 100644 index 00000000..1f26fbf8 --- /dev/null +++ b/src/android/app/src/test/java/info/cemu/cemu/tests/ArchitectureTests.kt @@ -0,0 +1,75 @@ +package info.cemu.cemu.tests + +import com.tngtech.archunit.base.DescribedPredicate +import com.tngtech.archunit.base.DescribedPredicate.alwaysTrue +import com.tngtech.archunit.base.DescribedPredicate.not +import com.tngtech.archunit.base.DescribedPredicate.or +import com.tngtech.archunit.core.domain.JavaClass +import com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage +import com.tngtech.archunit.core.domain.JavaClasses +import com.tngtech.archunit.core.importer.ImportOption.DoNotIncludeTests +import com.tngtech.archunit.junit.AnalyzeClasses +import com.tngtech.archunit.junit.ArchTest +import com.tngtech.archunit.junit.ArchUnitRunner +import com.tngtech.archunit.junit.CacheMode +import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses +import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition +import org.junit.runner.RunWith + +@RunWith(ArchUnitRunner::class) +@AnalyzeClasses( + packages = ["info.cemu.cemu"], + importOptions = [DoNotIncludeTests::class], + cacheMode = CacheMode.PER_CLASS +) +class ArchitectureTests { + private val isFromEntryPointCode = + object : DescribedPredicate("is from entry point code") { + private val entryPointFiles = listOf( + "MainActivity.kt", + "CemuApplication.kt", + ) + + override fun test(input: JavaClass?): Boolean { + return entryPointFiles.any { input?.sourceCodeLocation?.sourceFileName == it } + } + } + + private val isFromGeneratedCode = + object : DescribedPredicate("is from generated code") { + override fun test(input: JavaClass?): Boolean { + if (isFromEntryPointCode.test(input)) { + return false + } + + return input?.packageName == "info.cemu.cemu" + || input?.packageName?.contains("databinding") ?: false + } + } + + private val isFromTests = resideInAPackage("info.cemu.cemu.tests..") + + private fun JavaClasses.thatAreFromMainSources() = that(not(or(isFromGeneratedCode, isFromTests))) + + @ArchTest + fun `feature packages should not depend on each other`(javaClasses: JavaClasses) { + SlicesRuleDefinition.slices().matching("info.cemu.cemu.(*)..") + .should().notDependOnEachOther() + .ignoreDependency( + alwaysTrue(), + or( + resideInAPackage("info.cemu.cemu.common.."), + resideInAPackage("info.cemu.cemu.nativeinterface.."), + resideInAPackage("info.cemu.cemu.databinding..") + ) + ) + .check(javaClasses.thatAreFromMainSources()) + } + + @ArchTest + fun `no packages should depend on entry point code`(javaClasses: JavaClasses) { + noClasses().that(not(isFromEntryPointCode)) + .should().dependOnClassesThat(isFromEntryPointCode) + .check(javaClasses.thatAreFromMainSources()) + } +} diff --git a/src/android/gradle/libs.versions.toml b/src/android/gradle/libs.versions.toml index 00cbeeb3..43c13c27 100644 --- a/src/android/gradle/libs.versions.toml +++ b/src/android/gradle/libs.versions.toml @@ -2,10 +2,11 @@ agp = "8.11.1" aboutlibraries = "13.0.0-a01" appcompat = "1.7.1" -kotlin = "2.2.0" -androidx-core-ktx = "1.16.0" +archunit-junit4 = "1.4.1" +kotlin = "2.2.10" +androidx-core-ktx = "1.17.0" androidx-activity-compose = "1.10.1" -androidx-compose-bom = "2025.07.00" +androidx-compose-bom = "2025.08.00" androidx-compose-material3 = "1.3.2" google-android-material = "1.12.0" androidx-navigation-compose = "2.9.3" @@ -18,6 +19,7 @@ kotlinx-gettext = "0.7.0" [libraries] aboutlibraries-compose-m3 = { module = "com.mikepenz:aboutlibraries-compose-m3", version.ref = "aboutlibraries" } +archunit-junit4 = { module = "com.tngtech.archunit:archunit-junit4", version.ref = "archunit-junit4" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-junit" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" }