mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-20 12:48:56 +07:00
Allow access to the Dev Console on mobile devices (#11588)
* Make DeveloperConsole callable from WorldScreenMenuPopup * Better WorldScreenMenuPopup single/dual-column logic * Allow developer console to stay open and persist history * Fix console tab key closing onscreen keyboard * Give the now persisted console history an upper size limit * Ensure "up" always gives the new entry * Fix merge leftovers * Reviews
This commit is contained in:
@ -128,6 +128,11 @@ class GameSettings {
|
||||
/** Size of automatic display of UnitSet art in Civilopedia - 0 to disable */
|
||||
var pediaUnitArtSize = 0f
|
||||
|
||||
/** Don't close developer console after a successful command */
|
||||
var keepConsoleOpen = false
|
||||
/** Persist the history of successful developer console commands */
|
||||
val consoleCommandHistory = ArrayList<String>()
|
||||
|
||||
/** used to migrate from older versions of the settings */
|
||||
var version: Int? = null
|
||||
|
||||
|
@ -17,6 +17,7 @@ import com.unciv.ui.components.extensions.getOverlap
|
||||
import com.unciv.ui.components.extensions.right
|
||||
import com.unciv.ui.components.extensions.stageBoundingBox
|
||||
import com.unciv.ui.components.extensions.top
|
||||
import com.unciv.ui.components.input.KeyCharAndCode
|
||||
import com.unciv.ui.components.input.keyShortcuts
|
||||
import com.unciv.ui.popups.Popup
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
@ -42,7 +43,12 @@ object UncivTextField {
|
||||
*/
|
||||
fun create(hint: String, preEnteredText: String = "", onFocusChange: (TextField.(Boolean) -> Unit)? = null): TextField {
|
||||
@Suppress("UNCIV_RAW_TEXTFIELD")
|
||||
val textField = TextField(preEnteredText, BaseScreen.skin)
|
||||
val textField = object : TextField(preEnteredText, BaseScreen.skin) {
|
||||
override fun next(up: Boolean) {
|
||||
if (KeyCharAndCode.TAB in keyShortcuts) return
|
||||
super.next(up)
|
||||
}
|
||||
}
|
||||
val translatedHint = hint.tr()
|
||||
textField.messageText = translatedHint
|
||||
textField.addListener(object : FocusListener() {
|
||||
|
@ -61,6 +61,15 @@ open class KeyShortcutDispatcher {
|
||||
shortcuts.removeAll { it.shortcut.key.code == keyCode }
|
||||
}
|
||||
|
||||
operator fun contains(binding: KeyboardBinding) =
|
||||
shortcuts.any { it.shortcut.binding == binding }
|
||||
operator fun contains(key: KeyCharAndCode) =
|
||||
shortcuts.any { it.shortcut.key == key || it.shortcut.binding.defaultKey == key }
|
||||
operator fun contains(char: Char) =
|
||||
shortcuts.any { it.shortcut.key.char == char }
|
||||
operator fun contains(keyCode: Int) =
|
||||
shortcuts.any { it.shortcut.key.code == keyCode }
|
||||
|
||||
open fun isActive(): Boolean = true
|
||||
|
||||
|
||||
|
@ -68,8 +68,8 @@ open class Popup(
|
||||
*/
|
||||
enum class Scrollability { None, All, WithoutButtons }
|
||||
|
||||
private val maxPopupWidth = stageToShowOn.width * maxSizePercentage
|
||||
private val maxPopupHeight = stageToShowOn.height * maxSizePercentage
|
||||
protected val maxPopupWidth = stageToShowOn.width * maxSizePercentage
|
||||
protected val maxPopupHeight = stageToShowOn.height * maxSizePercentage
|
||||
|
||||
/** This exists to differentiate the actual popup (this table)
|
||||
* from the 'screen blocking' part of the popup (which covers the entire screen).
|
||||
|
@ -3,81 +3,100 @@ package com.unciv.ui.screens.devconsole
|
||||
import com.badlogic.gdx.Input
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.actions.Actions
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextField
|
||||
import com.unciv.Constants
|
||||
import com.unciv.logic.map.mapunit.MapUnit
|
||||
import com.unciv.ui.components.UncivTextField
|
||||
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.popups.Popup
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.ui.screens.worldscreen.WorldScreen
|
||||
|
||||
|
||||
class DevConsolePopup(val screen: WorldScreen) : Popup(screen) {
|
||||
companion object {
|
||||
val history = ArrayList<String>()
|
||||
private const val maxHistorySize = 42
|
||||
}
|
||||
private val history by screen.game.settings::consoleCommandHistory
|
||||
private var keepOpen by screen.game.settings::keepConsoleOpen
|
||||
|
||||
private var currentHistoryEntry = history.size
|
||||
|
||||
private val textField = TextField("", BaseScreen.skin)
|
||||
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 commandRoot = ConsoleCommandRoot()
|
||||
internal val gameInfo = screen.gameInfo
|
||||
|
||||
init {
|
||||
add(textField).width(stageToShowOn.width / 2).row()
|
||||
// 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
|
||||
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)
|
||||
|
||||
// Without this, console popup will always contain a `
|
||||
// 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 = "" }))
|
||||
|
||||
add(responseLabel).maxWidth(screen.stage.width * 0.8f)
|
||||
|
||||
open(true)
|
||||
add(responseLabel).colspan(2).maxWidth(screen.stage.width * 0.8f)
|
||||
|
||||
keyShortcuts.add(KeyCharAndCode.BACK) { close() }
|
||||
clickBehindToClose = true
|
||||
|
||||
keyShortcuts.add(KeyCharAndCode.TAB) {
|
||||
textField.keyShortcuts.add(KeyCharAndCode.TAB) {
|
||||
val textToAdd = getAutocomplete()
|
||||
textField.appendText(textToAdd)
|
||||
}
|
||||
|
||||
if (history.isNotEmpty()) {
|
||||
keyShortcuts.add(Input.Keys.UP) {
|
||||
if (currentHistoryEntry > 0) currentHistoryEntry--
|
||||
textField.text = history[currentHistoryEntry]
|
||||
textField.cursorPosition = textField.text.length
|
||||
}
|
||||
keyShortcuts.add(Input.Keys.DOWN) {
|
||||
if (currentHistoryEntry == history.size) currentHistoryEntry--
|
||||
if (currentHistoryEntry < history.lastIndex) currentHistoryEntry++
|
||||
textField.text = history[currentHistoryEntry]
|
||||
textField.cursorPosition = textField.text.length
|
||||
}
|
||||
}
|
||||
keyShortcuts.add(Input.Keys.UP) { navigateHistory(-1) }
|
||||
keyShortcuts.add(Input.Keys.DOWN) { navigateHistory(1) }
|
||||
|
||||
open(true)
|
||||
|
||||
screen.stage.keyboardFocus = textField
|
||||
}
|
||||
|
||||
private fun navigateHistory(delta: Int) {
|
||||
if (history.isEmpty()) return
|
||||
currentHistoryEntry = (currentHistoryEntry + delta).coerceIn(history.indices)
|
||||
textField.text = history[currentHistoryEntry]
|
||||
textField.cursorPosition = textField.text.length
|
||||
}
|
||||
|
||||
private fun onEnter() {
|
||||
val handleCommandResponse = handleCommand()
|
||||
if (handleCommandResponse.isOK) {
|
||||
screen.shouldUpdate = true
|
||||
if (history.isEmpty() || history.last() != textField.text)
|
||||
history.add(textField.text)
|
||||
close()
|
||||
addHistory()
|
||||
if (!keepOpen) close() else textField.text = ""
|
||||
return
|
||||
}
|
||||
showResponse(handleCommandResponse.message, handleCommandResponse.color)
|
||||
}
|
||||
|
||||
private fun addHistory() {
|
||||
val text = textField.text
|
||||
if (text.isBlank()) return
|
||||
if (history.isNotEmpty() && history.last().equals(text, true)) return
|
||||
if (history.size >= maxHistorySize) {
|
||||
history.removeAll { it.equals(text, true) }
|
||||
if (history.size >= maxHistorySize)
|
||||
history.removeAt(0)
|
||||
}
|
||||
history.add(textField.text)
|
||||
currentHistoryEntry = history.size
|
||||
}
|
||||
|
||||
internal fun showResponse(message: String?, color: Color) {
|
||||
responseLabel.setText(message)
|
||||
responseLabel.style.fontColor = color
|
||||
}
|
||||
|
||||
val splitStringRegex = Regex("\"([^\"]+)\"|\\S+") // Read: "(phrase)" OR non-whitespace
|
||||
private val splitStringRegex = Regex("\"([^\"]+)\"|\\S+") // Read: "(phrase)" OR non-whitespace
|
||||
private fun getParams(text: String): List<String> {
|
||||
return splitStringRegex.findAll(text).map { it.value.removeSurrounding("\"") }.filter { it.isNotEmpty() }.toList()
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import com.unciv.logic.multiplayer.storage.MultiplayerAuthException
|
||||
import com.unciv.logic.trade.TradeEvaluation
|
||||
import com.unciv.models.TutorialTrigger
|
||||
import com.unciv.models.metadata.GameSetupInfo
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.tile.ResourceType
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.ui.components.extensions.centerX
|
||||
@ -38,7 +37,6 @@ import com.unciv.ui.popups.ToastPopup
|
||||
import com.unciv.ui.popups.hasOpenPopups
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.ui.screens.cityscreen.CityScreen
|
||||
import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen
|
||||
import com.unciv.ui.screens.devconsole.DevConsolePopup
|
||||
import com.unciv.ui.screens.mainmenuscreen.MainMenuScreen
|
||||
import com.unciv.ui.screens.newgamescreen.NewGameScreen
|
||||
@ -285,11 +283,13 @@ class WorldScreen(
|
||||
globalShortcuts.add(KeyboardBinding.ToggleWorkedTilesDisplay) { minimapWrapper.populationImageButton.toggle() }
|
||||
globalShortcuts.add(KeyboardBinding.ToggleMovementDisplay) { minimapWrapper.movementsImageButton.toggle() }
|
||||
|
||||
globalShortcuts.add(KeyboardBinding.DeveloperConsole) {
|
||||
// No cheating unless you're by yourself
|
||||
if (gameInfo.civilizations.count { it.isHuman() } > 1) return@add
|
||||
val consolePopup = DevConsolePopup(this)
|
||||
globalShortcuts.add(KeyboardBinding.DeveloperConsole, action = ::openDeveloperConsole)
|
||||
}
|
||||
|
||||
fun openDeveloperConsole() {
|
||||
// No cheating unless you're by yourself
|
||||
if (gameInfo.civilizations.count { it.isHuman() } > 1) return
|
||||
val consolePopup = DevConsolePopup(this)
|
||||
}
|
||||
|
||||
private fun toggleUI() {
|
||||
|
@ -1,43 +1,67 @@
|
||||
package com.unciv.ui.screens.worldscreen.mainmenu
|
||||
|
||||
import com.unciv.UncivGame
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Cell
|
||||
import com.unciv.ui.components.input.KeyboardBinding
|
||||
import com.unciv.ui.components.input.onLongPress
|
||||
import com.unciv.ui.popups.Popup
|
||||
import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen
|
||||
import com.unciv.ui.screens.savescreens.LoadGameScreen
|
||||
import com.unciv.ui.screens.victoryscreen.VictoryScreen
|
||||
import com.unciv.ui.screens.worldscreen.WorldScreen
|
||||
|
||||
class WorldScreenMenuPopup(val worldScreen: WorldScreen) : Popup(worldScreen, scrollable = Scrollability.All) {
|
||||
/** The in-game menu called from the "Hamburger" button top-left
|
||||
*
|
||||
* Popup automatically opens as soon as it's initialized
|
||||
*/
|
||||
class WorldScreenMenuPopup(
|
||||
val worldScreen: WorldScreen,
|
||||
expertMode: Boolean = false
|
||||
) : Popup(worldScreen, scrollable = Scrollability.All) {
|
||||
private val singleColumn: Boolean
|
||||
private fun <T: Actor?> Cell<T>.nextColumn() {
|
||||
if (!singleColumn && column == 0) return
|
||||
row()
|
||||
}
|
||||
|
||||
init {
|
||||
worldScreen.autoPlay.stopAutoPlay()
|
||||
defaults().fillX()
|
||||
|
||||
addButton("Main menu") {
|
||||
val showSave = !worldScreen.gameInfo.gameParameters.isOnlineMultiplayer
|
||||
val showMusic = worldScreen.game.musicController.isMusicAvailable()
|
||||
val showConsole = showSave && expertMode
|
||||
val buttonCount = 8 + (if (showSave) 1 else 0) + (if (showMusic) 1 else 0) + (if (showConsole) 1 else 0)
|
||||
|
||||
val emptyPrefHeight = this.prefHeight
|
||||
val firstCell = addButton("Main menu") {
|
||||
worldScreen.game.goToMainMenu()
|
||||
}.row()
|
||||
}
|
||||
singleColumn = worldScreen.isCrampedPortrait() ||
|
||||
2 * prefWidth > maxPopupWidth || // Very coarse: Assume width of translated "Main menu" is representative
|
||||
buttonCount * (prefHeight - emptyPrefHeight) + emptyPrefHeight < maxPopupHeight
|
||||
firstCell.nextColumn()
|
||||
|
||||
addButton("Civilopedia", KeyboardBinding.Civilopedia) {
|
||||
close()
|
||||
worldScreen.openCivilopedia()
|
||||
}.row()
|
||||
if (!worldScreen.gameInfo.gameParameters.isOnlineMultiplayer)
|
||||
}.nextColumn()
|
||||
if (showSave)
|
||||
addButton("Save game", KeyboardBinding.SaveGame) {
|
||||
close()
|
||||
worldScreen.openSaveGameScreen()
|
||||
}.row()
|
||||
}.nextColumn()
|
||||
addButton("Load game", KeyboardBinding.LoadGame) {
|
||||
close()
|
||||
worldScreen.game.pushScreen(LoadGameScreen())
|
||||
}.row()
|
||||
}.nextColumn()
|
||||
addButton("Start new game", KeyboardBinding.NewGame) {
|
||||
close()
|
||||
worldScreen.openNewGameScreen()
|
||||
}.row()
|
||||
}.nextColumn()
|
||||
addButton("Victory status", KeyboardBinding.VictoryScreen) {
|
||||
close()
|
||||
worldScreen.game.pushScreen(VictoryScreen(worldScreen))
|
||||
}.row()
|
||||
}.nextColumn()
|
||||
val optionsCell = addButton("Options", KeyboardBinding.Options) {
|
||||
close()
|
||||
worldScreen.openOptionsPopup()
|
||||
@ -46,17 +70,26 @@ class WorldScreenMenuPopup(val worldScreen: WorldScreen) : Popup(worldScreen, sc
|
||||
close()
|
||||
worldScreen.openOptionsPopup(withDebug = true)
|
||||
}
|
||||
optionsCell.row()
|
||||
optionsCell.nextColumn()
|
||||
addButton("Community") {
|
||||
close()
|
||||
WorldScreenCommunityPopup(worldScreen).open(force = true)
|
||||
}.row()
|
||||
}.nextColumn()
|
||||
if (showMusic)
|
||||
addButton("Music", KeyboardBinding.MusicPlayer) {
|
||||
close()
|
||||
WorldScreenMusicPopup(worldScreen).open(force = true)
|
||||
}.row()
|
||||
}.nextColumn()
|
||||
|
||||
addCloseButton()
|
||||
if (showConsole)
|
||||
addButton("Developer Console", KeyboardBinding.DeveloperConsole) {
|
||||
close()
|
||||
worldScreen.openDeveloperConsole()
|
||||
}.nextColumn()
|
||||
|
||||
addCloseButton().run { colspan(if (singleColumn || column == 1) 1 else 2) }
|
||||
pack()
|
||||
|
||||
open(force = true)
|
||||
}
|
||||
}
|
||||
|
@ -16,10 +16,9 @@ import com.unciv.ui.components.fonts.Fonts
|
||||
import com.unciv.ui.components.input.KeyboardBinding
|
||||
import com.unciv.ui.components.input.onActivation
|
||||
import com.unciv.ui.components.input.onClick
|
||||
import com.unciv.ui.components.input.onRightClick
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.ui.screens.civilopediascreen.CivilopediaCategories
|
||||
import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen
|
||||
import com.unciv.ui.screens.overviewscreen.EmpireOverviewCategories
|
||||
import com.unciv.ui.screens.worldscreen.BackgroundActor
|
||||
import com.unciv.ui.screens.worldscreen.WorldScreen
|
||||
@ -212,9 +211,8 @@ class WorldScreenTopBar(internal val worldScreen: WorldScreen) : Table() {
|
||||
padTop((10f - descenderHeight).coerceAtLeast(0f))
|
||||
|
||||
menuButton.color = Color.WHITE
|
||||
menuButton.onActivation(binding = KeyboardBinding.Menu) {
|
||||
WorldScreenMenuPopup(worldScreen).open(force = true)
|
||||
}
|
||||
menuButton.onActivation(binding = KeyboardBinding.Menu) { WorldScreenMenuPopup(worldScreen) }
|
||||
menuButton.onRightClick { WorldScreenMenuPopup(worldScreen, true) }
|
||||
|
||||
val onNationClick = {
|
||||
worldScreen.openCivilopedia(worldScreen.selectedCiv.nation.makeLink())
|
||||
|
Reference in New Issue
Block a user