mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 19:11:08 +00:00
feat(ui): refine the sheet's beats, the lip's return and the hop's sink and swell
This commit is contained in:
1 parent
1b75436bc9
commit
b9d4ad2b0a
5 files changed
+58
-7
No files matched your search
@@ -138,12 +138,12 @@ internal fun Pane(
|
||||
pages[s.pageKey] = page
|
||||
if (s.pageKey !in origins) PageOrigin.take()?.let { o ->
|
||||
// A hop's page came out of its own rail item, and is that item's content.
|
||||
origins[s.pageKey] = o.translate(-paneAt[0]).let { if (it.owner == null) it.ownedBy(railContent) else it }
|
||||
origins[s.pageKey] = inPane(o.translate(-paneAt[0])).let { if (it.owner == null) it.ownedBy(railContent) else it }
|
||||
}
|
||||
} else if (target != before[0] && target !in origins) {
|
||||
// A section's own page opened from a control in it (an emulator's card, a ROM's) floods
|
||||
// out of that control and belongs to what was showing; a hop's comes out of its rail item.
|
||||
PageOrigin.take()?.let { o -> origins[target] = o.translate(-paneAt[0]).let { if (it.owner == null) it.ownedBy(before[0]) else it } }
|
||||
PageOrigin.take()?.let { o -> origins[target] = inPane(o.translate(-paneAt[0])).let { if (it.owner == null) it.ownedBy(before[0]) else it } }
|
||||
}
|
||||
before[0] = target
|
||||
val targetNow by rememberUpdatedState(target)
|
||||
@@ -175,8 +175,16 @@ internal fun Pane(
|
||||
},
|
||||
label = "pane",
|
||||
) { key ->
|
||||
val shown = (if (page != null && key == s.pageKey) page else pages[key])
|
||||
val drawn = (if (page != null && key == s.pageKey) page else pages[key])
|
||||
?: { Content(s, if (key == "games") gameShown[0] else key, a, Modifier.fillMaxSize(), onSelect, onAndroidAppClick, onOpenDeveloperOptions, onRequestWirelessAdb) }
|
||||
// A hop leaving this content: it sinks back as the dot leaves it (and comes forward
|
||||
// again, freshly drawn, when the hop is undone).
|
||||
val hopSink by androidx.compose.animation.core.animateFloatAsState(
|
||||
if (Hops.sinking == key) 1f else 0f, Motion.tw(260), label = "hopSink",
|
||||
)
|
||||
val shown: @Composable () -> Unit = {
|
||||
Box(Modifier.fillMaxSize().graphicsLayer { val k = 1f - 0.05f * hopSink; scaleX = k; scaleY = k; alpha = 1f - 0.35f * hopSink }) { drawn() }
|
||||
}
|
||||
run {
|
||||
DisposableEffect(key) { onDispose { if (key != livePage && key != targetNow) { pages.remove(key); origins.remove(key) } } }
|
||||
val from = origins[key]
|
||||
@@ -204,6 +212,14 @@ internal fun Pane(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An origin left of the pane (a rail item a hop flew to) starts half inside it, at the item's
|
||||
* height: the page swells out of the pane's edge beside the item. Wholly outside, its tile was a
|
||||
* bar that raced across from the far edge.
|
||||
*/
|
||||
internal fun inPane(o: Origin): Origin =
|
||||
if (o.bounds.right > 0f) o else o.translate(Offset(-o.bounds.right + o.bounds.width / 2f, 0f))
|
||||
|
||||
@Composable
|
||||
private fun Backdrop(art: File?) {
|
||||
val colors = MaterialTheme.colorScheme
|
||||
|
||||
@@ -483,6 +483,7 @@ private fun FrontEndScreenBody(s: FrontEndState, a: FrontEndActions, page: (@Com
|
||||
// Another section: no way back to keep, and no sheet to put back.
|
||||
Hops.origin = null
|
||||
Hops.restore = null
|
||||
Hops.sinking = null
|
||||
// Picked from the rail, Desktop starts at its hero; back from one of its pages it is where it was.
|
||||
if (key == "desktop" && selected != "desktop") DesktopMemory.scroll = 0
|
||||
selected = key
|
||||
|
||||
@@ -60,6 +60,9 @@ internal object Hops {
|
||||
/** The hop the page on screen was reached by, while B can still undo it. */
|
||||
var origin by mutableStateOf<HopOrigin?>(null)
|
||||
|
||||
/** The pane content a hop is leaving (a rail key): it sinks back as the dot leaves it. */
|
||||
var sinking by mutableStateOf<String?>(null)
|
||||
|
||||
/** Set while a hop's dot is flying: the rail item its selection has already glided to. */
|
||||
var railPreview by mutableStateOf<String?>(null)
|
||||
|
||||
@@ -100,6 +103,8 @@ internal object Hops {
|
||||
arrived++
|
||||
}
|
||||
if (railAt == null || Motion.scale == 0f) { finish(); return }
|
||||
// The surface the control is on sinks as its dot leaves, and stays down as it goes.
|
||||
sinking = origin.rail
|
||||
Flights.fly(Flight(
|
||||
origin.bounds, to = { railAt.center },
|
||||
onHalf = { railPreview = origin.dest },
|
||||
@@ -114,6 +119,7 @@ internal object Hops {
|
||||
fun back(close: () -> Unit): Boolean {
|
||||
val o = origin ?: return false
|
||||
origin = null
|
||||
sinking = null
|
||||
val railAt = railBounds[o.dest]
|
||||
railPreview = if (railAt != null && Motion.scale != 0f) o.dest else null
|
||||
restore = o
|
||||
@@ -130,7 +136,7 @@ internal object Hops {
|
||||
/** The page the hop opened has gone some other way: there is no way back to keep. */
|
||||
fun forget(pageKey: String?) {
|
||||
val o = origin ?: return
|
||||
if (pageKey != o.page) origin = null
|
||||
if (pageKey != o.page) { origin = null; sinking = null }
|
||||
}
|
||||
|
||||
/** Screen density, for the rail item's corner; set by the front end. */
|
||||
|
||||
@@ -221,6 +221,8 @@ internal fun LiftHero(
|
||||
var travel by remember { mutableStateOf(1) }
|
||||
// The control to focus once a sheet put back by a hop's B has lifted, instead of the first chip.
|
||||
var focusOnLift by remember { mutableStateOf<String?>(null) }
|
||||
// Set as the sheet closes and Play lands: the lip may come back before the sheet has quite gone.
|
||||
var lipBack by remember { mutableStateOf(false) }
|
||||
val pull = remember { Animatable(0f) }
|
||||
val sink = remember { Animatable(0f) }
|
||||
val lift = remember { Animatable(0f) }
|
||||
@@ -268,6 +270,7 @@ internal fun LiftHero(
|
||||
}
|
||||
LaunchedEffect(open, launching) {
|
||||
if (open && !launching) {
|
||||
lipBack = false
|
||||
shown = true
|
||||
onOpen()
|
||||
fold.snapTo(0f); foldTop.snapTo(0f)
|
||||
@@ -284,7 +287,8 @@ internal fun LiftHero(
|
||||
launch { ride.animateTo(1f, Motion.sp(0.7f, 380f)) }
|
||||
launch { hot.snapTo(1f); delay(Motion.ms(100).toLong()); hot.animateTo(0f, Motion.tw(200)) }
|
||||
}
|
||||
pieces.forEachIndexed { i, p -> launch { delay(Motion.ms(260 + i * 55).toLong()); p.animateTo(1f, Motion.tw(220)) } }
|
||||
// Each piece on its beat from the Lift, as the reel has it: 260ms + 55ms a piece after it starts.
|
||||
pieces.forEachIndexed { i, p -> launch { delay(Motion.ms(120 + 260 + i * 55).toLong()); p.animateTo(1f, Motion.tw(220)) } }
|
||||
if (inputMode.inputMode == InputMode.Keyboard) launch {
|
||||
val control = focusOnLift
|
||||
focusOnLift = null
|
||||
@@ -313,8 +317,11 @@ internal fun LiftHero(
|
||||
launch { delay(Motion.ms(60).toLong()); lift.animateTo(0f, Motion.sp(1f, 380f)) }
|
||||
launch { delay(Motion.ms(100).toLong()); ride.animateTo(0f, Motion.sp(0.55f, 300f)) }
|
||||
launch { sink.animateTo(0f, Motion.tw(300)) }
|
||||
// The lip tucks back under Play as it lands, not once every spring has settled.
|
||||
launch { delay(Motion.ms(420).toLong()); lipGone.snapTo(0f); lipBack = true }
|
||||
}
|
||||
shown = false
|
||||
lipBack = false
|
||||
lipGone.snapTo(0f)
|
||||
hot.snapTo(1f)
|
||||
} else {
|
||||
@@ -332,11 +339,12 @@ internal fun LiftHero(
|
||||
val touchOnly = inputMode.inputMode == InputMode.Touch
|
||||
val lipFocus = remember { FocusRequester() }
|
||||
var lipFocused by remember { mutableStateOf(false) }
|
||||
val lipWanted = !open && !shown && (playFocused || playHovered || touchOnly || lipFocused)
|
||||
val lipWanted = !open && (!shown || lipBack) && (playFocused || playHovered || touchOnly || lipFocused)
|
||||
val lipNow = rememberUpdatedState(lip)
|
||||
LaunchedEffect(lipWanted) {
|
||||
if (lipWanted) {
|
||||
delay(Motion.ms(160).toLong())
|
||||
// Back from the sheet it comes straight away; otherwise a beat after Play has focus.
|
||||
if (!lipBack) delay(Motion.ms(160).toLong())
|
||||
val now = lipNow.value()
|
||||
lit = lipChanged(LipMemory.seen[key], now)
|
||||
LipMemory.seen[key] = now.toMap()
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.droiddeck.launcher.ui
|
||||
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertSame
|
||||
import org.junit.Test
|
||||
|
||||
class InPaneTest {
|
||||
@Test fun aRailItemLeftOfThePaneStartsHalfInside() {
|
||||
val o = inPane(Origin(Rect(-200f, 300f, -20f, 420f), 14f))
|
||||
assertEquals(-90f, o.bounds.left, 0.01f)
|
||||
assertEquals(90f, o.bounds.right, 0.01f)
|
||||
assertEquals(300f, o.bounds.top, 0.01f)
|
||||
}
|
||||
|
||||
@Test fun aControlInThePaneStaysPut() {
|
||||
val origin = Origin(Rect(40f, 300f, 200f, 360f), 12f)
|
||||
assertSame(origin, inPane(origin))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user