UX: Dev Console easier to use without installing keyboard apps (#11706)

* UI replacements for Tab, Up, Down in DevConsolePopup

* DevConsole history display via command or longpress on the Android UI
This commit is contained in:
SomeTroglodyte
2024-06-08 20:59:33 +02:00
committed by GitHub
parent 68e29e9c53
commit aa74c557d2
3 changed files with 78 additions and 13 deletions

View File

@ -77,7 +77,7 @@ object GUI {
}
private var keyboardAvailableCache: Boolean? = null
/** Tests availability of a physical keyboard */
/** Tests availability of a physical keyboard - cached (connecting a keyboard while the game is running won't be recognized until relaunch) */
val keyboardAvailable: Boolean
get() {
// defer decision if Gdx.input not yet initialized

View File

@ -86,6 +86,10 @@ internal class ConsoleCommandRoot : ConsoleCommandNode {
"unit" to ConsoleUnitCommands(),
"city" to ConsoleCityCommands(),
"tile" to ConsoleTileCommands(),
"civ" to ConsoleCivCommands()
"civ" to ConsoleCivCommands(),
"history" to ConsoleAction("history") { console, _ ->
console.showHistory()
DevConsoleResponse.hint("") // Trick console into staying open
}
)
}

View File

@ -2,15 +2,24 @@ package com.unciv.ui.screens.devconsole
import com.badlogic.gdx.Input
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.actions.Actions
import com.badlogic.gdx.scenes.scene2d.ui.Label
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Scaling
import com.unciv.Constants
import com.unciv.GUI
import com.unciv.logic.civilization.Civilization
import com.unciv.logic.map.mapunit.MapUnit
import com.unciv.ui.components.UncivTextField
import com.unciv.ui.components.extensions.surroundWithCircle
import com.unciv.ui.components.extensions.toCheckBox
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.components.input.KeyCharAndCode
import com.unciv.ui.components.input.keyShortcuts
import com.unciv.ui.components.input.onClick
import com.unciv.ui.components.input.onRightClick
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.popups.Popup
import com.unciv.ui.screens.devconsole.CliInput.Companion.splitToCliInput
import com.unciv.ui.screens.worldscreen.WorldScreen
@ -27,6 +36,7 @@ class DevConsolePopup(val screen: WorldScreen) : Popup(screen) {
private val textField = UncivTextField.create("", "") // always has focus, so a hint won't show
private val responseLabel = "".toLabel(Color.RED).apply { wrap = true }
private val inputWrapper = Table()
private val commandRoot = ConsoleCommandRoot()
internal val gameInfo = screen.gameInfo
@ -34,12 +44,16 @@ class DevConsolePopup(val screen: WorldScreen) : Popup(screen) {
init {
// Use untranslated text here! The entire console, including messages, should stay English.
// But "Developer Console" *has* a translation from KeyboardBinding.DeveloperConsole.
// The extensions still help, even with a "don't translate" kludge ("Keep open" has no template but might in the future).
add("Developer Console {}".toLabel(fontSize = Constants.headingFontSize)).growX() // translation template is automatic via the keybinding
// The extensions still help, even with a "don't translate" kludge
add("Developer Console {}".toLabel(fontSize = Constants.headingFontSize)).growX()
add("Keep open {}".toCheckBox(keepOpen) { keepOpen = it }).right().row()
add(textField).width(stageToShowOn.width / 2).colspan(2).row()
textField.keyShortcuts.add(Input.Keys.ENTER, ::onEnter)
inputWrapper.defaults().space(5f)
if (!GUI.keyboardAvailable) inputWrapper.add(getAutocompleteButton())
inputWrapper.add(textField).growX()
if (!GUI.keyboardAvailable) inputWrapper.add(getHistoryButtons())
add(inputWrapper).minWidth(stageToShowOn.width / 2).growX().colspan(2).row()
// Without this, console popup will always contain the key used to open it - won't work perfectly if it's configured to a "dead key"
textField.addAction(Actions.delay(0.05f, Actions.run { textField.text = "" }))
@ -49,13 +63,8 @@ class DevConsolePopup(val screen: WorldScreen) : Popup(screen) {
keyShortcuts.add(KeyCharAndCode.BACK) { close() }
clickBehindToClose = true
textField.keyShortcuts.add(KeyCharAndCode.TAB) {
getAutocomplete()?.also {
fun String.removeFromEnd(n: Int) = substring(0, (length - n).coerceAtLeast(0))
textField.text = textField.text.removeFromEnd(it.first) + it.second
textField.cursorPosition = Int.MAX_VALUE // because the setText implementation actively resets it after the paste it uses (auto capped at length)
}
}
textField.keyShortcuts.add(Input.Keys.ENTER, ::onEnter)
textField.keyShortcuts.add(KeyCharAndCode.TAB, ::onAutocomplete)
keyShortcuts.add(Input.Keys.UP) { navigateHistory(-1) }
keyShortcuts.add(Input.Keys.DOWN) { navigateHistory(1) }
@ -65,6 +74,35 @@ class DevConsolePopup(val screen: WorldScreen) : Popup(screen) {
screen.stage.keyboardFocus = textField
}
private fun getAutocompleteButton() = ImageGetter.getArrowImage()
.surroundWithCircle(50f, color = Color.DARK_GRAY).onClick(::onAutocomplete)
private fun getHistoryButtons(): Actor {
val group = Table()
fun getArrow(rotation: Float, delta: Int) = ImageGetter.getImage("OtherIcons/ForwardArrow").apply {
name = if (delta > 0) "down" else "up"
setScaling(Scaling.fillX)
setSize(36f, 16f)
scaleX = 0.75f // no idea why this works
setOrigin(18f, 8f)
this.rotation = rotation
onClick {
navigateHistory(delta)
}
}
group.add(getArrow(90f, -1)).size(36f, 16f).padBottom(4f).row()
group.add(getArrow(-90f, 1)).size(36f, 16f)
group.setSize(40f, 40f)
return group.surroundWithCircle(50f, false, Color.DARK_GRAY).onRightClick(action = ::showHistory)
}
private fun onAutocomplete() {
val (toRemove, toAdd) = getAutocomplete() ?: return
fun String.removeFromEnd(n: Int) = substring(0, (length - n).coerceAtLeast(0))
textField.text = textField.text.removeFromEnd(toRemove) + toAdd
textField.cursorPosition = Int.MAX_VALUE // because the setText implementation actively resets it after the paste it uses (auto capped at length)
}
private fun navigateHistory(delta: Int) {
if (history.isEmpty()) return
currentHistoryEntry = (currentHistoryEntry + delta).coerceIn(history.indices)
@ -72,6 +110,29 @@ class DevConsolePopup(val screen: WorldScreen) : Popup(screen) {
textField.cursorPosition = textField.text.length
}
internal fun showHistory() {
if (history.isEmpty()) return
val popup = object : Popup(stageToShowOn) {
init {
for ((index, entry) in history.withIndex()) {
val label = Label(entry, skin)
label.onClick {
currentHistoryEntry = index
navigateHistory(0)
close()
}
add(label).row()
}
clickBehindToClose = true
if (screen.game.settings.forbidPopupClickBehindToClose) addCloseButton()
showListeners.add {
getScrollPane()?.run { scrollY = maxY }
}
}
}
popup.open(true)
}
private fun onEnter() {
val handleCommandResponse = handleCommand()
if (handleCommandResponse.isOK) {