diff --git a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/viewmodels/QuickSettings.kt b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/viewmodels/QuickSettings.kt index d1ac6b446..762407ecb 100644 --- a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/viewmodels/QuickSettings.kt +++ b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/viewmodels/QuickSettings.kt @@ -19,8 +19,17 @@ class QuickSettings(val activity: Activity) { } } + // --- Overlay Position + enum class OverlayMenuPosition { + BottomMiddle, BottomLeft, BottomRight, TopMiddle, TopLeft, TopRight + } + var orientationPreference: OrientationPreference + // --- Overlay Settings + var overlayMenuPosition: OverlayMenuPosition + var overlayMenuOpacity: Float + var ignoreMissingServices: Boolean var enablePptc: Boolean var enableLowPowerPptc: Boolean @@ -61,6 +70,12 @@ class QuickSettings(val activity: Activity) { val oriValue = sharedPref.getInt("orientationPreference", ActivityInfo.SCREEN_ORIENTATION_SENSOR) orientationPreference = OrientationPreference.fromValue(oriValue) + // --- NEU: Overlay Settings laden + overlayMenuPosition = OverlayMenuPosition.entries[ + sharedPref.getInt("overlayMenuPosition", OverlayMenuPosition.BottomMiddle.ordinal) + ] + overlayMenuOpacity = sharedPref.getFloat("overlayMenuOpacity", 1f).coerceIn(0f, 1f) + memoryManagerMode = MemoryManagerMode.entries.toTypedArray()[sharedPref.getInt("memoryManagerMode", MemoryManagerMode.HostMappedUnsafe.ordinal)] useNce = sharedPref.getBoolean("useNce", false) memoryConfiguration = MemoryConfiguration.entries.toTypedArray()[sharedPref.getInt("memoryConfiguration", MemoryConfiguration.MemoryConfiguration4GiB.ordinal)] @@ -100,6 +115,10 @@ class QuickSettings(val activity: Activity) { // --- Save orientation putInt("orientationPreference", orientationPreference.value) + // --- NEU: Overlay Settings speichern + putInt("overlayMenuPosition", overlayMenuPosition.ordinal) + putFloat("overlayMenuOpacity", overlayMenuOpacity.coerceIn(0f, 1f)) + putInt("memoryManagerMode", memoryManagerMode.ordinal) putBoolean("useNce", useNce) putInt("memoryConfiguration", memoryConfiguration.ordinal) diff --git a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/views/GameViews.kt b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/views/GameViews.kt index 96c764508..13c8fbb77 100644 --- a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/views/GameViews.kt +++ b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/views/GameViews.kt @@ -33,6 +33,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Popup +import androidx.compose.ui.draw.alpha // ← NEU import compose.icons.CssGgIcons import compose.icons.cssggicons.ToolbarBottom import org.kenjinx.android.GameController @@ -75,31 +76,36 @@ class GameViews { @Composable fun GameOverlay(mainViewModel: MainViewModel) { Box(modifier = Modifier.fillMaxSize()) { - val showStats = remember { - mutableStateOf(false) + val showStats = remember { mutableStateOf(false) } + val showController = remember { mutableStateOf(QuickSettings(mainViewModel.activity).useVirtualController) } + val vSyncMode = remember { mutableStateOf(QuickSettings(mainViewModel.activity).vSyncMode) } + val enableMotion = remember { mutableStateOf(QuickSettings(mainViewModel.activity).enableMotion) } + val showMore = remember { mutableStateOf(false) } + val showLoading = remember { mutableStateOf(true) } + val progressValue = remember { mutableStateOf(0.0f) } + val progress = remember { mutableStateOf("Loading") } + + // --- Read overlay settings + val overlayPositionState = remember { + mutableStateOf(QuickSettings(mainViewModel.activity).overlayMenuPosition) } - val showController = remember { - mutableStateOf(QuickSettings(mainViewModel.activity).useVirtualController) + val overlayOpacityState = remember { + mutableStateOf(QuickSettings(mainViewModel.activity).overlayMenuOpacity.coerceIn(0f, 1f)) } - val vSyncMode = remember { - mutableStateOf(QuickSettings(mainViewModel.activity).vSyncMode) - } - val enableMotion = remember { - mutableStateOf(QuickSettings(mainViewModel.activity).enableMotion) - } - val showMore = remember { - mutableStateOf(false) - } - val showLoading = remember { - mutableStateOf(true) - } - val progressValue = remember { - mutableStateOf(0.0f) - } - val progress = remember { - mutableStateOf("Loading") + + // Auxiliary mapping position → alignment + fun overlayAlignment(): Alignment { + return when (overlayPositionState.value) { + QuickSettings.OverlayMenuPosition.BottomMiddle -> Alignment.BottomCenter + QuickSettings.OverlayMenuPosition.BottomLeft -> Alignment.BottomStart + QuickSettings.OverlayMenuPosition.BottomRight -> Alignment.BottomEnd + QuickSettings.OverlayMenuPosition.TopMiddle -> Alignment.TopCenter + QuickSettings.OverlayMenuPosition.TopLeft -> Alignment.TopStart + QuickSettings.OverlayMenuPosition.TopRight -> Alignment.TopEnd + } } + if (showStats.value) { GameStats(mainViewModel) } @@ -130,18 +136,14 @@ class GameViews { position.y.roundToInt() ) } - PointerEventType.Release -> { KenjinxNative.inputReleaseTouchPoint() - } - PointerEventType.Move -> { KenjinxNative.inputSetTouchPoint( position.x.roundToInt(), position.y.roundToInt() ) - } } } @@ -149,13 +151,16 @@ class GameViews { } }) { } + if (!showLoading.value) { GameController.Compose(mainViewModel) + // --- Button at any corner/edge + transparency Row( modifier = Modifier - .align(Alignment.BottomCenter) + .align(overlayAlignment()) .padding(8.dp) + .alpha(overlayOpacityState.value) // 0f = unsichtbar, aber weiter klickbar ) { IconButton(modifier = Modifier.padding(4.dp), onClick = { showMore.value = true @@ -169,8 +174,9 @@ class GameViews { if (showMore.value) { Popup( - alignment = Alignment.BottomCenter, - onDismissRequest = { showMore.value = false }) { + alignment = overlayAlignment(), // --- NEU: Panel an gleicher Position + onDismissRequest = { showMore.value = false } + ) { Surface( modifier = Modifier.padding(16.dp), shape = MaterialTheme.shapes.medium @@ -194,12 +200,9 @@ class GameViews { } IconButton(modifier = Modifier.padding(4.dp), onClick = { showMore.value = false - if(vSyncMode.value == VSyncMode.Switch) - { + if(vSyncMode.value == VSyncMode.Switch) { vSyncMode.value= VSyncMode.Unbounded - } - else - { + } else { vSyncMode.value= VSyncMode.Switch } KenjinxNative.graphicsRendererSetVsync( @@ -246,17 +249,14 @@ class GameViews { } } - val showBackNotice = remember { - mutableStateOf(false) - } + val showBackNotice = remember { mutableStateOf(false) } - // NEW: If the software keyboard is open, catch Back and close ONLY the dialog. + // If the software keyboard is open, catch Back and close ONLY the dialog. val uiHandler = mainViewModel.activity.uiHandler BackHandler(enabled = uiHandler.showMessage.value) { KenjinxNative.uiHandlerSetResponse(false, "") uiHandler.showMessage.value = false } - BackHandler { showBackNotice.value = true } @@ -286,24 +286,12 @@ class GameViews { @Composable fun GameStats(mainViewModel: MainViewModel) { - val fifo = remember { - mutableDoubleStateOf(0.0) - } - val gameFps = remember { - mutableDoubleStateOf(0.0) - } - val gameTime = remember { - mutableDoubleStateOf(0.0) - } - val usedMem = remember { - mutableIntStateOf(0) - } - val totalMem = remember { - mutableIntStateOf(0) - } - val frequencies = remember { - mutableListOf() - } + val fifo = remember { mutableDoubleStateOf(0.0) } + val gameFps = remember { mutableDoubleStateOf(0.0) } + val gameTime = remember { mutableDoubleStateOf(0.0) } + val usedMem = remember { mutableIntStateOf(0) } + val totalMem = remember { mutableIntStateOf(0) } + val frequencies = remember { mutableListOf() } Surface( modifier = Modifier.padding(16.dp), @@ -324,10 +312,7 @@ class GameViews { if (i < frequencies.size) { val t = frequencies[i] Row { - Text( - modifier = Modifier.padding(2.dp), - text = "CPU $i" - ) + Text(modifier = Modifier.padding(2.dp), text = "CPU $i") Spacer(Modifier.weight(1f)) Text(text = "$t MHz") } diff --git a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/views/SettingViews.kt b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/views/SettingViews.kt index 212f2da11..419c5b24f 100644 --- a/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/views/SettingViews.kt +++ b/src/KenjinxAndroid/app/src/main/java/org/kenjinx/android/views/SettingViews.kt @@ -83,6 +83,7 @@ import org.kenjinx.android.widgets.SwitchSelector // >>> QuickSettings + OrientationPreference import org.kenjinx.android.viewmodels.QuickSettings import org.kenjinx.android.viewmodels.QuickSettings.OrientationPreference +import org.kenjinx.android.viewmodels.QuickSettings.OverlayMenuPosition // ← NEU // Import enums import org.kenjinx.android.SystemLanguage @@ -150,6 +151,14 @@ class SettingViews { val systemLanguage = remember { mutableStateOf(SystemLanguage.AmericanEnglish) } val regionCode = remember { mutableStateOf(RegionCode.USA) } + // Load overlay settings from QuickSettings + val overlayMenuPosition = remember { + mutableStateOf(QuickSettings(mainViewModel.activity).overlayMenuPosition) + } + val overlayOpacity = remember { + mutableFloatStateOf(QuickSettings(mainViewModel.activity).overlayMenuOpacity.coerceIn(0f, 1f)) + } + if (!loaded.value) { settingsViewModel.initializeState( memoryManagerMode, @@ -281,6 +290,69 @@ class SettingViews { } ) + // Overlay Menu Position (DropdownSelector as usual) + OverlayPositionDropdown( + selectedPosition = overlayMenuPosition.value, + onPositionSelected = { pos -> + overlayMenuPosition.value = pos + val qs = QuickSettings(mainViewModel.activity) + qs.overlayMenuPosition = pos + qs.save() + } + ) + + // Overlay transparency slider – identical style as controller stick sensitivity + val interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } + Row( + modifier = Modifier + .fillMaxWidth() + .height(56.dp) + .padding(horizontal = 8.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = "Overlay transparency", + modifier = Modifier.align(Alignment.CenterVertically) + ) + Slider( + modifier = Modifier.width(250.dp), + value = overlayOpacity.floatValue, + onValueChange = { + val clamped = it.coerceIn(0f, 1f) + overlayOpacity.floatValue = clamped + val qs = QuickSettings(mainViewModel.activity) + qs.overlayMenuOpacity = clamped + qs.save() + }, + valueRange = 0f..1f, + steps = 20, + interactionSource = interactionSource, + thumb = { + Label( + label = { + PlainTooltip( + modifier = Modifier + .sizeIn(45.dp, 25.dp) + .wrapContentWidth() + ) { + Text("${(overlayOpacity.floatValue * 100f).toInt()}%") + } + }, + interactionSource = interactionSource + ) { + Icon( + imageVector = org.kenjinx.android.Icons.circle( + color = MaterialTheme.colorScheme.primary + ), + contentDescription = null, + modifier = Modifier.size(ButtonDefaults.IconSize), + tint = MaterialTheme.colorScheme.primary + ) + } + } + ) + } + isGrid.SwitchSelector("Use Grid") Row( modifier = Modifier @@ -1335,6 +1407,39 @@ class SettingViews { } } + // ---- Overlay position dropdown ---- + @Composable + fun OverlayPositionDropdown( + selectedPosition: OverlayMenuPosition, + onPositionSelected: (OverlayMenuPosition) -> Unit + ) { + val options = listOf( + OverlayMenuPosition.BottomMiddle, + OverlayMenuPosition.BottomLeft, + OverlayMenuPosition.BottomRight, + OverlayMenuPosition.TopMiddle, + OverlayMenuPosition.TopLeft, + OverlayMenuPosition.TopRight + ) + + DropdownSelector( + label = "Overlay Menu Position", + selectedValue = selectedPosition, + options = options, + getDisplayText = { opt -> + when (opt) { + OverlayMenuPosition.BottomMiddle -> "bottom middle" + OverlayMenuPosition.BottomLeft -> "bottom left" + OverlayMenuPosition.BottomRight -> "bottom right" + OverlayMenuPosition.TopMiddle -> "top middle" + OverlayMenuPosition.TopLeft -> "top left" + OverlayMenuPosition.TopRight -> "top right" + } + }, + onOptionSelected = onPositionSelected + ) + } + // ---- Dropdowns for language & region ---- @Composable