From 4d1fb68c89a4cff82e4da1dacb37fb3696fc31a2 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Tue, 25 Jun 2019 21:52:17 -0400 Subject: [PATCH] Add DropdownButton --- .../net/shadowfacts/asmr/TestCacaoScreen.kt | 13 +- .../net/shadowfacts/cacao/CacaoScreen.kt | 29 ++- .../kotlin/net/shadowfacts/cacao/Window.kt | 15 +- .../shadowfacts/cacao/util/RenderHelper.kt | 21 +- .../properties/ObservableLateInitProperty.kt | 2 +- .../util/properties/ResettableLazyProperty.kt | 2 +- .../shadowfacts/cacao/view/NinePatchView.kt | 48 ++-- .../kotlin/net/shadowfacts/cacao/view/View.kt | 10 + .../cacao/view/button/AbstractButton.kt | 14 +- .../cacao/view/button/DropdownButton.kt | 209 ++++++++++++++++++ .../assets/asmr/textures/gui/dropdown.png | Bin 0 -> 427 bytes 11 files changed, 317 insertions(+), 46 deletions(-) create mode 100644 src/main/kotlin/net/shadowfacts/cacao/view/button/DropdownButton.kt create mode 100644 src/main/resources/assets/asmr/textures/gui/dropdown.png diff --git a/src/main/kotlin/net/shadowfacts/asmr/TestCacaoScreen.kt b/src/main/kotlin/net/shadowfacts/asmr/TestCacaoScreen.kt index 7fdbfee..15aa5ea 100644 --- a/src/main/kotlin/net/shadowfacts/asmr/TestCacaoScreen.kt +++ b/src/main/kotlin/net/shadowfacts/asmr/TestCacaoScreen.kt @@ -11,9 +11,7 @@ import net.shadowfacts.cacao.util.Color import net.shadowfacts.cacao.util.NinePatchTexture import net.shadowfacts.cacao.util.Texture import net.shadowfacts.cacao.view.* -import net.shadowfacts.cacao.view.button.Button -import net.shadowfacts.cacao.view.button.EnumButton -import net.shadowfacts.cacao.view.button.ToggleButton +import net.shadowfacts.cacao.view.button.DropdownButton /** * @author shadowfacts @@ -36,9 +34,14 @@ class TestCacaoScreen: CacaoScreen() { intrinsicContentSize = Size(50.0, 50.0) backgroundColor = Color(0x0000ff) }) - val purple = blue.addSubview(ToggleButton(false).apply { + val purple = blue.addSubview(DropdownButton( + initialValue = RedstoneMode.HIGH, + allValues = RedstoneMode.values().asIterable(), + createView = { Label(it.name) }, + updateView = { newValue, label -> label.text = newValue.name } + ).apply { handler = { - println("enum button clicked, new value: ${it.state}") + println("dropdown value: ${it.value}") } }) diff --git a/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt b/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt index 2c6c85d..3e1fbae 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt @@ -31,13 +31,26 @@ open class CacaoScreen: Screen(TextComponent("CacaoScreen")) { * * @param window The Window to add to this screen. * @param index The index to insert the window into the window list at. + * @return The window that was added, as a convenience. */ - fun addWindow(window: Window, index: Int = _windows.size) { + fun addWindow(window: T, index: Int = _windows.size): T { _windows.add(index, window) + window.screen = this + return window + } + + /** + * Removes the given window from this screen's window list. + */ + fun removeWindow(window: Window) { + _windows.remove(window) } override fun render(mouseX: Int, mouseY: Int, delta: Float) { - renderBackground() + if (minecraft != null) { + // workaround this.minecraft sometimes being null causing a crash + renderBackground() + } val mouse = Point(mouseX, mouseY) windows.forEach { @@ -47,11 +60,17 @@ open class CacaoScreen: Screen(TextComponent("CacaoScreen")) { override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { val window = windows.lastOrNull() - if (window?.mouseClicked(Point(mouseX, mouseY), MouseButton.fromMC(button)) == true) { - RenderHelper.playSound(SoundEvents.UI_BUTTON_CLICK) + val result = window?.mouseClicked(Point(mouseX, mouseY), MouseButton.fromMC(button)) + when (result) { + true -> + RenderHelper.playSound(SoundEvents.UI_BUTTON_CLICK) + false -> + if (windows.size > 1) { + removeWindow(windows.last()) + } } - return false + return result == true } } \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/cacao/Window.kt b/src/main/kotlin/net/shadowfacts/cacao/Window.kt index 649846a..ad61b09 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/Window.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/Window.kt @@ -16,6 +16,12 @@ import java.util.* */ class Window { + /** + * The screen that this window belongs to. + * Not initialized until this window is added to a screen, using it before that point will throw a runtime exception. + */ + lateinit var screen: CacaoScreen + var solver = Solver() // _views is the internal, mutable object, since we only want it to be mutated by the add/removeView methods @@ -28,6 +34,13 @@ class Window { private var viewsSortedByZIndex: List = listOf() + /** + * Convenience method that removes this window from its [screen]. + */ + fun removeFromScreen() { + screen.removeWindow(this) + } + /** * Adds the given view as a top-level view in this window. * @@ -100,7 +113,7 @@ class Window { * @return Whether the mouse click was handled by a view. */ fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean { - val view = viewsAtPoint(point).minBy(View::zIndex) + val view = viewsAtPoint(point).maxBy(View::zIndex) if (view != null) { val pointInView = Point(point.x - view.frame.left, point.y - view.frame.top) return view.mouseClicked(pointInView, mouseButton) diff --git a/src/main/kotlin/net/shadowfacts/cacao/util/RenderHelper.kt b/src/main/kotlin/net/shadowfacts/cacao/util/RenderHelper.kt index b282ae4..a3f2e64 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/util/RenderHelper.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/util/RenderHelper.kt @@ -3,9 +3,12 @@ package net.shadowfacts.cacao.util import com.mojang.blaze3d.platform.GlStateManager import net.minecraft.client.MinecraftClient import net.minecraft.client.gui.DrawableHelper +import net.minecraft.client.render.Tessellator +import net.minecraft.client.render.VertexFormats import net.minecraft.client.sound.PositionedSoundInstance import net.minecraft.sound.SoundEvent import net.shadowfacts.cacao.geometry.Rect +import org.lwjgl.opengl.GL11 /** * Helper methods for rendering using Minecraft's utilities from Cacao views. @@ -46,7 +49,23 @@ object RenderHelper { */ fun draw(x: Double, y: Double, u: Int, v: Int, width: Double, height: Double, textureWidth: Int, textureHeight: Int) { if (disabled) return - DrawableHelper.blit(x.toInt(), y.toInt(), u.toFloat(), v.toFloat(), width.toInt(), height.toInt(), textureWidth, textureHeight) + val uStart = u.toDouble() / textureWidth + val uEnd = (u + width) / textureWidth + val vStart = v.toDouble() / textureHeight + val vEnd = (v + height) / textureHeight + innerBlit(x, x + width, y, y + height, 0.0, uStart, uEnd, vStart, vEnd) + } + + // Copied from net.minecraft.client.gui.DrawableHelper + private fun innerBlit(xStart: Double, xEnd: Double, yStart: Double, yEnd: Double, z: Double, uStart: Double, uEnd: Double, vStart: Double, vEnd: Double) { + val tessellator = Tessellator.getInstance() + val buffer = tessellator.bufferBuilder + buffer.begin(GL11.GL_QUADS, VertexFormats.POSITION_UV) + buffer.vertex(xStart, yEnd, z).texture(uStart, vEnd).next() + buffer.vertex(xEnd, yEnd, z).texture(uEnd, vEnd).next() + buffer.vertex(xEnd, yStart, z).texture(uEnd, vStart).next() + buffer.vertex(xStart, yStart, z).texture(uStart, vStart).next() + tessellator.draw() } /** diff --git a/src/main/kotlin/net/shadowfacts/cacao/util/properties/ObservableLateInitProperty.kt b/src/main/kotlin/net/shadowfacts/cacao/util/properties/ObservableLateInitProperty.kt index a967e04..21e2176 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/util/properties/ObservableLateInitProperty.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/util/properties/ObservableLateInitProperty.kt @@ -1,4 +1,4 @@ -package net.shadowfacts.cacao.util +package net.shadowfacts.cacao.util.properties import kotlin.reflect.KProperty diff --git a/src/main/kotlin/net/shadowfacts/cacao/util/properties/ResettableLazyProperty.kt b/src/main/kotlin/net/shadowfacts/cacao/util/properties/ResettableLazyProperty.kt index d532d8d..dd7e106 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/util/properties/ResettableLazyProperty.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/util/properties/ResettableLazyProperty.kt @@ -1,4 +1,4 @@ -package net.shadowfacts.cacao.util +package net.shadowfacts.cacao.util.properties import kotlin.reflect.KProperty diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/NinePatchView.kt b/src/main/kotlin/net/shadowfacts/cacao/view/NinePatchView.kt index 3bb02b4..c5d5c4d 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/NinePatchView.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/NinePatchView.kt @@ -4,68 +4,70 @@ import net.shadowfacts.cacao.geometry.Point import net.shadowfacts.cacao.geometry.Rect import net.shadowfacts.cacao.util.NinePatchTexture import net.shadowfacts.cacao.util.RenderHelper -import net.shadowfacts.cacao.util.ResettableLazyProperty +import net.shadowfacts.cacao.util.properties.ResettableLazyProperty /** * A helper class for drawing a [NinePatchTexture] in a view. * `NinePatchView` will draw the given nine patch texture filling its bounds. * + * This class and the region properties are left open for internal framework use, overriding them is not recommended. + * * @author shadowfacts * @param ninePatch The nine patch texture that this view will draw. */ -class NinePatchView(val ninePatch: NinePatchTexture): View() { +open class NinePatchView(val ninePatch: NinePatchTexture): View() { // Corners - protected val `$topLeft` = ResettableLazyProperty { + private val topLeftDelegate = ResettableLazyProperty { Rect(0.0, 0.0, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble()) } - protected val topLeft by `$topLeft` + protected open val topLeft by topLeftDelegate - protected val `$topRight` = ResettableLazyProperty { + private val topRightDelegate = ResettableLazyProperty { Rect(bounds.width - ninePatch.cornerWidth, 0.0, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble()) } - protected val topRight by `$topRight` + protected open val topRight by topRightDelegate - protected val `$bottomLeft` = ResettableLazyProperty { + private val bottomLeftDelegate = ResettableLazyProperty { Rect(0.0, bounds.height - ninePatch.cornerHeight, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble()) } - protected val bottomLeft by `$bottomLeft` + protected open val bottomLeft by bottomLeftDelegate - protected val `$bottomRight` = ResettableLazyProperty { + private val bottomRightDelegate = ResettableLazyProperty { Rect(bounds.width - ninePatch.cornerWidth, bounds.height - ninePatch.cornerHeight, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble()) } - protected val bottomRight by `$bottomRight` + protected open val bottomRight by bottomRightDelegate // Edges - protected val `$topMiddle` = ResettableLazyProperty { + private val topMiddleDelegate = ResettableLazyProperty { Rect(ninePatch.cornerWidth.toDouble(), topLeft.top, bounds.width - 2 * ninePatch.cornerWidth, ninePatch.cornerHeight.toDouble()) } - protected val topMiddle by `$topMiddle` + protected open val topMiddle by topMiddleDelegate - protected val `$bottomMiddle` = ResettableLazyProperty { + private val bottomMiddleDelegate = ResettableLazyProperty { Rect(topMiddle.left, bottomLeft.top, topMiddle.width, topMiddle.height) } - protected val bottomMiddle by `$bottomMiddle` + protected open val bottomMiddle by bottomMiddleDelegate - protected val `$leftMiddle` = ResettableLazyProperty { + private val leftMiddleDelegate = ResettableLazyProperty { Rect(topLeft.left, ninePatch.cornerHeight.toDouble(), ninePatch.cornerWidth.toDouble(), bounds.height - 2 * ninePatch.cornerHeight) } - protected val leftMiddle by `$leftMiddle` + protected open val leftMiddle by leftMiddleDelegate - protected val `$rightMiddle` = ResettableLazyProperty { + private val rightMiddleDelegate = ResettableLazyProperty { Rect(topRight.left, leftMiddle.top, leftMiddle.width, leftMiddle.height) } - protected val rightMiddle by `$rightMiddle` + protected open val rightMiddle by rightMiddleDelegate // Center - protected val `$center` = ResettableLazyProperty { + private val centerDelegate = ResettableLazyProperty { Rect(topLeft.right, topLeft.bottom, topMiddle.width, leftMiddle.height) } - protected val center by `$center` + protected open val center by centerDelegate - protected val delegates = listOf(`$topLeft`, `$topRight`, `$bottomLeft`, `$bottomRight`, `$topMiddle`, `$bottomMiddle`, `$leftMiddle`, `$rightMiddle`, `$center`) + private val delegates = listOf(topLeftDelegate, topRightDelegate, bottomLeftDelegate, bottomRightDelegate, topMiddleDelegate, bottomMiddleDelegate, leftMiddleDelegate, rightMiddleDelegate, centerDelegate) override fun didLayout() { super.didLayout() @@ -89,8 +91,8 @@ class NinePatchView(val ninePatch: NinePatchTexture): View() { private fun drawEdges() { // Horizontal for (i in 0 until (topMiddle.width.toInt() / ninePatch.centerWidth)) { - RenderHelper.draw(topMiddle.left + i * ninePatch.centerWidth, topMiddle.top, ninePatch.topMiddle.u, ninePatch.topMiddle.v, ninePatch.centerWidth.toDouble(), ninePatch.cornerHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height) - RenderHelper.draw(bottomMiddle.left + i * ninePatch.centerWidth, bottomMiddle.top, ninePatch.bottomMiddle.u, ninePatch.bottomMiddle.v, ninePatch.centerWidth.toDouble(), ninePatch.cornerHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height) + RenderHelper.draw(topMiddle.left + i * ninePatch.centerWidth, topMiddle.top, ninePatch.topMiddle.u, ninePatch.topMiddle.v, ninePatch.centerWidth.toDouble(), topMiddle.height, ninePatch.texture.width, ninePatch.texture.height) + RenderHelper.draw(bottomMiddle.left + i * ninePatch.centerWidth, bottomMiddle.top, ninePatch.bottomMiddle.u, ninePatch.bottomMiddle.v, ninePatch.centerWidth.toDouble(), bottomMiddle.height, ninePatch.texture.width, ninePatch.texture.height) } val remWidth = topMiddle.width.toInt() % ninePatch.centerWidth if (remWidth > 0) { diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/View.kt b/src/main/kotlin/net/shadowfacts/cacao/view/View.kt index 1cadf1f..806e9ba 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/View.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/View.kt @@ -2,8 +2,10 @@ package net.shadowfacts.cacao.view import net.shadowfacts.kiwidsl.dsl import net.shadowfacts.cacao.LayoutVariable +import net.shadowfacts.cacao.Window import net.shadowfacts.cacao.geometry.* import net.shadowfacts.cacao.util.* +import net.shadowfacts.cacao.util.properties.ObservableLateInitProperty import no.birkett.kiwi.Constraint import no.birkett.kiwi.Solver import java.util.* @@ -16,6 +18,13 @@ import java.util.* */ open class View() { + /** + * The window whose view hierarchy this view belongs to. + * Not initialized until the root view in this hierarchy has been added to a hierarchy, + * using it before that will throw a runtime exception. + */ + lateinit var window: Window + /** * The constraint solver used by the [net.shadowfacts.cacao.Window] this view belongs to. * Not initialized until [wasAdded] called, using it before that will throw a runtime exception. @@ -156,6 +165,7 @@ open class View() { view.superview = this view.solver = solver + view.window = window view.wasAdded() diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/button/AbstractButton.kt b/src/main/kotlin/net/shadowfacts/cacao/view/button/AbstractButton.kt index 424dcf4..36f4098 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/button/AbstractButton.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/button/AbstractButton.kt @@ -1,6 +1,5 @@ package net.shadowfacts.cacao.view.button -import net.minecraft.sound.SoundEvents import net.minecraft.util.Identifier import net.shadowfacts.cacao.geometry.Point import net.shadowfacts.cacao.util.MouseButton @@ -113,14 +112,11 @@ abstract class AbstractButton>(val content: View, val override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean { if (disabled) return false - val handler = handler - if (handler != null) { - // We can perform an unchecked cast here because we are certain that Impl will be the concrete implementation - // of AbstractButton. - // For example, an implementing class may be defined as such: `class Button: AbstractButton