mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-15 03:37:09 -04:00
Merge pull request #24 from Droid-Deck/feat/drawer-tab-icons
Session drawer: QAM-style page tabs (Display, Controls, Settings)
This commit is contained in:
1 file changed
+73
-28
@@ -1,5 +1,14 @@
|
||||
package com.droiddeck.launcher.ui
|
||||
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.DesktopWindows
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material.icons.outlined.SportsEsports
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import android.view.Display
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.Image
|
||||
@@ -86,7 +95,9 @@ import com.droiddeck.launcher.input.SecondScreenDisplay
|
||||
import com.droiddeck.launcher.input.SecondScreenMode
|
||||
import kotlinx.coroutines.flow.collect
|
||||
|
||||
private val drawerPageTitles = listOf("Now", "Controls", "Session")
|
||||
private val drawerPageTitles = listOf("Display", "Controls", "Settings")
|
||||
/** One icon per drawer page, in page order (QAM-style tabs). */
|
||||
private val drawerPageIcons = listOf(Icons.Outlined.DesktopWindows, Icons.Outlined.SportsEsports, Icons.Outlined.Settings)
|
||||
private val drawerPageEntries = listOf("hud", "touch", "suspend")
|
||||
|
||||
private class DrawerFocus {
|
||||
@@ -384,32 +395,7 @@ fun SessionDrawer(open: Boolean, page: Int, controllerActive: Boolean, onPageCha
|
||||
DrawerOutlineButton("LB ‹", modifier = Modifier.height(48.dp).then(focus.track(page, "prev"))) {
|
||||
host.open = null; onPageChange((page + 2) % 3)
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.weight(1f),
|
||||
) {
|
||||
drawerPageTitles.forEachIndexed { index, title ->
|
||||
val dotSource = remember { MutableInteractionSource() }
|
||||
val dotFocused = dotSource.collectIsFocusedAsState().value
|
||||
val select = { host.open = null; onPageChange(index) }
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier.size(38.dp)
|
||||
.semantics { contentDescription = "$title page" }
|
||||
.hoverable(dotSource)
|
||||
.clickable(interactionSource = dotSource, indication = LocalIndication.current, onClick = select)
|
||||
.controllerConfirm(onClick = select),
|
||||
) {
|
||||
Box(
|
||||
Modifier.size(if (index == page) 16.dp else 10.dp)
|
||||
.clip(CircleShape)
|
||||
.background(if (index == page) pal.signal else colors.onSurfaceVariant)
|
||||
.border(if (dotFocused) 2.dp else 0.dp, colors.onBackground, CircleShape),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
DrawerPageTabs(page = page, modifier = Modifier.weight(1f)) { index -> host.open = null; onPageChange(index) }
|
||||
DrawerOutlineButton("› RB", modifier = Modifier.height(48.dp).then(focus.track(page, "next"))) {
|
||||
host.open = null; onPageChange((page + 1) % 3)
|
||||
}
|
||||
@@ -419,7 +405,7 @@ fun SessionDrawer(open: Boolean, page: Int, controllerActive: Boolean, onPageCha
|
||||
modifier = Modifier.weight(1f).fillMaxWidth().verticalScroll(pageScroll[page]),
|
||||
) {
|
||||
when (page) {
|
||||
0 -> SettingsGroup("Now") {
|
||||
0 -> SettingsGroup("Display") {
|
||||
ToggleRow(host, "hud", "Performance HUD", null, a.hudOn,
|
||||
chipModifier = focus.track(page, "hud"), onChange = a.onHud)
|
||||
if (a.fillScreen != null) ToggleRow(
|
||||
@@ -637,3 +623,62 @@ private fun ShareSessionLogsButton(modifier: Modifier = Modifier, onClick: () ->
|
||||
color = if (hot) pal.signal else colors.onSurfaceVariant, maxLines = 1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The drawer's page tabs, QAM-style: one icon per page standing in a line in page order. The
|
||||
* selected one steps forward - full size, bright, a soft glow - while the others step back to
|
||||
* half size and fade, and the whole line leans a little toward the selection. LB / RB still turn
|
||||
* the page (the buttons beside it and the pad's bumpers); a tab can be tapped or picked with the
|
||||
* pad like the dots it replaces.
|
||||
*/
|
||||
@Composable
|
||||
private fun DrawerPageTabs(page: Int, modifier: Modifier = Modifier, onSelect: (Int) -> Unit) {
|
||||
val colors = MaterialTheme.colorScheme
|
||||
val pal = LocalPalette.current
|
||||
val count = drawerPageIcons.size
|
||||
val middle = (count - 1) / 2f
|
||||
val motion = tween<Float>(durationMillis = 340, easing = FastOutSlowInEasing)
|
||||
val lean by animateFloatAsState(-(page - middle) * DRAWER_TAB_LEAN_DP, motion, label = "tabLean")
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = modifier.height(64.dp).clipToBounds(),
|
||||
) {
|
||||
drawerPageIcons.forEachIndexed { index, icon ->
|
||||
val selected = index == page
|
||||
val source = remember { MutableInteractionSource() }
|
||||
val focused = source.collectIsFocusedAsState().value
|
||||
val scale by animateFloatAsState(if (selected) 1f else DRAWER_TAB_SIDE_SCALE, motion, label = "tabScale")
|
||||
val alpha by animateFloatAsState(if (selected) 1f else DRAWER_TAB_SIDE_ALPHA, motion, label = "tabAlpha")
|
||||
val glow by animateFloatAsState(if (selected) 1f else 0f, motion, label = "tabGlow")
|
||||
val tint by animateColorAsState(if (selected) colors.onBackground else colors.onSurfaceVariant, tween(340), label = "tabTint")
|
||||
val select = { onSelect(index) }
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.offset(x = ((index - middle) * DRAWER_TAB_SPACING_DP + lean).dp)
|
||||
.size(56.dp)
|
||||
.graphicsLayer { scaleX = scale; scaleY = scale; this.alpha = alpha }
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(
|
||||
Brush.radialGradient(
|
||||
listOf(pal.signal.copy(alpha = 0.34f * glow), pal.signal.copy(alpha = 0.10f * glow), Color.Transparent),
|
||||
),
|
||||
)
|
||||
.border(if (focused) 2.dp else 0.dp, if (focused) colors.onBackground else Color.Transparent, RoundedCornerShape(16.dp))
|
||||
.semantics { contentDescription = "${drawerPageTitles[index]} page" }
|
||||
.hoverable(source)
|
||||
.clickable(interactionSource = source, indication = LocalIndication.current, onClick = select)
|
||||
.controllerConfirm(onClick = select),
|
||||
) {
|
||||
Icon(icon, contentDescription = null, tint = tint, modifier = Modifier.size(30.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The tab row's measure, from the approved mock: side tabs at half size, a little faded. */
|
||||
private const val DRAWER_TAB_SPACING_DP = 64f
|
||||
private const val DRAWER_TAB_LEAN_DP = 12f
|
||||
private const val DRAWER_TAB_SIDE_SCALE = 0.5f
|
||||
private const val DRAWER_TAB_SIDE_ALPHA = 0.7f
|
||||
|
||||
Reference in new issue
Block a user