Keep display and runtime driver controls visible in mix mode

(cherry picked from commit 0422eb44193cdcebf7a9f22dfff367b1441dc40e)
This commit is contained in:
K11MCH1 committed 2026-10-25 02:10:00 +09:00
1 parent 77e23cb397
commit c334ef309b
2 files changed
+90 -6

No files matched your search

@@ -3,6 +3,8 @@ package com.droiddeck.launcher.ui
import com.droiddeck.launcher.R
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.focusGroup
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.relocation.bringIntoViewRequester
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.layout.LayoutCoordinates
import androidx.compose.ui.layout.boundsInRoot
@@ -19,6 +21,7 @@ import androidx.compose.material.icons.outlined.SportsEsports
import androidx.compose.material.icons.outlined.ExpandLess
import androidx.compose.material.icons.outlined.ExpandMore
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.AnnotatedString
@@ -209,7 +212,7 @@ private fun DeviceInfoSection(section: DeviceInfo.Section) {
private const val STALE_MS = 6 * 3_600_000L
@OptIn(androidx.compose.ui.ExperimentalComposeUiApi::class)
@OptIn(androidx.compose.ui.ExperimentalComposeUiApi::class, androidx.compose.foundation.ExperimentalFoundationApi::class)
@Composable
internal fun GpuDriversBody(
gpu: GpuDriversState,
@@ -224,6 +227,13 @@ internal fun GpuDriversBody(
}
val scroll = rememberScrollState()
val scope = rememberCoroutineScope()
val driverControls = remember { BringIntoViewRequester() }
LaunchedEffect(gpu.mode) {
if (gpu.mode == GPU_MODE_MIX) {
withFrameNanos { }
driverControls.bringIntoView()
}
}
val placed = remember { arrayOfNulls<LayoutCoordinates>(2) }
// Focusing the top mode shows as much of the in-use card above it as fits, without hiding the mode itself.
val revealCard = {
@@ -251,7 +261,10 @@ internal fun GpuDriversBody(
stringResource(title), stringResource(detail), gpu.mode == id,
modifier = mod.then(if (gpu.mode == id) Modifier.focusRequester(first) else Modifier)
.then(if (i == 0) Modifier.onGloballyPositioned { placed[1] = it }.onFocusChanged { if (it.isFocused) revealCard() } else Modifier),
onClick = { if (gpu.mode != id) actions.onMode(id) },
onClick = {
if (gpu.mode != id) actions.onMode(id)
else if (id == GPU_MODE_MIX) scope.launch { driverControls.bringIntoView() }
},
)
}
if (single) Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { modes.indices.forEach { choice(it, Modifier) } }
@@ -261,7 +274,7 @@ internal fun GpuDriversBody(
when (gpu.mode) {
GPU_MODE_AUTO -> AutoBody(gpu)
GPU_MODE_PAIR -> PairBody(gpu, actions)
else -> MixBody(single, actions, slotFocus, onOpenSlot)
else -> MixBody(single, actions, slotFocus, onOpenSlot, Modifier.bringIntoViewRequester(driverControls))
}
gpu.tpUbwcMode?.let { selected ->
var advanced by rememberSaveable { mutableStateOf(false) }
@@ -395,7 +408,7 @@ private fun PairBody(gpu: GpuDriversState, actions: GpuDriversActions) {
}
@Composable
private fun MixBody(single: Boolean, actions: GpuDriversActions, slotFocus: Map<String, FocusRequester>, onOpenSlot: (String, Rect) -> Unit) {
private fun MixBody(single: Boolean, actions: GpuDriversActions, slotFocus: Map<String, FocusRequester>, onOpenSlot: (String, Rect) -> Unit, controlsModifier: Modifier) {
val pal = LocalPalette.current
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
PanelTitle(stringResource(R.string.gpu_mix_title), stringResource(R.string.gpu_mix_hint))
@@ -410,8 +423,8 @@ private fun MixBody(single: Boolean, actions: GpuDriversActions, slotFocus: Map<
slotButton(SLOT_DISPLAY, R.string.gpu_change_display, mod)
slotButton(SLOT_RUNTIME, R.string.gpu_change_runtime, mod)
}
if (single) Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { buttons(Modifier.fillMaxWidth()) }
else Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) { buttons(Modifier.weight(1f)) }
if (single) Column(controlsModifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) { buttons(Modifier.fillMaxWidth()) }
else Row(controlsModifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) { buttons(Modifier.weight(1f)) }
FocusText(stringResource(R.string.comp_import_zip), pal.signal, onClick = actions.onImportZip)
}
}
@@ -0,0 +1,71 @@
package com.droiddeck.launcher.ui
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.unit.dp
import com.droiddeck.launcher.R
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [28], qualifiers = "w960dp-h400dp-land")
class GpuDriverControlsVisibilityTest {
@get:Rule val compose = createRobolectricComposeRule()
private var openedSlot: String? = null
@Test fun openingMixedModeRevealsBothDriverButtonsOnAShortScreen() {
show(GPU_MODE_MIX)
assertControlsVisible()
compose.onNodeWithText(text(R.string.gpu_change_display)).performClick()
assertEquals(SLOT_DISPLAY, openedSlot)
}
@Test fun selectingMixedModeRevealsItsControlsBelowTheModeChoices() {
show(GPU_MODE_AUTO)
compose.onNodeWithText(text(R.string.gpu_mode_mix)).performScrollTo().performClick()
assertControlsVisible()
compose.onNodeWithText(text(R.string.gpu_change_runtime)).performClick()
assertEquals(SLOT_RUNTIME, openedSlot)
}
private fun assertControlsVisible() {
compose.onNodeWithText(text(R.string.gpu_change_display)).assertIsDisplayed()
compose.onNodeWithText(text(R.string.gpu_change_runtime)).assertIsDisplayed()
}
private fun text(id: Int) = RuntimeEnvironment.getApplication().getString(id)
private fun show(initialMode: String) {
val mode = mutableStateOf(initialMode)
val first = FocusRequester()
val slots = mapOf(SLOT_DISPLAY to FocusRequester(), SLOT_RUNTIME to FocusRequester())
compose.setContent {
DroidDeckTheme {
Box(Modifier.requiredSize(480.dp, 280.dp)) {
GpuDriversBody(
gpu = GpuDriversState(
gpuName = "Adreno 830", gpuFamily = "Adreno 8xx", mode = mode.value,
display = DriverSlot(name = "Automatic", status = "Applies after restarting DroidDeck"),
runtime = DriverSlot(name = "System Qualcomm driver", status = "Applies at the next session start"),
),
actions = GpuDriversActions(onMode = { mode.value = it }),
single = true, first = first, slotFocus = slots,
onOpenSlot = { slot, _ -> openedSlot = slot },
)
}
}
}
}
}