diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/gui/GuiRenderTestUtil.kt b/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/gui/GuiRenderTestUtil.kt new file mode 100644 index 000000000..76e9f145f --- /dev/null +++ b/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/gui/GuiRenderTestUtil.kt @@ -0,0 +1,59 @@ +/* + * Minosoft + * Copyright (C) 2020-2023 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.gui.rendering.gui + +import de.bixilon.kotlinglm.vec2.Vec2 +import de.bixilon.kotlinglm.vec2.Vec2i +import de.bixilon.kutil.observer.DataObserver +import de.bixilon.kutil.reflection.ReflectionUtil.forceSet +import de.bixilon.minosoft.data.registries.identified.Namespaces.minosoft +import de.bixilon.minosoft.gui.rendering.RenderContext +import de.bixilon.minosoft.gui.rendering.font.manager.FontManager +import de.bixilon.minosoft.gui.rendering.font.types.dummy.DummyFontType +import de.bixilon.minosoft.gui.rendering.gui.atlas.CodeTexturePart +import de.bixilon.minosoft.gui.rendering.gui.elements.Element +import de.bixilon.minosoft.gui.rendering.system.dummy.DummyRenderSystem +import de.bixilon.minosoft.gui.rendering.system.dummy.texture.DummyTexture +import de.bixilon.minosoft.gui.rendering.system.dummy.texture.DummyTextureManager +import de.bixilon.minosoft.test.IT.OBJENESIS +import org.testng.Assert.assertEquals + +object GuiRenderTestUtil { + + private fun createContext(): RenderContext { + val context = OBJENESIS.newInstance(RenderContext::class.java) + context.font = FontManager(DummyFontType) + context::renderSystem.forceSet(DummyRenderSystem(context)) + context::textureManager.forceSet(DummyTextureManager(context)) + + context.textureManager::whiteTexture.forceSet(CodeTexturePart(DummyTexture(minosoft("white")), size = Vec2i(16, 16))) + + return context + } + + fun create(size: Vec2 = Vec2(1920.0f, 1080.0f)): GUIRenderer { + val renderer = OBJENESIS.newInstance(GUIRenderer::class.java) + renderer::scaledSize.forceSet(DataObserver(size)) + renderer::halfSize.forceSet(size / 2.0f) + + renderer::context.forceSet(createContext()) + + return renderer + } + + + fun Element.assetSize(size: Vec2) { + assertEquals(size, this.size) + } +} diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/gui/elements/text/TextElementTest.kt b/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/gui/elements/text/TextElementTest.kt new file mode 100644 index 000000000..bbe4d96ba --- /dev/null +++ b/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/gui/elements/text/TextElementTest.kt @@ -0,0 +1,63 @@ +package de.bixilon.minosoft.gui.rendering.gui.elements.text + +import de.bixilon.kotlinglm.vec2.Vec2 +import de.bixilon.kotlinglm.vec4.Vec4 +import de.bixilon.minosoft.gui.rendering.gui.GuiRenderTestUtil +import de.bixilon.minosoft.gui.rendering.gui.GuiRenderTestUtil.assetSize +import de.bixilon.minosoft.gui.rendering.gui.elements.text.background.TextBackground +import org.testng.annotations.Test + +@Test(groups = ["font", "gui"]) +class TextElementTest { + + fun `size empty`() { + val element = TextElement(GuiRenderTestUtil.create(), "") + element.assetSize(Vec2(0, 0)) + } + + fun `size of single char`() { + val element = TextElement(GuiRenderTestUtil.create(), "b", background = null) + element.assetSize(Vec2(0.5f, 11.0f)) + } + + fun `size of multiple chars`() { + val element = TextElement(GuiRenderTestUtil.create(), "bc", background = null) + element.assetSize(Vec2(2.5f, 11.0f)) + } + + fun `size with new line`() { + val element = TextElement(GuiRenderTestUtil.create(), "bc\nbc", background = null) + element.assetSize(Vec2(2.5f, 22.0f)) + } + + fun `size with background`() { + val element = TextElement(GuiRenderTestUtil.create(), "bc") + element.assetSize(Vec2(4.5f, 13.0f)) + } + + fun `size with background and newlines`() { + val element = TextElement(GuiRenderTestUtil.create(), "bc\nbc") + element.assetSize(Vec2(4.5f, 24.0f)) + } + + fun `size if text changed`() { + val element = TextElement(GuiRenderTestUtil.create(), "bc\nbc") + element.text = "bcd\nbcd\nbcd" + element.assetSize(Vec2(6.0f, 35.0f)) + } + + fun `size if background cleared`() { + val element = TextElement(GuiRenderTestUtil.create(), "bcd\nbcd\nbcd") + element.background = null + element.assetSize(Vec2(4.0f, 33.0f)) + } + + fun `size if background set`() { + val element = TextElement(GuiRenderTestUtil.create(), "bcd\nbcd\nbcd") + element.background = TextBackground(size = Vec4(2.0f)) + element.assetSize(Vec2(8.0f, 37.0f)) + } + + + // TODO: test on mouse (click/hover events), rendering, size limiting +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/atlas/CodeTexturePart.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/atlas/CodeTexturePart.kt index 838e2836a..c1c3a4fae 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/atlas/CodeTexturePart.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/atlas/CodeTexturePart.kt @@ -19,7 +19,7 @@ import de.bixilon.minosoft.gui.rendering.system.base.texture.texture.AbstractTex class CodeTexturePart( override val texture: AbstractTexture, - override val uvStart: Vec2, - override val uvEnd: Vec2, + override val uvStart: Vec2 = Vec2(0.0f), + override val uvEnd: Vec2 = Vec2(1.0f), override val size: Vec2i, ) : TexturePart diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/FadingTextElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/FadingTextElement.kt index d937f0275..b5dd107c3 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/FadingTextElement.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/FadingTextElement.kt @@ -15,14 +15,12 @@ package de.bixilon.minosoft.gui.rendering.gui.elements.text import de.bixilon.kotlinglm.vec2.Vec2i import de.bixilon.kutil.primitive.BooleanUtil.decide -import de.bixilon.kutil.time.TimeUtil import de.bixilon.kutil.time.TimeUtil.millis -import de.bixilon.minosoft.data.text.formatting.color.RGBColor -import de.bixilon.minosoft.gui.rendering.RenderConstants import de.bixilon.minosoft.gui.rendering.font.renderer.element.TextRenderProperties import de.bixilon.minosoft.gui.rendering.gui.GUIRenderer import de.bixilon.minosoft.gui.rendering.gui.elements.Element import de.bixilon.minosoft.gui.rendering.gui.elements.Pollable +import de.bixilon.minosoft.gui.rendering.gui.elements.text.background.TextBackground import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexConsumer import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions.Companion.copy @@ -34,7 +32,7 @@ class FadingTextElement( var fadeInTime: Long = 100, var stayTime: Long = 1000, var fadeOutTime: Long = 100, - background: RGBColor? = RenderConstants.TEXT_BACKGROUND_COLOR, + background: TextBackground? = TextBackground.DEFAULT, parent: Element? = null, properties: TextRenderProperties, ) : TextElement(guiRenderer = guiRenderer, text = text, background = background, parent, properties), Pollable { diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/TextElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/TextElement.kt index 2a360a2b9..f3dcc894b 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/TextElement.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/TextElement.kt @@ -21,8 +21,6 @@ import de.bixilon.minosoft.Minosoft import de.bixilon.minosoft.data.text.ChatComponent import de.bixilon.minosoft.data.text.EmptyComponent import de.bixilon.minosoft.data.text.TextComponent -import de.bixilon.minosoft.data.text.formatting.color.RGBColor -import de.bixilon.minosoft.gui.rendering.RenderConstants import de.bixilon.minosoft.gui.rendering.font.renderer.component.ChatComponentRenderer import de.bixilon.minosoft.gui.rendering.font.renderer.element.LineRenderInfo import de.bixilon.minosoft.gui.rendering.font.renderer.element.TextOffset @@ -31,6 +29,7 @@ import de.bixilon.minosoft.gui.rendering.font.renderer.element.TextRenderPropert import de.bixilon.minosoft.gui.rendering.gui.GUIRenderer import de.bixilon.minosoft.gui.rendering.gui.elements.Element import de.bixilon.minosoft.gui.rendering.gui.elements.HorizontalAlignments.Companion.getOffset +import de.bixilon.minosoft.gui.rendering.gui.elements.text.background.TextBackground import de.bixilon.minosoft.gui.rendering.gui.input.mouse.MouseActions import de.bixilon.minosoft.gui.rendering.gui.input.mouse.MouseButtons import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIMesh @@ -39,13 +38,20 @@ import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions import de.bixilon.minosoft.gui.rendering.system.window.CursorShapes import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2Util.EMPTY import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2Util.MAX +import de.bixilon.minosoft.gui.rendering.util.vec.vec4.Vec4Util.bottom +import de.bixilon.minosoft.gui.rendering.util.vec.vec4.Vec4Util.offset +import de.bixilon.minosoft.gui.rendering.util.vec.vec4.Vec4Util.vertical import de.bixilon.minosoft.gui.rendering.util.vec.vec4.Vec4iUtil.offset import de.bixilon.minosoft.util.KUtil.charCount +/** + * A simple UI element that draws text on the screen + * A background color is supported, if set + */ open class TextElement( guiRenderer: GUIRenderer, text: Any, - background: RGBColor? = RenderConstants.TEXT_BACKGROUND_COLOR, + background: TextBackground? = TextBackground.DEFAULT, parent: Element? = null, properties: TextRenderProperties = TextRenderProperties.DEFAULT, ) : Element(guiRenderer, text.charCount * 6 * GUIMesh.GUIMeshStruct.FLOATS_PER_VERTEX), Labeled { @@ -53,13 +59,13 @@ open class TextElement( lateinit var info: TextRenderInfo private set - var background: RGBColor? = background + var background: TextBackground? = background set(value) { if (field == value) { return } field = value - cacheUpToDate = false + forceApply() } var properties: TextRenderProperties = properties set(value) { @@ -132,7 +138,7 @@ open class TextElement( override fun onChildChange(child: Element) = Broken("A TextElement can not have a child!") - private fun GUIVertexConsumer.renderBackground(color: RGBColor, properties: TextRenderProperties, info: TextRenderInfo, offset: Vec2, options: GUIVertexOptions?) { + private fun GUIVertexConsumer.renderBackground(background: TextBackground, properties: TextRenderProperties, info: TextRenderInfo, offset: Vec2, options: GUIVertexOptions?) { val start = Vec2() val end = Vec2() @@ -142,10 +148,15 @@ open class TextElement( start.x = offset.x + properties.alignment.getOffset(line.width, info.size.x) start.y = offset.y + (index * lineHeight) + (maxOf(index - 1, 0) * properties.lineSpacing) - end.x = start.x + line.width + end.x = start.x + line.width + background.size.vertical end.y = start.y + lineHeight - addQuad(start, end, context.textureManager.whiteTexture, color, options) + if (index == info.lines.size - 1) { + // last line + end.y += background.size.bottom + } + + addQuad(start, end, context.textureManager.whiteTexture, background.color, options) } } @@ -154,8 +165,12 @@ open class TextElement( val info = this.info val properties = this.properties val initialOffset = Vec2(offset + margin.offset) + val textOffset = Vec2(initialOffset) - this.background?.let { consumer.renderBackground(it, properties, info, initialOffset, options) } + this.background?.let { + consumer.renderBackground(it, properties, info, initialOffset, options) + textOffset += it.size.offset + } var vertices = ChatComponentRenderer.calculatePrimitiveCount(chatComponent) * consumer.order.size * GUIMesh.GUIMeshStruct.FLOATS_PER_VERTEX if (properties.shadow) { @@ -163,7 +178,7 @@ open class TextElement( } consumer.ensureSize(vertices) - ChatComponentRenderer.render(TextOffset(Vec2(initialOffset)), context.font, properties, info, consumer, options, chatComponent) + ChatComponentRenderer.render(TextOffset(textOffset), context.font, properties, info, consumer, options, chatComponent) info.rewind() } @@ -220,7 +235,7 @@ open class TextElement( private fun TextRenderInfo.getLineAt(lineHeight: Float, lineSpacing: Float, offset: Float): Pair? { var offset = offset - for ((index, line) in info.lines.withIndex()) { + for ((index, line) in lines.withIndex()) { if (offset in 0.0f..lineHeight) { return Pair(line, offset) } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/background/TextBackground.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/background/TextBackground.kt new file mode 100644 index 000000000..cbebf7f1d --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/background/TextBackground.kt @@ -0,0 +1,28 @@ +/* + * Minosoft + * Copyright (C) 2020-2023 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.gui.rendering.gui.elements.text.background + +import de.bixilon.kotlinglm.vec4.Vec4 +import de.bixilon.minosoft.data.text.formatting.color.RGBColor +import de.bixilon.minosoft.gui.rendering.RenderConstants + +data class TextBackground( + val color: RGBColor = RenderConstants.TEXT_BACKGROUND_COLOR, + val size: Vec4 = Vec4(1.0f), +) { + + companion object { + val DEFAULT = TextBackground() + } +} diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/mark/MarkTextElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/mark/MarkTextElement.kt index 3371f50cc..ba137fb29 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/mark/MarkTextElement.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/elements/text/mark/MarkTextElement.kt @@ -17,13 +17,12 @@ import de.bixilon.kotlinglm.vec2.Vec2i import de.bixilon.minosoft.config.key.KeyCodes import de.bixilon.minosoft.data.text.ChatComponent import de.bixilon.minosoft.data.text.formatting.color.ChatColors -import de.bixilon.minosoft.data.text.formatting.color.RGBColor -import de.bixilon.minosoft.gui.rendering.RenderConstants import de.bixilon.minosoft.gui.rendering.font.renderer.element.TextRenderProperties import de.bixilon.minosoft.gui.rendering.gui.GUIRenderer import de.bixilon.minosoft.gui.rendering.gui.elements.Element import de.bixilon.minosoft.gui.rendering.gui.elements.primitive.ColorElement import de.bixilon.minosoft.gui.rendering.gui.elements.text.TextElement +import de.bixilon.minosoft.gui.rendering.gui.elements.text.background.TextBackground import de.bixilon.minosoft.gui.rendering.gui.input.ModifierKeys import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexConsumer import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions @@ -32,7 +31,7 @@ import de.bixilon.minosoft.gui.rendering.system.window.KeyChangeTypes class MarkTextElement( guiRenderer: GUIRenderer, text: Any, - background: RGBColor? = RenderConstants.TEXT_BACKGROUND_COLOR, + background: TextBackground? = TextBackground.DEFAULT, parent: Element? = null, properties: TextRenderProperties = TextRenderProperties.DEFAULT, ) : TextElement(guiRenderer, text, background, parent, properties) { diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/util/vec/vec4/Vec4Util.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/util/vec/vec4/Vec4Util.kt index 3a6d4bc36..3ba1896ba 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/util/vec/vec4/Vec4Util.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/util/vec/vec4/Vec4Util.kt @@ -13,6 +13,7 @@ package de.bixilon.minosoft.gui.rendering.util.vec.vec4 +import de.bixilon.kotlinglm.vec2.Vec2 import de.bixilon.kotlinglm.vec4.Vec4 object Vec4Util { @@ -26,5 +27,30 @@ object Vec4Util { val Vec4.Companion.MAX: Vec4 get() = Vec4(Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE) + + val Vec4.top: Float + get() = this.x + + val Vec4.right: Float + get() = this.y + + val Vec4.bottom: Float + get() = this.z + + val Vec4.left: Float + get() = this.w + + val Vec4.horizontal: Float + get() = right + left + + val Vec4.vertical: Float + get() = top + bottom + + val Vec4.spaceSize: Vec2 + get() = Vec2(horizontal, vertical) + + val Vec4.offset: Vec2 + get() = Vec2(left, top) + fun FloatArray.dot(x: Float, y: Float, z: Float) = this[0] * x + this[1] * y + this[2] * z + this[3] }