mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-13 03:51:10 +00:00
front end: Setup rows join the rail's tracked column (the pill can reach Performance); the session drawer keeps its frame-generation dialog
This commit is contained in:
1 parent
8dd36b794e
commit
af3f377dfd
2 files changed
+120
-53
No files matched your search
@@ -0,0 +1,67 @@
|
||||
package com.steamdeck.launcher.ui
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.steamdeck.launcher.gpu.FrameGen
|
||||
|
||||
/**
|
||||
* Off, or an engine at 2x/3x/4x. The LSFG rows are dimmed and unpickable until Lossless Scaling
|
||||
* is installed in the Steam client — the engine cannot run without its DLL, so offering it would
|
||||
* only produce a setting that quietly does nothing.
|
||||
*/
|
||||
@Composable
|
||||
fun FrameGenDialog(
|
||||
engine: String,
|
||||
multiplier: Int,
|
||||
lsfgReady: Boolean,
|
||||
onPick: (engine: String, multiplier: Int) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
data class Row(val label: String, val engine: String, val multiplier: Int, val enabled: Boolean)
|
||||
val note = if (lsfgReady) "" else " — install Lossless Scaling in Steam"
|
||||
val rows = listOf(Row("Off", FrameGen.ENGINE_OFF, 2, true)) +
|
||||
(2..4).map { Row("Win-FG ${it}×", FrameGen.ENGINE_WINFG, it, true) } +
|
||||
(2..4).map { Row("LSFG ${it}×$note", FrameGen.ENGINE_LSFG, it, lsfgReady) }
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Frame generation") },
|
||||
text = {
|
||||
Column {
|
||||
Text(
|
||||
"Extra frames are generated between the real ones on the way to the screen, so a game running at 30 fps looks like 60. Takes effect at once, mid-game included.",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
)
|
||||
for (row in rows) {
|
||||
val selected = row.engine == engine && (row.engine == FrameGen.ENGINE_OFF || row.multiplier == multiplier)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.alpha(if (row.enabled) 1f else 0.38f)
|
||||
.clickable(enabled = row.enabled) { onPick(row.engine, row.multiplier) }
|
||||
.padding(vertical = 2.dp),
|
||||
) {
|
||||
RadioButton(selected = selected, onClick = null, enabled = row.enabled)
|
||||
Text(row.label, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(start = 4.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = { TextButton(onClick = onDismiss) { Text("OK") } },
|
||||
)
|
||||
}
|
||||
@@ -366,62 +366,62 @@ private fun Rail(
|
||||
if (s.emulators.none { it.installed }) NavItem("install emulators under Desktop & apps", "x", false, small = true, muted = true, register = register, unregister = unregister) { a.onApps() }
|
||||
NavItem("Settings", "settings:lxqt", selected == "settings:lxqt", small = true, tiny = true, muted = true, i = s.emulators.count { it.installed }, register = register, unregister = unregister) { a.onDesktopSettings() }
|
||||
}
|
||||
Spacer(Modifier.height(6.dp))
|
||||
// Setup: the same fold as Steam and Desktop. A row that holds a value opens a small menu
|
||||
// in place; a row that opens a page lights up while the page is shown.
|
||||
val menus = remember { MenuHost() }
|
||||
val item: @Composable (String, String?, Int, String?, () -> Unit) -> Unit = { label, value, i, key, act ->
|
||||
NavItem(label, key ?: "x", key != null && selected == key, small = true, tiny = true, muted = true, value = value, i = i, register = register, unregister = unregister) { act() }
|
||||
}
|
||||
NavItem("Setup", "x", false, caret = openSetup, count = 9, onClick = onToggleSetup)
|
||||
Sub(openSetup) {
|
||||
item("Files", null, 0, null, a.onFiles)
|
||||
item("Desktop & apps", null, 1, null, a.onApps)
|
||||
item("Compatibility tools", null, 2, null, a.onProtons)
|
||||
Box {
|
||||
item("Frame generation", s.frameGenLabel, 3, null) { menus.open = "fg" }
|
||||
AnchoredMenu(
|
||||
menus.open == "fg", onDismiss = { if (menus.open == "fg") menus.open = null }, title = "Frame generation",
|
||||
note = "Extra frames between the real ones on the way to the screen, so 30 fps looks like 60. Takes effect at once, mid-game included.",
|
||||
) {
|
||||
val need = if (s.lsfgReady) null else "install Lossless Scaling in Steam"
|
||||
MenuItem("Off", checked = s.frameGenEngine == FrameGen.ENGINE_OFF) { a.onFrameGenPick(FrameGen.ENGINE_OFF, 2); menus.open = null }
|
||||
for (m in 2..4) MenuItem("Win-FG ${m}×", checked = s.frameGenEngine == FrameGen.ENGINE_WINFG && s.frameGenMultiplier == m) { a.onFrameGenPick(FrameGen.ENGINE_WINFG, m); menus.open = null }
|
||||
for (m in 2..4) MenuItem("LSFG ${m}×", checked = s.frameGenEngine == FrameGen.ENGINE_LSFG && s.frameGenMultiplier == m, enabled = s.lsfgReady, detail = need) { a.onFrameGenPick(FrameGen.ENGINE_LSFG, m); menus.open = null }
|
||||
}
|
||||
}
|
||||
item("Performance", null, 4, "performance", a.onPerformance)
|
||||
item("ROMs folder", s.romsDir?.substringAfterLast('/')?.ifEmpty { s.romsDir } ?: "choose", 5, null, a.onRoms)
|
||||
Box {
|
||||
item("Session logs", if (s.logsEnabled) "on" else "off", 6, null) { menus.open = "logs" }
|
||||
AnchoredMenu(
|
||||
menus.open == "logs", onDismiss = { if (menus.open == "logs") menus.open = null }, title = "Session logs",
|
||||
note = "Each session leaves a folder in Downloads: the guest log, the device and network reports, the app's own log.",
|
||||
) {
|
||||
MenuItem("On", checked = s.logsEnabled) { if (!s.logsEnabled) a.onLogs(); menus.open = null }
|
||||
MenuItem("Off", checked = !s.logsEnabled) { if (s.logsEnabled) a.onLogs(); menus.open = null }
|
||||
}
|
||||
}
|
||||
Box {
|
||||
item("Start offline", when { s.offlineAccount == null -> "sign in first"; s.offline -> "on"; else -> "off" }, 7, null) { if (s.offlineAccount != null) menus.open = "offline" }
|
||||
AnchoredMenu(
|
||||
menus.open == "offline", onDismiss = { if (menus.open == "offline") menus.open = null }, title = "Start offline",
|
||||
note = "The client starts as ${s.offlineAccount ?: "the saved account"} without the network: the library and installed games, no store.",
|
||||
) {
|
||||
MenuItem("On", checked = s.offline) { if (!s.offline) a.onOffline(); menus.open = null }
|
||||
MenuItem("Off", checked = !s.offline) { if (s.offline) a.onOffline(); menus.open = null }
|
||||
}
|
||||
}
|
||||
item("Linux runtime", when {
|
||||
s.busy -> "working…"
|
||||
!s.ready -> "install"
|
||||
s.available != null && s.available != s.installed -> "update"
|
||||
else -> "remove"
|
||||
}, 8, null, a.onRuntime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(6.dp))
|
||||
// Setup: the same fold as Steam and Desktop. A row that holds a value opens a small menu
|
||||
// in place; a row that opens a page lights up while the page is shown.
|
||||
val menus = remember { MenuHost() }
|
||||
val item: @Composable (String, String?, Int, String?, () -> Unit) -> Unit = { label, value, i, key, act ->
|
||||
NavItem(label, key ?: "x", key != null && selected == key, small = true, tiny = true, muted = true, value = value, i = i, register = register, unregister = unregister) { act() }
|
||||
}
|
||||
NavItem("Setup", "x", false, caret = openSetup, count = 9, onClick = onToggleSetup)
|
||||
Sub(openSetup) {
|
||||
item("Files", null, 0, null, a.onFiles)
|
||||
item("Desktop & apps", null, 1, null, a.onApps)
|
||||
item("Compatibility tools", null, 2, null, a.onProtons)
|
||||
Box {
|
||||
item("Frame generation", s.frameGenLabel, 3, null) { menus.open = "fg" }
|
||||
AnchoredMenu(
|
||||
menus.open == "fg", onDismiss = { if (menus.open == "fg") menus.open = null }, title = "Frame generation",
|
||||
note = "Extra frames between the real ones on the way to the screen, so 30 fps looks like 60. Takes effect at once, mid-game included.",
|
||||
) {
|
||||
val need = if (s.lsfgReady) null else "install Lossless Scaling in Steam"
|
||||
MenuItem("Off", checked = s.frameGenEngine == FrameGen.ENGINE_OFF) { a.onFrameGenPick(FrameGen.ENGINE_OFF, 2); menus.open = null }
|
||||
for (m in 2..4) MenuItem("Win-FG ${m}×", checked = s.frameGenEngine == FrameGen.ENGINE_WINFG && s.frameGenMultiplier == m) { a.onFrameGenPick(FrameGen.ENGINE_WINFG, m); menus.open = null }
|
||||
for (m in 2..4) MenuItem("LSFG ${m}×", checked = s.frameGenEngine == FrameGen.ENGINE_LSFG && s.frameGenMultiplier == m, enabled = s.lsfgReady, detail = need) { a.onFrameGenPick(FrameGen.ENGINE_LSFG, m); menus.open = null }
|
||||
}
|
||||
}
|
||||
item("Performance", null, 4, "performance", a.onPerformance)
|
||||
item("ROMs folder", s.romsDir?.substringAfterLast('/')?.ifEmpty { s.romsDir } ?: "choose", 5, null, a.onRoms)
|
||||
Box {
|
||||
item("Session logs", if (s.logsEnabled) "on" else "off", 6, null) { menus.open = "logs" }
|
||||
AnchoredMenu(
|
||||
menus.open == "logs", onDismiss = { if (menus.open == "logs") menus.open = null }, title = "Session logs",
|
||||
note = "Each session leaves a folder in Downloads: the guest log, the device and network reports, the app's own log.",
|
||||
) {
|
||||
MenuItem("On", checked = s.logsEnabled) { if (!s.logsEnabled) a.onLogs(); menus.open = null }
|
||||
MenuItem("Off", checked = !s.logsEnabled) { if (s.logsEnabled) a.onLogs(); menus.open = null }
|
||||
}
|
||||
}
|
||||
Box {
|
||||
item("Start offline", when { s.offlineAccount == null -> "sign in first"; s.offline -> "on"; else -> "off" }, 7, null) { if (s.offlineAccount != null) menus.open = "offline" }
|
||||
AnchoredMenu(
|
||||
menus.open == "offline", onDismiss = { if (menus.open == "offline") menus.open = null }, title = "Start offline",
|
||||
note = "The client starts as ${s.offlineAccount ?: "the saved account"} without the network: the library and installed games, no store.",
|
||||
) {
|
||||
MenuItem("On", checked = s.offline) { if (!s.offline) a.onOffline(); menus.open = null }
|
||||
MenuItem("Off", checked = !s.offline) { if (s.offline) a.onOffline(); menus.open = null }
|
||||
}
|
||||
}
|
||||
item("Linux runtime", when {
|
||||
s.busy -> "working…"
|
||||
!s.ready -> "install"
|
||||
s.available != null && s.available != s.installed -> "update"
|
||||
else -> "remove"
|
||||
}, 8, null, a.onRuntime)
|
||||
}
|
||||
Text(
|
||||
"credits", fontSize = 11.sp, color = colors.onSurfaceVariant,
|
||||
modifier = Modifier.clip(Shape10).clickable(onClick = a.onCredits).padding(horizontal = 10.dp, vertical = 8.dp),
|
||||
|
||||
Reference in new issue
Block a user