diff --git a/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt b/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt index 4d6406ce7f..3480c17e60 100644 --- a/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt +++ b/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt @@ -177,6 +177,9 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { } private var unfoldedPanel by mutableStateOf(null) private var unfoldedColumn: android.widget.LinearLayout? = null + private var unfoldedDeckSurface: com.droiddeck.launcher.ui.InputSurfaceView? = null + private var unfoldedDeckPanel by mutableStateOf(null) + private var deckPanelLayouts: Map = emptyMap() private lateinit var sessionRoot: FrameLayout private var inputRouter: InputRouter? = null private var physicalInputBindings: com.droiddeck.launcher.input.PhysicalInputBindings? = null @@ -564,6 +567,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { private var sessionActivityDisplayId = Display.DEFAULT_DISPLAY private lateinit var displayManager: DisplayManager private var secondScreenPresentation: SecondScreenPresentation? = null + private val inputWindowDisplays = mutableListOf() private val bottomScreenListener: () -> Unit = { if (secondScreenPresentation?.display?.displayId?.let(com.droiddeck.launcher.input.DualScreen::taken) == true) closeSecondScreen(reset = false) updateBottomGame() @@ -1221,7 +1225,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { textureLodBias = if (drawerGameProfileId == null) textureLodBias else drawerTextureLodBias, frameGen = frameGen, lossless = lossless, - oscMode = oscMode, onScreenButtonsVisible = onScreenButtonsVisible, suspendPolicy = suspendPolicy, + oscMode = oscMode, suspendPolicy = suspendPolicy, steamDownloadsInBackground = steamDownloadsInBackground, touchMode = touchMode, micAvailable = SessionState.micAvailable && !SessionState.suspended, micEnabled = SessionState.micEnabled, @@ -1442,6 +1446,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { } }, onClose = { drawerOpen = false }, + onDismissScreen = if (docked) ({ selectSecondScreenMode(SecondScreenMode.NONE) }) else null, components = drawerComponents, onComponentsRefresh = { refreshDrawerComponents() @@ -2825,6 +2830,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { return onScreenControls?.let { takes(it, it::takesTouchAt) } == true || steamTouchControls?.let { takes(it, it::takesTouchAt) } == true || unfoldedPanel?.let { takes(it, it::takesTouchAt) } == true || + unfoldedDeckSurface?.let { takes(it, it::takesTouchAt) } == true || unfoldedColumn?.getChildAt(0)?.let { takes(it) { _, _ -> true } } == true } @@ -3377,6 +3383,10 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { } var presentation = secondScreenPresentation if (presentation?.display?.displayId != target.displayId) { + if (target.displayId in inputWindowDisplays) { + panelMode(mode) + return + } dropDockedDrawer() presentation?.closeControls() presentation = secondScreenPresentation(target) @@ -3517,8 +3527,15 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { val main = window.decorView.display if (role == InputRole.PRIMARY) return bottomGame?.display ?: main if (bottomGame != null) return main - return displayManager.getDisplay(selectedSecondScreenDisplay) - ?: secondScreenDisplays.firstOrNull()?.let { displayManager.getDisplay(it.id) } + return com.droiddeck.launcher.input.SecondScreenDisplays.editorCandidates(selectedSecondScreenDisplay, + secondScreenDisplays.map { it.id }, com.droiddeck.launcher.input.DualScreen::taken).firstNotNullOfOrNull(displayManager::getDisplay) + } + + fun inputWindowShown(displayId: Int) { inputWindowDisplays += displayId } + + fun inputWindowClosed(displayId: Int) { + inputWindowDisplays -= displayId + if (started && displayId !in inputWindowDisplays && secondScreenMode != SecondScreenMode.NONE && secondScreenPresentation == null) showSecondScreen(secondScreenMode) } private fun refreshInputProfile() { @@ -3529,6 +3546,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { adaptiveSticks = primaryInputLayout?.adaptiveSticks quickButtons = primaryInputLayout?.takeIf { it.editable }?.let { l -> l.quickButtons.associateWith { it !in l.style.hidden } }.orEmpty() secondaryInputLayout = library.panelLayout(desktop, secondScreenMode.panel(this)) + deckPanelLayouts = com.droiddeck.launcher.input.DeckPanel.entries.associateWith { library.panelLayout(desktop, it.bindings) } inputUsesLocalActions = InputControl.padControls.any { inputProfile.action(InputRole.PHYSICAL, it) !is InputAction.Native } inputUsesPointer = inputProfile.bindings.values.any { bindings -> bindings.values.any { it is InputAction.Pointer || it is InputAction.MouseButton || it is InputAction.Scroll } @@ -3542,6 +3560,8 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { onScreenControls?.updateLayout(primaryInputLayout) bottomInputControls?.updateLayout(primaryInputLayout) unfoldedPanel?.updateLayout(primaryInputLayout) + if (unfoldedPanel != null) applyUnfoldedControls() + showUnfoldedDeckPanel() updateOnScreenPad() secondScreenPresentation?.refreshControls() } @@ -3593,11 +3613,15 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { Log.i(TAG, if (wanted) "unfolded controls: game ${width}x$gameHeight, controls ${width}x${height - gameHeight}" else "unfolded controls: game full screen") } - val dock = com.droiddeck.launcher.input.OnScreenLayout.Dock(!com.droiddeck.launcher.session.SessionDisplay.foldable(this), SessionState.deckPad) + val portrait = !com.droiddeck.launcher.session.SessionDisplay.foldable(this) + val dock = if (SessionState.deckPad) com.droiddeck.launcher.input.OnScreenLayout.Dock.deck(portrait, deckPanelLayouts[com.droiddeck.launcher.input.DeckPanel.GAMEPAD]) + else com.droiddeck.launcher.input.OnScreenLayout.Dock(portrait, false) var panel = unfoldedPanel if (panel != null && (!wanted || panel.dock != dock)) { sessionRoot.removeView(unfoldedColumn) unfoldedColumn = null + unfoldedDeckSurface = null + unfoldedDeckPanel = null unfoldedPanel = null panel = null if (!wanted) updateOnScreenControls() @@ -3614,14 +3638,36 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { sessionRoot.addView(column, sessionRoot.indexOfChild(surfaceView) + 1) unfoldedColumn = column unfoldedPanel = panel + showUnfoldedDeckPanel() updateOnScreenControls() } unfoldedColumn?.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, height - gameHeight, Gravity.BOTTOM) } private fun unfoldedBar(): View = com.droiddeck.launcher.ui.bottomScreenBarView(this) { - com.droiddeck.launcher.ui.sessionBarModel(getString(R.string.deck_tab_gamepad), { chooseUnfoldedControls(false) }, - { drawerOpen = true }, ::tapSteamButton, ::tapQam) + val deck = unfoldedDeckPanel + com.droiddeck.launcher.ui.sessionBarModel(getString(if (deck != null) R.string.second_mode_deck_controls else R.string.deck_tab_gamepad), + { chooseUnfoldedControls(false) }, { drawerOpen = true }, ::tapSteamButton, ::tapQam, + tabs = deck?.let { com.droiddeck.launcher.ui.deckPanelTabs(this, it, ::selectDeckPanel) }) + } + + private fun showUnfoldedDeckPanel() { + val column = unfoldedColumn + val panel = unfoldedPanel + val selected = SessionPrefs.deckPanel(this).takeIf { column != null && panel?.dock?.grips == true } + unfoldedDeckPanel = selected + val layout = selected?.takeIf { it != com.droiddeck.launcher.input.DeckPanel.GAMEPAD }?.let { deckPanelLayouts[it] } + if (layout != null && unfoldedDeckSurface?.tag == layout) return + unfoldedDeckSurface?.let { it.releaseInputs(); column?.removeView(it) } + unfoldedDeckSurface = null + if (column == null || panel == null) return + if (layout == null) { panel.visibility = View.VISIBLE; return } + panel.releaseAll() + panel.visibility = View.GONE + unfoldedDeckSurface = com.droiddeck.launcher.ui.InputSurfaceView(this, padBridge, layout.elements, InputRole.PRIMARY, layout.style, haptics = true).also { + it.tag = layout + column.addView(it, android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, 0, 1f)) + } } private fun bottomKeyboardPresentation(): SecondScreenPresentation? { @@ -4244,7 +4290,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { updateOnScreenPad() val c = onScreenControls glassesSlot = when { - c == null || c.visibility != View.VISIBLE -> 0 + c == null || c.visibility != View.VISIBLE || c.isPinnedOnly -> 0 c.isButtonsOnly -> -1 primaryInputLayout?.let { l -> l.elements.any { it.quickButton == InputCommand.KEYBOARD && !l.style.hides(it) } } ?: (controllerSettings?.keyboardButton != false) -> 2 @@ -4259,12 +4305,17 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { ?.let { FileUtils.readString(it)?.trim()?.lowercase() } ?: SessionPrefs.oscMode(this) val controls = onScreenControls ?: return + fun showPinned() { + controls.setPinnedOnly(true) + val pinned = controls.hasPinned && !pipUi && !inputSettingsOpen + if (!pinned) controls.releaseAll() + controls.visibility = if (pinned) View.VISIBLE else View.GONE + } if (pipUi || unfoldedPanel != null || pcKeyboardOpen || inputSettingsOpen || (SessionState.mode == SessionService.MODE_STEAM && !SessionPrefs.builtinPad(this))) { onScreenButtonsVisible = false hideBottomControls() - controls.releaseAll() - controls.visibility = View.GONE + showPinned() steamTouchControls?.let { it.cancelEditing(); it.releaseAll(); it.visibility = View.GONE } com.droiddeck.launcher.input.SteamTouchDevice.current?.plugged = false applyTouchRouting() @@ -4297,7 +4348,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { val drawn = show && !keyboardCoversPad() if (steam != null) { hideBottomControls() - if (controls.visibility == View.VISIBLE) { controls.releaseAll(); controls.visibility = View.GONE } + showPinned() if (!drawn) { steam.cancelEditing(); steam.releaseAll() } steam.visibility = if (drawn) View.VISIBLE else View.GONE com.droiddeck.launcher.input.SteamTouchDevice.current?.plugged = show @@ -4306,7 +4357,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { return } val bottomRoot = bottomGame?.surfaceView?.parent as? android.view.ViewGroup - if (drawn && bottomRoot != null) { + if (bottomRoot != null && (drawn || controls.hasPinned)) { controls.releaseAll(); controls.visibility = View.GONE var bottom = bottomInputControls if (bottom?.parent !== bottomRoot) { @@ -4319,13 +4370,14 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { } bottom.updateLayout(primaryInputLayout) bottom.setButtonsOnly(buttonsOnly) + bottom.setPinnedOnly(!drawn) bottom.visibility = View.VISIBLE return } hideBottomControls() - if (drawn == (controls.visibility == View.VISIBLE)) return - if (!drawn) controls.releaseAll() - controls.visibility = if (drawn) View.VISIBLE else View.GONE + val wasDrawn = controls.visibility == View.VISIBLE && !controls.isPinnedOnly + if (!drawn) showPinned() else { controls.setPinnedOnly(false); controls.visibility = View.VISIBLE } + if (drawn == wasDrawn) return Log.i(TAG, "on-screen controls " + when { !drawn -> "hidden" buttonsOnly -> "Steam + QAM" @@ -5063,7 +5115,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { com.droiddeck.launcher.ui.BottomScreenBar(bar(getString(SecondScreenMode.KEYBOARD_TRACKPAD.label), listOf(keyboardExtra))) SecondScreenMode.DECK_CONTROLS -> com.droiddeck.launcher.ui.BottomScreenBar(bar(getString(SecondScreenMode.DECK_CONTROLS.label), tabs = com.droiddeck.launcher.ui.deckPanelTabs(this@SessionActivity, SessionPrefs.deckPanel(this@SessionActivity), ::selectDeckPanel))) - else -> Unit + else -> com.droiddeck.launcher.ui.BottomScreenBar(bar(getString(secondScreenMode.label))) } androidx.compose.runtime.key(profileLayout) { androidx.compose.ui.viewinterop.AndroidView( diff --git a/app/src/main/java/com/droiddeck/launcher/input/BottomScreenGuests.kt b/app/src/main/java/com/droiddeck/launcher/input/BottomScreenGuests.kt index ae44dc0e6c..574536e18a 100644 --- a/app/src/main/java/com/droiddeck/launcher/input/BottomScreenGuests.kt +++ b/app/src/main/java/com/droiddeck/launcher/input/BottomScreenGuests.kt @@ -9,6 +9,7 @@ import android.hardware.display.DisplayManager import android.os.Build import android.os.Handler import android.os.HandlerThread +import android.os.SystemClock import android.util.Log import android.view.Display import android.view.accessibility.AccessibilityEvent @@ -33,6 +34,8 @@ object BottomScreenGuests { else -> null } + internal fun current(arrivedAt: Long, ownShownAt: Long?): Boolean = ownShownAt == null || arrivedAt >= ownShownAt + internal fun ignorable(type: Int): Boolean = type == AccessibilityWindowInfo.TYPE_INPUT_METHOD || type == AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY || type == AccessibilityWindowInfo.TYPE_SPLIT_SCREEN_DIVIDER || @@ -43,7 +46,7 @@ object BottomScreenGuests { } class BottomScreenWatch(private val service: AccessibilityService) { - private class Arrival(val pkg: String) + private class Arrival(val pkg: String, val at: Long) private val thread = HandlerThread("bottom-screen").apply { start() } private val handler = Handler(thread.looper) @@ -52,6 +55,8 @@ class BottomScreenWatch(private val service: AccessibilityService) { private var homes: Set = emptySet() private var keyboards: Set = emptySet() private var holders: Map = emptyMap() + private var ownWindows: Map> = emptyMap() + private val ownShownAt = mutableMapOf() private val check = Runnable { evaluate() } fun start() { @@ -72,7 +77,7 @@ class BottomScreenWatch(private val service: AccessibilityService) { AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> { val pkg = event.packageName?.toString() ?: return if (event.displayId == Display.DEFAULT_DISPLAY || pkg == own || pkg == SYSTEM_UI) return - arrivals[event.displayId] = Arrival(pkg) + arrivals[event.displayId] = Arrival(pkg, event.eventTime) } AccessibilityEvent.TYPE_WINDOWS_CHANGED -> Unit else -> return @@ -97,19 +102,25 @@ class BottomScreenWatch(private val service: AccessibilityService) { val all = runCatching { service.windowsOnAllDisplays }.getOrNull() ?: return val displays = service.getSystemService(DisplayManager::class.java) ?: return val taken = mutableMapOf() + val shown = mutableMapOf>() + val now = SystemClock.uptimeMillis() for (i in 0 until all.size()) { val id = all.keyAt(i) if (id == Display.DEFAULT_DISPLAY) continue val display = displays.getDisplay(id)?.takeIf(DualScreen::builtIn) ?: continue val windows = all.valueAt(i) - val topPkg = windows.asSequence() .filter { !BottomScreenGuests.ignorable(it.type) && cover(it, display) >= BottomScreenGuests.MIN_COVER } .sortedByDescending { it.layer } - .firstNotNullOfOrNull { packageOf(it)?.takeUnless { pkg -> pkg == own } } - val arrival = seen[id] - if (topPkg != null && arrival != null) arrivals.remove(id, arrival) + .map { it to packageOf(it) } + val ours = windows.filter { it.second == own }.map { it.first.id }.toSet() + if (!ownWindows[id].orEmpty().containsAll(ours)) ownShownAt[id] = now + shown[id] = ours + val topPkg = windows.firstNotNullOfOrNull { it.second?.takeUnless { pkg -> pkg == own } } + val arrival = seen[id]?.takeIf { BottomScreenGuests.current(it.at, ownShownAt[id]) } + seen[id]?.takeIf { topPkg != null || arrival == null }?.let { arrivals.remove(id, it) } BottomScreenGuests.holder(topPkg, arrival?.pkg?.takeUnless { it in keyboards }, homes)?.let { taken[id] = it } } + ownWindows = shown if (current !== this) return if (taken != holders) Log.i(TAG, "bottom screen holders: $taken") holders = taken diff --git a/app/src/main/java/com/droiddeck/launcher/input/ControllerPrefs.kt b/app/src/main/java/com/droiddeck/launcher/input/ControllerPrefs.kt index 1edc2395ce..5231a753ac 100644 --- a/app/src/main/java/com/droiddeck/launcher/input/ControllerPrefs.kt +++ b/app/src/main/java/com/droiddeck/launcher/input/ControllerPrefs.kt @@ -73,7 +73,7 @@ object ControllerPrefs { keyboardButton = p.getBoolean("keyboardButton", true), selectSteam = p.getBoolean("selectSteam", false), joyConVr = joyConVr(context), - snap = p.getBoolean("snap", true), + snap = snap(context), customLayout = p.all.keys.any { it.startsWith("layout.") }, mapping = mappable.associate { (id, _) -> id to target(context, id) }, deadZone = deadZone(context), @@ -87,11 +87,6 @@ object ControllerPrefs { prefs(context).edit().putString("map.$id", target).apply() } - fun resetMapping(context: Context) { - val p = prefs(context) - p.edit().apply { p.all.keys.filter { it.startsWith("map.") }.forEach { remove(it) } }.apply() - } - fun setTint(context: Context, tint: Int) { prefs(context).edit().putInt("tint", tint).apply() } @@ -129,10 +124,6 @@ object ControllerPrefs { prefs(context).edit().putBoolean("steamButton", on).apply() } - fun setQamButton(context: Context, on: Boolean) { - prefs(context).edit().putBoolean("qamButton", on).apply() - } - fun setKeyboardButton(context: Context, on: Boolean) { prefs(context).edit().putBoolean("keyboardButton", on).apply() } @@ -147,6 +138,8 @@ object ControllerPrefs { prefs(context).edit().putBoolean("joyConVr", on).apply() } + fun snap(context: Context): Boolean = prefs(context).getBoolean("snap", true) + fun setSnap(context: Context, on: Boolean) { prefs(context).edit().putBoolean("snap", on).apply() } @@ -172,9 +165,4 @@ object ControllerPrefs { fun resetAll(context: Context) { prefs(context).edit().clear().putBoolean("rumble", true).apply() } - - fun resetAllLayouts(context: Context) { - val p = prefs(context) - p.edit().apply { p.all.keys.filter { it.startsWith("layout.") }.forEach { remove(it) } }.apply() - } } diff --git a/app/src/main/java/com/droiddeck/launcher/input/InputLibraries.kt b/app/src/main/java/com/droiddeck/launcher/input/InputLibraries.kt index 074fd834a8..bcc641ebaa 100644 --- a/app/src/main/java/com/droiddeck/launcher/input/InputLibraries.kt +++ b/app/src/main/java/com/droiddeck/launcher/input/InputLibraries.kt @@ -4,12 +4,13 @@ import android.content.Context import android.hardware.display.DisplayManager import android.util.DisplayMetrics import android.view.Display +import com.droiddeck.launcher.session.SessionPrefs import org.json.JSONArray import org.json.JSONObject import java.util.UUID object InputLibraries { - private const val VERSION = 12 + private const val VERSION = 14 private const val KEY = "library" private const val RECOVERY = "library-recovery." const val OVERLAY = "builtin.overlay" @@ -30,6 +31,8 @@ object InputLibraries { if (version < 11) library = neutralStockLayouts(library) if (version < 10) library = restyleGamepads(context, library) if (version < 12) library = barStockLayouts(library) + if (version < 13) library = LegacyScreenKeyMigration.pin(library) + if (version < 14) library = deckGamepadSurfaces(context, library) if (version < 6 || !p.contains(LegacyScreenKeyMigration.BACKUP)) { val legacy = LegacyScreenKeyMigration.snapshot(context) if (legacy != null) { @@ -263,6 +266,15 @@ object InputLibraries { return addStockLayouts(result) } + private fun deckGamepadSurfaces(context: Context, library: InputLibrary): InputLibrary { + val leftPad = SessionPrefs.gamepadSurface(context, right = false) == SessionPrefs.SURFACE_TRACKPAD + val rightStick = SessionPrefs.gamepadSurface(context, right = true) == SessionPrefs.SURFACE_STICK + if (!leftPad && !rightStick) return library + val saved = library.layouts.firstOrNull { it.id == DeckPanel.GAMEPAD.layout } ?: return library + if (saved.elements != InputStockLayouts.gamepad(leftPad = false, rightStick = false)) return library + return library.withLayout(saved.copy(elements = InputStockLayouts.gamepad(leftPad, rightStick))).validate() + } + private fun migrationBindingId(library: InputLibrary, binding: String, layout: String): String { val seed = JSONArray(listOf(binding, layout)).toString().toByteArray(Charsets.UTF_8) val base = "migrated.bindings." + UUID.nameUUIDFromBytes(seed) @@ -292,7 +304,7 @@ object InputLibraries { for (e in layout.elements) elements.put(JSONObject().put("id", e.id).put("kind", e.kind.name) .put("control", e.control.id).put("name", e.name).put("x", e.x.toDouble()).put("y", e.y.toDouble()) .put("width", e.width.toDouble()).put("height", e.height.toDouble()).put("floating", e.floating).put("hideWhenIdle", e.hideWhenIdle).put("mouseGestures", e.mouseGestures).put("toggle", e.toggle).put("icon", e.icon).put("image", e.image) - .put("elementOpacity", e.opacity.toDouble()).put("buttonShape", e.buttonShape.name).put("showLabel", e.showLabel)) + .put("elementOpacity", e.opacity.toDouble()).put("buttonShape", e.buttonShape.name).put("showLabel", e.showLabel).put("alwaysShown", e.alwaysShown)) layouts.put(JSONObject().put("id", layout.id).put("name", layout.name).put("template", layout.template.name).put("elements", elements) .put("tint", layout.style.tint ?: JSONObject.NULL).put("opacity", layout.style.opacity).put("screen", layout.screen.name).put("aspect", layout.aspect.toDouble()).put("scale", layout.scale.toDouble()) .put("hidden", JSONArray(layout.style.hidden.map { it.name }.sorted())).put("origin", layout.origin ?: JSONObject.NULL)) @@ -333,7 +345,7 @@ object InputLibraries { e.getDouble("y").toFloat(), e.getDouble("width").toFloat(), e.getDouble("height").toFloat(), e.getString("id"), InputElementKind.valueOf(e.getString("kind")), e.getString("name"), e.optBoolean("floating", true), e.optBoolean("hideWhenIdle", false), e.optBoolean("mouseGestures", false), e.optBoolean("toggle", false), e.optInt("icon", 0), e.optString("image", ""), - e.optDouble("elementOpacity", 1.0).toFloat(), InputButtonShape.valueOf(e.optString("buttonShape", InputButtonShape.ROUNDED.name)), e.optBoolean("showLabel", true)) + e.optDouble("elementOpacity", 1.0).toFloat(), InputButtonShape.valueOf(e.optString("buttonShape", InputButtonShape.ROUNDED.name)), e.optBoolean("showLabel", true), e.optBoolean("alwaysShown", false)) } SavedInputLayout(layout.getString("id"), layout.getString("name"), elements, InputLayoutTemplate.valueOf(layout.getString("template")), InputSurfaceStyle(if (!layout.has("tint")) ControllerPrefs.STEAM_BLUE else if (layout.isNull("tint")) null else layout.getInt("tint"), layout.optInt("opacity", 100), diff --git a/app/src/main/java/com/droiddeck/launcher/input/InputStockLayouts.kt b/app/src/main/java/com/droiddeck/launcher/input/InputStockLayouts.kt index 059fbb1dda..f3c451d6f7 100644 --- a/app/src/main/java/com/droiddeck/launcher/input/InputStockLayouts.kt +++ b/app/src/main/java/com/droiddeck/launcher/input/InputStockLayouts.kt @@ -45,6 +45,14 @@ internal object InputStockLayouts { InputSurfaceElement(InputControl.SELECT, .42f, .3f, .07f, .1f, buttonShape = InputButtonShape.PILL), InputSurfaceElement(InputControl.START, .51f, .3f, .07f, .1f, buttonShape = InputButtonShape.PILL)) } + fun gamepad(leftPad: Boolean, rightStick: Boolean) = gamepad().map { + when { + leftPad && it.control == InputControl.LS -> InputSurfaceElement(InputControl.LEFT_PAD, it.x, it.y, it.width, it.height) + rightStick && it.control == InputControl.RIGHT_PAD -> InputSurfaceElement(InputControl.RS, it.x, it.y, it.width, it.height) + else -> it + } + } + fun layouts() = listOf( SavedInputLayout(InputLibraries.DECK, "Deck grips + trackpads", pads() + grips(), style = NEUTRAL, screen = InputLayoutScreen.SECONDARY), SavedInputLayout(InputLibraries.TRACKPAD, "Trackpad + keyboard", listOf( diff --git a/app/src/main/java/com/droiddeck/launcher/input/InputSurfaceLayout.kt b/app/src/main/java/com/droiddeck/launcher/input/InputSurfaceLayout.kt index de16d485f4..e7e1c58a36 100644 --- a/app/src/main/java/com/droiddeck/launcher/input/InputSurfaceLayout.kt +++ b/app/src/main/java/com/droiddeck/launcher/input/InputSurfaceLayout.kt @@ -34,6 +34,7 @@ data class InputSurfaceElement( val opacity: Float = 1f, val buttonShape: InputButtonShape = InputButtonShape.ROUNDED, val showLabel: Boolean = true, + val alwaysShown: Boolean = false, ) { val hasClick get() = kind == InputElementKind.STICK || kind == InputElementKind.TRACKPAD diff --git a/app/src/main/java/com/droiddeck/launcher/input/InputSurfaceSource.kt b/app/src/main/java/com/droiddeck/launcher/input/InputSurfaceSource.kt index 555f99b3e0..7012731ae0 100644 --- a/app/src/main/java/com/droiddeck/launcher/input/InputSurfaceSource.kt +++ b/app/src/main/java/com/droiddeck/launcher/input/InputSurfaceSource.kt @@ -11,6 +11,7 @@ class InputSurfaceSource(private val pad: PadBridge, private val role: InputRole private val gestureRight = if (element.mouseGestures) router?.channel(role, InputControl.A, fixedAction = InputAction.MouseButton(273)) else null private val gestureMiddle = if (element.mouseGestures) router?.channel(role, InputControl.A, fixedAction = InputAction.MouseButton(274)) else null val mouseGestures get() = element.mouseGestures && route?.action is InputAction.Pointer + val directions get() = route?.action as? InputAction.Directions fun gestureContact(touching: Boolean) { if (!closed) route?.update(0f, 0f, touching) } fun pointerDelta(dx: Float, dy: Float) { diff --git a/app/src/main/java/com/droiddeck/launcher/input/LegacyGamepadLayout.kt b/app/src/main/java/com/droiddeck/launcher/input/LegacyGamepadLayout.kt index f2857900af..e281a2b741 100644 --- a/app/src/main/java/com/droiddeck/launcher/input/LegacyGamepadLayout.kt +++ b/app/src/main/java/com/droiddeck/launcher/input/LegacyGamepadLayout.kt @@ -225,7 +225,8 @@ internal class LegacyGamepadLayout(private val context: Context, private val wid ) } - fun dock(stored: List, portrait: Boolean, grips: Boolean): List { + fun dock(stored: List, portrait: Boolean, grips: Boolean, left: InputControl = InputControl.LS, + right: InputControl = InputControl.RS): List { val boxes = GamepadGeometry.layout(width.toFloat(), height.toFloat(), resources.displayMetrics.density, portrait) val placed = mutableListOf() fun box(control: InputControl, slot: GamepadGeometry.Slot, shape: InputButtonShape) { @@ -243,9 +244,9 @@ internal class LegacyGamepadLayout(private val context: Context, private val wid b.cx + direction.first * spread, b.cy + direction.second * spread, radius) } } - box(InputControl.LS, GamepadGeometry.Slot.LEFT_MAIN, InputButtonShape.CIRCLE) + box(left, GamepadGeometry.Slot.LEFT_MAIN, InputButtonShape.CIRCLE) cross(DPAD_ORDER, GamepadGeometry.Slot.LEFT_SECOND) - box(InputControl.RS, GamepadGeometry.Slot.RIGHT_MAIN, InputButtonShape.CIRCLE) + box(right, GamepadGeometry.Slot.RIGHT_MAIN, InputButtonShape.CIRCLE) cross(FACE_ORDER, GamepadGeometry.Slot.RIGHT_SECOND) box(InputControl.LT, GamepadGeometry.Slot.LEFT_OUTER_SHOULDER, InputButtonShape.PILL) box(InputControl.LB, GamepadGeometry.Slot.LEFT_INNER_SHOULDER, InputButtonShape.PILL) @@ -254,7 +255,8 @@ internal class LegacyGamepadLayout(private val context: Context, private val wid box(InputControl.SELECT, GamepadGeometry.Slot.VIEW, InputButtonShape.PILL) box(InputControl.START, GamepadGeometry.Slot.MENU, InputButtonShape.PILL) if (grips) GRIPS.forEach { (control, slot) -> box(control, slot, InputButtonShape.ROUNDED) } - return withGeometry(stored, placed) + placed.filter { p -> GRIPS.any { it.first == p.control } && stored.none { it.id == p.id } } + val surfaces = listOf(left, right).filter { it.isTrackpad } + return withGeometry(stored, placed) + placed.filter { p -> (GRIPS.any { it.first == p.control } || p.control in surfaces) && stored.none { it.id == p.id } } } private fun circle(e: InputSurfaceElement, cx: Float, cy: Float, radius: Float) = diff --git a/app/src/main/java/com/droiddeck/launcher/input/LegacyScreenKeyMigration.kt b/app/src/main/java/com/droiddeck/launcher/input/LegacyScreenKeyMigration.kt index 58329fc88e..9921fdf960 100644 --- a/app/src/main/java/com/droiddeck/launcher/input/LegacyScreenKeyMigration.kt +++ b/app/src/main/java/com/droiddeck/launcher/input/LegacyScreenKeyMigration.kt @@ -40,7 +40,7 @@ internal object LegacyScreenKeyMigration { if (result.profiles.any { it.id == id }) continue val converted = convert(context, set.second, metrics.widthPixels, metrics.heightPixels, metrics.density) if (converted.isEmpty()) continue - val layout = SavedInputLayout("$id.layout", set.first, converted.map { it.first }, style = style, screen = InputLayoutScreen.MAIN, + val layout = SavedInputLayout("$id.layout", set.first, converted.map { it.first.copy(alwaysShown = true) }, style = style, screen = InputLayoutScreen.MAIN, aspect = (metrics.widthPixels.coerceAtLeast(1).toFloat() / metrics.heightPixels.coerceAtLeast(1)).coerceIn(.05f, 20f)) val bindings = SavedInputBindings("$id.bindings", set.first, false, layout.id, elements = converted.associate { it.first.id to it.second }) val base = library.profile(library.steam) @@ -68,6 +68,17 @@ internal object LegacyScreenKeyMigration { return result.validate() } + fun pin(library: InputLibrary): InputLibrary { + val keys = library.layouts.firstOrNull { it.id == "$SHOWN.layout" }?.elements.orEmpty() + return library.copy(layouts = library.layouts.map { layout -> + val imported = layout.id.startsWith("migrated.screenkeys.") + layout.copy(elements = layout.elements.map { e -> + val key = e.id.startsWith("imported:") && (imported || keys.any { it.id == e.id && it.kind == e.kind && it.name == e.name }) + if (key) e.copy(alwaysShown = true) else e + }) + }) + } + internal fun convert(context: Context, raw: String, width: Int, height: Int, density: Float): List> = convert(context, parse(raw), width, height, density) diff --git a/app/src/main/java/com/droiddeck/launcher/input/OnScreenControls.kt b/app/src/main/java/com/droiddeck/launcher/input/OnScreenControls.kt index e285337124..4923221fa0 100644 --- a/app/src/main/java/com/droiddeck/launcher/input/OnScreenControls.kt +++ b/app/src/main/java/com/droiddeck/launcher/input/OnScreenControls.kt @@ -29,6 +29,9 @@ class OnScreenControls(context: Context, pad: PadBridge?, val dock: OnScreenLayo private set var isQuickHidden = false private set + var isPinnedOnly = false + private set + val hasPinned get() = layout?.elements?.any { it.alwaysShown } == true var onTouchMiss: ((MotionEvent) -> Unit)? get() = surface.onTouchMiss set(value) { surface.onTouchMiss = value } @@ -61,6 +64,13 @@ class OnScreenControls(context: Context, pad: PadBridge?, val dock: OnScreenLayo render() } + fun setPinnedOnly(enabled: Boolean) { + if (isPinnedOnly == enabled) return + releaseAll() + isPinnedOnly = enabled + render() + } + private data class Placement(val layout: SavedInputLayout?, val width: Int, val height: Int, val safe: Rect, val buttonsOnly: Boolean) private fun placed(): List { @@ -72,12 +82,16 @@ class OnScreenControls(context: Context, pad: PadBridge?, val dock: OnScreenLayo private fun render() { val elements = placed() - surface.updateCustomLayout(if (isQuickHidden) elements.filter { it.id == "builtin:keyboard" } else elements) + surface.updateCustomLayout(when { + isPinnedOnly -> elements.filter { it.alwaysShown } + isQuickHidden -> elements.filter { it.id == "builtin:keyboard" || it.alwaysShown } + else -> elements + }) val style = layout?.style ?: InputSurfaceStyle() surface.updateStyle(style) quickToggle.tint = style.tint quickToggle.alpha = style.opacity / 100f - quickToggle.visibility = if (isButtonsOnly || layout == null || dock != null) GONE else VISIBLE + quickToggle.visibility = if (isButtonsOnly || isPinnedOnly || layout == null || dock != null) GONE else VISIBLE quickToggle.invalidate() } diff --git a/app/src/main/java/com/droiddeck/launcher/input/OnScreenLayout.kt b/app/src/main/java/com/droiddeck/launcher/input/OnScreenLayout.kt index b7c1bafea6..3935c1a565 100644 --- a/app/src/main/java/com/droiddeck/launcher/input/OnScreenLayout.kt +++ b/app/src/main/java/com/droiddeck/launcher/input/OnScreenLayout.kt @@ -7,7 +7,18 @@ import kotlin.math.max import kotlin.math.min object OnScreenLayout { - data class Dock(val portrait: Boolean, val grips: Boolean) + data class Dock(val portrait: Boolean, val grips: Boolean, val left: InputControl = InputControl.LS, val right: InputControl = InputControl.RS) { + companion object { + fun deck(portrait: Boolean, gamepad: SavedInputLayout?): Dock { + fun side(right: Boolean) = gamepad?.elements?.firstOrNull { + (it.kind == InputElementKind.STICK || it.kind == InputElementKind.TRACKPAD) && (it.x + it.width / 2f >= .5f) == right + }?.kind + val left = if (side(false) == InputElementKind.TRACKPAD) InputControl.LEFT_PAD else InputControl.LS + val right = if (side(true) == InputElementKind.STICK) InputControl.RS else InputControl.RIGHT_PAD + return Dock(portrait, true, left, right) + } + } + } private const val TOLERANCE = .01f private const val KEYBOARD_ID = "builtin:keyboard" @@ -17,13 +28,13 @@ object OnScreenLayout { val stored = layout?.elements ?: return emptyList() if (width <= 0 || height <= 0) return stored val generator = LegacyGamepadLayout(context, width, height, safe, layout.scale) - if (buttonsOnly && dock == null) return generator.buttonsOnly(stored) val (reference, generated) = reference(context, layout, width, height) + if (buttonsOnly && dock == null) return generator.buttonsOnly(stored) + fit(stored.filter { it.alwaysShown }, reference.first, reference.second, width, height, safe) val gamepad = if (generated) stored.filter(::isGamepad) else emptyList() - val others = fit(stored.filterNot { generated && isGamepad(it) }, reference.first, reference.second, width, height, safe) + val others = fit(stored.filterNot { generated && isGamepad(it) || dock != null && it.alwaysShown }, reference.first, reference.second, width, height, safe) val placed = when { gamepad.isEmpty() -> emptyList() - dock != null -> generator.dock(gamepad, dock.portrait, dock.grips) + dock != null -> generator.dock(gamepad, dock.portrait, dock.grips, dock.left, dock.right) else -> generator.regenerate(gamepad) } val byId = (placed + others).associateBy { it.id } diff --git a/app/src/main/java/com/droiddeck/launcher/input/SecondScreenMode.kt b/app/src/main/java/com/droiddeck/launcher/input/SecondScreenMode.kt index 316ac08243..2d0006925f 100644 --- a/app/src/main/java/com/droiddeck/launcher/input/SecondScreenMode.kt +++ b/app/src/main/java/com/droiddeck/launcher/input/SecondScreenMode.kt @@ -52,4 +52,7 @@ object SecondScreenDisplays { ) } .toList() + + fun editorCandidates(selected: Int, available: List, taken: (Int) -> Boolean): List = + (listOf(selected) + available).distinct().filterNot(taken) } diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt index 6c10b18336..56d7e67f77 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionPrefs.kt @@ -1554,10 +1554,6 @@ object SessionPrefs { prefs(context).getString(if (right) "gamepadRight" else "gamepadLeft", null) ?: if (right) SURFACE_TRACKPAD else SURFACE_STICK - fun setGamepadSurface(context: Context, right: Boolean, surface: String) { - prefs(context).edit().putString(if (right) "gamepadRight" else "gamepadLeft", surface).apply() - } - fun deckPanel(context: Context): com.droiddeck.launcher.input.DeckPanel = com.droiddeck.launcher.input.DeckPanel.from(prefs(context).getString("deckPanel", null)) diff --git a/app/src/main/java/com/droiddeck/launcher/ui/InputDisplayContent.kt b/app/src/main/java/com/droiddeck/launcher/ui/InputDisplayContent.kt index 65bcd52be6..013161f9d6 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/InputDisplayContent.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/InputDisplayContent.kt @@ -56,12 +56,17 @@ internal fun InputDisplayContent(host: ComponentActivity, display: Display, onDi dismiss() } } + (host as? com.droiddeck.launcher.SessionActivity)?.inputWindowShown(display.displayId) showPresentation() val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_STOP) presentation.hide() if (event == Lifecycle.Event.ON_START) showPresentation() } host.lifecycle.addObserver(observer) - onDispose { host.lifecycle.removeObserver(observer); presentation.dismiss() } + onDispose { + host.lifecycle.removeObserver(observer) + presentation.dismiss() + (host as? com.droiddeck.launcher.SessionActivity)?.inputWindowClosed(display.displayId) + } } } diff --git a/app/src/main/java/com/droiddeck/launcher/ui/InputElementProperties.kt b/app/src/main/java/com/droiddeck/launcher/ui/InputElementProperties.kt index 33f5a68f80..d55d6fd22d 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/InputElementProperties.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/InputElementProperties.kt @@ -24,7 +24,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @Composable -internal fun InputElementProperties(element: InputSurfaceElement, onChange: (InputSurfaceElement) -> Unit) { +internal fun InputElementProperties(element: InputSurfaceElement, action: InputAction? = null, onChange: (InputSurfaceElement) -> Unit) { val current by rememberUpdatedState(element) val change by rememberUpdatedState(onChange) OutlinedTextField(element.name, { onChange(element.copy(name = it.take(80))) }, label = { Text(stringResource(R.string.input_name)) }, @@ -72,7 +72,9 @@ internal fun InputElementProperties(element: InputSurfaceElement, onChange: (Inp } val host = rememberMenuHost() if ((element.kind == InputElementKind.BUTTON || element.kind == InputElementKind.TRIGGER) && element.icon == 0 && element.image.isEmpty() || - element.kind == InputElementKind.STICK && element.id.startsWith("legacy:")) + element.kind == InputElementKind.STICK && element.id.startsWith("legacy:") || + (element.kind == InputElementKind.STICK || element.kind == InputElementKind.DPAD) && (action as? InputAction.Directions) + ?.let { listOf(it.up, it.right, it.down, it.left).any { code -> code != 0 } } == true) ToggleRow(host, "element-label", stringResource(R.string.input_show_label), null, element.showLabel) { onChange(element.copy(showLabel = it)) } SliderRow(stringResource(R.string.ctrl_opacity), null, (element.opacity * 100).roundToInt().coerceIn(20, 100), 20..100, step = 5, format = { "$it%" }, modifier = Modifier.testTag("setting-element-opacity")) { onChange(element.copy(opacity = it / 100f)) } diff --git a/app/src/main/java/com/droiddeck/launcher/ui/InputSettingsPage.kt b/app/src/main/java/com/droiddeck/launcher/ui/InputSettingsPage.kt index 66d81ffa33..70ee4a7c44 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/InputSettingsPage.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/InputSettingsPage.kt @@ -169,7 +169,7 @@ private fun InputSettingsContent(onBack: () -> Unit, onChanged: () -> Unit, embe ?: b.controls[editing.control] ?: InputAction.Native() InputActionEditor(editing.control, current, if (editing.click) stringResource(R.string.input_click_name, editing.element!!.name) else editing.element?.name ?: editing.key?.label ?: editing.control.label, - properties = { if (!editing.click) elementProperties?.let { e -> InputElementProperties(e) { elementProperties = it } } }, + properties = { if (!editing.click) elementProperties?.let { e -> InputElementProperties(e, current) { elementProperties = it } } }, onBack = { edit = null }, element = editing.element, nativeTargets = editing.key == null, click = editing.click, detail = if (library.usersOfBindings(b.id).size > 1) stringResource(R.string.input_shared_detail, b.name, library.usersOfBindings(b.id).size) else "${profile.name} · ${roleLabel(role)}", onSave = { action -> diff --git a/app/src/main/java/com/droiddeck/launcher/ui/InputSurfaceDialog.kt b/app/src/main/java/com/droiddeck/launcher/ui/InputSurfaceDialog.kt index 2c414aba0e..5dfa83a2ff 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/InputSurfaceDialog.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/InputSurfaceDialog.kt @@ -69,7 +69,7 @@ private class GamepadScaler(private val context: android.content.Context, privat } private class SurfaceDraft(val layout: SavedInputLayout, val preset: SavedInputBindings, val defaults: List? = null, - private val gamepad: GamepadScaler? = null) { + private val gamepad: GamepadScaler? = null, snap: Boolean = true) { var elements by mutableStateOf(layout.elements) var actions by mutableStateOf(preset.elements) var clicks by mutableStateOf(preset.clicks) @@ -77,7 +77,7 @@ private class SurfaceDraft(val layout: SavedInputLayout, val preset: SavedInputB var toolsExpanded by mutableStateOf(false) var toolsX by mutableFloatStateOf(.02f) var toolsY by mutableFloatStateOf(.02f) - var snap by mutableStateOf(true) + var snap by mutableStateOf(snap) var adding by mutableStateOf(false) var properties by mutableStateOf(false) var clickProperties by mutableStateOf(false) @@ -132,7 +132,7 @@ fun InputSurfaceDialog(profile: String, onDismiss: () -> Unit, onChanged: () -> val (w, h) = OnScreenLayout.generatedSize(context, savedLayout, metrics.widthPixels, metrics.heightPixels) ?: (metrics.widthPixels to metrics.heightPixels) GamepadScaler(display, w, h) } - SurfaceDraft(savedLayout, savedBindings, InputLibraries.defaultPositions(context, library, savedLayout), gamepad) + SurfaceDraft(savedLayout, savedBindings, InputLibraries.defaultPositions(context, library, savedLayout), gamepad, ControllerPrefs.snap(context)) } val manager = remember { context.getSystemService(DisplayManager::class.java) } var revision by remember { mutableIntStateOf(0) } @@ -162,7 +162,7 @@ fun InputSurfaceDialog(profile: String, onDismiss: () -> Unit, onChanged: () -> val listener = object : DisplayManager.DisplayListener { override fun onDisplayAdded(id: Int) { revision++ } override fun onDisplayRemoved(id: Int) { revision++ } - override fun onDisplayChanged(id: Int) { revision++ } + override fun onDisplayChanged(id: Int) = Unit } manager.registerDisplayListener(listener, Handler(Looper.getMainLooper())) onDispose { manager.unregisterDisplayListener(listener) } @@ -171,13 +171,18 @@ fun InputSurfaceDialog(profile: String, onDismiss: () -> Unit, onChanged: () -> val presentation = SurfaceEditorPresentation(host, target, transparent = host is SessionActivity && role == InputRole.PRIMARY) { SurfaceCanvas(draft, transparent = host is SessionActivity && role == InputRole.PRIMARY) } + (host as? SessionActivity)?.inputWindowShown(target.displayId) runCatching { presentation.show() }.onFailure { failure = it.message } val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_STOP) presentation.hide() if (event == Lifecycle.Event.ON_START) runCatching { presentation.show() } } host.lifecycle.addObserver(observer) - onDispose { host.lifecycle.removeObserver(observer); presentation.dismiss() } + onDispose { + host.lifecycle.removeObserver(observer) + presentation.dismiss() + (host as? SessionActivity)?.inputWindowClosed(target.displayId) + } } // Compose 1.6 custom dialog sizing subtracts system bars even when they are hidden. Dialog(onDismissRequest = ::leave, properties = DialogProperties(usePlatformDefaultWidth = true, decorFitsSystemWindows = false)) { @@ -199,7 +204,7 @@ fun InputSurfaceDialog(profile: String, onDismiss: () -> Unit, onChanged: () -> if (draft.clickProperties) stringResource(R.string.input_click_name, element.name) else element.name, onBack = { draft.properties = false }, element = element, click = draft.clickProperties, properties = { - if (!draft.clickProperties) InputElementProperties(properties) { properties = it } + if (!draft.clickProperties) InputElementProperties(properties, draft.action(element)) { properties = it } if (layoutUsers > 1) Text(stringResource(R.string.input_editor_shared, layoutUsers), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(bottom = 12.dp)) @@ -309,13 +314,14 @@ private fun FloatingSurfaceTools(draft: SurfaceDraft, onSave: () -> Unit, onLeav @OptIn(ExperimentalLayoutApi::class) @Composable private fun SurfaceToolbar(draft: SurfaceDraft, onSave: () -> Unit, onLeave: () -> Unit) { + val context = LocalContext.current FlowRow(Modifier.fillMaxWidth().padding(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { SecondaryButton(stringResource(R.string.common_cancel), compact = true, onClick = onLeave) SecondaryButton(stringResource(R.string.input_editor_add), enabled = draft.elements.size < 64, compact = true) { draft.adding = true } SecondaryButton(stringResource(R.string.input_editor_undo), enabled = draft.history.isNotEmpty(), compact = true) { draft.undo() } if (draft.defaults != null) SecondaryButton(stringResource(R.string.input_reset_positions), enabled = draft.positioned != null, compact = true) { draft.resetPositions() } Row(verticalAlignment = Alignment.CenterVertically) { - Checkbox(draft.snap, { draft.snap = it }) + Checkbox(draft.snap, { draft.snap = it; ControllerPrefs.setSnap(context, it) }) Text(stringResource(R.string.ctrl_editor_snap)) } PrimaryButton(stringResource(R.string.input_editor_done), compact = true, onClick = onSave) diff --git a/app/src/main/java/com/droiddeck/launcher/ui/InputSurfaceView.kt b/app/src/main/java/com/droiddeck/launcher/ui/InputSurfaceView.kt index b1cab727ea..f121a9a73a 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/InputSurfaceView.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/InputSurfaceView.kt @@ -32,10 +32,12 @@ import com.droiddeck.launcher.input.InputSurfaceStyle import com.droiddeck.launcher.input.pointerSubset import com.droiddeck.launcher.input.InputSurfaceSource import com.droiddeck.launcher.input.InputElementKind +import com.droiddeck.launcher.input.InputAction import com.droiddeck.launcher.input.InputButtonIcons import com.droiddeck.launcher.input.InputButtonShape import com.droiddeck.launcher.input.InputButtonImages import com.droiddeck.launcher.input.PadBridge +import com.droiddeck.launcher.input.pcKeyLabel import com.droiddeck.launcher.session.SessionPrefs import kotlin.math.atan2 import kotlin.math.hypot @@ -195,7 +197,7 @@ class InputSurfaceView( canvas.addView(when (element.kind) { InputElementKind.STICK -> StickView(context, element, source, element.floating, element.hideWhenIdle && pad != null) InputElementKind.TRACKPAD -> TrackpadView(context, element.control == InputControl.RIGHT_PAD, source = source) - InputElementKind.DPAD -> DirectionPad(context, source) + InputElementKind.DPAD -> DirectionPad(context, element, source) else -> HoldButton(context, element, source) }.apply { tag = element; contentDescription = element.name; styleElement(this); source?.onStateChanged = { postInvalidate() } }) } @@ -541,6 +543,7 @@ class InputSurfaceView( if (touching || clicking) shade(color, 160, 1f) else shade(color, 120, 0.35f), if (touching) light(color, 230, 0.7f) else light(color, 150, 0.25f)) if (!element.showLabel) return labelPaint.color = textColor(touching) + if (drawDirectionLabels(canvas, source, false, cx, cy, radius * 0.66f, radius * 0.34f, labelPaint)) return labelPaint.textSize = knobRadius * 0.8f val label = if (element.name == element.control.label) glyph(element.control) else element.name canvas.drawText(label, cx + knobX, cy + knobY + labelPaint.textSize * 0.35f, labelPaint) @@ -552,6 +555,9 @@ class InputSurfaceView( canvas.drawCircle(cx, cy, reach(), ring) knob.color = if (touching) ACCENT else KNOB canvas.drawCircle(cx + knobX, cy + knobY, reach() * 0.45f, knob) + if (!element.showLabel) return + labelPaint.color = if (touching) ACCENT else TEXT + drawDirectionLabels(canvas, source, false, cx, cy, reach() * 1.32f, reach() * 0.68f, labelPaint) } @SuppressLint("ClickableViewAccessibility") @@ -613,10 +619,11 @@ class InputSurfaceView( } } - private inner class DirectionPad(context: Context, private val source: InputSurfaceSource? = null) : View(context) { + private inner class DirectionPad(context: Context, private val element: InputSurfaceElement, private val source: InputSurfaceSource? = null) : View(context) { private val density = resources.displayMetrics.density private var held = 0 private val arm = Paint(Paint.ANTI_ALIAS_FLAG) + private val label = Paint(Paint.ANTI_ALIAS_FLAG).apply { textAlign = Paint.Align.CENTER; typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD); color = TEXT } private val rect = RectF() override fun onDraw(canvas: Canvas) { @@ -636,6 +643,7 @@ class InputSurfaceView( armRect(2, cx + thick, cy - thick, cx + half, cy + thick) arm.color = KNOB canvas.drawRect(cx - thick, cy - thick, cx + thick, cy + thick, arm) + if (element.showLabel) drawDirectionLabels(canvas, source, true, cx, cy, (half + thick) / 2f, thick * 1.7f, label) } @SuppressLint("ClickableViewAccessibility") @@ -717,6 +725,29 @@ class InputSurfaceView( else -> control.id.uppercase() } + private val CARDINAL = listOf(0f to -1f, 1f to 0f, 0f to 1f, -1f to 0f) + private val DIAGONAL = listOf(.7071f to -.7071f, .7071f to .7071f, -.7071f to .7071f, -.7071f to -.7071f) + + internal fun directionLabels(context: Context, action: InputAction.Directions?, cardinalOnly: Boolean): List, String>> { + if (action == null) return emptyList() + val codes = listOf(action.up, action.right, action.down, action.left).zip(CARDINAL) + + if (cardinalOnly) emptyList() else action.diagonals.orEmpty().zip(DIAGONAL) + return codes.filter { it.first != 0 }.map { (code, unit) -> unit to pcKeyLabel(context, code) } + } + + private fun View.drawDirectionLabels(canvas: Canvas, source: InputSurfaceSource?, cardinalOnly: Boolean, cx: Float, cy: Float, + distance: Float, box: Float, paint: Paint): Boolean { + val labels = directionLabels(context, source?.directions, cardinalOnly) + paint.style = Paint.Style.FILL + for ((unit, text) in labels) { + paint.textSize = box * .6f + val measured = paint.measureText(text) + if (measured > box) paint.textSize *= box / measured + canvas.drawText(text, cx + unit.first * distance, cy + unit.second * distance - (paint.ascent() + paint.descent()) / 2f, paint) + } + return labels.isNotEmpty() + } + private fun drawArrow(canvas: Canvas, x: Float, y: Float, size: Float, direction: Int, paint: Paint) { val (fx, fy) = when (direction) { 0 -> 0f to -1f; 1 -> 1f to 0f; 2 -> 0f to 1f; else -> -1f to 0f } val path = Path() diff --git a/app/src/main/java/com/droiddeck/launcher/ui/SecondScreenPresentation.kt b/app/src/main/java/com/droiddeck/launcher/ui/SecondScreenPresentation.kt index 000aa8afc6..d9a4967d76 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/SecondScreenPresentation.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/SecondScreenPresentation.kt @@ -323,7 +323,7 @@ class SecondScreenPresentation( context.getString(if (keyboardShown.value) R.string.second_hide_keyboard else R.string.second_show_keyboard), icon = R.drawable.ic_bottom_keyboard, active = keyboardShown.value, tag = "bottom-keyboard") { showKeyboard() })) SecondScreenMode.DECK_CONTROLS -> model(swap, deckPanelTabs(context, deckPanel.value, ::selectDeckPanel)) - SecondScreenMode.DASHBOARD -> model(swap) + SecondScreenMode.DASHBOARD, SecondScreenMode.CUSTOM_CONTROLS -> model(swap) SecondScreenMode.TERMINAL -> model(listOf(BarExtra(context.getString(R.string.drawer_keyboard), icon = R.drawable.ic_bottom_keyboard, tag = "bottom-keyboard") { terminalPanel?.showKeyboard() })) else -> null diff --git a/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt b/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt index dc0e8f3202..7315817c1c 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/SessionOverlay.kt @@ -219,7 +219,6 @@ class DrawerActions( val frameGen: FrameGen.Mode, val lossless: Lossless.State, val oscMode: String, - val onScreenButtonsVisible: Boolean, val suspendPolicy: String, val steamDownloadsInBackground: Boolean = false, val micAvailable: Boolean = false, @@ -325,6 +324,7 @@ class DrawerActions( val onStop: () -> Unit, val onStopFrom: ((androidx.compose.ui.geometry.Rect?) -> Unit)? = null, val onClose: () -> Unit, + val onDismissScreen: (() -> Unit)? = null, val components: ComponentsManager.Snapshot? = null, val systemPowerVr: Boolean = false, val onComponentsRefresh: () -> Unit = {}, @@ -488,7 +488,7 @@ internal fun SessionDrawer(open: Boolean, requestedPage: SessionDrawerPage, cont ) { SteamActionPill( stringResource(R.string.common_close), Icons.Outlined.Close, focus.track(page, "close"), - ) { host.open = null; a.onClose() } + ) { host.open = null; (a.onDismissScreen ?: a.onClose)() } if (roomyHeader) Spacer(Modifier.weight(1f)) SteamActionPill( stringResource(R.string.stores_dl_pause), Icons.Outlined.Pause, focus.track(page, "pause"), diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index b5e68c3826..d79102fe67 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -1257,7 +1257,6 @@ يد التحكّم عصا التحكّم لوحة اللمس - التبديل بين عصا التحكّم ولوحة اللمس إغلاق عناصر التحكّم على الشاشة الثانية فتح قائمة Steam لوحة اللمس اليمنى diff --git a/app/src/main/res/values-b+es+419/strings.xml b/app/src/main/res/values-b+es+419/strings.xml index fe24a6b46f..6719162e7c 100644 --- a/app/src/main/res/values-b+es+419/strings.xml +++ b/app/src/main/res/values-b+es+419/strings.xml @@ -1306,7 +1306,6 @@ Control Stick Trackpad - Cambiar entre stick y trackpad Cerrar controles de la segunda pantalla Abrir menú de Steam Trackpad derecho diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 1d89d523ea..0dd66dceb6 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -1229,7 +1229,6 @@ Геймпад Стик Тракпад - Превключване между стик и тракпад Затваряне на контролите за втория екран Отваряне на менюто на Steam Десен тракпад diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index ba8dd05ddd..c586d18804 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -1243,7 +1243,6 @@ Gamepad Páčka Trackpad - Přepnout mezi páčkou a trackpadem Zavřít ovládání druhé obrazovky Otevřít nabídku Steamu Pravý trackpad diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 572c522b03..84131804a8 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -1229,7 +1229,6 @@ Gamepad Stick Trackpad - Skift mellem stick og trackpad Luk kontrollerne til den anden skærm Åbn Steam-menuen Højre trackpad diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 03d89f7209..ae490f7714 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1221,7 +1221,6 @@ Gamepad Stick Trackpad - Zwischen Stick und Trackpad wechseln Steuerung auf dem Zweitbildschirm schließen Steam-Menü öffnen Rechtes Trackpad diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 97b593f5e2..ed3e10961e 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -1229,7 +1229,6 @@ Χειριστήριο Μοχλός Πινακίδα αφής - Εναλλαγή μεταξύ μοχλού και πινακίδας αφής Κλείσιμο χειριστηρίων δεύτερης οθόνης Άνοιγμα μενού Steam Δεξιά πινακίδα αφής diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index e0368f571f..d9da57ebb8 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1236,7 +1236,6 @@ Mando Stick Trackpad - Cambiar entre stick y trackpad Cerrar controles de la segunda pantalla Abrir menú de Steam Trackpad derecho diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 34a7db2910..a57a2c21be 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -1229,7 +1229,6 @@ Peliohjain Tatti Kosketuslevy - Vaihda tatin ja kosketuslevyn välillä Sulje toisen näytön ohjaimet Avaa Steam-valikko Oikea kosketuslevy diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 7a178546b6..cf3d3402e3 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1236,7 +1236,6 @@ Manette Joystick Pavé tactile - Basculer entre joystick et pavé tactile Fermer les commandes du deuxième écran Ouvrir le menu Steam Pavé tactile droit diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 987457fb56..87a25e9575 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -1229,7 +1229,6 @@ गेमपैड स्टिक ट्रैकपैड - स्टिक और ट्रैकपैड के बीच बदलें दूसरी स्क्रीन के कंट्रोल बंद करें Steam मेनू खोलें दायां ट्रैकपैड diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 7130a4afe2..f3272e1ce3 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -1229,7 +1229,6 @@ Kontroller Kar Érintőpad - Váltás a kar és az érintőpad között Második képernyős vezérlők bezárása Steam-menü megnyitása Jobb érintőpad diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 9806030f20..40b3f7e3a7 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -1222,7 +1222,6 @@ Gamepad Stik Trackpad - Beralih antara stik dan trackpad Tutup kontrol layar kedua Buka menu Steam Trackpad kanan diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 4495768c15..a4544b2f1a 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1228,7 +1228,6 @@ Gamepad Levetta Trackpad - Alterna tra levetta e trackpad Chiudi i controlli sul secondo schermo Apri il menu Steam Trackpad destro diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index af0a2c76d5..ebd1964a33 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -1222,7 +1222,6 @@ ゲームパッド スティック トラックパッド - スティックとトラックパッドを切り替え セカンドスクリーンコントロールを閉じる Steamメニューを開く 右トラックパッド diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index d076c7d0ef..3069e40f68 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -1222,7 +1222,6 @@ 게임패드 스틱 터치패드 - 스틱과 터치패드 간 전환 보조 화면 컨트롤 닫기 Steam 메뉴 열기 오른쪽 터치패드 diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index 30f1be10bd..6b81087306 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -1222,7 +1222,6 @@ Gamepad Stik Trackpad - Tukar antara stik dan trackpad Tutup kawalan skrin kedua Buka menu Steam Trackpad kanan diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index 52561aeeab..f14e168672 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -1229,7 +1229,6 @@ Kontroller Stikke Styreflate - Bytt mellom stikke og styreflate Lukk kontroller for andre skjerm Åpne Steam-menyen Høyre styreflate diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 1d72169e66..daac0b219f 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -1229,7 +1229,6 @@ Gamepad Stick Trackpad - Wisselen tussen stick en trackpad Bediening op tweede scherm sluiten Steam-menu openen Rechtertrackpad diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index ef5e9a2960..f5218b00ed 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -1243,7 +1243,6 @@ Gamepad Gałka Trackpad - Przełącz między gałką a trackpadem Zamknij sterowanie na drugim ekranie Otwórz menu Steam Prawy trackpad diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 7b1133cc2c..374372f102 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -1228,7 +1228,6 @@ Comando Joystick Trackpad - Alternar entre joystick e trackpad Fechar controlos do segundo ecrã Abrir menu Steam Trackpad direito diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 85c48fa7c4..20a77ae66b 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1228,7 +1228,6 @@ Controle Analógico Trackpad - Alternar entre analógico e trackpad Fechar controles da segunda tela Abrir o menu Steam Trackpad direito diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 21d8685736..61985823e0 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -1236,7 +1236,6 @@ Gamepad Manșă Trackpad - Comută între manșă și trackpad Închide comenzile celui de-al doilea ecran Deschide meniul Steam Trackpad-ul drept diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 25414fa125..e7d9c5d653 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1280,7 +1280,6 @@ Геймпад Стик Тачпад - Переключение между стиком и тачпадом Закрыть управление вторым экраном Открыть меню Steam Правый тачпад diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 00631845b9..0bfcc26671 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -1229,7 +1229,6 @@ Handkontroll Styrspak Styrplatta - Växla mellan styrspak och styrplatta Stäng kontrollerna på andra skärmen Öppna Steam-menyn Höger styrplatta diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index d5aa598d63..609aa8c7fa 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -1222,7 +1222,6 @@ เกมแพด สติ๊ก แทร็กแพด - สลับระหว่างสติ๊กและแทร็กแพด ปิดการควบคุมหน้าจอที่สอง เปิดเมนู Steam แทร็กแพดขวา diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 9f9d07edae..ea8d974f63 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1221,7 +1221,6 @@ Oyun kolu Çubuk Dokunmatik yüzey - Çubuk ile dokunmatik yüzey arasında geçiş yap İkinci ekran kontrollerini kapat Steam menüsünü aç Sağ dokunmatik yüzey diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 1ba7514e17..0276779115 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1243,7 +1243,6 @@ Геймпад Стік Трекпад - Перемкнути між стіком і трекпадом Закрити керування на другому екрані Відкрити меню Steam Правий трекпад diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 99b00b3330..0a9043b039 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -1222,7 +1222,6 @@ Tay cầm Cần Trackpad - Chuyển giữa cần và trackpad Đóng điều khiển màn hình phụ Mở menu Steam Trackpad phải diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 3f9eb6d2cb..2bf8eeb2ce 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1306,7 +1306,6 @@ 手柄 摇杆 触控板 - 在摇杆与触控板之间切换 关闭副屏控制 打开 Steam 菜单 右触控板 diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index b1b41d210f..87b5775c8c 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -1222,7 +1222,6 @@ 控制器 搖桿 觸控板 - 在搖桿和觸控板之間切換 關閉第二螢幕控制 開啟 Steam 選單 右側觸控板 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 6c62968f91..cecf611f2a 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1222,7 +1222,6 @@ 手把 搖桿 觸控板 - 在搖桿與觸控板之間切換 關閉第二螢幕控制 開啟 Steam 選單 右側觸控板 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e747e3d23b..0f612bb7a6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1380,7 +1380,6 @@ Gamepad Stick Trackpad - Switch between stick and trackpad Close second-screen controls Open Steam menu Right trackpad diff --git a/app/src/test/java/com/droiddeck/launcher/input/BottomScreenGuestsTest.kt b/app/src/test/java/com/droiddeck/launcher/input/BottomScreenGuestsTest.kt index d9c959dae9..3bc8e76afa 100644 --- a/app/src/test/java/com/droiddeck/launcher/input/BottomScreenGuestsTest.kt +++ b/app/src/test/java/com/droiddeck/launcher/input/BottomScreenGuestsTest.kt @@ -48,6 +48,17 @@ class BottomScreenGuestsTest { assertEquals(null, BottomScreenGuests.holder(null, null, homes)) } + @Test fun anAppThatArrivedBeforeDroidDeckCoveredTheScreenAgainNoLongerHoldsIt() { + assertFalse(BottomScreenGuests.current(arrivedAt = 1_000, ownShownAt = 2_000)) + assertTrue(BottomScreenGuests.current(arrivedAt = 3_000, ownShownAt = 2_000)) + assertTrue(BottomScreenGuests.current(arrivedAt = 2_000, ownShownAt = 2_000)) + assertTrue(BottomScreenGuests.current(arrivedAt = 1_000, ownShownAt = null)) + fun holder(arrivedAt: Long, ownShownAt: Long) = BottomScreenGuests.holder(null, + "com.android.chrome".takeIf { BottomScreenGuests.current(arrivedAt, ownShownAt) }, homes) + assertEquals(null, holder(arrivedAt = 1_000, ownShownAt = 2_000)) + assertEquals("com.android.chrome", holder(arrivedAt = 3_000, ownShownAt = 2_000)) + } + @Test fun keyboardsAndOverlaysNeverTakeTheScreen() { assertTrue(BottomScreenGuests.ignorable(AccessibilityWindowInfo.TYPE_INPUT_METHOD)) assertTrue(BottomScreenGuests.ignorable(AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY)) diff --git a/app/src/test/java/com/droiddeck/launcher/input/OnScreenControlsTest.kt b/app/src/test/java/com/droiddeck/launcher/input/OnScreenControlsTest.kt index 1aa7c9e20f..2c9745693e 100644 --- a/app/src/test/java/com/droiddeck/launcher/input/OnScreenControlsTest.kt +++ b/app/src/test/java/com/droiddeck/launcher/input/OnScreenControlsTest.kt @@ -205,6 +205,28 @@ class OnScreenControlsTest { assertFalse(view.isQuickHidden) } + @Test fun hidingTheGamepadKeepsAlwaysShownKeysDrawnAndTouchable() { + val key = InputSurfaceElement(InputControl.A, .45f, .1f, .1f, .1f, "imported:0", name = "Find", alwaysShown = true) + view.updateLayout(SavedInputLayout("test", "Test", listOf(left, button, key), aspect = ASPECT)) + measure(view) + assertTrue(view.hasPinned) + view.setPinnedOnly(true) + measure(view) + assertTrue(view.takesTouchAt(500f, 90f)) + assertFalse(view.takesTouchAt(800f, 250f)) + assertFalse(view.takesTouchAt(200f, 300f)) + view.setPinnedOnly(false) + measure(view) + assertTrue(view.takesTouchAt(800f, 250f)) + assertTrue(view.takesTouchAt(500f, 90f)) + view.setQuickHidden(true) + measure(view) + assertTrue(view.takesTouchAt(500f, 90f)) + assertFalse(view.takesTouchAt(800f, 250f)) + view.updateLayout(SavedInputLayout("test", "Test", listOf(left, button), aspect = ASPECT)) + assertFalse(view.hasPinned) + } + private companion object { const val ASPECT = 1000f / 600f } private fun touch(action: Int, vararg pointers: Pair>, flags: Int = 0): Boolean { diff --git a/app/src/test/java/com/droiddeck/launcher/input/OnScreenLayoutTest.kt b/app/src/test/java/com/droiddeck/launcher/input/OnScreenLayoutTest.kt index 8c137fbf2a..0a95382937 100644 --- a/app/src/test/java/com/droiddeck/launcher/input/OnScreenLayoutTest.kt +++ b/app/src/test/java/com/droiddeck/launcher/input/OnScreenLayoutTest.kt @@ -19,7 +19,7 @@ class OnScreenLayoutTest { private val density get() = context.resources.displayMetrics.density @Before fun reset() { - for (name in listOf("input-profiles", "input-surfaces", "controller")) context.getSharedPreferences(name, Context.MODE_PRIVATE).edit().clear().commit() + for (name in listOf("input-profiles", "input-surfaces", "controller", "session")) context.getSharedPreferences(name, Context.MODE_PRIVATE).edit().clear().commit() } private fun gamepad() = InputLibraries.read(context).layouts.first { it.id == InputLibraries.OVERLAY } @@ -188,4 +188,105 @@ class OnScreenLayoutTest { val selected = old.selectProfile(false, "migrated.screenkeys.0").selectProfile(true, "migrated.screenkeys.0") assertEquals(selected, LegacyScreenKeyMigration.merge(selected, shown)) } + + @Test fun alwaysShownKeysStayInButtonsOnlyModeAndLeaveTheUnfoldedStrip() { + val key = InputSurfaceElement(InputControl.A, .45f, .1f, .1f, .1f, "imported:0", name = "Find", alwaysShown = true) + val plain = InputSurfaceElement(InputControl.A, .45f, .4f, .1f, .1f, "imported:1", name = "Jump") + val layout = gamepad().let { it.copy(elements = it.elements + key + plain) } + val buttons = OnScreenLayout.elements(context, layout, 1000, 500, Rect(), buttonsOnly = true) + assertTrue(buttons.any { it.id == key.id }) + assertFalse(buttons.any { it.id == plain.id }) + assertEquals(OnScreenLayout.elements(context, gamepad(), 1000, 500, Rect(), buttonsOnly = true), buttons.filterNot { it.id == key.id }) + val strip = OnScreenLayout.elements(context, layout, 2208, 800, Rect(), dock = OnScreenLayout.Dock(false, grips = false)) + assertFalse(strip.any { it.id == key.id }) + assertTrue(strip.any { it.id == plain.id }) + assertTrue(OnScreenLayout.elements(context, layout, 1000, 500, Rect()).map { it.id }.containsAll(listOf(key.id, plain.id))) + } + + @Test fun migratedScreenKeysAreAlwaysShownButOtherElementsAreNot() { + prefs.edit().clear().commit() + ControllerPrefs.prefs(context).edit().putString("screenKeys", legacyKeys).commit() + val migrated = InputLibraries.read(context) + assertTrue(importedOn(migrated).all { it.alwaysShown }) + assertTrue(migrated.layouts.first { it.id == "migrated.screenkeys.0.layout" }.elements.all { it.alwaysShown }) + assertTrue(migrated.layouts.flatMap { it.elements }.filterNot { it.id.startsWith("imported:") }.none { it.alwaysShown }) + assertFalse(LegacyScreenKeyMigration.convert(context, legacyKeys, 1000, 500, 2f).any { it.first.alwaysShown }) + } + + @Test fun keysMergedByAnEarlierVersionBecomeAlwaysShownOnUpgrade() { + val fresh = InputLibraries.read(context) + val backup = JSONObject().put("screenKeys", legacyKeys).toString() + val unpinned = LegacyScreenKeyMigration.merge(LegacyScreenKeyMigration.migrate(context, fresh, backup), backup).let { library -> + library.copy(layouts = library.layouts.map { l -> l.copy(elements = l.elements.map { it.copy(alwaysShown = false) }) }) + } + val other = SavedInputLayout("user.keys", "Mine", listOf(InputSurfaceElement(InputControl.A, .1f, .1f, .1f, .1f, "imported:0", name = "Other"))) + val stored = unpinned.withLayout(other) + prefs.edit().putString("library", JSONObject(InputLibraries.encode(stored)).put("version", 12).toString()) + .putString(LegacyScreenKeyMigration.BACKUP, backup).putBoolean(LegacyScreenKeyMigration.MERGED, true).commit() + val upgraded = InputLibraries.read(context) + assertEquals(1, importedOn(upgraded).size) + assertTrue(importedOn(upgraded).single().alwaysShown) + assertTrue(upgraded.layouts.first { it.id == "migrated.screenkeys.0.layout" }.elements.all { it.alwaysShown }) + assertFalse(upgraded.layouts.first { it.id == "user.keys" }.elements.single().alwaysShown) + assertEquals(stored.layouts.map { l -> l.elements.map { it.copy(alwaysShown = false) } }, upgraded.layouts.filter { l -> stored.layouts.any { it.id == l.id } } + .map { l -> l.elements.map { it.copy(alwaysShown = false) } }) + } + + @Test fun aDeckStripPutsTheDeckGamepadSurfacesInTheStickSlotsAndOtherPadsKeepSticks() { + val stock = InputStockLayouts.layouts().first { it.id == DeckPanel.GAMEPAD.layout } + val deck = OnScreenLayout.Dock.deck(false, stock) + assertEquals(OnScreenLayout.Dock(false, true, InputControl.LS, InputControl.RIGHT_PAD), deck) + assertEquals(deck, OnScreenLayout.Dock.deck(false, null)) + val (w, h) = 2208 to 800 + val boxes = GamepadGeometry.layout(w.toFloat(), h.toFloat(), density, false) + val placed = OnScreenLayout.elements(context, gamepad(), w, h, Rect(), dock = deck) + val pad = placed.single { it.control == InputControl.RIGHT_PAD } + assertEquals(InputElementKind.TRACKPAD, pad.kind) + assertEquals(boxes.getValue(GamepadGeometry.Slot.RIGHT_MAIN).left, pad.x * w, 1f) + assertNull(placed.firstOrNull { it.control == InputControl.RS }) + assertTrue(placed.any { it.control == InputControl.LS }) + val plain = OnScreenLayout.elements(context, gamepad(), w, h, Rect(), dock = OnScreenLayout.Dock(false, false)) + assertTrue(plain.any { it.control == InputControl.RS }) + assertNull(plain.firstOrNull { it.kind == InputElementKind.TRACKPAD }) + val swapped = stock.copy(elements = stock.elements.map { + when (it.control) { + InputControl.LS -> it.copy(control = InputControl.LEFT_PAD, id = "legacy:left-pad", kind = InputElementKind.TRACKPAD) + InputControl.RIGHT_PAD -> it.copy(control = InputControl.RS, id = "legacy:rs", kind = InputElementKind.STICK) + else -> it + } + }) + assertEquals(OnScreenLayout.Dock(false, true, InputControl.LEFT_PAD, InputControl.RS), OnScreenLayout.Dock.deck(false, swapped)) + } + + private fun deckGamepad(library: InputLibrary) = library.layouts.first { it.id == DeckPanel.GAMEPAD.layout } + + private fun upgradeWithSurfaces(left: String?, right: String?, change: (InputLibrary) -> InputLibrary = { it }): InputLibrary { + val stored = change(InputLibraries.read(context)) + context.getSharedPreferences("session", Context.MODE_PRIVATE).edit().putString("gamepadLeft", left).putString("gamepadRight", right).commit() + prefs.edit().putString("library", JSONObject(InputLibraries.encode(stored)).put("version", 13).toString()).commit() + return InputLibraries.read(context) + } + + @Test fun theSavedDeckGamepadStickOrTrackpadChoiceMovesIntoTheUntouchedDeckGamepadOnce() { + val upgraded = upgradeWithSurfaces("trackpad", "stick") + val elements = deckGamepad(upgraded).elements + assertEquals(InputStockLayouts.gamepad(leftPad = true, rightStick = true), elements) + assertEquals(InputElementKind.TRACKPAD, elements.single { it.control == InputControl.LEFT_PAD }.kind) + assertEquals(InputElementKind.STICK, elements.single { it.control == InputControl.RS }.kind) + assertNull(elements.firstOrNull { it.control == InputControl.LS || it.control == InputControl.RIGHT_PAD }) + assertEquals(OnScreenLayout.Dock(false, true, InputControl.LEFT_PAD, InputControl.RS), OnScreenLayout.Dock.deck(false, deckGamepad(upgraded))) + assertEquals(upgraded, InputLibraries.read(context)) + } + + @Test fun theDefaultDeckGamepadChoiceOrAnEditedDeckGamepadIsLeftAlone() { + val stock = InputStockLayouts.layouts().first { it.id == DeckPanel.GAMEPAD.layout } + assertEquals(stock.elements, deckGamepad(upgradeWithSurfaces(null, null)).elements) + assertEquals(stock.elements, deckGamepad(upgradeWithSurfaces("stick", "trackpad")).elements) + val moved = stock.elements.map { if (it.control == InputControl.A) it.copy(x = .7f) else it } + val edited = upgradeWithSurfaces("trackpad", "stick") { it.withLayout(deckGamepad(it).copy(elements = moved)) } + assertEquals(moved, deckGamepad(edited).elements) + val others = InputLibraries.read(context).layouts.filter { it.id != DeckPanel.GAMEPAD.layout } + assertEquals(InputStockLayouts.layouts().filter { it.id != DeckPanel.GAMEPAD.layout }.map { it.elements }, + others.filter { l -> InputStockLayouts.layouts().any { it.id == l.id } }.map { it.elements }) + } } diff --git a/app/src/test/java/com/droiddeck/launcher/input/SecondScreenModeTest.kt b/app/src/test/java/com/droiddeck/launcher/input/SecondScreenModeTest.kt index 3da439ee5c..9956f1aa6d 100644 --- a/app/src/test/java/com/droiddeck/launcher/input/SecondScreenModeTest.kt +++ b/app/src/test/java/com/droiddeck/launcher/input/SecondScreenModeTest.kt @@ -64,4 +64,13 @@ class SecondScreenModeTest { assertEquals(SecondScreenMode.TERMINAL, SecondScreenMode.opening("terminal", deckPad, dashboard, desktopSession = true)) } } + + @Test fun theEditorOpensOnTheChosenBottomScreenUnlessAnotherAppHoldsIt() { + val free = { _: Int -> false } + assertEquals(listOf(2, 3), SecondScreenDisplays.editorCandidates(2, listOf(2, 3), free)) + assertEquals(listOf(3, 2), SecondScreenDisplays.editorCandidates(3, listOf(2, 3), free)) + assertEquals(listOf(-1, 2), SecondScreenDisplays.editorCandidates(-1, listOf(2), free)) + assertEquals(listOf(3), SecondScreenDisplays.editorCandidates(2, listOf(2, 3)) { it == 2 }) + assertEquals(emptyList(), SecondScreenDisplays.editorCandidates(2, listOf(2)) { it == 2 }) + } } diff --git a/app/src/test/java/com/droiddeck/launcher/ui/InputSettingsTest.kt b/app/src/test/java/com/droiddeck/launcher/ui/InputSettingsTest.kt index 679655d20b..c93a6b1c3e 100644 --- a/app/src/test/java/com/droiddeck/launcher/ui/InputSettingsTest.kt +++ b/app/src/test/java/com/droiddeck/launcher/ui/InputSettingsTest.kt @@ -383,9 +383,10 @@ class InputSettingsTest { @Test fun elementPropertiesEditOpacityLabelsAndOfferImages() { val element = mutableStateOf(InputSurfaceElement(InputControl.A, .1f, .1f, .2f, .2f, "custom", name = "Custom button")) + val action = mutableStateOf(null) compose.setContent { DroidDeckTheme { androidx.compose.foundation.layout.Column(androidx.compose.ui.Modifier.verticalScroll(androidx.compose.foundation.rememberScrollState())) { - InputElementProperties(element.value) { element.value = it } + InputElementProperties(element.value, action.value) { element.value = it } } } } compose.onNodeWithTag("setting-element-opacity").performScrollTo().performSemanticsAction(androidx.compose.ui.semantics.SemanticsActions.SetProgress) { it(60f) } @@ -399,6 +400,14 @@ class InputSettingsTest { compose.runOnIdle { element.value = InputSurfaceElement(InputControl.LEFT_PAD, .1f, .1f, .4f, .4f, "pad", InputElementKind.TRACKPAD, "Pad") } compose.onNodeWithTag("setting-element-label").assertDoesNotExist() compose.onNodeWithTag("setting-element-opacity").assertExists() + for (kind in listOf(InputElementKind.DPAD, InputElementKind.STICK)) { + compose.runOnIdle { element.value = InputSurfaceElement(kind.control, .1f, .1f, .4f, .4f, "imported:0", kind, "WASD"); action.value = InputAction.Directions(17, 32, 31, 30) } + compose.onNodeWithTag("setting-element-label").performScrollTo().performClick() + compose.runOnIdle { assertFalse(element.value.showLabel); action.value = InputAction.Native() } + compose.onNodeWithTag("setting-element-label").assertDoesNotExist() + } + compose.runOnIdle { element.value = InputSurfaceElement(InputControl.LS, .1f, .1f, .4f, .4f, "legacy:left-stick", InputElementKind.STICK, "Move") } + compose.onNodeWithTag("setting-element-label").assertExists() } @Test fun editorScalesEveryControlAndRestoresStockPositions() { @@ -421,6 +430,23 @@ class InputSettingsTest { compose.runOnIdle { assertEquals(layout.elements, InputLibraries.read(context).layouts.first { it.id == layout.id }.elements) } } + @Test fun editorSnapChoiceIsRemembered() { + com.droiddeck.launcher.input.ControllerPrefs.setSnap(context, true) + val library = InputLibraries.read(context) + val layout = library.layout("game", InputRole.SECONDARY)!! + val round = mutableStateOf(0) + compose.setContent { DroidDeckTheme { androidx.compose.runtime.key(round.value) { InputSurfaceDialog("game", {}, {}, role = InputRole.PRIMARY, + layoutId = layout.id, bindingsId = library.profile("game").binding(InputRole.SECONDARY)) } } } + val snap = isToggleable() and hasAnySibling(hasText(context.getString(R.string.ctrl_editor_snap))) + compose.onNodeWithTag("input-editor-tools").performClick() + compose.onNode(snap).performScrollTo().assertIsOn().performClick() + compose.runOnIdle { assertFalse(com.droiddeck.launcher.input.ControllerPrefs.snap(context)) } + compose.runOnIdle { round.value = 1 } + compose.onNodeWithTag("input-editor-tools").performClick() + compose.onNode(snap).performScrollTo().assertIsOff() + com.droiddeck.launcher.input.ControllerPrefs.setSnap(context, true) + } + @Test fun quickButtonTogglesHideBuiltInButtonsWithoutDeletingThem() { compose.setContent { DroidDeckTheme { InputSettingsPage({}) } } compose.onNodeWithTag("input-profile-game").performScrollTo().performClick() diff --git a/app/src/test/java/com/droiddeck/launcher/ui/InputSurfaceViewTest.kt b/app/src/test/java/com/droiddeck/launcher/ui/InputSurfaceViewTest.kt index 48616c03a5..6bf1d9ddaf 100644 --- a/app/src/test/java/com/droiddeck/launcher/ui/InputSurfaceViewTest.kt +++ b/app/src/test/java/com/droiddeck/launcher/ui/InputSurfaceViewTest.kt @@ -58,6 +58,16 @@ class InputSurfaceViewTest { } finally { root.removeAllViews(); bridge.stop(); controller.pause().stop().destroy() } } + @Test fun keyedDirectionsAreLabelledWithTheirKeysAndOtherActionsAreNot() { + val context = RuntimeEnvironment.getApplication() + val wasd = InputAction.Directions(17, 32, 31, 30, listOf(18, 0, 0, 16)) + assertEquals(listOf((0f to -1f) to "W", (1f to 0f) to "D", (0f to 1f) to "S", (-1f to 0f) to "A"), + InputSurfaceView.directionLabels(context, wasd, cardinalOnly = true)) + assertEquals(listOf("W", "D", "S", "A", "E", "Q"), InputSurfaceView.directionLabels(context, wasd, cardinalOnly = false).map { it.second }) + assertEquals(listOf("W", "A"), InputSurfaceView.directionLabels(context, InputAction.Directions(17, 0, 0, 30), cardinalOnly = false).map { it.second }) + assertTrue(InputSurfaceView.directionLabels(context, null, cardinalOnly = false).isEmpty()) + } + @Test fun takesTouchAtMatchesWhatATouchDoes() { val directory = Files.createTempDirectory("deck-panel").toFile() try { diff --git a/app/src/test/java/com/droiddeck/launcher/ui/SecondScreenBarTest.kt b/app/src/test/java/com/droiddeck/launcher/ui/SecondScreenBarTest.kt index 8a16a55e0b..4779843153 100644 --- a/app/src/test/java/com/droiddeck/launcher/ui/SecondScreenBarTest.kt +++ b/app/src/test/java/com/droiddeck/launcher/ui/SecondScreenBarTest.kt @@ -99,7 +99,7 @@ class SecondScreenBarTest { } finally { panel.closeControls() } } - @Test fun deckPanelHasTheBarWithSwapAndTabsAndProfileControlsStayFullScreen() { + @Test fun deckPanelHasTheBarWithSwapAndTabsAndProfileControlsKeepTheBar() { SessionState.mode = SessionService.MODE_STEAM SessionState.deckPad = true var swaps = 0 @@ -116,8 +116,14 @@ class SecondScreenBarTest { assertEquals(DeckPanel.entries.map { context.getString(it.label) }, bar.tabs!!.labels) assertEquals(DeckPanel.BOTH.ordinal, bar.tabs!!.selected) panel.showMode(SecondScreenMode.CUSTOM_CONTROLS) - assertNull(panel.barModel(SecondScreenMode.CUSTOM_CONTROLS)) - assertTrue(rootChildren(panel)[0] is InputSurfaceView) + val profile = panel.barModel(SecondScreenMode.CUSTOM_CONTROLS)!! + assertEquals(context.getString(R.string.input_profile_controls_mode), profile.title) + assertEquals(listOf(context.getString(R.string.swap_screens)), profile.extras.map { it.description }) + assertNotNull(profile.onClose); assertNotNull(profile.onMenu) + profile.extras.single().onClick() + assertEquals(2, swaps) + assertTrue(rootChildren(panel)[0] is ComposeView) + assertTrue(rootChildren(panel)[1] is InputSurfaceView) } finally { panel.closeControls() } val noSwap = presentation(swap = null) try { @@ -221,6 +227,47 @@ class SecondScreenBarTest { compose.onNodeWithTag("bottom-bar-close").assertIsDisplayed() } + @Test fun swappedTopScreenProfileControlsHaveTheBarWithSwap() { + SessionState.mode = SessionService.MODE_STEAM + val activity = Robolectric.buildActivity(SessionActivity::class.java).get() + ReflectionHelpers.getField>(activity, "secondScreenMode\$delegate").value = SecondScreenMode.CUSTOM_CONTROLS + val method = SessionActivity::class.java.getDeclaredMethod("SwappedTopPanel", androidx.compose.runtime.Composer::class.java, Int::class.javaPrimitiveType) + method.isAccessible = true + compose.setContent { DroidDeckTheme { method.invoke(activity, currentComposer, 0) } } + compose.onNodeWithText(context.getString(R.string.input_profile_controls_mode)).assertIsDisplayed() + compose.onNodeWithContentDescription(context.getString(R.string.swap_screens)).assertIsDisplayed() + compose.onNodeWithTag("bottom-bar-close").assertIsDisplayed() + } + + @Test fun theBottomPanelWaitsUnderAnOpenInputEditorAndReturnsWhenItCloses() { + SessionState.mode = SessionService.MODE_STEAM + val id = org.robolectric.shadows.ShadowDisplayManager.addDisplay("w1280dp-h960dp") + val activity = Robolectric.buildActivity(SessionActivity::class.java).get() + val displays = activity.getSystemService(DisplayManager::class.java) + org.robolectric.Shadows.shadowOf(displays.getDisplay(id)).setFlags(Display.FLAG_PRESENTATION) + ReflectionHelpers.setField(activity, "displayManager", displays) + ReflectionHelpers.setField(activity, "started", true) + ReflectionHelpers.getField>(activity, "selectedSecondScreenDisplay\$delegate").value = id + val mode = ReflectionHelpers.getField>(activity, "secondScreenMode\$delegate") + mode.value = SecondScreenMode.KEYBOARD_TRACKPAD + fun panel() = ReflectionHelpers.getField(activity, "secondScreenPresentation") + fun show() = ReflectionHelpers.callInstanceMethod(activity, "showSecondScreen", + ReflectionHelpers.ClassParameter.from(SecondScreenMode::class.java, SecondScreenMode.KEYBOARD_TRACKPAD)) + activity.inputWindowShown(Display.DEFAULT_DISPLAY) + activity.inputWindowShown(id) + activity.inputWindowShown(id) + show() + assertNull(panel()) + assertEquals(SecondScreenMode.KEYBOARD_TRACKPAD, mode.value) + activity.inputWindowClosed(id) + assertNull(panel()) + activity.inputWindowClosed(id) + try { + assertEquals(id, panel()!!.display.displayId) + assertTrue(panel()!!.isShowing) + } finally { panel()?.closeControls() } + } + @Test fun unfoldedControlsGetTheSessionBar() { SessionState.mode = SessionService.MODE_STEAM val activity = Robolectric.buildActivity(SessionActivity::class.java).get() diff --git a/app/src/test/java/com/droiddeck/launcher/ui/SessionSecondScreenTest.kt b/app/src/test/java/com/droiddeck/launcher/ui/SessionSecondScreenTest.kt index 12098a566b..dc2ff1cc6c 100644 --- a/app/src/test/java/com/droiddeck/launcher/ui/SessionSecondScreenTest.kt +++ b/app/src/test/java/com/droiddeck/launcher/ui/SessionSecondScreenTest.kt @@ -376,7 +376,7 @@ class SessionSecondScreenTest { isHomeApp = false, androidApps = emptyList(), hudOn = false, micAvailable = micAvailable, micEnabled = micEnabled, onMicEnabled = onMic, frameGen = FrameGen.Mode.OFF, lossless = Lossless.State.NONE, oscMode = "never", - onScreenButtonsVisible = false, suspendPolicy = "auto", backActionsInverted = false, + suspendPolicy = "auto", backActionsInverted = false, touchMode = "auto", touchAuto = "trackpad", fexPreset = "default", secondScreenMode = SecondScreenMode.NONE, secondScreenDisplays = displays, selectedSecondScreenDisplay = 1, onHud = {}, onFrameGenPick = {}, onImportLossless = {}, onKeyboard = {}, onHardwareKeyboard = {},