mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-12 11:31:08 +00:00
Main screen: scale to the window, and keep clear of the system bars
Panels and densities vary too much for fixed dp: a 7" 1080p handheld in landscape has barely 400dp of height and the column did not fit it, with the logo under the status bar and the credits off the bottom. The column is now designed at one size and scaled by the height available — logo, text, buttons and spacing shrink together until it fits, down to 55% — with scrolling as the last resort and a width cap so the buttons never span a wide panel.
This commit is contained in:
1 parent
3877d0696f
commit
033db93058
1 file changed
+85
-81
@@ -3,7 +3,6 @@ package com.steamdeck.launcher.ui
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
@@ -12,6 +11,7 @@ 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.systemBarsPadding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
@@ -28,6 +28,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.steamdeck.launcher.R
|
||||
|
||||
/** Everything the main screen shows; the activity owns the values and the work behind them. */
|
||||
@@ -44,8 +45,13 @@ class MainUiState(
|
||||
)
|
||||
|
||||
/**
|
||||
* The whole app outside a session, in one scrolling column that stays a sensible width on a wide
|
||||
* panel: is the runtime installed, is there a newer one, frame generation, and Play.
|
||||
* The whole app outside a session, in one column that is sized to the window it has.
|
||||
*
|
||||
* Panels and densities vary too much for fixed dp: a 7" 1080p handheld in landscape has barely
|
||||
* 400dp of height, a foldable's inner screen has 900. So the column is designed at one size and
|
||||
* scaled by the height available — everything on it, text included, shrinks together until it
|
||||
* fits — with scrolling left as the last resort, and a width cap so the buttons never span a
|
||||
* wide panel. The system bars are kept clear of.
|
||||
*/
|
||||
@Composable
|
||||
fun MainScreen(
|
||||
@@ -58,92 +64,90 @@ fun MainScreen(
|
||||
BoxWithConstraints(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
contentAlignment = Alignment.TopCenter,
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.systemBarsPadding(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
val wide = maxWidth > 600.dp
|
||||
// The column as designed needs about this much height; below it, scale everything.
|
||||
val designHeight = 480.dp
|
||||
val k = (maxHeight / designHeight).coerceIn(0.55f, 1f)
|
||||
val colors = MaterialTheme.colorScheme
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.widthIn(max = 420.dp * k)
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 24.dp, vertical = 20.dp),
|
||||
.padding(horizontal = 24.dp * k, vertical = 12.dp * k),
|
||||
) {
|
||||
// The column never spans a landscape panel: the buttons stay a readable width.
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.widthIn(max = 420.dp).fillMaxWidth(),
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.logo),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(if (wide) 88.dp else 72.dp),
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text("SteamDeck", style = MaterialTheme.typography.headlineSmall, color = MaterialTheme.colorScheme.onBackground)
|
||||
Text(
|
||||
"Valve's native ARM64 Steam client, under gamescope",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Spacer(Modifier.height(18.dp))
|
||||
Image(
|
||||
painter = painterResource(R.drawable.logo),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(72.dp * k),
|
||||
)
|
||||
Spacer(Modifier.height(6.dp * k))
|
||||
Text("SteamDeck", fontSize = 22.sp * k, color = colors.onBackground)
|
||||
Text(
|
||||
"Valve's native ARM64 Steam client, under gamescope",
|
||||
fontSize = 12.sp * k, color = colors.onSurfaceVariant, textAlign = TextAlign.Center,
|
||||
)
|
||||
Spacer(Modifier.height(14.dp * k))
|
||||
|
||||
val status = when {
|
||||
state.busy -> "Working…"
|
||||
!state.ready -> "Linux runtime not installed"
|
||||
else -> "Ready"
|
||||
}
|
||||
Text(status, style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onBackground)
|
||||
val detail = when {
|
||||
state.busy -> if (state.percent >= 0) "${state.stage} ${state.percent}%" else state.stage
|
||||
state.failed -> "Install failed — nothing was changed"
|
||||
!state.ready -> state.available?.let { "$it · ${state.availableSize} download, one time" }
|
||||
?: "Could not reach the runtime catalog"
|
||||
state.available != null && state.available != state.installed ->
|
||||
"Runtime ${state.installed ?: "?"} installed · ${state.available} available"
|
||||
else -> "Runtime ${state.installed ?: "?"} installed"
|
||||
}
|
||||
Text(
|
||||
detail,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
)
|
||||
if (state.busy) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
if (state.percent >= 0) {
|
||||
LinearProgressIndicator(progress = { state.percent / 100f }, modifier = Modifier.fillMaxWidth())
|
||||
} else {
|
||||
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(18.dp))
|
||||
|
||||
Button(onClick = onPlay, enabled = state.ready && !state.busy, modifier = Modifier.fillMaxWidth()) {
|
||||
Text("Play")
|
||||
}
|
||||
val runtimeLabel = when {
|
||||
state.busy -> "Working…"
|
||||
!state.ready -> "Install Linux runtime"
|
||||
state.available != null && state.available != state.installed -> "Update Linux runtime"
|
||||
else -> "Remove Linux runtime"
|
||||
}
|
||||
OutlinedButton(onClick = onRuntime, enabled = !state.busy, modifier = Modifier.fillMaxWidth().padding(top = 6.dp)) {
|
||||
Text(runtimeLabel)
|
||||
}
|
||||
OutlinedButton(onClick = onFrameGen, modifier = Modifier.fillMaxWidth().padding(top = 6.dp)) {
|
||||
Text("Frame generation: ${state.frameGenLabel}")
|
||||
}
|
||||
Spacer(Modifier.height(14.dp))
|
||||
Text(
|
||||
"by The412Banner and maxjivi05 · credits",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.clickable(onClick = onCredits).padding(6.dp),
|
||||
)
|
||||
val status = when {
|
||||
state.busy -> "Working…"
|
||||
!state.ready -> "Linux runtime not installed"
|
||||
else -> "Ready"
|
||||
}
|
||||
Text(status, fontSize = 15.sp * k, color = colors.onBackground)
|
||||
val detail = when {
|
||||
state.busy -> if (state.percent >= 0) "${state.stage} ${state.percent}%" else state.stage
|
||||
state.failed -> "Install failed — nothing was changed"
|
||||
!state.ready -> state.available?.let { "$it · ${state.availableSize} download, one time" }
|
||||
?: "Could not reach the runtime catalog"
|
||||
state.available != null && state.available != state.installed ->
|
||||
"Runtime ${state.installed ?: "?"} installed · ${state.available} available"
|
||||
else -> "Runtime ${state.installed ?: "?"} installed"
|
||||
}
|
||||
Text(
|
||||
detail, fontSize = 12.sp * k, color = colors.onSurfaceVariant, textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(top = 3.dp * k),
|
||||
)
|
||||
if (state.busy) {
|
||||
Spacer(Modifier.height(6.dp * k))
|
||||
if (state.percent >= 0) {
|
||||
LinearProgressIndicator(progress = { state.percent / 100f }, modifier = Modifier.fillMaxWidth())
|
||||
} else {
|
||||
LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(14.dp * k))
|
||||
|
||||
val buttonHeight = 40.dp * k
|
||||
Button(
|
||||
onClick = onPlay, enabled = state.ready && !state.busy,
|
||||
modifier = Modifier.fillMaxWidth().height(buttonHeight),
|
||||
) { Text("Play", fontSize = 14.sp * k) }
|
||||
val runtimeLabel = when {
|
||||
state.busy -> "Working…"
|
||||
!state.ready -> "Install Linux runtime"
|
||||
state.available != null && state.available != state.installed -> "Update Linux runtime"
|
||||
else -> "Remove Linux runtime"
|
||||
}
|
||||
OutlinedButton(
|
||||
onClick = onRuntime, enabled = !state.busy,
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 5.dp * k).height(buttonHeight + 5.dp * k),
|
||||
) { Text(runtimeLabel, fontSize = 13.sp * k) }
|
||||
OutlinedButton(
|
||||
onClick = onFrameGen,
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 5.dp * k).height(buttonHeight + 5.dp * k),
|
||||
) { Text("Frame generation: ${state.frameGenLabel}", fontSize = 13.sp * k) }
|
||||
Spacer(Modifier.height(10.dp * k))
|
||||
Text(
|
||||
"by The412Banner and maxjivi05 · credits",
|
||||
fontSize = 11.sp * k, color = colors.onSurfaceVariant,
|
||||
modifier = Modifier.clickable(onClick = onCredits).padding(4.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user