mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-27 22:06:05 -04:00
Scene2D debug tool (#9579)
This commit is contained in:
parent
c56644cd6d
commit
b0a1eed872
@ -19,17 +19,22 @@ import com.unciv.UncivGame
|
|||||||
import com.unciv.models.TutorialTrigger
|
import com.unciv.models.TutorialTrigger
|
||||||
import com.unciv.models.skins.SkinStrings
|
import com.unciv.models.skins.SkinStrings
|
||||||
import com.unciv.ui.components.Fonts
|
import com.unciv.ui.components.Fonts
|
||||||
|
import com.unciv.ui.components.extensions.isNarrowerThan4to3
|
||||||
import com.unciv.ui.components.input.KeyShortcutDispatcher
|
import com.unciv.ui.components.input.KeyShortcutDispatcher
|
||||||
|
import com.unciv.ui.components.input.KeyShortcutDispatcherVeto
|
||||||
import com.unciv.ui.components.input.DispatcherVetoer
|
import com.unciv.ui.components.input.DispatcherVetoer
|
||||||
import com.unciv.ui.components.input.installShortcutDispatcher
|
import com.unciv.ui.components.input.installShortcutDispatcher
|
||||||
import com.unciv.ui.components.input.keyShortcuts
|
import com.unciv.ui.components.input.keyShortcuts
|
||||||
import com.unciv.ui.components.extensions.isNarrowerThan4to3
|
import com.unciv.ui.crashhandling.CrashScreen
|
||||||
import com.unciv.ui.components.input.KeyShortcutDispatcherVeto
|
|
||||||
import com.unciv.ui.images.ImageGetter
|
import com.unciv.ui.images.ImageGetter
|
||||||
import com.unciv.ui.popups.Popup
|
import com.unciv.ui.popups.Popup
|
||||||
import com.unciv.ui.popups.activePopup
|
import com.unciv.ui.popups.activePopup
|
||||||
import com.unciv.ui.popups.options.OptionsPopup
|
import com.unciv.ui.popups.options.OptionsPopup
|
||||||
|
|
||||||
|
// Both `this is CrashScreen` and `this::createPopupBasedDispatcherVetoer` are flagged.
|
||||||
|
// First - not a leak; second - passes out a pure function
|
||||||
|
@Suppress("LeakingThis")
|
||||||
|
|
||||||
abstract class BaseScreen : Screen {
|
abstract class BaseScreen : Screen {
|
||||||
|
|
||||||
val game: UncivGame = UncivGame.Current
|
val game: UncivGame = UncivGame.Current
|
||||||
@ -50,10 +55,11 @@ abstract class BaseScreen : Screen {
|
|||||||
/** The ExtendViewport sets the _minimum_(!) world size - the actual world size will be larger, fitted to screen/window aspect ratio. */
|
/** The ExtendViewport sets the _minimum_(!) world size - the actual world size will be larger, fitted to screen/window aspect ratio. */
|
||||||
stage = UncivStage(ExtendViewport(height, height))
|
stage = UncivStage(ExtendViewport(height, height))
|
||||||
|
|
||||||
if (enableSceneDebug) {
|
if (enableSceneDebug && this !is CrashScreen) {
|
||||||
stage.setDebugUnderMouse(true)
|
stage.setDebugUnderMouse(true)
|
||||||
stage.setDebugTableUnderMouse(true)
|
stage.setDebugTableUnderMouse(true)
|
||||||
stage.setDebugParentUnderMouse(true)
|
stage.setDebugParentUnderMouse(true)
|
||||||
|
stage.mouseOverDebug = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("LeakingThis")
|
@Suppress("LeakingThis")
|
||||||
|
202
core/src/com/unciv/ui/screens/basescreen/StageMouseOverDebug.kt
Normal file
202
core/src/com/unciv/ui/screens/basescreen/StageMouseOverDebug.kt
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
package com.unciv.ui.screens.basescreen
|
||||||
|
|
||||||
|
import com.badlogic.gdx.Gdx
|
||||||
|
import com.badlogic.gdx.graphics.Color
|
||||||
|
import com.badlogic.gdx.graphics.GL20
|
||||||
|
import com.badlogic.gdx.graphics.g2d.Batch
|
||||||
|
import com.badlogic.gdx.graphics.glutils.ShapeRenderer
|
||||||
|
import com.badlogic.gdx.math.Vector2
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.Group
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.Stage
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Label
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
|
||||||
|
import com.badlogic.gdx.utils.Align
|
||||||
|
import com.unciv.ui.components.Fonts
|
||||||
|
import com.unciv.ui.images.ImageGetter
|
||||||
|
|
||||||
|
|
||||||
|
private typealias AddToStringBuilderFactory = (sb: StringBuilder) -> Unit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A debug helper drawing mouse-over info and world coordinate axes onto a Stage.
|
||||||
|
*
|
||||||
|
* Usage: save an instance, and in your `Stage.draw` override, call [draw] (yourStage) *after* `super.draw()`.
|
||||||
|
*
|
||||||
|
* Implementation notes:
|
||||||
|
* * Uses the stage's [Batch], but its own [ShapeRenderer]
|
||||||
|
* * Tries to avoid any memory allocation in [draw], hence for building nice Actor names,
|
||||||
|
* the reusable StringBuilder is filled using those lambdas, and those are built trying
|
||||||
|
* to use as few closures as possible.
|
||||||
|
*/
|
||||||
|
internal class StageMouseOverDebug {
|
||||||
|
private val label: Label
|
||||||
|
private val mouseCoords = Vector2()
|
||||||
|
private lateinit var shapeRenderer: ShapeRenderer
|
||||||
|
private val axisColor = Color.RED.cpy().apply { a = overlayAlpha }
|
||||||
|
private val sb = StringBuilder(160)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val padding = 3f
|
||||||
|
private const val overlayAlpha = 0.8f
|
||||||
|
|
||||||
|
private const val axisInterval = 20
|
||||||
|
private const val axisTickLength = 6f
|
||||||
|
private const val axisTickWidth = 1.5f
|
||||||
|
|
||||||
|
private const val maxChildScan = 10
|
||||||
|
private const val maxTextLength = 20
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
val style = Label.LabelStyle(Fonts.font, Color.WHITE)
|
||||||
|
style.background = ImageGetter.getWhiteDotDrawable().tint(Color.DARK_GRAY).apply {
|
||||||
|
leftWidth = padding
|
||||||
|
rightWidth = padding
|
||||||
|
topHeight = padding
|
||||||
|
bottomHeight = padding
|
||||||
|
}
|
||||||
|
style.fontColor = Color.GOLDENROD
|
||||||
|
label = Label("", style)
|
||||||
|
label.setAlignment(Align.center)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun draw(stage: Stage) {
|
||||||
|
mouseCoords.set(Gdx.input.x.toFloat(), Gdx.input.y.toFloat())
|
||||||
|
stage.screenToStageCoordinates(mouseCoords)
|
||||||
|
|
||||||
|
sb.clear()
|
||||||
|
sb.append(mouseCoords.x.toInt())
|
||||||
|
sb.append(" / ")
|
||||||
|
sb.append(mouseCoords.y.toInt())
|
||||||
|
sb.append(" (")
|
||||||
|
sb.append(Gdx.graphics.framesPerSecond)
|
||||||
|
sb.append(")\n")
|
||||||
|
addActorLabel(stage.hit(mouseCoords.x, mouseCoords.y, false))
|
||||||
|
|
||||||
|
label.setText(sb)
|
||||||
|
layoutLabel(stage)
|
||||||
|
|
||||||
|
val batch = stage.batch
|
||||||
|
batch.projectionMatrix = stage.camera.combined
|
||||||
|
batch.begin()
|
||||||
|
label.draw(batch, overlayAlpha)
|
||||||
|
batch.end()
|
||||||
|
|
||||||
|
stage.drawAxes()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addActorLabel(actor: Actor?) {
|
||||||
|
if (actor == null) return
|
||||||
|
|
||||||
|
// For this actor, see if it has a descriptive name
|
||||||
|
val actorBuilder = getActorDescriptiveName(actor)
|
||||||
|
var parentBuilder: AddToStringBuilderFactory? = null
|
||||||
|
var childBuilder: AddToStringBuilderFactory? = null
|
||||||
|
|
||||||
|
// If there's no descriptive name for this actor, look for parent or children
|
||||||
|
if (actorBuilder == null) {
|
||||||
|
// Try to get a descriptive name from parent
|
||||||
|
if (actor.parent != null)
|
||||||
|
parentBuilder = getActorDescriptiveName(actor.parent)
|
||||||
|
|
||||||
|
// If that failed, try to get a descriptive name from first few children
|
||||||
|
if (parentBuilder == null && actor is Group)
|
||||||
|
childBuilder = actor.children.asSequence()
|
||||||
|
.take(maxChildScan)
|
||||||
|
.map { getActorDescriptiveName(it) }
|
||||||
|
.firstOrNull { it != null }
|
||||||
|
}
|
||||||
|
|
||||||
|
// assemble name parts with fallback to plain class names for parent and actor
|
||||||
|
if (parentBuilder != null) {
|
||||||
|
parentBuilder(sb)
|
||||||
|
sb.append('.')
|
||||||
|
} else if (actor.parent != null) {
|
||||||
|
sb.append((actor.parent)::class.java.simpleName)
|
||||||
|
sb.append('.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actorBuilder != null)
|
||||||
|
actorBuilder(sb)
|
||||||
|
else
|
||||||
|
sb.append(actor::class.java.simpleName)
|
||||||
|
|
||||||
|
if (childBuilder != null) {
|
||||||
|
sb.append('(')
|
||||||
|
childBuilder(sb)
|
||||||
|
sb.append(')')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getActorDescriptiveName(actor: Actor): AddToStringBuilderFactory? {
|
||||||
|
if (actor.name != null) {
|
||||||
|
val className = actor::class.java.simpleName
|
||||||
|
if (actor.name.startsWith(className))
|
||||||
|
return { sb -> sb.append(actor.name) }
|
||||||
|
return { sb ->
|
||||||
|
sb.append(className)
|
||||||
|
sb.append(':')
|
||||||
|
sb.append(actor.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (actor is Label && actor.text.isNotBlank()) return { sb ->
|
||||||
|
sb.append("Label\"")
|
||||||
|
sb.appendLimited(actor.text)
|
||||||
|
sb.append('\"')
|
||||||
|
}
|
||||||
|
if (actor is TextButton && actor.text.isNotBlank()) return { sb ->
|
||||||
|
sb.append("TextButton\"")
|
||||||
|
sb.appendLimited(actor.text)
|
||||||
|
sb.append('\"')
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.appendLimited(text: CharSequence) {
|
||||||
|
val lf = text.indexOf('\n') + 1
|
||||||
|
val len = (if (lf == 0) text.length else lf).coerceAtMost(maxTextLength)
|
||||||
|
if (len == text.length) {
|
||||||
|
append(text)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
append(text, 0, len)
|
||||||
|
append('‥') // '…' is taken
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun layoutLabel(stage: Stage) {
|
||||||
|
if (!label.needsLayout()) return
|
||||||
|
val width = label.prefWidth + 2 * padding
|
||||||
|
label.setSize(width, label.prefHeight + 2 * padding)
|
||||||
|
label.setPosition(stage.width - width, 0f)
|
||||||
|
label.validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Stage.drawAxes() {
|
||||||
|
if (!::shapeRenderer.isInitialized) {
|
||||||
|
shapeRenderer = ShapeRenderer()
|
||||||
|
shapeRenderer.setAutoShapeType(true)
|
||||||
|
}
|
||||||
|
val sr = shapeRenderer
|
||||||
|
|
||||||
|
Gdx.gl.glEnable(GL20.GL_BLEND)
|
||||||
|
sr.projectionMatrix = viewport.camera.combined
|
||||||
|
sr.begin()
|
||||||
|
sr.set(ShapeRenderer.ShapeType.Filled)
|
||||||
|
|
||||||
|
for (x in 0..width.toInt() step axisInterval) {
|
||||||
|
val xf = x.toFloat()
|
||||||
|
sr.rectLine(xf, 0f, xf, axisTickLength, axisTickWidth, axisColor, axisColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
val x2 = width
|
||||||
|
val x1 = x2 - axisTickLength
|
||||||
|
for (y in 0..height.toInt() step axisInterval) {
|
||||||
|
val yf = y.toFloat()
|
||||||
|
sr.rectLine(x1, yf, x2, yf, axisTickWidth, axisColor, axisColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
sr.end()
|
||||||
|
Gdx.gl.glDisable(GL20.GL_BLEND)
|
||||||
|
}
|
||||||
|
}
|
@ -29,6 +29,13 @@ class UncivStage(viewport: Viewport) : Stage(viewport, getBatch()) {
|
|||||||
var lastKnownVisibleArea: Rectangle
|
var lastKnownVisibleArea: Rectangle
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
var mouseOverDebug: Boolean
|
||||||
|
get() = mouseOverDebugImpl != null
|
||||||
|
set(value) {
|
||||||
|
mouseOverDebugImpl = if (value) StageMouseOverDebug() else null
|
||||||
|
}
|
||||||
|
private var mouseOverDebugImpl: StageMouseOverDebug? = null
|
||||||
|
|
||||||
private val events = EventBus.EventReceiver()
|
private val events = EventBus.EventReceiver()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -49,8 +56,10 @@ class UncivStage(viewport: Viewport) : Stage(viewport, getBatch()) {
|
|||||||
super.act()
|
super.act()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun draw() =
|
override fun draw() {
|
||||||
{ super.draw() }.wrapCrashHandlingUnit()()
|
{ super.draw() }.wrapCrashHandlingUnit()()
|
||||||
|
mouseOverDebugImpl?.draw(this)
|
||||||
|
}
|
||||||
|
|
||||||
/** libGDX has no built-in way to disable/enable pointer enter/exit events. It is simply being done in [Stage.act]. So to disable this, we have
|
/** libGDX has no built-in way to disable/enable pointer enter/exit events. It is simply being done in [Stage.act]. So to disable this, we have
|
||||||
* to replicate the [Stage.act] method without the code for pointer enter/exit events. This is of course inherently brittle, but the only way. */
|
* to replicate the [Stage.act] method without the code for pointer enter/exit events. This is of course inherently brittle, but the only way. */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user