mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-15 03:37:09 -04:00
ui: every way out of a session leaves as a session arrives
Backing out of the loading screen, closing the ended screen and the session ending on its own used the slide-down card exit. Now, while the session is on screen, the blue spreads out of the throbber's ball (the middle of the screen once a game is up) until it covers the screen, and the front end opens on it and draws it back into the button the session came from, as it already did after the drawer's Stop. A retry, a session closing off screen and animations off still close at once; a timeout closes it should the flood never run.
This commit is contained in:
1 parent
c711c627b8
commit
83cc0fe585
3 files changed
+68
-2
No files matched your search
@@ -181,6 +181,10 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
private var quitFrom by mutableStateOf<androidx.compose.ui.geometry.Rect?>(null)
|
||||
/** Left behind that flood: the front end opens on its blue, so no sink of our own. */
|
||||
private var quitFlooded = false
|
||||
/** Leaving behind a flood spreading out of the throbber (FloodSpread); see finish(). */
|
||||
private var leaving by mutableStateOf(false)
|
||||
/** Close without the leaving flood: a retry opens a fresh session at once. */
|
||||
private var closeAtOnce = false
|
||||
/** The drawer's Components tab: the Protons as last read (ComponentsManager). */
|
||||
private var drawerComponents by mutableStateOf<ComponentsManager.Snapshot?>(null)
|
||||
private var drawerPage by mutableIntStateOf(0)
|
||||
@@ -500,6 +504,10 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
title = pausedTitle(),
|
||||
onResume = { SessionService.resume(this@SessionActivity) },
|
||||
)
|
||||
if (leaving) com.droiddeck.launcher.ui.FloodSpread(
|
||||
com.droiddeck.launcher.ui.LocalPalette.current.signal,
|
||||
ball = if (loading.visible) com.droiddeck.launcher.ui.ThrobberSpot.ball else null,
|
||||
) { completeLeave() }
|
||||
quitFrom?.let { from ->
|
||||
val error = androidx.compose.material3.MaterialTheme.colorScheme.error
|
||||
com.droiddeck.launcher.ui.LaunchFlood(
|
||||
@@ -1062,6 +1070,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
/** The same start again, in a fresh activity: the ended one keeps no session to reuse. */
|
||||
private fun retrySession() {
|
||||
val again = Intent(intent)
|
||||
closeAtOnce = true
|
||||
finish()
|
||||
startActivity(again)
|
||||
}
|
||||
@@ -1735,14 +1744,41 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
|
||||
finish()
|
||||
}
|
||||
|
||||
/** Leaving the session sinks its surface back down onto the front end. */
|
||||
/**
|
||||
* Every way out on screen (backing out of the loading screen, closing the ended screen, the
|
||||
* session ending) leaves as a session arrives, in reverse: the blue spreads out of the
|
||||
* throbber's ball, or the middle of the screen once a game is up, and the front end opens on it
|
||||
* and draws it back into the button the session came from. Off screen, or with animations
|
||||
* off, it just closes.
|
||||
*/
|
||||
override fun finish() {
|
||||
if (!quitFlooded && !closeAtOnce && com.droiddeck.launcher.ui.Motion.scale != 0f &&
|
||||
lifecycle.currentState.isAtLeast(androidx.lifecycle.Lifecycle.State.RESUMED)) {
|
||||
if (!leaving) {
|
||||
leaving = true
|
||||
// Should the flood never get its frames, the session still closes.
|
||||
uiHandler.postDelayed({ completeLeave() }, LEAVE_TIMEOUT_MS)
|
||||
}
|
||||
return
|
||||
}
|
||||
super.finish()
|
||||
if (quitFlooded) overridePendingTransition(0, 0)
|
||||
else overridePendingTransition(R.anim.session_hold, R.anim.session_sink)
|
||||
}
|
||||
|
||||
/** The screen is all blue: close onto the front end, which opens on the same blue. */
|
||||
private fun completeLeave() {
|
||||
if (isFinishing) return
|
||||
val signal = com.droiddeck.launcher.ui.Themes.byId(SessionPrefs.theme(this)).signal
|
||||
com.droiddeck.launcher.ui.QuitFlood.mark(signal.toArgb())
|
||||
window.setBackgroundDrawable(android.graphics.drawable.ColorDrawable(signal.toArgb()))
|
||||
quitFlooded = true
|
||||
finish()
|
||||
}
|
||||
|
||||
companion object {
|
||||
/** How long the leaving flood may take before the session closes without it. */
|
||||
private const val LEAVE_TIMEOUT_MS = 1_500L
|
||||
private const val TAG = "SessionActivity"
|
||||
private const val UNBUFFERED_SOURCES = InputDevice.SOURCE_CLASS_JOYSTICK or InputDevice.SOURCE_CLASS_TRACKBALL or InputDevice.SOURCE_CLASS_POSITION
|
||||
private const val CURSOR_PAD_HOLD_MS = 1200L
|
||||
|
||||
@@ -48,6 +48,8 @@ import kotlin.math.sin
|
||||
// loading screen opens on that blue and gathers it into the ball of the throbber.
|
||||
// Stopping one from the drawer runs it the other way: Stop grows until the page is blue, and the
|
||||
// front end opens on that blue and draws it back down into the button the session came from.
|
||||
// Any other way out (backing out of the loading screen, closing the ended screen, the session
|
||||
// ending) spreads the blue back out of the throbber's ball instead (FloodSpread).
|
||||
|
||||
/** Carries the flood's colour to the session, which opens on it. */
|
||||
const val EXTRA_FLOOD = "com.droiddeck.launcher.FLOOD"
|
||||
@@ -277,3 +279,31 @@ internal fun FloodGather(flood: Color, ball: Pair<Offset, Float>?, onLanded: ()
|
||||
drawCircle(flood, radius = lerp(far, r, gather.value), center = c)
|
||||
}
|
||||
}
|
||||
|
||||
/** Where the loading screen's throbber ball sits now (centre and radius, root px), for a flood leaving from it. */
|
||||
internal object ThrobberSpot {
|
||||
var ball: Pair<Offset, Float>? = null
|
||||
}
|
||||
|
||||
private val Spread = CubicBezierEasing(0.7f, 0f, 0.4f, 1f)
|
||||
|
||||
/**
|
||||
* Leaving a session: [flood] spreading out of [ball] (centre and radius in root px; the middle of
|
||||
* the screen when there is none) until it covers the screen, the way FloodGather drew it in. Calls
|
||||
* [onCovered] then, for the front end to open on the same blue. Swallows touches while it runs.
|
||||
*/
|
||||
@Composable
|
||||
internal fun FloodSpread(flood: Color, ball: Pair<Offset, Float>?, onCovered: () -> Unit) {
|
||||
VeilRing()
|
||||
val grow = remember { Animatable(0f) }
|
||||
val covered by rememberUpdatedState(onCovered)
|
||||
LaunchedEffect(Unit) {
|
||||
grow.animateTo(1f, Motion.tw(520, easing = Spread))
|
||||
covered()
|
||||
}
|
||||
Canvas(Modifier.fillMaxSize().pointerInput(Unit) { awaitEachGesture { while (true) awaitPointerEvent().changes.forEach { it.consume() } } }) {
|
||||
val (c, r) = ball ?: (Offset(size.width / 2f, size.height / 2f) to 0f)
|
||||
val far = maxOf(hypot(c.x, c.y), hypot(size.width - c.x, c.y), hypot(c.x, size.height - c.y), hypot(size.width - c.x, size.height - c.y))
|
||||
drawCircle(flood, radius = lerp(r, far, grow.value), center = c)
|
||||
}
|
||||
}
|
||||
@@ -182,7 +182,7 @@ fun LoadingOverlay(
|
||||
// Held on the plain D while the flood gathers, and started fresh when it lands.
|
||||
key(flood == null) {
|
||||
LogoThrobber(
|
||||
Modifier.width(if (ended) 88.dp else 110.dp).onGloballyPositioned { ball = throbberBall(it.boundsInRoot()) },
|
||||
Modifier.width(if (ended) 88.dp else 110.dp).onGloballyPositioned { ball = throbberBall(it.boundsInRoot()).also { b -> ThrobberSpot.ball = b } },
|
||||
running = !ended && flood == null,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user