mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-26 15:49:14 +07:00
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:
@ -77,7 +77,7 @@ object GUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var keyboardAvailableCache: Boolean? = null
|
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
|
val keyboardAvailable: Boolean
|
||||||
get() {
|
get() {
|
||||||
// defer decision if Gdx.input not yet initialized
|
// defer decision if Gdx.input not yet initialized
|
||||||
|
@ -86,6 +86,10 @@ internal class ConsoleCommandRoot : ConsoleCommandNode {
|
|||||||
"unit" to ConsoleUnitCommands(),
|
"unit" to ConsoleUnitCommands(),
|
||||||
"city" to ConsoleCityCommands(),
|
"city" to ConsoleCityCommands(),
|
||||||
"tile" to ConsoleTileCommands(),
|
"tile" to ConsoleTileCommands(),
|
||||||
"civ" to ConsoleCivCommands()
|
"civ" to ConsoleCivCommands(),
|
||||||
|
"history" to ConsoleAction("history") { console, _ ->
|
||||||
|
console.showHistory()
|
||||||
|
DevConsoleResponse.hint("") // Trick console into staying open
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,24 @@ package com.unciv.ui.screens.devconsole
|
|||||||
|
|
||||||
import com.badlogic.gdx.Input
|
import com.badlogic.gdx.Input
|
||||||
import com.badlogic.gdx.graphics.Color
|
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.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.Constants
|
||||||
|
import com.unciv.GUI
|
||||||
import com.unciv.logic.civilization.Civilization
|
import com.unciv.logic.civilization.Civilization
|
||||||
import com.unciv.logic.map.mapunit.MapUnit
|
import com.unciv.logic.map.mapunit.MapUnit
|
||||||
import com.unciv.ui.components.UncivTextField
|
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.toCheckBox
|
||||||
import com.unciv.ui.components.extensions.toLabel
|
import com.unciv.ui.components.extensions.toLabel
|
||||||
import com.unciv.ui.components.input.KeyCharAndCode
|
import com.unciv.ui.components.input.KeyCharAndCode
|
||||||
import com.unciv.ui.components.input.keyShortcuts
|
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.popups.Popup
|
||||||
import com.unciv.ui.screens.devconsole.CliInput.Companion.splitToCliInput
|
import com.unciv.ui.screens.devconsole.CliInput.Companion.splitToCliInput
|
||||||
import com.unciv.ui.screens.worldscreen.WorldScreen
|
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 textField = UncivTextField.create("", "") // always has focus, so a hint won't show
|
||||||
private val responseLabel = "".toLabel(Color.RED).apply { wrap = true }
|
private val responseLabel = "".toLabel(Color.RED).apply { wrap = true }
|
||||||
|
private val inputWrapper = Table()
|
||||||
|
|
||||||
private val commandRoot = ConsoleCommandRoot()
|
private val commandRoot = ConsoleCommandRoot()
|
||||||
internal val gameInfo = screen.gameInfo
|
internal val gameInfo = screen.gameInfo
|
||||||
@ -34,12 +44,16 @@ class DevConsolePopup(val screen: WorldScreen) : Popup(screen) {
|
|||||||
init {
|
init {
|
||||||
// Use untranslated text here! The entire console, including messages, should stay English.
|
// Use untranslated text here! The entire console, including messages, should stay English.
|
||||||
// But "Developer Console" *has* a translation from KeyboardBinding.DeveloperConsole.
|
// 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).
|
// The extensions still help, even with a "don't translate" kludge
|
||||||
add("Developer Console {}".toLabel(fontSize = Constants.headingFontSize)).growX() // translation template is automatic via the keybinding
|
add("Developer Console {}".toLabel(fontSize = Constants.headingFontSize)).growX()
|
||||||
add("Keep open {}".toCheckBox(keepOpen) { keepOpen = it }).right().row()
|
add("Keep open {}".toCheckBox(keepOpen) { keepOpen = it }).right().row()
|
||||||
|
|
||||||
add(textField).width(stageToShowOn.width / 2).colspan(2).row()
|
inputWrapper.defaults().space(5f)
|
||||||
textField.keyShortcuts.add(Input.Keys.ENTER, ::onEnter)
|
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"
|
// 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 = "" }))
|
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() }
|
keyShortcuts.add(KeyCharAndCode.BACK) { close() }
|
||||||
clickBehindToClose = true
|
clickBehindToClose = true
|
||||||
|
|
||||||
textField.keyShortcuts.add(KeyCharAndCode.TAB) {
|
textField.keyShortcuts.add(Input.Keys.ENTER, ::onEnter)
|
||||||
getAutocomplete()?.also {
|
textField.keyShortcuts.add(KeyCharAndCode.TAB, ::onAutocomplete)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keyShortcuts.add(Input.Keys.UP) { navigateHistory(-1) }
|
keyShortcuts.add(Input.Keys.UP) { navigateHistory(-1) }
|
||||||
keyShortcuts.add(Input.Keys.DOWN) { navigateHistory(1) }
|
keyShortcuts.add(Input.Keys.DOWN) { navigateHistory(1) }
|
||||||
@ -65,6 +74,35 @@ class DevConsolePopup(val screen: WorldScreen) : Popup(screen) {
|
|||||||
screen.stage.keyboardFocus = textField
|
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) {
|
private fun navigateHistory(delta: Int) {
|
||||||
if (history.isEmpty()) return
|
if (history.isEmpty()) return
|
||||||
currentHistoryEntry = (currentHistoryEntry + delta).coerceIn(history.indices)
|
currentHistoryEntry = (currentHistoryEntry + delta).coerceIn(history.indices)
|
||||||
@ -72,6 +110,29 @@ class DevConsolePopup(val screen: WorldScreen) : Popup(screen) {
|
|||||||
textField.cursorPosition = textField.text.length
|
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() {
|
private fun onEnter() {
|
||||||
val handleCommandResponse = handleCommand()
|
val handleCommandResponse = handleCommand()
|
||||||
if (handleCommandResponse.isOK) {
|
if (handleCommandResponse.isOK) {
|
||||||
|
Reference in New Issue
Block a user