Compare commits

...
6 changed files with 97 additions and 60 deletions

No files matched your search

@@ -475,10 +475,6 @@ class MainActivity : ComponentActivity() {
}
}, "share-logs").start()
},
onOffline = {
OfflineMode.setEnabled(this, !OfflineMode.enabled(this))
offline = OfflineMode.enabled(this)
},
onPageBack = { settingsMode = null; showPerformance = false; showProtons = false; showComponents = false; showMapping = false },
onTheme = { id -> SessionPrefs.setTheme(this, id); theme = id },
onLauncherFullscreen = { on ->
@@ -964,6 +960,8 @@ class MainActivity : ComponentActivity() {
deckyLatestRelease = if (mode == SessionService.MODE_STEAM) decky.deckyReleases.firstOrNull() else null,
deckyChecking = decky.deckyChecking, deckyStage = decky.deckyStage, deckyPercent = decky.deckyPercent,
deckyEnabled = decky.deckySupervisor, deckySessionRunning = SessionState.running,
offlineAccount = if (mode == SessionService.MODE_STEAM) offlineAccount else null,
offline = mode == SessionService.MODE_STEAM && offline,
),
ModeSettingsActions(
onResolution = { cap -> SessionPrefs.setResolutionCap(this, mode, cap); resolutionCap = cap },
@@ -1009,6 +1007,10 @@ class MainActivity : ComponentActivity() {
SessionPrefs.setRunSteamAtStartup(this, on)
runSteamAtStartup = on
},
onOffline = {
OfflineMode.setEnabled(this, !OfflineMode.enabled(this))
offline = OfflineMode.enabled(this)
},
onPickAddedGamesDir = { pickAddedGamesDir.launch(InAppFilePicker.buildDirIntent(this, "Choose a folder of your own games", addedGamesDirs.lastOrNull())) },
onAddedGamesArt = { on -> SessionPrefs.setAddedGamesArt(this, on); addedGamesArt = on; if (on) refreshAddedGames() },
onForgetAddedGamesDir = { dir -> SessionPrefs.setAddedGamesDirs(this, addedGamesDirs - dir); addedGamesDirs = SessionPrefs.addedGamesDirs(this); refreshAddedGames(); refresh() },
@@ -1039,6 +1041,12 @@ class MainActivity : ComponentActivity() {
gamescopeRealtime = gamescopeRealtime,
gpuClockPin = gpuClockPin,
prootNoSeccomp = prootNoSeccomp, guestHostname = guestHostname, phantomWarning = phantomWarning,
frameGenEngine = FrameGen.engine(this), frameGenMultiplier = FrameGen.multiplier(this),
lsfgReady = LsfgNative.isInstalled(this),
onFrameGenPick = { engine, multiplier ->
FrameGen.set(this, engine, multiplier)
frameGenLabel = FrameGen.label(this)
},
onClientOverride = { on -> SessionPrefs.setClientCpusOverride(this, on); clientOverride = on },
onTuSysmem = { on -> SessionPrefs.setTuSysmem(this, on); tuSysmem = on },
onZinkLazy = { on -> SessionPrefs.setZinkLazy(this, on); zinkLazy = on },
@@ -153,7 +153,6 @@ class FrontEndActions(
val onBrowseFiles: (File) -> Unit = {},
val onLogs: () -> Unit,
val onShareLogs: () -> Unit = {},
val onOffline: () -> Unit,
val onPageBack: () -> Unit = {},
val onTheme: (String) -> Unit = {},
val onLauncherFullscreen: (Boolean) -> Unit = {},
@@ -59,7 +59,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalInputModeManager
@@ -74,7 +73,6 @@ import com.droiddeck.launcher.core.DeviceSupport
import com.droiddeck.launcher.core.PhantomProcessLimit
import com.droiddeck.launcher.core.PhantomProcessStatus
import com.droiddeck.launcher.session.SessionPrefs
import com.droiddeck.launcher.R
// The Setup page: runtime and device checks, tools, frame generation and launch settings.
@@ -153,18 +151,15 @@ internal fun SetupPanel(
}
val colors = MaterialTheme.colorScheme
val pal = LocalPalette.current
// Tested hardware passes; an Adreno below it (a 610, say) warns rather than claiming support.
val gpu = remember { com.droiddeck.launcher.gpu.GpuInfo.detect() }
val gpuOk = gpu.support == com.droiddeck.launcher.gpu.GpuInfo.Support.TESTED
val gpuOk = remember { DeviceSupport.adreno() }
val gpuName = remember { DeviceSupport.gpuName() }
val limitBlocks = PhantomProcessLimit.blocksSteam(s.phantomProcessStatus)
val signedIn = s.offlineAccount != null
var showLimitDetails by rememberSaveable { mutableStateOf(false) }
val checks = 4
val readyCount = listOf(gpuOk, s.ready && !s.busy, !limitBlocks, signedIn).count { it }
// Four tabs instead of one long scroll; LB and RB turn them from anywhere on the page. Build
// and credits are on the Updates page.
val tabs = listOf("Overview", "Controller", "Session", "Launcher")
// Four durable buckets instead of a miscellaneous Session tab; LB and RB turn them from anywhere on the page.
val tabs = listOf("Overview", "Controller", "Launcher", "Support")
var tab by rememberSaveable { mutableStateOf(0) }
val tabFocus = remember { List(tabs.size) { FocusRequester() } }
var tabTurned by remember { mutableStateOf(false) }
@@ -197,16 +192,8 @@ internal fun SetupPanel(
Column(modifier = Modifier.fillMaxWidth().clip(Shape14).background(colors.surface).border(1.dp, pal.line, Shape14)) {
CheckRow(
if (gpuOk) CheckState.OK else CheckState.WARN,
when (gpu.support) {
com.droiddeck.launcher.gpu.GpuInfo.Support.TESTED -> "Device supported"
com.droiddeck.launcher.gpu.GpuInfo.Support.UNTESTED -> "Untested GPU"
else -> "GPU not supported"
},
when (gpu.support) {
com.droiddeck.launcher.gpu.GpuInfo.Support.TESTED -> "${gpu.name} · $gpuName"
com.droiddeck.launcher.gpu.GpuInfo.Support.UNTESTED -> "${gpu.name}: ${gpu.supportText.replaceFirstChar { it.lowercase() }}"
else -> "Steam draws with an Adreno driver; $gpuName may show a black screen"
},
if (gpuOk) "Device supported" else "GPU not supported",
if (gpuOk) gpuName else "Steam draws with an Adreno driver; $gpuName may show a black screen",
)
CheckRow(
when { s.busy -> CheckState.BUSY; !s.ready -> CheckState.WARN; else -> CheckState.OK },
@@ -271,9 +258,7 @@ internal fun SetupPanel(
ControllerRows(host, s.oscMode, controller, a.controller)
}
if (s.controller == null || a.controller == null) Note("Controller settings are unavailable.")
}
2 -> {
SettingsGroup("Session") {
SettingsGroup("Navigation") {
ChoiceRow(
host, "back-actions", "Back", SessionPrefs.backActionsOrder(s.backActionsInverted),
listOf(
@@ -281,22 +266,9 @@ internal fun SetupPanel(
true to SessionPrefs.BACK_QAM_THEN_MENU,
), s.backActionsInverted, onPick = a.onBackActionsInverted,
)
SettingsRow(stringResource(R.string.frame_gen_title), stringResource(R.string.frame_gen_hint)) {
Box {
ValueChip(s.frameGenLabel, host.open == "fg") { host.open = if (host.open == "fg") null else "fg" }
FrameGenMenu(s, a, host)
}
}
ToggleRow(host, "logs", "Session logs", "Saved after each session", s.logsEnabled) { a.onLogs() }
ActionRow("Latest session logs", "Send them with a bug report", "Share logs", a.onShareLogs)
ToggleRow(
host, "offline", "Offline mode",
s.offlineAccount?.let { "Signed in as $it" } ?: "Sign in to Steam first",
s.offline, enabled = s.offlineAccount != null,
) { a.onOffline() }
}
}
3 -> {
2 -> {
SettingsGroup("Launcher") {
SettingsRow("Theme", "Choose the launcher appearance") {
Box {
@@ -340,6 +312,12 @@ internal fun SetupPanel(
) { a.onAppImagesEnabled(it) }
}
}
else -> {
SettingsGroup("Support") {
ToggleRow(host, "logs", "Session logs", "Saved after each session", s.logsEnabled) { a.onLogs() }
ActionRow("Latest session logs", "Send them with a bug report", "Share logs", a.onShareLogs)
}
}
}
}
}
@@ -353,7 +331,7 @@ private fun ToolGrid(s: FrontEndState, a: FrontEndActions) {
val tools = listOf(
ToolSpec(Icons.Outlined.Folder, "Files", "Browse and manage files", a.onFiles),
ToolSpec(Icons.Outlined.Extension, "Proton versions", "Install ARM64 Proton builds", a.onProtons),
ToolSpec(Icons.Outlined.Speed, "Performance", "CPU core assignment", a.onPerformance),
ToolSpec(Icons.Outlined.Speed, "Performance", "CPU, rendering and session tuning", a.onPerformance),
ToolSpec(Icons.Outlined.VideogameAsset, "ROMs folder", s.romsDir ?: "Choose where emulator games are stored", a.onRoms),
)
Column(verticalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.fillMaxWidth()) {
@@ -382,7 +360,7 @@ private fun ToolCard(t: ToolSpec, modifier: Modifier) {
.graphicsLayer { scaleX = scale; scaleY = scale }
.clip(Shape14)
.background(if (hot) pal.signal.copy(alpha = 0.10f) else colors.surface)
.glideBorder(hot, Shape14, pal.signal, pal.line)
.border(if (hot) 2.dp else 1.dp, if (hot) pal.signal else pal.line, Shape14)
.hoverable(src).clickable(interactionSource = src, indication = LocalIndication.current, role = Role.Button, onClick = t.onClick)
.controllerConfirm(onClick = t.onClick)
.padding(horizontal = 14.dp, vertical = 12.dp),
@@ -395,7 +373,7 @@ private fun ToolCard(t: ToolSpec, modifier: Modifier) {
@Composable
private fun FrameGenMenu(s: FrontEndState, a: FrontEndActions, host: MenuHost) {
FrameGenMenu(host, s.frameGen, s.lossless, a.onFrameGenPick, a.onImportLossless)
FrameGenMenu(host, s.frameGenEngine, s.frameGenMultiplier, s.lsfgReady, a.onFrameGenPick)
}
/**
@@ -411,7 +389,7 @@ internal fun LaunchSettings(s: FrontEndState, a: FrontEndActions, host: MenuHost
add { m -> SettingCard("Components", "FEX, DXVK, VKD3D", "card:components", m) { a.onComponents(true) } }
add { m ->
Box(m) {
SettingCard(stringResource(R.string.frame_gen_title), s.frameGenLabel, "card:fg", Modifier.fillMaxSize()) {
SettingCard("Frame generation", s.frameGenLabel, "card:fg", Modifier.fillMaxSize()) {
host.open = if (host.open == "fg") null else "fg"
}
FrameGenMenu(s, a, host)
@@ -437,13 +415,14 @@ private fun SettingCard(label: String, value: String, id: String, modifier: Modi
val hot = rememberHot(src)
val pressed by src.collectIsPressedAsState()
val scale by animateFloatAsState(if (pressed) 0.97f else 1f, Motion.sp(0.5f, Spring.StiffnessMedium), label = "cardScale")
val edge by animateColorAsState(if (hot) pal.signal else pal.line2, Motion.tw(220), label = "cardEdge")
Column(
verticalArrangement = Arrangement.spacedBy(3.dp),
modifier = modifier.paneItem(id)
.graphicsLayer { scaleX = scale; scaleY = scale }
.clip(Shape14)
.background(if (hot) pal.signal.copy(alpha = 0.10f) else colors.surface)
.glideBorder(hot, Shape14, pal.signal, pal.line2)
.border(if (hot) 2.dp else 1.dp, edge, Shape14)
.hoverable(src).clickable(interactionSource = src, indication = LocalIndication.current, role = Role.Button, onClick = onClick)
.controllerConfirm(onClick = onClick)
.padding(horizontal = 14.dp, vertical = 12.dp),
@@ -1,6 +1,7 @@
package com.droiddeck.launcher.ui
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.Row
@@ -15,6 +16,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import com.droiddeck.launcher.R
@@ -78,6 +80,9 @@ class ModeSettings(
val steamController: String? = null,
/** Steam only: start a Steam session when DroidDeck opens. */
val runSteamAtStartup: Boolean = false,
/** Steam only: remembered account and requested offline state. */
val offlineAccount: String? = null,
val offline: Boolean = false,
/** Steam only: the user's chosen Games folders; null outside Steam. */
val addedGamesDirs: List<String>? = null,
val addedGames: List<AddedGameRow> = emptyList(),
@@ -122,6 +127,7 @@ class ModeSettingsActions(
val onMangoapp: (Boolean) -> Unit = {},
val onSteamController: (String) -> Unit = {},
val onRunSteamAtStartup: (Boolean) -> Unit = {},
val onOffline: () -> Unit = {},
val onPickAddedGamesDir: () -> Unit = {},
val onForgetAddedGamesDir: (path: String) -> Unit = {},
val onAddedGamesArt: (Boolean) -> Unit = {},
@@ -140,6 +146,22 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) {
val host = rememberMenuHost()
var confirmDeckyRemoval by remember { mutableStateOf(false) }
val pageScroll = androidx.compose.foundation.rememberScrollState()
val tabs = if (steam) listOf("Display", "Controls", "Runtime", "Steam", "Games")
else listOf("Display", "Controls", "Runtime")
var tab by rememberSaveable(s.mode) { mutableStateOf(0) }
val tabFocus = remember { List(tabs.size) { androidx.compose.ui.focus.FocusRequester() } }
var tabTurned by remember { mutableStateOf(false) }
val inputModeManager = androidx.compose.ui.platform.LocalInputModeManager.current
val pickTab: (Int) -> Unit = { index -> host.open = null; tab = index; tabTurned = true }
androidx.compose.runtime.LaunchedEffect(tab) {
if (tabTurned) {
pageScroll.scrollTo(0)
if (inputModeManager.inputMode == androidx.compose.ui.input.InputMode.Keyboard) {
androidx.compose.runtime.withFrameNanos { }
runCatching { tabFocus[tab].requestFocus() }
}
}
}
val firstChip = remember { androidx.compose.ui.focus.FocusRequester() }
androidx.compose.runtime.LaunchedEffect(Unit) {
// One frame first: the box has to be laid out before it can take focus. Opened from the
@@ -153,7 +175,14 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) {
onBack = a.onDismiss,
scroll = pageScroll,
) {
SettingsGroup("Display") {
Column(
modifier = Modifier.fillMaxWidth().bumpers(
onPrevious = { pickTab((tab + tabs.size - 1) % tabs.size) },
onNext = { pickTab((tab + 1) % tabs.size) },
),
) {
TabStrip(tabs, tab, pickTab, Modifier.padding(bottom = 4.dp), tabFocus)
if (tab == 0) SettingsGroup("Display") {
val default = SessionPrefs.defaultResolutionCap(s.mode)
var editCustom by remember { mutableStateOf(false) }
val custom = s.customResolution
@@ -183,7 +212,7 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) {
onDismiss = { editCustom = false },
)
}
SettingsGroup("HDR") {
if (tab == 0) SettingsGroup("HDR") {
ToggleRow(
host, "hdr", "HDR10 output",
s.hdrReason?.let { "Not available: $it." }
@@ -191,12 +220,12 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) {
checked = s.hdr && s.hdrReason == null, enabled = s.hdrReason == null, onChange = a.onHdr,
)
}
SettingsGroup("Drivers") {
if (tab == 2) SettingsGroup("Drivers") {
SettingsRow("GPU drivers", "Shared by Steam and the desktop, on the Components page.") {
ValueChip(s.gpuDrivers, open = false) { a.onGpuDrivers() }
}
}
SettingsGroup(if (steam) "Touch & controls" else "Touch") {
if (tab == 1) SettingsGroup(if (steam) "Touch & controls" else "Touch") {
ChoiceRow(
host, "touch", "Touch", null,
listOf("auto" to "Auto", "touchpad" to "Touchpad", "direct" to "Direct"), s.touchMode,
@@ -231,7 +260,7 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) {
), s.backActionsInverted, onPick = a.onBackActionsInverted,
)
}
SettingsGroup("Session") {
if (tab == 2) SettingsGroup("Session") {
ChoiceRow(
host, "suspend", "Background behavior",
"How this session behaves when the app leaves the screen or the display turns off.",
@@ -245,14 +274,19 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) {
onPick = a.onSuspendPolicy,
)
}
if (steam) SettingsGroup("Startup") {
if (steam && tab == 3) SettingsGroup("Startup & account") {
ToggleRow(
host, "steam-startup", "Run Steam when DroidDeck starts",
"Open the Steam session when you launch DroidDeck.",
s.runSteamAtStartup, onChange = a.onRunSteamAtStartup,
)
ToggleRow(
host, "offline", "Offline mode",
s.offlineAccount?.let { "Signed in as $it" } ?: "Sign in to Steam first",
s.offline, enabled = s.offlineAccount != null,
) { a.onOffline() }
}
if (steam) SettingsGroup("Decky") {
if (steam && tab == 3) SettingsGroup("Decky") {
val updateAvailable = s.deckyInstalled != null && s.deckyLatestRelease != null &&
s.deckyInstalled != s.deckyLatestRelease.tag
val status = when {
@@ -304,7 +338,7 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) {
)
}
}
if (steam && s.steamChannel != null) SettingsGroup("Client") {
if (steam && tab == 3 && s.steamChannel != null) SettingsGroup("Client") {
ToggleRow(
host, "steamdeck", "Steam Deck mode",
"Enables Steam's Deck interface and Quick Access performance overlay controls. Applies next session.",
@@ -324,7 +358,7 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) {
onPick = a.onSteamChannel,
)
}
if (steam && s.addedGamesDirs != null) SettingsGroup("Added games") {
if (steam && tab == 4 && s.addedGamesDirs != null) SettingsGroup("Added games") {
for (dir in s.addedGamesDirs) {
val n = s.addedGames.count { it.folderPath.startsWith("$dir/") }
ActionRow(
@@ -349,7 +383,7 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) {
onPick = { path -> if (path == "__pick__") a.onPickAddedGameExe(g.folderPath) else a.onAddedGameExe(g.folderPath, path) },
)
}
if (steam && s.fexPreset != null) SettingsGroup(stringResource(R.string.game_settings_title)) {
if (steam && tab == 4 && s.fexPreset != null) SettingsGroup(stringResource(R.string.game_settings_title)) {
ChoiceRow(
host, "fex", stringResource(R.string.fex_preset_title), stringResource(R.string.fex_next_launch),
FexPreset.all.map { it.id to stringResource(it.label) }, s.fexPreset,
@@ -362,7 +396,7 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) {
s.forceFullscreen, onChange = a.onForceFullscreen,
)
}
if (steam && s.directAudio != null && s.mic != null) SettingsGroup("Audio") {
if (steam && tab == 2 && s.directAudio != null && s.mic != null) SettingsGroup("Audio") {
ToggleRow(host, "da", "DirectAudio for games", "Bypasses PulseAudio for lower latency in games.", s.directAudio, onChange = a.onDirectAudio)
ChoiceRow(
host, "clientAudio", "Steam client audio", "Classic is the AAudio sink from 0.1.5. DirectAudio goes through the relay. Applies next session.",
@@ -371,7 +405,7 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) {
)
ToggleRow(host, "mic", "Microphone", "Uses the device microphone for voice chat.", s.mic, onChange = a.onMic)
}
if (steam && s.gameStorage != null) SettingsGroup("Game storage") {
if (steam && tab == 4 && s.gameStorage != null) SettingsGroup("Game storage") {
val custom = s.gameStorage.isNotEmpty() && s.gameStorage != "off" && s.storageOptions.none { it.second == s.gameStorage }
val options = buildList {
add("" to ("Automatic - the SD card when one is in" + (if (s.storageOptions.isEmpty()) " (none right now)" else "")))
@@ -402,7 +436,7 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) {
}
}
}
if (!steam && s.renderer != null) SettingsGroup("Renderer") {
if (!steam && tab == 2 && s.renderer != null) SettingsGroup("Renderer") {
ChoiceRow(
host, "renderer", "Desktop renderer", "Composites the desktop.",
listOf("vulkan" to "vulkan - GPU", "gles2" to "gles2 - GPU (experimental)", "pixman" to "pixman - software"), s.renderer,
@@ -412,6 +446,7 @@ fun ModeSettingsPage(s: ModeSettings, a: ModeSettingsActions) {
onPick = a.onRenderer,
)
}
}
}
if (confirmDeckyRemoval) AlertDialog(
onDismissRequest = { confirmDeckyRemoval = false },
@@ -22,6 +22,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.droiddeck.launcher.gpu.FrameGen
import com.droiddeck.launcher.session.SessionPrefs
class CoreRow(val core: Int, val label: String)
@@ -42,6 +43,10 @@ fun PerformancePage(
prootNoSeccomp: Boolean,
guestHostname: String,
phantomWarning: String?,
frameGenEngine: String,
frameGenMultiplier: Int,
lsfgReady: Boolean,
onFrameGenPick: (engine: String, multiplier: Int) -> Unit,
onClientOverride: (Boolean) -> Unit,
onTuSysmem: (Boolean) -> Unit,
onZinkLazy: (Boolean) -> Unit,
@@ -61,9 +66,20 @@ fun PerformancePage(
val coreItems = cores.map { it.core to it.label }
SettingsPage(
host, title = "Performance",
lede = "CPU cores, session fixes and the host name. Applies next session.",
lede = "Rendering, CPU cores and session tuning. Most changes apply next session.",
onBack = onDismiss,
) {
SettingsGroup("Rendering") {
val fgOpen = host.open == "fg"
SettingsRow("Frame generation", "Default for game launches; it can still be changed per game or while running.", highlighted = fgOpen) {
androidx.compose.foundation.layout.Box {
ValueChip(FrameGen.label(frameGenEngine, frameGenMultiplier), fgOpen) {
host.open = if (fgOpen) null else "fg"
}
FrameGenMenu(host, frameGenEngine, frameGenMultiplier, lsfgReady, onFrameGenPick)
}
}
}
SettingsGroup("Steam client cores") {
ToggleRow(
host, "override", "Override Steam's own core choice",
@@ -124,7 +124,7 @@ import com.droiddeck.launcher.input.SecondScreenDisplay
import com.droiddeck.launcher.input.SecondScreenMode
import kotlinx.coroutines.flow.collect
private val drawerPageTitles = listOf("Display", "Controls", "Components", "Settings")
private val drawerPageTitles = listOf("Display", "Controls", "Components", "Session")
/** One icon per drawer page, in page order (QAM-style tabs). */
private val drawerPageIcons = listOf(Icons.Outlined.DesktopWindows, Icons.Outlined.SportsEsports, Icons.Outlined.Layers, Icons.Outlined.Settings)