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 8ae382828..510ee60c6 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 @@ -48,6 +48,15 @@ import org.kenjinx.android.viewmodels.VSyncMode import org.kenjinx.android.widgets.SimpleAlertDialog import java.util.Locale import kotlin.math.roundToInt +import android.net.Uri +import android.widget.Toast +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.ui.platform.LocalConfiguration +import org.kenjinx.android.viewmodels.QuickSettings.VirtualControllerPreset + class GameViews { companion object { @@ -55,7 +64,7 @@ class GameViews { fun Main() { Surface( modifier = Modifier.fillMaxSize(), - color = MaterialTheme.colorScheme.background + color = Color.Black ) { GameView(mainViewModel = MainActivity.mainViewModel!!) } @@ -63,17 +72,69 @@ class GameViews { @Composable fun GameView(mainViewModel: MainViewModel) { - Box(modifier = Modifier.fillMaxSize()) { - AndroidView( + val cfg = LocalConfiguration.current + val isLandscape = cfg.screenWidthDp >= cfg.screenHeightDp + val isLarge = (cfg.smallestScreenWidthDp >= 600) || (cfg.screenWidthDp >= 900) + + // Setting from preferences (read at game start) + val stretch = QuickSettings(mainViewModel.activity).stretchToFullscreen + + // Default aspect ratio (Switch 16:9). If you later want to read dynamically from the renderer, + // you can update gameAspect here at runtime. + val gameAspect = 16f / 9f + + if (stretch) { + // Stretch to full screen (no letterbox), anchored to the top + Box( modifier = Modifier.fillMaxSize(), - factory = { context -> - GameHost(context, mainViewModel) + contentAlignment = Alignment.TopCenter + ) { + AndroidView( + modifier = Modifier + .fillMaxSize() + .align(Alignment.TopCenter), + factory = { context -> GameHost(context, mainViewModel) } + ) + GameOverlay(mainViewModel) + } + } else { + // Keep letterbox but pin to the top. Phones: smart-fit, + // Tablets/Foldables in landscape: enforce fitWidth (as desired). + BoxWithConstraints(modifier = Modifier.fillMaxSize()) { + val containerAspect = maxWidth.value / maxHeight.value + + val useFitWidth = + if (isLandscape && isLarge) true + else containerAspect < gameAspect + + val fitModifier = + if (useFitWidth) { + Modifier + .fillMaxWidth() + .aspectRatio(gameAspect) + .align(Alignment.TopCenter) + } else { + Modifier + .fillMaxHeight() + .aspectRatio(gameAspect) + .align(Alignment.TopCenter) + } + + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.TopCenter + ) { + AndroidView( + modifier = fitModifier, + factory = { context -> GameHost(context, mainViewModel) } + ) + GameOverlay(mainViewModel) } - ) - GameOverlay(mainViewModel) + } } } + @OptIn(ExperimentalMaterial3Api::class) @Composable fun GameOverlay(mainViewModel: MainViewModel) {