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 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()) } } }