From de067f96a784db5251ced4a15a1da8cf0b8e5e71 Mon Sep 17 00:00:00 2001 From: Duron27 Date: Sat, 26 Oct 2024 20:37:20 -0400 Subject: [PATCH] latest push --- app/build.gradle.kts | 1 + app/src/main/AndroidManifest.xml | 4 +- .../main/java/org/openmw/EngineActivity.kt | 109 ++--- app/src/main/java/org/openmw/HomeScreen.kt | 1 + app/src/main/java/org/openmw/MainActivity.kt | 113 +---- app/src/main/java/org/openmw/SettingScreen.kt | 5 +- .../org/openmw/navigation/TopBottomBar.kt | 100 +++- .../org/openmw/ui/controls/DynamicButtons.kt | 38 +- .../org/openmw/ui/controls/StateManager.kt | 48 +- .../main/java/org/openmw/ui/controls/mouse.kt | 2 +- .../java/org/openmw/ui/overlay/Overlay.kt | 462 +++++++++--------- .../java/org/openmw/utils/ManageAssets.kt | 16 + app/src/main/java/org/openmw/utils/UITools.kt | 138 ++++++ app/src/main/res/drawable/pointer_icon.xml | 9 + app/src/main/res/layout/engine_activity.xml | 48 +- gradle/libs.versions.toml | 11 +- 16 files changed, 613 insertions(+), 492 deletions(-) create mode 100644 app/src/main/res/drawable/pointer_icon.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3597d156..70c8aca6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -94,6 +94,7 @@ dependencies { implementation(libs.androidx.runner) implementation(libs.androidx.espresso.core) implementation(libs.androidx.profileinstaller) + implementation(libs.core.ktx) implementation(libs.reorderable) implementation(libs.relinker) implementation(libs.androidx.window) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 342e17fb..1c60a1f6 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -57,8 +57,10 @@ + android:theme="@style/Theme.AppCompat.DayNight.NoActionBar" + tools:ignore="DiscouragedApi,LockedOrientationActivity"> diff --git a/app/src/main/java/org/openmw/EngineActivity.kt b/app/src/main/java/org/openmw/EngineActivity.kt index ab840255..0da30b9e 100644 --- a/app/src/main/java/org/openmw/EngineActivity.kt +++ b/app/src/main/java/org/openmw/EngineActivity.kt @@ -1,6 +1,7 @@ package org.openmw import android.annotation.SuppressLint +import android.os.Build import android.os.Bundle import android.os.Process import android.system.ErrnoException @@ -8,39 +9,27 @@ import android.system.Os import android.util.Log import android.view.KeyEvent import android.view.View +import android.view.ViewGroup import android.widget.FrameLayout import androidx.activity.enableEdgeToEdge -import androidx.compose.foundation.border -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Text +import androidx.annotation.RequiresApi import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat import org.libsdl.app.SDLActivity import org.openmw.ui.controls.ButtonState import org.openmw.ui.controls.CustomCursorView -import org.openmw.ui.controls.DynamicButtonManager import org.openmw.ui.controls.ResizableDraggableButton import org.openmw.ui.controls.ResizableDraggableThumbstick import org.openmw.ui.controls.UIStateManager import org.openmw.ui.controls.loadButtonState import org.openmw.ui.controls.saveButtonState import org.openmw.ui.overlay.OverlayUI +import org.openmw.utils.enableLogcat class EngineActivity : SDLActivity() { private lateinit var customCursorView: CustomCursorView @@ -65,6 +54,8 @@ class EngineActivity : SDLActivity() { return OPENMW_MAIN_LIB } + @OptIn(ExperimentalComposeUiApi::class) + @RequiresApi(Build.VERSION_CODES.O) @SuppressLint("ClickableViewAccessibility") override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() @@ -73,21 +64,24 @@ class EngineActivity : SDLActivity() { sdlView = getContentView() customCursorView = findViewById(R.id.customCursorView) - customCursorView.apply { - sdlView = this@EngineActivity.sdlView - } - // Ensure the correct initial state of the cursor setupInitialCursorState() - // Load saved buttons + // Load UI saved buttons, 99 is the Thumbstick. Without these 3 lines the button loader will read 99 + // from the UI.cfg file and create a duplicate as a button val allButtons = loadButtonState(this) val thumbstick = allButtons.find { it.id == 99 } createdButtons.addAll(allButtons.filter { it.id != 99 }) // Add SDL view programmatically val sdlContainer = findViewById(R.id.sdl_container) + sdlContainer.addView(sdlView) + + // Remove sdlView from its parent if necessary + (sdlView.parent as? ViewGroup)?.removeView(sdlView) sdlContainer.addView(sdlView) // Add SDL view to the sdl_container + (customCursorView.parent as? ViewGroup)?.removeView(customCursorView) + sdlContainer.addView(customCursorView) WindowCompat.setDecorFitsSystemWindows(window, false) // Hide the system bars @@ -96,26 +90,33 @@ class EngineActivity : SDLActivity() { controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE } - // Create an instance of LogCat and call enableLogcat - //val logCat = LogCat(this) - //logCat.enableLogcat() getPathToJni(filesDir.parent!!, Constants.USER_FILE_STORAGE) - // Setup Compose overlay for buttons - val composeViewMenu = findViewById(R.id.compose_overlayMenu) - composeViewMenu.setContent { - OverlayUI(engineActivityContext = this) + if (UIStateManager.isLogcatEnabled) { + enableLogcat() + } + // Setup Compose overlay for buttons + val composeViewUI = findViewById(R.id.compose_overlayUI) + (composeViewUI.parent as? ViewGroup)?.removeView(composeViewUI) + sdlContainer.addView(composeViewUI) + composeViewUI.setContent { + OverlayUI( + engineActivityContext = this, + editMode = editMode, + createdButtons = createdButtons, + customCursorView = customCursorView + ) createdButtons.forEach { button -> ResizableDraggableButton( - context = this@EngineActivity, + context = this, id = button.id, keyCode = button.keyCode, editMode = editMode.value, - onDelete = { deleteButton(button.id) } + onDelete = { deleteButton(button.id) }, + customCursorView = customCursorView ) } - thumbstick?.let { ResizableDraggableThumbstick( context = this, @@ -134,47 +135,8 @@ class EngineActivity : SDLActivity() { } ) } - - DynamicButtonManager( - context = this@EngineActivity, - onNewButtonAdded = { newButtonState -> - createdButtons.add(newButtonState) - }, - editMode = editMode, - createdButtons = createdButtons // Pass created buttons to DynamicButtonManager - ) - //HiddenMenu() // RadialMenu.kt } - - // Setup Compose overlay for buttons - val composeViewButtons = findViewById(R.id.compose_overlayButtons) - composeViewButtons.setContent { - Column( - verticalArrangement = Arrangement.SpaceBetween, - horizontalAlignment = Alignment.End, - modifier = Modifier.fillMaxHeight() - ) { - // Toggle Custom Cursor Visibility Button - Button( - onClick = { toggleCustomCursor() }, - colors = ButtonDefaults.buttonColors( - containerColor = Color.Transparent - ), - modifier = Modifier - .size(50.dp) - .align(Alignment.End) - .border(3.dp, Color.Black, shape = CircleShape) // Add border - ) { - Text( - text = "M", - color = Color.White, - fontSize = 15.sp, - fontWeight = FontWeight.Bold - ) - } - } - } } private fun setupInitialCursorState() { @@ -190,13 +152,6 @@ class EngineActivity : SDLActivity() { saveButtonState(this, createdButtons + loadButtonState(this).filter { it.id == 99 }) // Ensure thumbstick is not removed } - fun toggleCustomCursor() { - runOnUiThread { - UIStateManager.isCustomCursorEnabled = !UIStateManager.isCustomCursorEnabled - customCursorView.visibility = if (UIStateManager.isCustomCursorEnabled) View.VISIBLE else View.GONE - } - } - private fun setEnvironmentVariables() { try { Os.setenv("OSG_TEXT_SHADER_TECHNIQUE", "ALL", true) diff --git a/app/src/main/java/org/openmw/HomeScreen.kt b/app/src/main/java/org/openmw/HomeScreen.kt index 5cd0166a..2c21df92 100755 --- a/app/src/main/java/org/openmw/HomeScreen.kt +++ b/app/src/main/java/org/openmw/HomeScreen.kt @@ -49,6 +49,7 @@ import kotlinx.coroutines.flow.map import org.openmw.fragments.SettingsFragment import org.openmw.navigation.MyFloatingActionButton import org.openmw.navigation.MyTopBar +import org.openmw.utils.BouncingBackground import org.openmw.utils.ModValue import org.openmw.utils.ModValuesList diff --git a/app/src/main/java/org/openmw/MainActivity.kt b/app/src/main/java/org/openmw/MainActivity.kt index 3024495e..c9fd4c3a 100755 --- a/app/src/main/java/org/openmw/MainActivity.kt +++ b/app/src/main/java/org/openmw/MainActivity.kt @@ -1,38 +1,16 @@ package org.openmw import android.content.Context -import android.os.Build import android.os.Bundle -import android.view.WindowInsets -import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.size import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Surface import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.scale -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.dp import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat @@ -43,15 +21,13 @@ import androidx.datastore.preferences.preferencesDataStore import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import androidx.window.layout.WindowMetrics -import androidx.window.layout.WindowMetricsCalculator -import kotlinx.coroutines.delay import org.openmw.ui.theme.OpenMWTheme import org.openmw.utils.CaptureCrash import org.openmw.utils.ModValue import org.openmw.utils.PermissionHelper -import org.openmw.utils.getAvailableStorageSpace +import org.openmw.utils.getScreenWidthAndHeight import org.openmw.utils.readModValues +import org.openmw.utils.updateResolutionInConfig import java.io.File val Context.dataStore: DataStore by preferencesDataStore(name = "game_files_prefs") @@ -82,13 +58,6 @@ class MainActivity : ComponentActivity() { controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE } - // a little extra protection from people deleting the thumbstick by mistake - val file = File("${Constants.USER_CONFIG}/UI.cfg") - if (!file.exists()) { - file.createNewFile() - file.appendText("ButtonID_99(200.0;200.56776;281.6349;false;29)\n") - } - setContent { OpenMWTheme { Surface(modifier = Modifier.fillMaxSize()) { @@ -96,44 +65,6 @@ class MainActivity : ComponentActivity() { } } } - - // Get storage space - val availableSpace = getAvailableStorageSpace(this) - println("Available storage space: $availableSpace bytes") - } - - private fun updateResolutionInConfig(width: Int, height: Int) { - val file = File(Constants.SETTINGS_FILE) - val lines = file.readLines().map { line -> - when { - line.startsWith("resolution y =") -> "resolution y = $width" // mix these up to convert to landscape - line.startsWith("resolution x =") -> "resolution x = $height" - else -> line - } - } - file.writeText(lines.joinToString("\n")) - } - - private fun getScreenWidthAndHeight(context: Context): Pair { - val windowMetrics: WindowMetrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context) - val bounds = windowMetrics.bounds - var width = bounds.width() - var height = bounds.height() - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - val windowManager = context.getSystemService(WINDOW_SERVICE) as WindowManager - val windowInsets: WindowInsets = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - windowManager.currentWindowMetrics.windowInsets - } else { - TODO("VERSION.SDK_INT < R") - } - val displayCutout = windowInsets.displayCutout - if (displayCutout != null) { - width += displayCutout.safeInsetLeft + displayCutout.safeInsetRight - height += displayCutout.safeInsetTop + displayCutout.safeInsetBottom - } - } - return Pair(width, height) } } @@ -168,43 +99,3 @@ sealed class Screen(val route: String) { object Setting: Screen(Route.SETTINGS) object Home: Screen(Route.HOME) } - -@Composable -fun BouncingBackground() { - val image: Painter = painterResource(id = R.drawable.backgroundbouncebw) - val configuration = LocalConfiguration.current - val screenWidth = configuration.screenWidthDp * configuration.densityDpi / 160 - val screenHeight = configuration.screenHeightDp * configuration.densityDpi / 160 - - val imageWidth = 2000 // Replace with your image width - val imageHeight = 2337 // Replace with your image height - - var offset: Offset by remember { mutableStateOf(Offset.Zero) } - val xDirection by remember { mutableFloatStateOf(1f) } - val yDirection by remember { mutableFloatStateOf(1f) } - - // Adjust this value to increase the distance - val stepSize = 1f - - LaunchedEffect(Unit) { - while (true) { - offset = Offset( - x = (offset.x + xDirection * stepSize) % screenWidth, - y = (offset.y + yDirection * stepSize) % screenHeight - ) - - delay(16L) // Update every frame (approx 60fps) - } - } - - Box(modifier = Modifier.fillMaxSize()) { - Image( - painter = image, - contentDescription = null, - modifier = Modifier - .offset { IntOffset(offset.x.toInt(), offset.y.toInt()) } - .size(imageWidth.dp, imageHeight.dp) // Convert Int to Dp - .scale(6f) // Scale the image up by a factor of 5 - .background(color = Color.LightGray)) - } -} diff --git a/app/src/main/java/org/openmw/SettingScreen.kt b/app/src/main/java/org/openmw/SettingScreen.kt index fd0868a7..f72a69bc 100755 --- a/app/src/main/java/org/openmw/SettingScreen.kt +++ b/app/src/main/java/org/openmw/SettingScreen.kt @@ -12,12 +12,12 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll @@ -43,6 +43,7 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import org.openmw.navigation.MyFloatingActionButton import org.openmw.navigation.MyTopBar +import org.openmw.utils.BouncingBackground import org.openmw.utils.ExpandableBox import org.openmw.utils.ReadAndDisplayIniValues import org.openmw.utils.exportCrashAndLogcatFiles @@ -66,7 +67,7 @@ fun SettingScreen(context: Context, navigateToHome: () -> Unit) { BouncingBackground() Box( modifier = Modifier - .fillMaxSize() + .wrapContentHeight() .padding(top = 40.dp, bottom = 80.dp), ) { Column( diff --git a/app/src/main/java/org/openmw/navigation/TopBottomBar.kt b/app/src/main/java/org/openmw/navigation/TopBottomBar.kt index 8c06bd5e..44ee3ff4 100644 --- a/app/src/main/java/org/openmw/navigation/TopBottomBar.kt +++ b/app/src/main/java/org/openmw/navigation/TopBottomBar.kt @@ -9,7 +9,10 @@ import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Menu import androidx.compose.material3.AlertDialog @@ -23,7 +26,9 @@ import androidx.compose.material3.FloatingActionButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Switch import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -31,6 +36,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -47,6 +53,7 @@ import org.openmw.dataStore import org.openmw.fragments.NavmeshActivity import org.openmw.fragments.containsMorrowindFolder import org.openmw.fragments.getGameFilesUri +import org.openmw.ui.controls.UIStateManager import org.openmw.ui.theme.transparentBlack import org.openmw.utils.UnzipWithProgress import org.openmw.utils.UserManageAssets @@ -60,6 +67,7 @@ import java.util.zip.ZipInputStream fun MyTopBar(context: Context) { var expanded by remember { mutableStateOf(false) } var showDialog by remember { mutableStateOf(false) } + var showDialog2 by remember { mutableStateOf(false) } val settingsFile = File(SETTINGS_FILE) val destDirectory = LocalContext.current.getExternalFilesDir(null)?.absolutePath + "/Morrowind" var directoryExists by remember { mutableStateOf(File(destDirectory).exists()) } @@ -107,32 +115,11 @@ fun MyTopBar(context: Context) { } }, onClick = { - if (directoryExists) { - // Code to delete the Morrowind folder - File(destDirectory).deleteRecursively() - directoryExists = false // Update the state - } else { - // Check if the zip file exists - val zipFile = File(zipFilePath) - if (!zipFile.exists()) { - Toast.makeText(context, "Zip file does not exist.", Toast.LENGTH_LONG).show() - } else { - // Check if the zip file contains the Morrowind folder before setting showUnzipProgress - if (containsMorrowindFolder(zipFilePath)) { - showUnzipProgress = true - } else { - Toast.makeText(context, "Zip file does not contain a Morrowind folder.", Toast.LENGTH_LONG).show() - } - } - } + expanded = false + // Directly set the showDialog2 state + showDialog2 = true } ) - if (showUnzipProgress) { - UnzipWithProgress { - showUnzipProgress = false - directoryExists = true // Update the state after unzipping - } - } DropdownMenuItem( text = { Text("Build Navmesh", color = Color.White) }, onClick = { @@ -152,6 +139,21 @@ fun MyTopBar(context: Context) { showDialog = true } ) + DropdownMenuItem( + text = { + Row(verticalAlignment = Alignment.CenterVertically) { + Text("Enable Logcat", color = Color.White) + Spacer(modifier = Modifier.width(8.dp)) + Switch( + checked = UIStateManager.isLogcatEnabled, + onCheckedChange = { + UIStateManager.isLogcatEnabled = it + } + ) + } + }, + onClick = { /* Handle click if necessary */ } + ) if (showDialog) { AlertDialog( onDismissRequest = { showDialog = false }, @@ -184,6 +186,56 @@ fun MyTopBar(context: Context) { ) } } + if (showDialog2) { + AlertDialog( + onDismissRequest = { showDialog2 = false }, + title = { Text("Confirm Action") }, + text = { + if (directoryExists) { + Text("Are you sure you want to uninstall Morrowind files?") + } else { + Text("Are you sure you want to install Morrowind files? \nThis extracts from a Morrowind.zip in the download folder into the launcher assigned folder. \n\nOnly needed on strict devices.") + } + }, + confirmButton = { + TextButton( + onClick = { + showDialog2 = false + if (directoryExists) { + File(destDirectory).deleteRecursively() + directoryExists = false // Update the state + } else { + val zipFile = File(zipFilePath) + if (!zipFile.exists()) { + Toast.makeText(context, "Zip file does not exist.", Toast.LENGTH_LONG).show() + } else { + if (containsMorrowindFolder(zipFilePath)) { + showUnzipProgress = true + } else { + Toast.makeText(context, "Zip file does not contain a Morrowind folder.", Toast.LENGTH_LONG).show() + } + } + } + } + ) { + Text("Yes") + } + }, + dismissButton = { + TextButton( + onClick = { showDialog2 = false } + ) { + Text("No") + } + } + ) + if (showUnzipProgress) { + UnzipWithProgress { + showUnzipProgress = false + directoryExists = true // Update the state after unzipping + } + } + } }, modifier = Modifier.height(60.dp) ) diff --git a/app/src/main/java/org/openmw/ui/controls/DynamicButtons.kt b/app/src/main/java/org/openmw/ui/controls/DynamicButtons.kt index c9b2baa5..20085212 100644 --- a/app/src/main/java/org/openmw/ui/controls/DynamicButtons.kt +++ b/app/src/main/java/org/openmw/ui/controls/DynamicButtons.kt @@ -22,6 +22,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -37,6 +38,7 @@ import androidx.compose.ui.unit.dp import org.libsdl.app.SDLActivity.onNativeKeyDown import org.libsdl.app.SDLActivity.onNativeKeyUp import org.openmw.ui.overlay.sendKeyEvent +import org.openmw.utils.vibrate import kotlin.math.roundToInt @Composable @@ -45,7 +47,8 @@ fun ResizableDraggableButton( id: Int, keyCode: Int, editMode: Boolean, - onDelete: () -> Unit // Add onDelete parameter + onDelete: () -> Unit, + customCursorView: CustomCursorView ) { var buttonState by remember { mutableStateOf( @@ -54,8 +57,8 @@ fun ResizableDraggableButton( ) } var buttonSize by remember { mutableStateOf(buttonState.size.dp) } - var offsetX by remember { mutableStateOf(buttonState.offsetX) } - var offsetY by remember { mutableStateOf(buttonState.offsetY) } + var offsetX by remember { mutableFloatStateOf(buttonState.offsetX) } + var offsetY by remember { mutableFloatStateOf(buttonState.offsetY) } var isDragging by remember { mutableStateOf(false) } var isPressed by remember { mutableStateOf(false) } val context = LocalContext.current @@ -125,7 +128,7 @@ fun ResizableDraggableButton( if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { if (isPressed) Color.Green else Color(alpha = 0.6f, red = 0f, green = 0f, blue = 0f) } else { - Color(alpha = 0.6f, red = 0f, green = 0f, blue = 0f) + Color(alpha = 0.25f, red = 0f, green = 0f, blue = 0f) }, shape = CircleShape ) .clickable { @@ -142,6 +145,32 @@ fun ResizableDraggableButton( sendKeyEvent(keyCode) onNativeKeyUp(keyCode) } + if (keyCode == KeyEvent.KEYCODE_A) { + onNativeKeyDown(keyCode) + sendKeyEvent(keyCode) + onNativeKeyUp(keyCode) + + if (UIStateManager.isCustomCursorEnabled) { + customCursorView.performMouseClick() + } else { + // Trigger KEYCODE_ENTER + onNativeKeyDown(KeyEvent.KEYCODE_ENTER) + sendKeyEvent(KeyEvent.KEYCODE_ENTER) + onNativeKeyUp(KeyEvent.KEYCODE_ENTER) + } + + if (UIStateManager.isVibrationEnabled) { + vibrate(context) + } + } else if (keyCode == KeyEvent.KEYCODE_E) { + onNativeKeyDown(keyCode) + sendKeyEvent(keyCode) + onNativeKeyUp(keyCode) + + if (UIStateManager.isVibrationEnabled) { + vibrate(context) + } + } }, contentAlignment = Alignment.Center ) { @@ -207,7 +236,6 @@ fun ResizableDraggableButton( } } - fun keyCodeToChar(keyCode: Int): String { return when (keyCode) { in KeyEvent.KEYCODE_F1..KeyEvent.KEYCODE_F12 -> "F${keyCode - KeyEvent.KEYCODE_F1 + 1}" diff --git a/app/src/main/java/org/openmw/ui/controls/StateManager.kt b/app/src/main/java/org/openmw/ui/controls/StateManager.kt index 1bdf5266..d7b6494c 100644 --- a/app/src/main/java/org/openmw/ui/controls/StateManager.kt +++ b/app/src/main/java/org/openmw/ui/controls/StateManager.kt @@ -2,6 +2,7 @@ package org.openmw.ui.controls import android.content.Context import android.view.KeyEvent +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -24,6 +25,7 @@ import androidx.compose.material.icons.filled.Build import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.Divider +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -57,8 +59,17 @@ object UIStateManager { var isUIHidden by mutableStateOf(false) var visible by mutableStateOf(true) var isVibrationEnabled by mutableStateOf(true) - var isRunEnabled by mutableStateOf(false) var isCustomCursorEnabled by mutableStateOf(false) + + // Add the shared states + var memoryInfoText by mutableStateOf("") + var batteryStatus by mutableStateOf("") + var logMessages by mutableStateOf("") + var isMemoryInfoEnabled by mutableStateOf(false) + var isBatteryStatusEnabled by mutableStateOf(false) + var isLoggingEnabled by mutableStateOf(false) + var isLogcatEnabled by mutableStateOf(false) + val scrollState = ScrollState(0) } fun saveButtonState(context: Context, state: List) { @@ -103,7 +114,7 @@ fun loadButtonState(context: Context): List { } @Composable -fun KeySelectionMenu(onKeySelected: (Int) -> Unit, usedKeys: List) { +fun KeySelectionMenu(onKeySelected: (Int) -> Unit, usedKeys: List, editMode: MutableState) { val letterKeys = ('A'..'Z').toList().filter { key -> val keyCode = KeyEvent.KEYCODE_A + key.minus('A') keyCode !in usedKeys @@ -123,8 +134,16 @@ fun KeySelectionMenu(onKeySelected: (Int) -> Unit, usedKeys: List) { ).filter { keyCode -> keyCode !in usedKeys } var showDialog by remember { mutableStateOf(false) } - IconButton(onClick = { showDialog = true }) { - Icon(Icons.Default.Add, contentDescription = "Add Button") + IconButton(onClick = { + showDialog = true + editMode.value = true + }) { + Icon( + Icons.Default.Add, + contentDescription = "Add Button", + modifier = Modifier.size(36.dp), // Adjust the icon size here + tint = Color.Red // Change the color here + ) } if (showDialog) { @@ -172,8 +191,7 @@ fun KeySelectionMenu(onKeySelected: (Int) -> Unit, usedKeys: List) { } } } - - Divider(color = Color.White, thickness = 1.dp, modifier = Modifier.padding(vertical = 16.dp)) + HorizontalDivider(color = Color.White, thickness = 1.dp, modifier = Modifier.padding(vertical = 16.dp)) Text( text = "Select a Function Key", style = MaterialTheme.typography.titleMedium, @@ -206,8 +224,7 @@ fun KeySelectionMenu(onKeySelected: (Int) -> Unit, usedKeys: List) { } } } - - Divider(color = Color.White, thickness = 1.dp, modifier = Modifier.padding(vertical = 16.dp)) + HorizontalDivider(color = Color.White, thickness = 1.dp, modifier = Modifier.padding(vertical = 16.dp)) Text( text = "Select a Unique Key, The shift keys toggle.", style = MaterialTheme.typography.titleMedium, @@ -252,7 +269,10 @@ fun KeySelectionMenu(onKeySelected: (Int) -> Unit, usedKeys: List) { } Spacer(modifier = Modifier.height(16.dp)) Button( - onClick = { showDialog = false }, + onClick = { + showDialog = false + editMode.value = true + }, modifier = Modifier.align(Alignment.End) ) { Text("Cancel") @@ -272,7 +292,9 @@ fun DynamicButtonManager( ) { var showDialog by remember { mutableStateOf(false) } - Column { + Column( + modifier = Modifier.padding(start = 40.dp) // Add padding to space from the left + ) { Row(verticalAlignment = Alignment.CenterVertically) { IconButton(onClick = { showDialog = !showDialog @@ -303,11 +325,11 @@ fun DynamicButtonManager( saveButtonState(context, updatedButtons) onNewButtonAdded(newButtonState) showDialog = false + editMode.value = true }, - usedKeys = createdButtons.map { it.keyCode } + usedKeys = createdButtons.map { it.keyCode }, + editMode = editMode ) } } } - - diff --git a/app/src/main/java/org/openmw/ui/controls/mouse.kt b/app/src/main/java/org/openmw/ui/controls/mouse.kt index 8a7484e8..bbd6a24c 100644 --- a/app/src/main/java/org/openmw/ui/controls/mouse.kt +++ b/app/src/main/java/org/openmw/ui/controls/mouse.kt @@ -113,4 +113,4 @@ class CustomCursorView(context: Context, attrs: AttributeSet?) : View(context, a override fun performClick(): Boolean { return super.performClick() } -} +} \ No newline at end of file diff --git a/app/src/main/java/org/openmw/ui/overlay/Overlay.kt b/app/src/main/java/org/openmw/ui/overlay/Overlay.kt index 7f4c754d..0f74d117 100644 --- a/app/src/main/java/org/openmw/ui/overlay/Overlay.kt +++ b/app/src/main/java/org/openmw/ui/overlay/Overlay.kt @@ -1,39 +1,55 @@ package org.openmw.ui.overlay +import android.annotation.SuppressLint import android.content.Context -import android.view.KeyEvent +import android.view.View import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.SizeTransform import androidx.compose.animation.core.keyframes import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Person -import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material.icons.filled.Star import androidx.compose.material.icons.rounded.Settings import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.getValue +import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread import kotlinx.coroutines.* import org.libsdl.app.SDLActivity.onNativeKeyDown import org.libsdl.app.SDLActivity.onNativeKeyUp +import org.openmw.ui.controls.ButtonState +import org.openmw.ui.controls.CustomCursorView +import org.openmw.ui.controls.DynamicButtonManager import org.openmw.ui.controls.UIStateManager import org.openmw.utils.* +import kotlin.math.roundToInt fun sendKeyEvent(keyCode: Int) { onNativeKeyDown(keyCode) @@ -46,50 +62,48 @@ data class MemoryInfo( val usedMemory: String ) +@SuppressLint("RestrictedApi") +fun toggleCustomCursor(customCursorView: CustomCursorView) { + runOnUiThread { + UIStateManager.isCustomCursorEnabled = !UIStateManager.isCustomCursorEnabled + customCursorView.visibility = if (UIStateManager.isCustomCursorEnabled) View.VISIBLE else View.GONE + } +} + @Composable -fun OverlayUI(engineActivityContext: Context) { +fun OverlayUI( + engineActivityContext: Context, + editMode: MutableState, + createdButtons: SnapshotStateList, + customCursorView: CustomCursorView +) { val context = LocalContext.current var expanded by remember { mutableStateOf(false) } - var memoryInfoText by remember { mutableStateOf("") } - var batteryStatus by remember { mutableStateOf("") } - var logMessages by remember { mutableStateOf("") } - val scrollState = rememberScrollState() - var isMemoryInfoEnabled by remember { mutableStateOf(false) } - var isBatteryStatusEnabled by remember { mutableStateOf(false) } - var isLoggingEnabled by remember { mutableStateOf(false) } - val isUIHidden = UIStateManager.isUIHidden - var isRunEnabled = UIStateManager.isRunEnabled - var isF10Enabled by remember { mutableStateOf(false) } // New state for F10 - var isBacktickEnabled by remember { mutableStateOf(false) } - var isF2Enabled by remember { mutableStateOf(false) } - var isF3Enabled by remember { mutableStateOf(false) } + val visible = UIStateManager.visible + val density = LocalDensity.current LaunchedEffect(Unit) { getMessages() // Ensure logcat is enabled - while (true) { - if (isMemoryInfoEnabled) { + if (UIStateManager.isMemoryInfoEnabled) { val memoryInfo = getMemoryInfo(context) - memoryInfoText = "Total memory: ${memoryInfo.totalMemory}\n" + + UIStateManager.memoryInfoText = "Total memory: ${memoryInfo.totalMemory}\n" + "Available memory: ${memoryInfo.availableMemory}\n" + "Used memory: ${memoryInfo.usedMemory}" } else { - memoryInfoText = "" + UIStateManager.memoryInfoText = "" } - - if (isBatteryStatusEnabled) { - batteryStatus = getBatteryStatus(context) + if (UIStateManager.isBatteryStatusEnabled) { + UIStateManager.batteryStatus = getBatteryStatus(context) } else { - batteryStatus = "" + UIStateManager.batteryStatus = "" } - - if (isLoggingEnabled) { - scrollState.animateScrollTo(scrollState.maxValue) - logMessages = getMessages().joinToString("\n") + if (UIStateManager.isLoggingEnabled) { + UIStateManager.scrollState.animateScrollTo(UIStateManager.scrollState.maxValue) + UIStateManager.logMessages = getMessages().joinToString("\n") } else { - logMessages = "" + UIStateManager.logMessages = "" } - delay(1000) } } @@ -118,7 +132,8 @@ fun OverlayUI(engineActivityContext: Context) { } } } - }, label = "size transform" + }, + label = "size transform" ) { targetExpanded -> if (targetExpanded) { Column( @@ -133,170 +148,94 @@ fun OverlayUI(engineActivityContext: Context) { .padding(5.dp) ) { item { - Text( - text = "Show Memory Info", - color = Color.White, - fontSize = 10.sp - ) - Switch( - checked = isMemoryInfoEnabled, - onCheckedChange = { isMemoryInfoEnabled = it } - ) - Text( - text = "Show Battery Status", - color = Color.White, - fontSize = 10.sp - ) - Switch( - checked = isBatteryStatusEnabled, - onCheckedChange = { isBatteryStatusEnabled = it } - ) - Text( - text = "Show Logcat", - color = Color.White, - fontSize = 10.sp - ) - Switch( - checked = isLoggingEnabled, - onCheckedChange = { isLoggingEnabled = it } - ) - Text(text = "Enable Vibration", color = Color.White, fontSize = 10.sp) - Switch( - checked = UIStateManager.isVibrationEnabled, - onCheckedChange = { UIStateManager.isVibrationEnabled = it }) - Text(text = "Hide UI", color = Color.White, fontSize = 10.sp) - Switch(checked = isUIHidden, onCheckedChange = { - UIStateManager.isUIHidden = it - UIStateManager.visible = !it - }) - // Run/Walk Toggle Switch - Text(text = "Run / Walk", color = Color.White, fontSize = 10.sp) - Switch(checked = isRunEnabled, onCheckedChange = { - UIStateManager.isRunEnabled = it - if (it) onNativeKeyDown(KeyEvent.KEYCODE_SHIFT_LEFT) - else onNativeKeyUp(KeyEvent.KEYCODE_SHIFT_LEFT) - }) - // F10 Toggle Switch - Text(text = "Press F10", color = Color.White, fontSize = 10.sp) - Switch(checked = isF10Enabled, onCheckedChange = { - isF10Enabled = it - if (it) { - onNativeKeyDown(KeyEvent.KEYCODE_F10) - onNativeKeyUp(KeyEvent.KEYCODE_F10) - } else { - onNativeKeyDown(KeyEvent.KEYCODE_F10) - onNativeKeyUp(KeyEvent.KEYCODE_F10) - } - }) - // Backtick Toggle Switch - Text(text = "Console", color = Color.White, fontSize = 10.sp) - Switch(checked = isBacktickEnabled, onCheckedChange = { - isBacktickEnabled = it - if (it) { - onNativeKeyDown(KeyEvent.KEYCODE_GRAVE) - onNativeKeyUp(KeyEvent.KEYCODE_GRAVE) - } else { - onNativeKeyDown(KeyEvent.KEYCODE_GRAVE) - onNativeKeyUp(KeyEvent.KEYCODE_GRAVE) - } - }) - } - } - LazyRow( - modifier = Modifier - .background(Color(alpha = 0.6f, red = 0f, green = 0f, blue = 0f)) - .padding(5.dp) - ) { - item { - // Button for J (Journal) - IconButton(onClick = { - onNativeKeyDown(KeyEvent.KEYCODE_J) - onNativeKeyUp(KeyEvent.KEYCODE_J) - }) { - Text(text = "Journal", color = Color.White, fontSize = 10.sp) + Box( + modifier = Modifier + .padding(4.dp) + .background(Color.Red, shape = RoundedCornerShape(8.dp)) + .clickable { expanded = false } + .padding(16.dp), + contentAlignment = Alignment.Center + ) { + Text(text = "Close", color = Color.White, fontSize = 20.sp) } - // Button for F5 (Quicksave) - IconButton(onClick = { - onNativeKeyDown(KeyEvent.KEYCODE_F5) - onNativeKeyUp(KeyEvent.KEYCODE_F5) - }) { - Text(text = "Quicksave", color = Color.White, fontSize = 10.sp) + Spacer(modifier = Modifier.width(20.dp)) + ClickableBox("Hide UI", UIStateManager.isUIHidden) { + UIStateManager.isUIHidden = !UIStateManager.isUIHidden + UIStateManager.visible = !UIStateManager.isUIHidden } - - // Button for F6 (Quickload) - IconButton(onClick = { - onNativeKeyDown(KeyEvent.KEYCODE_F6) - onNativeKeyUp(KeyEvent.KEYCODE_F6) - }) { - Text(text = "Quickload", color = Color.White, fontSize = 10.sp) + ClickableBox("Enable Vibration", UIStateManager.isVibrationEnabled) { + UIStateManager.isVibrationEnabled = !UIStateManager.isVibrationEnabled } - - // Button for F12 (Screenshot) - IconButton(onClick = { - onNativeKeyDown(KeyEvent.KEYCODE_F12) - onNativeKeyUp(KeyEvent.KEYCODE_F12) - }) { - Text(text = "Screenshot", color = Color.White, fontSize = 10.sp) + ClickableBox("Show Memory Info", UIStateManager.isMemoryInfoEnabled) { + UIStateManager.isMemoryInfoEnabled = !UIStateManager.isMemoryInfoEnabled } - Spacer(modifier = Modifier.width(16.dp)) - // F2 Icon - IconButton(onClick = { - isF2Enabled = !isF2Enabled - if (isF2Enabled) { - onNativeKeyDown(KeyEvent.KEYCODE_F2) - onNativeKeyUp(KeyEvent.KEYCODE_F2) - } else { - onNativeKeyDown(KeyEvent.KEYCODE_F2) - onNativeKeyUp(KeyEvent.KEYCODE_F2) - } - }) { - Text(text = "F2", color = Color.White, fontSize = 20.sp) + ClickableBox("Show Battery Status", UIStateManager.isBatteryStatusEnabled) { + UIStateManager.isBatteryStatusEnabled = !UIStateManager.isBatteryStatusEnabled } - Spacer(modifier = Modifier.width(16.dp)) - // F3 Icon - IconButton(onClick = { - isF3Enabled = !isF3Enabled - if (isF3Enabled) { - onNativeKeyDown(KeyEvent.KEYCODE_F3) - onNativeKeyUp(KeyEvent.KEYCODE_F3) - } else { - onNativeKeyDown(KeyEvent.KEYCODE_F3) - onNativeKeyUp(KeyEvent.KEYCODE_F3) - } - }) { - Text(text = "F3", color = Color.White, fontSize = 20.sp) + ClickableBox("Show Logcat", UIStateManager.isLoggingEnabled) { + UIStateManager.isLoggingEnabled = !UIStateManager.isLoggingEnabled } } } } } else { - Icon(Icons.Rounded.Settings, contentDescription = "Settings") - Button( - onClick = { - onNativeKeyDown(KeyEvent.KEYCODE_J) - onNativeKeyUp(KeyEvent.KEYCODE_J) - }, - colors = ButtonDefaults.buttonColors( - containerColor = Color.Transparent - ), - modifier = Modifier - .padding(start = 60.dp) - ) { - Icon(Icons.Default.Person, contentDescription = "Sneak", tint = Color.Black) - } - Button( - onClick = { - onNativeKeyDown(KeyEvent.KEYCODE_T) - onNativeKeyUp(KeyEvent.KEYCODE_T) - }, - colors = ButtonDefaults.buttonColors( - containerColor = Color.Transparent - ), - modifier = Modifier - .padding(start = 20.dp) + Row( + modifier = Modifier.align(Alignment.TopEnd) ) { - Icon(Icons.Default.Refresh, contentDescription = "Rest", tint = Color.Black) + Icon( + Icons.Rounded.Settings, + contentDescription = "Settings", + modifier = Modifier + .padding(top = 10.dp, start = 20.dp) + .size(30.dp), + tint = Color.Black + ) + + AnimatedVisibility( + visible = visible, + enter = slideInVertically( + initialOffsetY = { with(density) { -20.dp.roundToPx() } }, + animationSpec = tween(durationMillis = 1000) + ) + expandVertically( + expandFrom = Alignment.Bottom, + animationSpec = tween(durationMillis = 1000) + ) + fadeIn( + initialAlpha = 0.3f, + animationSpec = tween(durationMillis = 1000) + ), + exit = slideOutVertically( + targetOffsetY = { with(density) { -20.dp.roundToPx() } }, + animationSpec = tween(durationMillis = 1000) + ) + shrinkVertically( + animationSpec = tween(durationMillis = 1000) + ) + fadeOut( + animationSpec = tween(durationMillis = 1000) + ) + ) { + DynamicButtonManager( + context = engineActivityContext, + onNewButtonAdded = { newButtonState -> + createdButtons.add(newButtonState) + }, + editMode = editMode, + createdButtons = createdButtons + ) + IconButton( + onClick = { toggleCustomCursor(customCursorView) }, + colors = IconButtonDefaults.iconButtonColors( + containerColor = Color.Transparent + ) + ) { + Icon( + Icons.Default.Star, + contentDescription = "Toggle Custom Cursor", + modifier = Modifier.size(30.dp), + tint = Color.Black + ) + } + } } + } } } @@ -308,45 +247,128 @@ fun OverlayUI(engineActivityContext: Context) { horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { - if (isMemoryInfoEnabled) { - Text( - text = memoryInfoText, - color = Color.White, - fontSize = 10.sp - ) - Spacer(modifier = Modifier.height(8.dp)) + if (UIStateManager.isMemoryInfoEnabled) { + DraggableBox(editMode = editMode.value) { fontSize -> + Text( + text = UIStateManager.memoryInfoText, + color = Color.White, + fontSize = fontSize.sp + ) + Spacer(modifier = Modifier.height(8.dp)) + } } - if (isBatteryStatusEnabled) { - Text( - text = batteryStatus, - color = Color.White, - fontSize = 10.sp - ) - Spacer(modifier = Modifier.height(8.dp)) + + if (UIStateManager.isBatteryStatusEnabled) { + DraggableBox(editMode = editMode.value) { fontSize -> + Text( + text = UIStateManager.batteryStatus, + color = Color.White, + fontSize = fontSize.sp + ) + Spacer(modifier = Modifier.height(8.dp)) + } } - if (isLoggingEnabled) { - Box( - modifier = Modifier - .width(400.dp) - .height(200.dp) - .padding(vertical = 35.dp) - .verticalScroll(rememberScrollState()), - contentAlignment = Alignment.BottomCenter - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp) - ) { - // Your text content here - Text( - text = logMessages, - color = Color.White, - fontSize = 10.sp + if (UIStateManager.isLoggingEnabled) { + DraggableBox(editMode = editMode.value) { fontSize -> + Text( + text = UIStateManager.logMessages, + color = Color.White, + fontSize = fontSize.sp + ) + Spacer(modifier = Modifier.height(8.dp)) + } + } + } + } +} + +@Composable +fun DraggableBox( + editMode: Boolean, + content: @Composable (Float) -> Unit +) { + var offsetX by remember { mutableFloatStateOf(0f) } + var offsetY by remember { mutableFloatStateOf(0f) } + var boxWidth by remember { mutableFloatStateOf(200f) } + var boxHeight by remember { mutableFloatStateOf(100f) } + var isDragging by remember { mutableStateOf(false) } + var isResizing by remember { mutableStateOf(false) } + var fontSize by remember { mutableFloatStateOf(10f) } + + Box( + modifier = Modifier + .offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) } + .size(width = boxWidth.dp, height = boxHeight.dp) + .background(Color.Transparent) + .then( + if (editMode) { + Modifier.pointerInput(Unit) { + detectDragGestures( + onDragStart = { isDragging = true }, + onDrag = { change, dragAmount -> + offsetX += dragAmount.x + offsetY += dragAmount.y + }, + onDragEnd = { isDragging = false } ) } - } + } else Modifier + ) + .border(2.dp, if (isDragging || isResizing) Color.Red else Color.Transparent) + .padding(8.dp) + ) { + Column(modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) { + Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.Center) { + content(fontSize) + } + if (editMode) { + Slider( + value = fontSize, + onValueChange = { fontSize = it }, + valueRange = 5f..30f, + colors = SliderDefaults.colors( + thumbColor = Color.Red, + activeTrackColor = Color(alpha = .9f, red = 0f, green = 0f, blue = 0f), + inactiveTrackColor = Color(alpha = 0.6f, red = 0f, green = 0f, blue = 0f) + ), + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp) + ) + } } + if (editMode) { + Box( + modifier = Modifier + .size(16.dp) + .offset { IntOffset(boxWidth.roundToInt() - 16.dp.toPx().roundToInt() - 16, boxHeight.roundToInt() - 16.dp.toPx().roundToInt() - 16) } + .background(Color.Red) + .pointerInput(Unit) { + detectDragGestures( + onDragStart = { isResizing = true }, + onDrag = { change, dragAmount -> + boxWidth += dragAmount.x + boxHeight += dragAmount.y + }, + onDragEnd = { isResizing = false } + ) + } + .align(Alignment.BottomEnd) + ) + } + } +} + +@Composable +fun ClickableBox(text: String, enabled: Boolean, onClick: () -> Unit) { + Box( + modifier = Modifier + .padding(4.dp) + .background(Color.White, shape = RoundedCornerShape(8.dp)) + .clickable { onClick() } + .padding(16.dp), + contentAlignment = Alignment.Center + ) { + Text(text = text, color = if (enabled) Color.Green else Color.Red, fontSize = 15.sp) } } + diff --git a/app/src/main/java/org/openmw/utils/ManageAssets.kt b/app/src/main/java/org/openmw/utils/ManageAssets.kt index 712691cd..736d5a32 100755 --- a/app/src/main/java/org/openmw/utils/ManageAssets.kt +++ b/app/src/main/java/org/openmw/utils/ManageAssets.kt @@ -141,6 +141,22 @@ class UserManageAssets(val context: Context) { File(Constants.USER_OPENMW_CFG).writeText("# This is the user openmw.cfg. Feel free to modify it as you wish.\n") } + // Create default UI + val file = File("${Constants.USER_CONFIG}/UI.cfg") + if (!file.exists()) { + file.createNewFile() + file.appendText(""" + ButtonID_1(60.0;2054.6936;18.942787;false;111) + ButtonID_2(60.0;1805.0613;700.42505;false;29) + ButtonID_3(60.0;1942.9843;561.5578;false;30) + ButtonID_4(60.0;1805.0613;422.69055;false;53) + ButtonID_5(60.0;1668.5325;561.5578;false;52) + ButtonID_6(60.0;1335.1458;770.3131;false;62) + ButtonID_7(60.0;750.73267;770.3131;false;66) + ButtonID_99(200.0;200.56776;281.6349;false;29) + """.trimIndent()) + } + // copy user settings file if (!File(Constants.SETTINGS_FILE).exists()) { Log.d("ManageAssets", "Copying resources to ${Constants.SETTINGS_FILE}") diff --git a/app/src/main/java/org/openmw/utils/UITools.kt b/app/src/main/java/org/openmw/utils/UITools.kt index 150dd230..d7be1a16 100644 --- a/app/src/main/java/org/openmw/utils/UITools.kt +++ b/app/src/main/java/org/openmw/utils/UITools.kt @@ -3,17 +3,61 @@ package org.openmw.utils import android.annotation.SuppressLint import android.app.ActivityManager import android.content.Context +import android.content.Context.WINDOW_SERVICE import android.content.Intent import android.content.IntentFilter import android.os.BatteryManager +import android.os.Build import android.os.Environment import android.os.StatFs +import android.os.VibrationEffect +import android.os.Vibrator +import android.view.WindowInsets +import android.view.WindowManager +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.dp +import androidx.window.layout.WindowMetrics +import androidx.window.layout.WindowMetricsCalculator +import kotlinx.coroutines.delay +import org.openmw.Constants +import org.openmw.R import org.openmw.ui.overlay.MemoryInfo import java.io.BufferedReader +import java.io.File import java.io.InputStreamReader import kotlin.math.ln import kotlin.math.pow +@Suppress("DEPRECATION") +fun vibrate(context: Context) { + val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + if (vibrator.hasVibrator()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE)) + } + } +} + fun getAvailableStorageSpace(context: Context): String { val storageDirectory = Environment.getExternalStorageDirectory() val stat = StatFs(storageDirectory.toString()) @@ -21,6 +65,10 @@ fun getAvailableStorageSpace(context: Context): String { return humanReadableByteCountBin(availableBytes) } +// Get storage space +//val availableSpace = getAvailableStorageSpace(this) +//println("Available storage space: $availableSpace bytes") + fun getMemoryInfo(context: Context): MemoryInfo { val memoryInfo = ActivityManager.MemoryInfo() val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager @@ -67,3 +115,93 @@ fun getMessages(): List { } return logMessages } + +fun enableLogcat() { + val logcatFile = File(Constants.USER_CONFIG + "/openmw_logcat.txt") + if (logcatFile.exists()) { + logcatFile.delete() + } + + val processBuilder = ProcessBuilder() + val commandToExecute = arrayOf("/system/bin/sh", "-c", "logcat *:W -d -f ${Constants.USER_CONFIG}/openmw_logcat.txt") + processBuilder.command(*commandToExecute) + processBuilder.redirectErrorStream(true) + processBuilder.start() +} + + +fun updateResolutionInConfig(width: Int, height: Int) { + val file = File(Constants.SETTINGS_FILE) + val lines = file.readLines().map { line -> + when { + // These are incorrect, I swapped $width and $height so the resolution would be correct. The issue + // Starts when you grab the device specs in portrait then the game jum,ps to landscape. + line.startsWith("resolution y =") -> "resolution y = $width" + line.startsWith("resolution x =") -> "resolution x = $height" + else -> line + } + } + file.writeText(lines.joinToString("\n")) +} + +fun getScreenWidthAndHeight(context: Context): Pair { + val windowMetrics: WindowMetrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context) + val bounds = windowMetrics.bounds + var width = bounds.width() + var height = bounds.height() + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + val windowManager = context.getSystemService(WINDOW_SERVICE) as WindowManager + val windowInsets: WindowInsets = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + windowManager.currentWindowMetrics.windowInsets + } else { + TODO("VERSION.SDK_INT < R") + } + val displayCutout = windowInsets.displayCutout + if (displayCutout != null) { + width += displayCutout.safeInsetLeft + displayCutout.safeInsetRight + height += displayCutout.safeInsetTop + displayCutout.safeInsetBottom + } + } + return Pair(width, height) +} + +@Composable +fun BouncingBackground() { + val image: Painter = painterResource(id = R.drawable.backgroundbouncebw) + val configuration = LocalConfiguration.current + val screenWidth = configuration.screenWidthDp * configuration.densityDpi / 160 + val screenHeight = configuration.screenHeightDp * configuration.densityDpi / 160 + + val imageWidth = 2000 // Replace with your image width + val imageHeight = 2337 // Replace with your image height + + var offset: Offset by remember { mutableStateOf(Offset.Zero) } + val xDirection by remember { mutableFloatStateOf(1f) } + val yDirection by remember { mutableFloatStateOf(1f) } + + // Adjust this value to increase the distance + val stepSize = 1f + + LaunchedEffect(Unit) { + while (true) { + offset = Offset( + x = (offset.x + xDirection * stepSize) % screenWidth, + y = (offset.y + yDirection * stepSize) % screenHeight + ) + + delay(16L) // Update every frame (approx 60fps) + } + } + + Box(modifier = Modifier.fillMaxSize()) { + Image( + painter = image, + contentDescription = null, + modifier = Modifier + .offset { IntOffset(offset.x.toInt(), offset.y.toInt()) } + .size(imageWidth.dp, imageHeight.dp) // Convert Int to Dp + .scale(6f) // Scale the image up by a factor of 6 + .background(color = Color.LightGray)) + } +} diff --git a/app/src/main/res/drawable/pointer_icon.xml b/app/src/main/res/drawable/pointer_icon.xml new file mode 100644 index 00000000..a03edb16 --- /dev/null +++ b/app/src/main/res/drawable/pointer_icon.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/engine_activity.xml b/app/src/main/res/layout/engine_activity.xml index 307a7927..f65cef62 100644 --- a/app/src/main/res/layout/engine_activity.xml +++ b/app/src/main/res/layout/engine_activity.xml @@ -6,39 +6,21 @@ + android:layout_height="match_parent"> - - - - - - - - - - - + + + + + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 656acf72..69a03f25 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] -agp = "8.7.0" +agp = "8.7.1" +coreKtxVersion = "1.8.1" datastore = "1.1.1" kotlin = "1.9.0" coreKtx = "1.13.1" @@ -7,9 +8,9 @@ junit = "4.13.2" junitVersion = "1.2.1" espressoCore = "3.6.1" lifecycleRuntimeKtx = "2.8.6" -activityCompose = "1.9.2" -composeBom = "2024.09.03" -navigationCompose = "2.8.2" +activityCompose = "1.9.3" +composeBom = "2024.10.00" +navigationCompose = "2.8.3" appcompat = "1.7.0" profileinstaller = "1.4.1" protobufJavalite = "4.28.2" @@ -25,6 +26,7 @@ window = "1.3.0" androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "datastore" } androidx-profileinstaller = { module = "androidx.profileinstaller:profileinstaller", version.ref = "profileinstaller" } +core-ktx = { module = "com.google.android.play:core-ktx", version.ref = "coreKtxVersion" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } @@ -51,4 +53,3 @@ androidx-window = { group = "androidx.window", name = "window", version.ref = "w [plugins] android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } -android-library = { id = "com.android.library", version.ref = "agp" }