PhysicalConnectivity/src/main/kotlin/net/shadowfacts/cacao/view/Label.kt

143 lines
4.4 KiB
Kotlin

package net.shadowfacts.cacao.view
import net.minecraft.client.MinecraftClient
import net.minecraft.client.font.TextRenderer
import net.minecraft.client.util.math.MatrixStack
import net.minecraft.text.LiteralText
import net.minecraft.text.OrderedText
import net.minecraft.text.Text
import net.shadowfacts.cacao.geometry.Point
import net.shadowfacts.cacao.geometry.Size
import net.shadowfacts.cacao.util.Color
import net.shadowfacts.cacao.util.RenderHelper
import kotlin.math.min
/**
* A simple View that displays text. Allows for controlling the color and shadow of the text. Label cannot be used
* for multi-line text, instead use [TextView].
*
* @author shadowfacts
* @param text The text of this label.
* @param shadow Whether the text should be rendered with a shadow or not.
* @param maxLines The maximum number of lines of text to render.
* `0` means that there is no line limit and all lines will be rendered.
* When using a non-zero [maxLines] value, the [intrinsicContentSize] of the Label still assumes all
* content on one line. So, constraints must be provided to calculate the actual width to use for line
* wrapping.
*/
class Label(
text: Text,
var shadow: Boolean = false,
val maxLines: Int = 0,
val wrappingMode: WrappingMode = WrappingMode.WRAP,
var textAlignment: TextAlignment = TextAlignment.LEFT
): View() {
companion object {
private val textRenderer: TextRenderer
get() = MinecraftClient.getInstance().textRenderer
}
enum class WrappingMode {
WRAP, NO_WRAP
}
enum class TextAlignment {
LEFT, CENTER, RIGHT
}
constructor(
text: String,
shadow: Boolean = false,
maxLines: Int = 0,
wrappingMode: WrappingMode = WrappingMode.WRAP,
textAlignment: TextAlignment = TextAlignment.LEFT,
): this(LiteralText(text), shadow, maxLines, wrappingMode, textAlignment)
/**
* The text of this label. Mutating this field will update the intrinsic content size and trigger a layout.
*/
var text: Text = text
set(value) {
field = value
// todo: uhhhh
updateIntrinsicContentSize(true)
// todo: setNeedsLayout instead of force unwrapping window
window!!.layout()
}
private lateinit var lines: List<OrderedText>
var textColor = Color.WHITE
set(value) {
field = value
textColorARGB = value.argb
}
private var textColorARGB: Int = textColor.argb
override fun wasAdded() {
super.wasAdded()
updateIntrinsicContentSize(false)
}
private fun updateIntrinsicContentSize(canWrap: Boolean, isFromDidLayout: Boolean = false): Boolean {
if (RenderHelper.disabled) return false
val oldSize = intrinsicContentSize
// don't wrap until we've laid out without wrapping to ensure the current bounds reflect the maximum available space
if (wrappingMode == WrappingMode.WRAP && canWrap && hasSolver && isFromDidLayout) {
val lines = textRenderer.wrapLines(text, bounds.width.toInt())
val height = (if (maxLines == 0) lines.size else min(lines.size, maxLines)) * textRenderer.fontHeight
intrinsicContentSize = Size(bounds.width, height.toDouble())
} else {
val width = textRenderer.getWidth(text)
val height = textRenderer.fontHeight
intrinsicContentSize = Size(width.toDouble(), height.toDouble())
}
return oldSize != intrinsicContentSize
}
override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {
if (!this::lines.isInitialized) {
computeLines()
}
for (i in 0 until lines.size) {
val x = when (textAlignment) {
TextAlignment.LEFT -> 0.0
TextAlignment.CENTER -> (bounds.width - textRenderer.getWidth(lines[i])) / 2
TextAlignment.RIGHT -> bounds.width - textRenderer.getWidth(lines[i])
}
val y = i * textRenderer.fontHeight
if (shadow) {
textRenderer.drawWithShadow(matrixStack, lines[i], x.toFloat(), y.toFloat(), textColorARGB)
} else {
textRenderer.draw(matrixStack, lines[i], x.toFloat(), y.toFloat(), textColorARGB)
}
}
}
override fun didLayout() {
super.didLayout()
computeLines()
if (updateIntrinsicContentSize(true, true)) {
// if the intrinsic content size changes, relayout
window!!.layout()
}
}
private fun computeLines() {
if (wrappingMode == WrappingMode.WRAP) {
var lines = textRenderer.wrapLines(text, bounds.width.toInt())
if (maxLines > 0 && maxLines < lines.size) {
lines = lines.dropLast(lines.size - maxLines)
}
this.lines = lines
} else {
this.lines = listOf(text.asOrderedText())
}
}
}