Compare commits

...

7 Commits

Author SHA1 Message Date
Shadowfacts e9df08aa30
Move View class to view package 2019-06-22 11:17:56 -04:00
Shadowfacts 008354b74a
Add Window layout unit tests 2019-06-22 11:17:29 -04:00
Shadowfacts 1dffc67339
Add Rect unit tests 2019-06-22 11:06:39 -04:00
Shadowfacts 27518c68df
Add Color unit tests 2019-06-22 11:02:21 -04:00
Shadowfacts 2b739899a1
Add missing JavaDoc headers 2019-06-22 10:59:18 -04:00
Shadowfacts 50b91f276b
Add Label 2019-06-22 10:56:12 -04:00
Shadowfacts 349c09f630
Add View intrinsic content size 2019-06-22 10:21:29 -04:00
15 changed files with 299 additions and 19 deletions

View File

@ -5,6 +5,9 @@ import net.fabricmc.fabric.api.registry.CommandRegistry
import net.minecraft.client.MinecraftClient import net.minecraft.client.MinecraftClient
import net.minecraft.server.command.CommandManager import net.minecraft.server.command.CommandManager
/**
* @author shadowfacts
*/
object ASMR: ModInitializer { object ASMR: ModInitializer {
override fun onInitialize() { override fun onInitialize() {

View File

@ -2,10 +2,15 @@ package net.shadowfacts.asmr
import net.shadowfacts.kiwidsl.dsl import net.shadowfacts.kiwidsl.dsl
import net.shadowfacts.shadowui.Screen import net.shadowfacts.shadowui.Screen
import net.shadowfacts.shadowui.View import net.shadowfacts.shadowui.view.View
import net.shadowfacts.shadowui.Window import net.shadowfacts.shadowui.Window
import net.shadowfacts.shadowui.geometry.Size
import net.shadowfacts.shadowui.util.Color import net.shadowfacts.shadowui.util.Color
import net.shadowfacts.shadowui.view.Label
/**
* @author shadowfacts
*/
class TestScreen: Screen() { class TestScreen: Screen() {
init { init {
@ -22,6 +27,10 @@ class TestScreen: Screen() {
val purple = green.addSubview(View().apply { val purple = green.addSubview(View().apply {
backgroundColor = Color(0x800080) backgroundColor = Color(0x800080)
}) })
purple.intrinsicContentSize = Size(width = 150.0, height = 150.0)
val label = purple.addSubview(Label("Hello, world!").apply {
textColor = Color.WHITE
})
solver.dsl { solver.dsl {
red.leftAnchor equalTo 0 red.leftAnchor equalTo 0
@ -39,10 +48,13 @@ class TestScreen: Screen() {
blue.topAnchor equalTo (green.topAnchor + green.heightAnchor) blue.topAnchor equalTo (green.topAnchor + green.heightAnchor)
blue.heightAnchor equalTo 50 blue.heightAnchor equalTo 50
purple.widthAnchor equalTo 100 // purple.widthAnchor equalTo 100
purple.heightAnchor equalTo 100 // purple.heightAnchor equalTo 100
purple.centerXAnchor equalTo green.centerXAnchor purple.centerXAnchor equalTo green.centerXAnchor
purple.centerYAnchor equalTo green.centerYAnchor purple.centerYAnchor equalTo green.centerYAnchor
label.centerXAnchor equalTo purple.centerXAnchor
label.centerYAnchor equalTo purple.centerYAnchor
} }
layout() layout()

View File

@ -1,7 +1,11 @@
package net.shadowfacts.shadowui package net.shadowfacts.shadowui
import net.shadowfacts.shadowui.view.View
import no.birkett.kiwi.Variable import no.birkett.kiwi.Variable
/**
* @author shadowfacts
*/
class LayoutVariable(val owner: View, val property: String): Variable("LayoutVariable") { class LayoutVariable(val owner: View, val property: String): Variable("LayoutVariable") {
override fun getName() = "$owner.$property" override fun getName() = "$owner.$property"

View File

@ -5,6 +5,9 @@ import net.minecraft.network.chat.TextComponent
import net.shadowfacts.shadowui.geometry.Point import net.shadowfacts.shadowui.geometry.Point
import no.birkett.kiwi.Solver import no.birkett.kiwi.Solver
/**
* @author shadowfacts
*/
open class Screen: Screen(TextComponent("Screen")) { open class Screen: Screen(TextComponent("Screen")) {
val windows = mutableListOf<Window>() val windows = mutableListOf<Window>()

View File

@ -1,8 +1,12 @@
package net.shadowfacts.shadowui package net.shadowfacts.shadowui
import net.shadowfacts.shadowui.geometry.Point import net.shadowfacts.shadowui.geometry.Point
import net.shadowfacts.shadowui.view.View
import no.birkett.kiwi.Solver import no.birkett.kiwi.Solver
/**
* @author shadowfacts
*/
class Window { class Window {
var solver = Solver() var solver = Solver()

View File

@ -1,6 +1,9 @@
package net.shadowfacts.shadowui.geometry package net.shadowfacts.shadowui.geometry
class Point(val x: Double, val y: Double) { /**
* @author shadowfacts
*/
data class Point(val x: Double, val y: Double) {
constructor(x: Int, y: Int): this(x.toDouble(), y.toDouble()) constructor(x: Int, y: Int): this(x.toDouble(), y.toDouble())

View File

@ -1,15 +1,33 @@
package net.shadowfacts.shadowui.geometry package net.shadowfacts.shadowui.geometry
/**
* @author shadowfacts
*/
data class Rect(val left: Double, val top: Double, val width: Double, val height: Double) { data class Rect(val left: Double, val top: Double, val width: Double, val height: Double) {
val right: Double val right: Double by lazy {
get() = left + width left + width
val bottom: Double }
get() = top + height val bottom: Double by lazy {
top + height
}
val midX: Double val midX: Double by lazy {
get() = left + width / 2 left + width / 2
val midY: Double }
get() = top + height / 2 val midY: Double by lazy {
top + height / 2
}
val origin: Point by lazy {
Point(left, top)
}
val center: Point by lazy {
Point(midX, midY)
}
val size: Size by lazy {
Size(width, height)
}
} }

View File

@ -0,0 +1,6 @@
package net.shadowfacts.shadowui.geometry
/**
* @author shadowfacts
*/
data class Size(val width: Double, val height: Double)

View File

@ -1,5 +1,8 @@
package net.shadowfacts.shadowui.util package net.shadowfacts.shadowui.util
/**
* @author shadowfacts
*/
data class Color(val red: Int, val green: Int, val blue: Int, val alpha: Int = 255) { data class Color(val red: Int, val green: Int, val blue: Int, val alpha: Int = 255) {
constructor(rgb: Int, alpha: Int = 255): this(rgb shr 16, (rgb shr 8) and 255, rgb and 255, alpha) constructor(rgb: Int, alpha: Int = 255): this(rgb shr 16, (rgb shr 8) and 255, rgb and 255, alpha)
@ -7,4 +10,10 @@ data class Color(val red: Int, val green: Int, val blue: Int, val alpha: Int = 2
val argb: Int val argb: Int
get() = ((alpha and 255) shl 24) or ((red and 255) shl 16) or ((green and 255) shl 8) or (blue and 255) get() = ((alpha and 255) shl 24) or ((red and 255) shl 16) or ((green and 255) shl 8) or (blue and 255)
companion object {
val CLEAR = Color(0, alpha = 0)
val WHITE = Color(0xffffff)
val BLACK = Color(0)
}
} }

View File

@ -3,6 +3,9 @@ package net.shadowfacts.shadowui.util
import net.minecraft.client.gui.DrawableHelper import net.minecraft.client.gui.DrawableHelper
import net.shadowfacts.shadowui.geometry.Rect import net.shadowfacts.shadowui.geometry.Rect
/**
* @author shadowfacts
*/
object RenderHelper { object RenderHelper {
fun fill(rect: Rect, color: Color) { fun fill(rect: Rect, color: Color) {

View File

@ -0,0 +1,42 @@
package net.shadowfacts.shadowui.view
import net.minecraft.client.MinecraftClient
import net.minecraft.client.font.TextRenderer
import net.shadowfacts.shadowui.geometry.Size
import net.shadowfacts.shadowui.util.Color
/**
* @author shadowfacts
*/
class Label(text: String): View() {
companion object {
val textRenderer: TextRenderer
get() = MinecraftClient.getInstance().textRenderer
}
var text: String = text
set(value) {
field = value
updateIntrinsicContentSize()
}
var textColor = Color(0x404040)
override fun wasAdded() {
super.wasAdded()
updateIntrinsicContentSize()
}
private fun updateIntrinsicContentSize() {
val width = textRenderer.getStringWidth(text)
val height = textRenderer.fontHeight
intrinsicContentSize = Size(width.toDouble(), height.toDouble())
}
override fun drawContent() {
textRenderer.draw(text, 0f, 0f, textColor.argb)
}
}

View File

@ -1,13 +1,19 @@
package net.shadowfacts.shadowui package net.shadowfacts.shadowui.view
import com.mojang.blaze3d.platform.GlStateManager import com.mojang.blaze3d.platform.GlStateManager
import net.shadowfacts.kiwidsl.dsl import net.shadowfacts.kiwidsl.dsl
import net.shadowfacts.shadowui.LayoutVariable
import net.shadowfacts.shadowui.geometry.Rect import net.shadowfacts.shadowui.geometry.Rect
import net.shadowfacts.shadowui.geometry.Size
import net.shadowfacts.shadowui.util.Color import net.shadowfacts.shadowui.util.Color
import net.shadowfacts.shadowui.util.RenderHelper import net.shadowfacts.shadowui.util.RenderHelper
import no.birkett.kiwi.Constraint
import no.birkett.kiwi.Solver import no.birkett.kiwi.Solver
class View { /**
* @author shadowfacts
*/
open class View {
lateinit var solver: Solver lateinit var solver: Solver
@ -26,7 +32,15 @@ class View {
// The rectangle for this view in its own coordinate system. // The rectangle for this view in its own coordinate system.
lateinit var bounds: Rect lateinit var bounds: Rect
var backgroundColor = Color(0xff0000) var intrinsicContentSize: Size? = null
set(value) {
updateIntrinsicContentSizeConstraints(intrinsicContentSize, value)
field = value
}
private var intrinsicContentSizeWidthConstraint: Constraint? = null
private var intrinsicContentSizeHeightConstraint: Constraint? = null
var backgroundColor = Color.CLEAR
var parent: View? = null var parent: View? = null
val subviews = mutableListOf<View>() val subviews = mutableListOf<View>()
@ -41,11 +55,11 @@ class View {
return view return view
} }
fun wasAdded() { open fun wasAdded() {
createInternalConstraints() createInternalConstraints()
} }
fun createInternalConstraints() { open fun createInternalConstraints() {
solver.dsl { solver.dsl {
rightAnchor equalTo (leftAnchor + widthAnchor) rightAnchor equalTo (leftAnchor + widthAnchor)
bottomAnchor equalTo (topAnchor + heightAnchor) bottomAnchor equalTo (topAnchor + heightAnchor)
@ -54,7 +68,20 @@ class View {
} }
} }
fun didLayout() { private fun updateIntrinsicContentSizeConstraints(old: Size?, new: Size?) {
if (old != null) {
solver.removeConstraint(intrinsicContentSizeWidthConstraint!!)
solver.removeConstraint(intrinsicContentSizeHeightConstraint!!)
}
if (new != null) {
solver.dsl {
this@View.intrinsicContentSizeWidthConstraint = (widthAnchor.equalTo(new.width, strength = WEAK))
this@View.intrinsicContentSizeHeightConstraint = (heightAnchor.equalTo(new.height, strength = WEAK))
}
}
}
open fun didLayout() {
subviews.forEach(View::didLayout) subviews.forEach(View::didLayout)
val parentLeft = parent?.leftAnchor?.value ?: 0.0 val parentLeft = parent?.leftAnchor?.value ?: 0.0
@ -76,6 +103,6 @@ class View {
GlStateManager.popMatrix() GlStateManager.popMatrix()
} }
fun drawContent() {} open fun drawContent() {}
} }

View File

@ -0,0 +1,82 @@
package net.shadowfacts.shadowui
import net.shadowfacts.kiwidsl.dsl
import net.shadowfacts.shadowui.geometry.Size
import net.shadowfacts.shadowui.view.View
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
/**
* @author shadowfacts
*/
class WindowTests {
lateinit var window: Window
@BeforeEach
fun setup() {
window = Window()
}
@Test
fun testConstraintToConstant() {
val view = window.addView(View())
window.solver.dsl {
view.leftAnchor equalTo 100
view.rightAnchor equalTo 200
view.topAnchor equalTo 100
view.heightAnchor equalTo 200
}
window.layout()
assertEquals(100.0, view.widthAnchor.value)
assertEquals(300.0, view.bottomAnchor.value)
}
@Test
fun testConstraintToView() {
val one = window.addView(View())
val two = window.addView(View())
window.solver.dsl {
one.leftAnchor equalTo 0
one.widthAnchor equalTo 100
one.topAnchor equalTo 0
one.heightAnchor equalTo 200
two.leftAnchor equalTo one.rightAnchor
two.rightAnchor equalTo 400
two.topAnchor equalTo one.bottomAnchor
two.heightAnchor equalTo one.heightAnchor
}
window.layout()
assertEquals(100.0, two.leftAnchor.value)
assertEquals(300.0, two.widthAnchor.value)
assertEquals(200.0, two.topAnchor.value)
assertEquals(400.0, two.bottomAnchor.value)
}
@Test
fun testIntrinsicContentSize() {
val view = window.addView(View()).apply {
intrinsicContentSize = Size(100.0, 200.0)
}
window.solver.dsl {
view.leftAnchor equalTo 0
view.topAnchor equalTo 100
}
window.layout()
assertEquals(100.0, view.widthAnchor.value)
assertEquals(100.0, view.rightAnchor.value)
assertEquals(200.0, view.heightAnchor.value)
assertEquals(300.0, view.bottomAnchor.value)
}
}

View File

@ -0,0 +1,39 @@
package net.shadowfacts.shadowui.geometry
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
/**
* @author shadowfacts
*/
class RectTest {
@Test
fun testTrailingEdges() {
val rect = Rect(25.0, 50.0, 100.0, 200.0)
assertEquals(125.0, rect.right)
assertEquals(250.0, rect.bottom)
}
@Test
fun testCenter() {
val rect = Rect(25.0, 50.0, 100.0, 200.0)
assertEquals(75.0, rect.midX)
assertEquals(150.0, rect.midY)
}
@Test
fun testPoints() {
val rect = Rect(25.0, 50.0, 100.0, 200.0)
assertEquals(Point(25.0, 50.0), rect.origin)
assertEquals(Point(75.0, 150.0), rect.center)
}
@Test
fun testSize() {
val rect = Rect(25.0, 50.0, 100.0, 200.0)
assertEquals(Size(100.0, 200.0), rect.size)
}
}

View File

@ -0,0 +1,25 @@
package net.shadowfacts.shadowui.util
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
/**
* @author shadowfacts
*/
class ColorTest {
@Test
fun fromRGB() {
val color = Color(0x123456)
assertEquals(0x12, color.red)
assertEquals(0x34, color.green)
assertEquals(0x56, color.blue)
}
@Test
fun toARGB() {
val color = Color(red = 0x12, green = 0x34, blue = 0x56, alpha = 0x78)
assertEquals(0x78123456, color.argb)
}
}