diff --git a/src/main/kotlin/net/shadowfacts/asmr/TestCacaoScreen.kt b/src/main/kotlin/net/shadowfacts/asmr/TestCacaoScreen.kt index 4b045dd..447afa0 100644 --- a/src/main/kotlin/net/shadowfacts/asmr/TestCacaoScreen.kt +++ b/src/main/kotlin/net/shadowfacts/asmr/TestCacaoScreen.kt @@ -44,6 +44,8 @@ class TestCacaoScreen: CacaoScreen() { } }) + val label = addView(Label("test ".repeat(100), maxLines = 5)) + solver.dsl { stack.topAnchor equalTo 0 stack.centerXAnchor equalTo this@apply.centerXAnchor @@ -51,9 +53,11 @@ class TestCacaoScreen: CacaoScreen() { purple.centerXAnchor equalTo blue.centerXAnchor purple.centerYAnchor equalTo blue.centerYAnchor // purple.widthAnchor equalTo 50 - } - layout() + label.topAnchor equalTo 0 + label.leftAnchor equalTo 0 + label.widthAnchor equalTo 100 + } // val red = addView(View().apply { diff --git a/src/main/kotlin/net/shadowfacts/cacao/Window.kt b/src/main/kotlin/net/shadowfacts/cacao/Window.kt index 69ae2c9..f15c9ec 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/Window.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/Window.kt @@ -86,7 +86,7 @@ class Window { * Creates the internal constraints used by the window. * If overridden, the super-class method must be called. */ - protected open fun createInternalConstraints() { + protected fun createInternalConstraints() { solver.dsl { leftAnchor equalTo 0 topAnchor equalTo 0 diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/Label.kt b/src/main/kotlin/net/shadowfacts/cacao/view/Label.kt index ea623e4..77fbf82 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/Label.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/Label.kt @@ -6,6 +6,7 @@ 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.roundToInt /** * A simple View that displays text. Allows for controlling the color and shadow of the text. Label cannot be used @@ -13,14 +14,29 @@ import net.shadowfacts.cacao.util.RenderHelper * * @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: String): View() { +class Label( + text: String, + val shadow: Boolean = true, + val maxLines: Int = 0, + val wrappingMode: WrappingMode = WrappingMode.WRAP +): View() { companion object { private val textRenderer: TextRenderer get() = MinecraftClient.getInstance().textRenderer } + enum class WrappingMode { + WRAP, NO_WRAP + } + /** * The text of this label. Mutating this field will update the intrinsic content size and trigger a layout. */ @@ -28,9 +44,20 @@ class Label(text: String): View() { set(value) { field = value updateIntrinsicContentSize() + window.layout() } + private lateinit var lines: List var textColor = Color.WHITE + set(value) { + field = value + textColorARGB = value.argb + } + private var textColorARGB: Int = textColor.argb + + private val drawFunc by lazy { + if (shadow) textRenderer::drawWithShadow else textRenderer::draw + } override fun wasAdded() { super.wasAdded() @@ -47,7 +74,34 @@ class Label(text: String): View() { } override fun drawContent(mouse: Point, delta: Float) { - textRenderer.draw(text, 0f, 0f, textColor.argb) + if (!this::lines.isInitialized) { + computeLines() + } + + for (i in 0 until lines.size) { + val y = i * textRenderer.fontHeight + drawFunc(lines[i], 0f, y.toFloat(), textColorARGB) + } + } + + override fun didLayout() { + super.didLayout() + + computeLines() + } + + private fun computeLines() { + var lines = text.split("\n") + if (wrappingMode == WrappingMode.WRAP) { + lines = lines.flatMap { + val maxWidth = bounds.width.toInt() + textRenderer.wrapStringToWidthAsList(it, maxWidth) + } + } + if (0 < maxLines && maxLines < lines.size) { + lines = lines.dropLast(lines.size - maxLines) + } + this.lines = lines } } \ No newline at end of file