mirror of
https://github.com/yairm210/Unciv.git
synced 2025-02-07 01:28:23 +07: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.skins.SkinStrings
|
||||
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.KeyShortcutDispatcherVeto
|
||||
import com.unciv.ui.components.input.DispatcherVetoer
|
||||
import com.unciv.ui.components.input.installShortcutDispatcher
|
||||
import com.unciv.ui.components.input.keyShortcuts
|
||||
import com.unciv.ui.components.extensions.isNarrowerThan4to3
|
||||
import com.unciv.ui.components.input.KeyShortcutDispatcherVeto
|
||||
import com.unciv.ui.crashhandling.CrashScreen
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.popups.Popup
|
||||
import com.unciv.ui.popups.activePopup
|
||||
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 {
|
||||
|
||||
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. */
|
||||
stage = UncivStage(ExtendViewport(height, height))
|
||||
|
||||
if (enableSceneDebug) {
|
||||
if (enableSceneDebug && this !is CrashScreen) {
|
||||
stage.setDebugUnderMouse(true)
|
||||
stage.setDebugTableUnderMouse(true)
|
||||
stage.setDebugParentUnderMouse(true)
|
||||
stage.mouseOverDebug = true
|
||||
}
|
||||
|
||||
@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
|
||||
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()
|
||||
|
||||
init {
|
||||
@ -49,8 +56,10 @@ class UncivStage(viewport: Viewport) : Stage(viewport, getBatch()) {
|
||||
super.act()
|
||||
}
|
||||
|
||||
override fun draw() =
|
||||
{ super.draw() }.wrapCrashHandlingUnit()()
|
||||
override fun draw() {
|
||||
{ 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
|
||||
* 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…
Reference in New Issue
Block a user