Compare commits

..

5 Commits

Author SHA1 Message Date
Shadowfacts df3523347c
Add ToggleButton 2019-06-23 22:21:59 -04:00
Shadowfacts f2d7e0a656
Fix button test package 2019-06-23 21:56:58 -04:00
Shadowfacts 204731e03c
Add EnumButton 2019-06-23 21:55:42 -04:00
Shadowfacts b48b72d5bb
Add EnumHelper 2019-06-23 21:29:11 -04:00
Shadowfacts 4070baaa63
Add default button backgrounds and click sound 2019-06-23 21:22:54 -04:00
15 changed files with 368 additions and 16 deletions

View File

@ -1,6 +1,7 @@
package net.shadowfacts.asmr
import net.minecraft.util.Identifier
import net.shadowfacts.asmr.util.RedstoneMode
import net.shadowfacts.kiwidsl.dsl
import net.shadowfacts.cacao.CacaoScreen
import net.shadowfacts.cacao.Window
@ -11,6 +12,8 @@ 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
/**
* @author shadowfacts
@ -33,13 +36,10 @@ class TestCacaoScreen: CacaoScreen() {
intrinsicContentSize = Size(50.0, 50.0)
backgroundColor = Color(0x0000ff)
})
val purple = blue.addSubview(Button(Label("Hello, button!").apply {
textColor = Color.WHITE
}).apply {
val purple = blue.addSubview(ToggleButton(false).apply {
handler = {
println("$it clicked!")
println("enum button clicked, new value: ${it.state}")
}
background = NinePatchView(buttonNinePatch)
})
solver.dsl {
@ -48,6 +48,7 @@ class TestCacaoScreen: CacaoScreen() {
stack.rightAnchor equalTo 150
purple.centerXAnchor equalTo blue.centerXAnchor
purple.centerYAnchor equalTo blue.centerYAnchor
// purple.widthAnchor equalTo 50
}
layout()

View File

@ -0,0 +1,14 @@
package net.shadowfacts.asmr.util
/**
* @author shadowfacts
*/
enum class RedstoneMode {
HIGH, LOW, TOGGLE;
companion object {
fun localize(value: RedstoneMode): String {
return value.name.toLowerCase().capitalize()
}
}
}

View File

@ -0,0 +1,20 @@
package net.shadowfacts.cacao.util
/**
* @author shadowfacts
*/
object EnumHelper {
fun <E: Enum<E>> next(value: E): E {
val constants = value.declaringClass.enumConstants
val index = constants.indexOf(value) + 1
return if (index < constants.size) constants[index] else constants.first()
}
fun <E: Enum<E>> previous(value: E): E {
val constants = value.declaringClass.enumConstants
val index = constants.indexOf(value) - 1
return if (index >= 0) constants[index] else constants.last()
}
}

View File

@ -4,14 +4,15 @@ package net.shadowfacts.cacao.util
* @author shadowfacts
*/
enum class MouseButton {
LEFT, RIGHT, MIDDLE;
LEFT, RIGHT, MIDDLE, UNKNOWN;
companion object {
fun fromMC(button: Int): MouseButton {
return when (button) {
0 -> LEFT
1 -> RIGHT
2 -> MIDDLE
else -> LEFT
else -> UNKNOWN
}
}
}

View File

@ -3,6 +3,8 @@ 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.sound.PositionedSoundInstance
import net.minecraft.sound.SoundEvent
import net.shadowfacts.cacao.geometry.Rect
/**
@ -13,7 +15,13 @@ import net.shadowfacts.cacao.geometry.Rect
*/
object RenderHelper {
private val disabled = (System.getProperty("cacao.drawing.disabled") ?: "false").toBoolean()
val disabled = (System.getProperty("cacao.drawing.disabled") ?: "false").toBoolean()
// TODO: find a better place for this
fun playSound(event: SoundEvent) {
if (disabled) return
MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(event, 1f))
}
/**
* Draws a solid [rect] filled with the given [color].
@ -65,6 +73,9 @@ object RenderHelper {
GlStateManager.translated(x, y, z)
}
/**
* @see org.lwjgl.opengl.GL11.glColor4f
*/
fun color(r: Float, g: Float, b: Float, alpha: Float) {
if (disabled) return
GlStateManager.color4f(r, g, b, alpha)

View File

@ -5,6 +5,7 @@ import net.minecraft.client.font.TextRenderer
import net.shadowfacts.cacao.geometry.Point
import net.shadowfacts.cacao.geometry.Size
import net.shadowfacts.cacao.util.Color
import net.shadowfacts.cacao.util.RenderHelper
/**
* A simple View that displays text. Allows for controlling the color and shadow of the text. Label cannot be used
@ -29,7 +30,7 @@ class Label(text: String): View() {
updateIntrinsicContentSize()
}
var textColor = Color(0x404040)
var textColor = Color.WHITE
override fun wasAdded() {
super.wasAdded()
@ -38,6 +39,8 @@ class Label(text: String): View() {
}
private fun updateIntrinsicContentSize() {
if (RenderHelper.disabled) return
val width = textRenderer.getStringWidth(text)
val height = textRenderer.fontHeight
intrinsicContentSize = Size(width.toDouble(), height.toDouble())

View File

@ -10,7 +10,7 @@ import net.shadowfacts.cacao.util.Texture
*
* @author shadowfacts
*/
class TextureView(val texture: Texture): View() {
class TextureView(var texture: Texture): View() {
override fun drawContent(mouse: Point, delta: Float) {
RenderHelper.draw(bounds, texture)

View File

@ -1,8 +1,13 @@
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
import net.shadowfacts.cacao.util.NinePatchTexture
import net.shadowfacts.cacao.util.RenderHelper
import net.shadowfacts.cacao.util.Texture
import net.shadowfacts.cacao.view.NinePatchView
import net.shadowfacts.cacao.view.View
import net.shadowfacts.kiwidsl.dsl
@ -19,6 +24,12 @@ import net.shadowfacts.kiwidsl.dsl
*/
abstract class AbstractButton<Impl: AbstractButton<Impl>>(val content: View, val padding: Double = 4.0): View() {
companion object {
val DEFAULT_BG = NinePatchTexture(Texture(Identifier("textures/gui/widgets.png"), 0, 66), 3, 3, 194, 14)
val HOVERED_BG = NinePatchTexture(Texture(Identifier("textures/gui/widgets.png"), 0, 86), 3, 3, 194, 14)
val DISABLED_BG = NinePatchTexture(Texture(Identifier("textures/gui/widgets.png"), 0, 46), 3, 3, 194, 14)
}
/**
* The function that handles when this button is clicked.
* The parameter is the type of the concrete button implementation that was used.
@ -40,19 +51,24 @@ abstract class AbstractButton<Impl: AbstractButton<Impl>>(val content: View, val
* If a [backgroundColor] is specified, it will be drawn behind the background View and thus not visible
* unless the background view is not fully opaque.
*/
var background: View? = null
var background: View? = NinePatchView(DEFAULT_BG)
/**
* The background to draw when the button is hovered over by the mouse.
* If `null`, the normal [background] will be used.
* @see background
*/
var hoveredBackground: View? = null
var hoveredBackground: View? = NinePatchView(HOVERED_BG)
/**
* The background to draw when the button is [disabled].
* If `null`, the normal [background] will be used.
* @see background
*/
var disabledBackground: View? = null
var disabledBackground: View? = NinePatchView(DISABLED_BG)
/**
* If the button will play the Minecraft button click sound when clicked.
*/
var clickSoundEnabled = true
override fun wasAdded() {
solver.dsl {
@ -109,6 +125,10 @@ abstract class AbstractButton<Impl: AbstractButton<Impl>>(val content: View, val
// For example, an implementing class may be defined as such: `class Button: AbstractButton<Button>`
@Suppress("UNCHECKED_CAST")
handler(this as Impl)
if (clickSoundEnabled && !RenderHelper.disabled) {
RenderHelper.playSound(SoundEvents.UI_BUTTON_CLICK)
}
}
}

View File

@ -0,0 +1,45 @@
package net.shadowfacts.cacao.view.button
import net.shadowfacts.cacao.geometry.Point
import net.shadowfacts.cacao.util.EnumHelper
import net.shadowfacts.cacao.util.MouseButton
import net.shadowfacts.cacao.view.Label
/**
* A button that cycles through enum values.
* Left click: forwards
* Right click: backwards
* All other mouse buttons call the handler with the unchanged value
*
* @author shadowfacts
* @param initialValue The initial enum value for this button.
* @param localizer A function that takes an enum value and converts into a string for the button's label.
*/
class EnumButton<E: Enum<E>>(initialValue: E, val localizer: (E) -> String): AbstractButton<EnumButton<E>>(Label(localizer(initialValue))) {
private val label: Label
get() = content as Label
/**
* The current value of the enum button.
* Updating this property will use the [localizer] to update the label.
*/
var value: E = initialValue
set(value) {
field = value
label.text = localizer(value)
}
override fun mouseClicked(point: Point, mouseButton: MouseButton) {
if (!disabled) {
value = when (mouseButton) {
MouseButton.LEFT -> EnumHelper.next(value)
MouseButton.RIGHT -> EnumHelper.previous(value)
else -> value
}
}
super.mouseClicked(point, mouseButton)
}
}

View File

@ -0,0 +1,46 @@
package net.shadowfacts.cacao.view.button
import net.minecraft.util.Identifier
import net.shadowfacts.cacao.geometry.Point
import net.shadowfacts.cacao.geometry.Size
import net.shadowfacts.cacao.util.MouseButton
import net.shadowfacts.cacao.util.Texture
import net.shadowfacts.cacao.view.TextureView
/**
* A button for toggling between on/off states.
*
* @author shadowfacts
* @param initialState Whether the button starts as on or off.
*/
class ToggleButton(initialState: Boolean): AbstractButton<ToggleButton>(TextureView(if (initialState) ON else OFF).apply {
intrinsicContentSize = Size(19.0, 19.0)
}, padding = 0.0) {
companion object {
val ON = Texture(Identifier("asmr", "textures/gui/toggle.png"), 0, 0)
val OFF = Texture(Identifier("asmr", "textures/gui/toggle.png"), 0, 19)
}
private val textureView: TextureView
get() = content as TextureView
/**
* The button's current on/off state.
* Updating this property updates the button's texture.
*/
var state: Boolean = initialState
set(value) {
field = value
textureView.texture = if (value) ON else OFF
}
override fun mouseClicked(point: Point, mouseButton: MouseButton) {
if (!disabled && (mouseButton == MouseButton.LEFT || mouseButton == MouseButton.RIGHT)) {
state = !state
}
super.mouseClicked(point, mouseButton)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,27 @@
package net.shadowfacts.cacao.util
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
/**
* @author shadowfacts
*/
class EnumHelperTests {
enum class MyEnum {
ONE, TWO
}
@Test
fun testNext() {
assertEquals(MyEnum.TWO, EnumHelper.next(MyEnum.ONE))
assertEquals(MyEnum.ONE, EnumHelper.next(MyEnum.TWO))
}
@Test
fun testPrev() {
assertEquals(MyEnum.ONE, EnumHelper.previous(MyEnum.TWO))
assertEquals(MyEnum.TWO, EnumHelper.previous(MyEnum.ONE))
}
}

View File

@ -1,11 +1,10 @@
package net.shadowfacts.cacao.view
package net.shadowfacts.cacao.view.button
import net.shadowfacts.cacao.Window
import net.shadowfacts.cacao.geometry.Point
import net.shadowfacts.cacao.geometry.Rect
import net.shadowfacts.cacao.geometry.Size
import net.shadowfacts.cacao.util.MouseButton
import net.shadowfacts.cacao.view.button.Button
import net.shadowfacts.cacao.view.View
import net.shadowfacts.kiwidsl.dsl
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue

View File

@ -0,0 +1,93 @@
package net.shadowfacts.cacao.view.button
import net.shadowfacts.cacao.Window
import net.shadowfacts.cacao.geometry.Point
import net.shadowfacts.cacao.geometry.Rect
import net.shadowfacts.cacao.util.MouseButton
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.util.concurrent.CompletableFuture
/**
* @author shadowfacts
*/
class EnumButtonTests {
companion object {
@BeforeAll
@JvmStatic
fun setupAll() {
System.setProperty("cacao.drawing.disabled", "true")
}
}
enum class MyEnum {
ONE, TWO, THREE
}
lateinit var window: Window
@BeforeEach
fun setup() {
window = Window()
}
@Test
fun testHandlerCalled() {
val called = CompletableFuture<Boolean>()
val button = window.addView(EnumButton(MyEnum.ONE, MyEnum::name).apply {
frame = Rect(0.0, 0.0, 25.0, 25.0)
content.frame = bounds
handler = {
called.complete(true)
}
})
window.mouseClicked(Point(5.0, 5.0), MouseButton.LEFT)
assertTrue(called.getNow(false))
assertEquals(MyEnum.TWO, button.value)
}
@Test
fun testCyclesValues() {
val button = window.addView(EnumButton(MyEnum.ONE, MyEnum::name).apply {
frame = Rect(0.0, 0.0, 25.0, 25.0)
content.frame = bounds
})
window.mouseClicked(Point(5.0, 5.0), MouseButton.LEFT)
assertEquals(MyEnum.TWO, button.value)
window.mouseClicked(Point(5.0, 5.0), MouseButton.LEFT)
assertEquals(MyEnum.THREE, button.value)
window.mouseClicked(Point(5.0, 5.0), MouseButton.LEFT)
assertEquals(MyEnum.ONE, button.value)
}
@Test
fun testCyclesValuesBackwards() {
val button = window.addView(EnumButton(MyEnum.ONE, MyEnum::name).apply {
frame = Rect(0.0, 0.0, 25.0, 25.0)
content.frame = Rect(0.0, 0.0, 25.0, 25.0)
})
window.mouseClicked(Point(5.0, 5.0), MouseButton.LEFT)
assertEquals(MyEnum.TWO, button.value)
window.mouseClicked(Point(5.0, 5.0), MouseButton.RIGHT)
assertEquals(MyEnum.ONE, button.value)
}
@Test
fun testMiddleClickDoesNotChangeValue() {
val button = window.addView(EnumButton(MyEnum.ONE, MyEnum::name).apply {
frame = Rect(0.0, 0.0, 25.0, 25.0)
content.frame = Rect(0.0, 0.0, 25.0, 25.0)
})
window.mouseClicked(Point(5.0, 5.0), MouseButton.MIDDLE)
assertEquals(MyEnum.ONE, button.value)
}
}

View File

@ -0,0 +1,72 @@
package net.shadowfacts.cacao.view.button
import net.shadowfacts.cacao.Window
import net.shadowfacts.cacao.geometry.Point
import net.shadowfacts.cacao.geometry.Rect
import net.shadowfacts.cacao.util.MouseButton
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.util.concurrent.CompletableFuture
/**
* @author shadowfacts
*/
class ToggleButtonTests {
companion object {
@BeforeAll
@JvmStatic
fun setupAll() {
System.setProperty("cacao.drawing.disabled", "true")
}
}
lateinit var window: Window
@BeforeEach
fun setup() {
window = Window()
}
@Test
fun testHandlerCalled() {
val called = CompletableFuture<Boolean>()
val button = window.addView(ToggleButton(false).apply {
frame = Rect(0.0, 0.0, 25.0, 25.0)
content.frame = bounds
handler = {
called.complete(true)
}
})
window.mouseClicked(Point(5.0, 5.0), MouseButton.LEFT)
assertTrue(called.getNow(false))
}
@Test
fun testTogglesValues() {
val button = window.addView(ToggleButton(false).apply {
frame = Rect(0.0, 0.0, 25.0, 25.0)
content.frame = bounds
})
window.mouseClicked(Point(5.0, 5.0), MouseButton.LEFT)
assertTrue(button.state)
window.mouseClicked(Point(5.0, 5.0), MouseButton.LEFT)
assertFalse(button.state)
}
@Test
fun testMiddleClickDoesNotChangeValue() {
val button = window.addView(ToggleButton(false).apply {
frame = Rect(0.0, 0.0, 25.0, 25.0)
content.frame = bounds
})
window.mouseClicked(Point(5.0, 5.0), MouseButton.MIDDLE)
assertFalse(button.state)
}
}