mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-13 03:51:10 +00:00
Main screen: a ▶ chip per installed emulator under Desktop, and a ? that explains them
One chip per emulator the desktop packages installed, starting it fullscreen under gamescope; the ? says what they are, how to use them (ROMs at /root/ROMs, the phone at /root/Storage) and why they are here rather than on the desktop (labwc on pixman offers no dma-buf).
This commit is contained in:
1 parent
785bf23087
commit
eb8d6b76c2
2 files changed
+102
-10
No files matched your search
@@ -121,6 +121,8 @@ class MainActivity : ComponentActivity() {
|
||||
private var touchMode by mutableStateOf(SessionPrefs.TOUCH_AUTO)
|
||||
private var oscMode by mutableStateOf(SessionPrefs.OSC_AUTO)
|
||||
private var renderer by mutableStateOf("pixman")
|
||||
private var emulators by mutableStateOf<List<Pair<String, String>>>(emptyList())
|
||||
private var showEmulatorHelp by mutableStateOf(false)
|
||||
private var romsDir by mutableStateOf<String?>(null)
|
||||
private var logsEnabled by mutableStateOf(true)
|
||||
private var showRoms by mutableStateOf(false)
|
||||
@@ -138,7 +140,7 @@ class MainActivity : ComponentActivity() {
|
||||
frameGenLabel = frameGenLabel,
|
||||
desktopInstalled = desktopInstalled,
|
||||
offlineAccount = offlineAccount, offline = offline,
|
||||
romsDir = romsDir, logsEnabled = logsEnabled,
|
||||
romsDir = romsDir, logsEnabled = logsEnabled, emulators = emulators,
|
||||
),
|
||||
onPlay = { startActivity(Intent(this, SessionActivity::class.java)) },
|
||||
onDesktop = {
|
||||
@@ -158,6 +160,8 @@ class MainActivity : ComponentActivity() {
|
||||
onPerformance = { refreshCores(); showPerformance = true },
|
||||
onRoms = { showRoms = true },
|
||||
onFiles = { startActivity(Intent(this, com.steamdeck.launcher.files.FileManagerActivity::class.java)) },
|
||||
onLaunchEmulator = { path -> launchProgram(path) },
|
||||
onEmulatorHelp = { showEmulatorHelp = true },
|
||||
onLogs = {
|
||||
SessionPrefs.setLogsEnabled(this, !SessionPrefs.logsEnabled(this))
|
||||
logsEnabled = SessionPrefs.logsEnabled(this)
|
||||
@@ -177,12 +181,7 @@ class MainActivity : ComponentActivity() {
|
||||
rows = packageRows, busyStage = pkgStage, busyPercent = pkgPercent,
|
||||
onInstall = { id -> installPackage(id) },
|
||||
onRemove = { id -> catalog?.firstOrNull { it.id == id }?.let { DesktopCatalog.remove(this, it) }; refreshPackages() },
|
||||
onLaunch = { path ->
|
||||
showApps = false
|
||||
startActivity(Intent(this, SessionActivity::class.java)
|
||||
.putExtra(SessionService.EXTRA_MODE, SessionService.MODE_RUN)
|
||||
.putExtra(SessionService.EXTRA_PROGRAM, path))
|
||||
},
|
||||
onLaunch = { path -> showApps = false; launchProgram(path) },
|
||||
onDismiss = { showApps = false },
|
||||
)
|
||||
if (showProtons) ProtonDialog(
|
||||
@@ -271,6 +270,7 @@ class MainActivity : ComponentActivity() {
|
||||
onDismiss = { showFrameGen = false },
|
||||
)
|
||||
if (showCredits) CreditsDialog { showCredits = false }
|
||||
if (showEmulatorHelp) com.steamdeck.launcher.ui.EmulatorHelpDialog { showEmulatorHelp = false }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,15 +305,39 @@ class MainActivity : ComponentActivity() {
|
||||
DesktopCatalog.installed(this, it.id), launchersOf(it))
|
||||
}
|
||||
desktopInstalled = DesktopCatalog.desktopInstalled(this)
|
||||
emulators = installedEmulators()
|
||||
}
|
||||
|
||||
/** What a package can start on its own under gamescope; paths inside the runtime. */
|
||||
private fun launchersOf(entry: DesktopCatalog.Entry): List<Pair<String, String>> = when {
|
||||
entry.kind == "appimage" -> listOf(entry.name.substringBefore(" (").trim() to "/opt/appimages/${entry.id}.AppImage")
|
||||
entry.id == "emulators" -> listOf("PPSSPP" to "/usr/bin/PPSSPPSDL", "RetroArch" to "/usr/bin/retroarch")
|
||||
private fun launchersOf(entry: DesktopCatalog.Entry): List<Pair<String, String>> = launchersOf(entry.id)
|
||||
|
||||
/**
|
||||
* The same, by package id alone, so the main screen's row is known from the install markers
|
||||
* before (or without) the catalog. The AppImage packages launch as /opt/appimages/<id>.AppImage.
|
||||
*/
|
||||
private fun launchersOf(id: String): List<Pair<String, String>> = when (id) {
|
||||
"emulators" -> listOf("PPSSPP" to "/usr/bin/PPSSPPSDL", "RetroArch" to "/usr/bin/retroarch")
|
||||
"rpcs3" -> listOf("RPCS3" to "/opt/appimages/rpcs3.AppImage")
|
||||
"pcsx2" -> listOf("PCSX2" to "/opt/appimages/pcsx2.AppImage")
|
||||
"dolphin" -> listOf("Dolphin" to "/opt/appimages/dolphin.AppImage")
|
||||
"duckstation" -> listOf("DuckStation" to "/opt/appimages/duckstation.AppImage")
|
||||
"melonds" -> listOf("melonDS" to "/opt/appimages/melonds.AppImage")
|
||||
"cemu" -> listOf("Cemu" to "/opt/appimages/cemu.AppImage")
|
||||
else -> emptyList()
|
||||
}
|
||||
|
||||
/** Every installed emulator's launcher, for the main screen. */
|
||||
private fun installedEmulators(): List<Pair<String, String>> =
|
||||
listOf("rpcs3", "pcsx2", "dolphin", "duckstation", "melonds", "cemu", "emulators")
|
||||
.filter { DesktopCatalog.installed(this, it) != null }
|
||||
.flatMap { launchersOf(it) }
|
||||
|
||||
private fun launchProgram(path: String) {
|
||||
startActivity(Intent(this, SessionActivity::class.java)
|
||||
.putExtra(SessionService.EXTRA_MODE, SessionService.MODE_RUN)
|
||||
.putExtra(SessionService.EXTRA_PROGRAM, path))
|
||||
}
|
||||
|
||||
private fun installPackage(id: String) {
|
||||
val entry = catalog?.firstOrNull { it.id == id } ?: return
|
||||
if (pkgStage != null) return
|
||||
@@ -444,6 +468,7 @@ class MainActivity : ComponentActivity() {
|
||||
frameGenLabel = FrameGen.label(this)
|
||||
romsDir = SessionPrefs.romsDir(this).takeIf { it.isNotEmpty() }
|
||||
logsEnabled = SessionPrefs.logsEnabled(this)
|
||||
emulators = if (ready) installedEmulators() else emptyList()
|
||||
}
|
||||
|
||||
private fun onRuntimeButton() {
|
||||
|
||||
@@ -8,6 +8,8 @@ import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@@ -57,6 +59,8 @@ class MainUiState(
|
||||
/** The folder every session shows at /root/ROMs, or null if none is chosen. */
|
||||
val romsDir: String?,
|
||||
val logsEnabled: Boolean,
|
||||
/** Installed emulators that can run under gamescope: label to path inside the runtime. */
|
||||
val emulators: List<Pair<String, String>> = emptyList(),
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -72,6 +76,7 @@ class MainUiState(
|
||||
* setting's current value under it, so the menu is read at a glance and fits without scrolling
|
||||
* on a handheld.
|
||||
*/
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun MainScreen(
|
||||
state: MainUiState,
|
||||
@@ -87,6 +92,8 @@ fun MainScreen(
|
||||
onOffline: () -> Unit,
|
||||
onRoms: () -> Unit,
|
||||
onFiles: () -> Unit,
|
||||
onLaunchEmulator: (path: String) -> Unit,
|
||||
onEmulatorHelp: () -> Unit,
|
||||
onLogs: () -> Unit,
|
||||
onCredits: () -> Unit,
|
||||
) {
|
||||
@@ -168,6 +175,28 @@ fun MainScreen(
|
||||
) { Text(if (state.desktopInstalled) "Desktop" else "Desktop — install it under Desktop & apps", fontSize = 13.sp * k, maxLines = 1, overflow = TextOverflow.Ellipsis) }
|
||||
CogButton(k, onDesktopSettings)
|
||||
}
|
||||
// Every installed emulator, as a session of its own under gamescope (where the GPU
|
||||
// is); the ? says why they are here and not on the desktop.
|
||||
if (state.emulators.isNotEmpty()) {
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp * k),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp * k),
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 5.dp * k),
|
||||
) {
|
||||
for ((label, path) in state.emulators) {
|
||||
OutlinedButton(
|
||||
onClick = { onLaunchEmulator(path) }, enabled = state.ready && !state.busy,
|
||||
contentPadding = PaddingValues(horizontal = 10.dp * k, vertical = 0.dp),
|
||||
modifier = Modifier.height(30.dp * k),
|
||||
) { Text("▶ $label", fontSize = 11.sp * k, maxLines = 1) }
|
||||
}
|
||||
OutlinedButton(
|
||||
onClick = onEmulatorHelp,
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
modifier = Modifier.width(30.dp * k).height(30.dp * k),
|
||||
) { Text("?", fontSize = 12.sp * k) }
|
||||
}
|
||||
}
|
||||
// Offline is read while the client starts, so it is decided here rather than in the
|
||||
// session's drawer, and it needs credentials from an earlier sign-in to be possible.
|
||||
OutlinedButton(
|
||||
@@ -265,6 +294,44 @@ private fun MenuTile(
|
||||
}
|
||||
}
|
||||
|
||||
/** The ? beside the emulator row: what the ▶ buttons are, how to use them, and why. */
|
||||
@Composable
|
||||
fun EmulatorHelpDialog(onDismiss: () -> Unit) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text("Emulators") },
|
||||
text = {
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
Text("What the ▶ buttons are", style = MaterialTheme.typography.titleSmall)
|
||||
Text(
|
||||
"One per emulator installed under Desktop & apps. Each starts that emulator fullscreen, as a " +
|
||||
"session of its own under gamescope — the same way the Steam client runs — with the Steam " +
|
||||
"cog's display, driver and HDR settings.",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text("How to use them", style = MaterialTheme.typography.titleSmall)
|
||||
Text(
|
||||
"Put your games in the ROMs folder (main screen). In the emulator, open root › ROMs " +
|
||||
"(/root/ROMs); the whole phone is at root › Storage. Firmware and BIOS files go in the " +
|
||||
"same way (RPCS3: File › Install Firmware). Back opens the drawer; Stop session ends it.",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text("Why not from the desktop", style = MaterialTheme.typography.titleSmall)
|
||||
Text(
|
||||
"The desktop draws in software (labwc on pixman) and cannot hand a program the GPU: a Vulkan " +
|
||||
"emulator started there dies at its first frame (\"Surface lost\"). gamescope can, so an " +
|
||||
"emulator that renders with Vulkan or OpenGL belongs here. The desktop stays for files, " +
|
||||
"Firefox and anything that draws in software.",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
}
|
||||
},
|
||||
confirmButton = { TextButton(onClick = onDismiss) { Text("OK") } },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ConfirmDialog(title: String, text: String, confirm: String, onConfirm: () -> Unit, onDismiss: () -> Unit) {
|
||||
AlertDialog(
|
||||
|
||||
Reference in new issue
Block a user