Compare commits

..

No commits in common. "d750339c07c53c4cf97d04e7c09136d5d9db348d" and "cb66900cb5f52ead1e1284144faa31233f88c969" have entirely different histories.

11 changed files with 29 additions and 281 deletions

View File

@ -20,7 +20,7 @@ class TestCacaoScreen: CacaoScreen() {
init { init {
addWindow(Window().apply { addWindow(Window().apply {
val stack = addView(StackView(Axis.VERTICAL, StackView.Distribution.CENTER, spacing = 4.0).apply { val stack = addView(StackView(Axis.VERTICAL, StackView.Distribution.CENTER).apply {
backgroundColor = Color.WHITE backgroundColor = Color.WHITE
}) })
val red = stack.addArrangedSubview(TextureView(Texture(Identifier("textures/block/birch_log_top.png"), 0, 0, 16, 16)).apply { val red = stack.addArrangedSubview(TextureView(Texture(Identifier("textures/block/birch_log_top.png"), 0, 0, 16, 16)).apply {
@ -41,23 +41,9 @@ class TestCacaoScreen: CacaoScreen() {
).apply { ).apply {
handler = { handler = {
println("dropdown value: ${it.value}") println("dropdown value: ${it.value}")
val dialog = DialogView(
"New redstone mode",
it.value.name,
arrayOf(DialogView.DefaultButtonType.OK),
Texture(Identifier("textures/block/birch_log_top.png"), 0, 0, 16, 16),
buttonCallback = { _, window ->
window.removeFromScreen()
}
)
val dialogWindow = Window()
dialogWindow.addView(dialog)
this@TestCacaoScreen.addWindow(dialogWindow)
} }
}) })
val label = addView(Label("test ".repeat(100), maxLines = 5))
solver.dsl { solver.dsl {
stack.topAnchor equalTo 0 stack.topAnchor equalTo 0
stack.centerXAnchor equalTo this@apply.centerXAnchor stack.centerXAnchor equalTo this@apply.centerXAnchor
@ -65,12 +51,10 @@ class TestCacaoScreen: CacaoScreen() {
purple.centerXAnchor equalTo blue.centerXAnchor purple.centerXAnchor equalTo blue.centerXAnchor
purple.centerYAnchor equalTo blue.centerYAnchor purple.centerYAnchor equalTo blue.centerYAnchor
// purple.widthAnchor equalTo 50 // purple.widthAnchor equalTo 50
label.topAnchor equalTo 0
label.leftAnchor equalTo 0
label.widthAnchor equalTo 100
} }
layout()
// val red = addView(View().apply { // val red = addView(View().apply {
// backgroundColor = Color(0xff0000) // backgroundColor = Color(0xff0000)

View File

@ -38,7 +38,6 @@ open class CacaoScreen: Screen(TextComponent("CacaoScreen")) {
fun <T: Window> addWindow(window: T, index: Int = _windows.size): T { fun <T: Window> addWindow(window: T, index: Int = _windows.size): T {
_windows.add(index, window) _windows.add(index, window)
window.screen = this window.screen = this
window.resize(width, height)
return window return window
} }
@ -72,12 +71,16 @@ open class CacaoScreen: Screen(TextComponent("CacaoScreen")) {
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
val window = windows.lastOrNull() val window = windows.lastOrNull()
val result = window?.mouseClicked(Point(mouseX, mouseY), MouseButton.fromMC(button)) val result = window?.mouseClicked(Point(mouseX, mouseY), MouseButton.fromMC(button))
return if (result == true) { when (result) {
RenderHelper.playSound(SoundEvents.UI_BUTTON_CLICK) true ->
true RenderHelper.playSound(SoundEvents.UI_BUTTON_CLICK)
} else { false ->
false if (windows.size > 1) {
removeWindow(windows.last())
}
} }
return result == true
} }
} }

View File

@ -86,7 +86,7 @@ class Window {
* Creates the internal constraints used by the window. * Creates the internal constraints used by the window.
* If overridden, the super-class method must be called. * If overridden, the super-class method must be called.
*/ */
protected fun createInternalConstraints() { protected open fun createInternalConstraints() {
solver.dsl { solver.dsl {
leftAnchor equalTo 0 leftAnchor equalTo 0
topAnchor equalTo 0 topAnchor equalTo 0
@ -195,11 +195,6 @@ class Window {
if (view != null) { if (view != null) {
val pointInView = Point(point.x - view.frame.left, point.y - view.frame.top) val pointInView = Point(point.x - view.frame.left, point.y - view.frame.top)
return view.mouseClicked(pointInView, mouseButton) return view.mouseClicked(pointInView, mouseButton)
} else {
// remove the window from the screen when the mouse clicks outside the window and this is not the primary window
if (screen.windows.size > 1) {
removeFromScreen()
}
} }
return false return false
} }

View File

@ -1,115 +0,0 @@
package net.shadowfacts.cacao.view
import net.minecraft.ChatFormat
import net.shadowfacts.cacao.Window
import net.shadowfacts.cacao.geometry.Axis
import net.shadowfacts.cacao.util.Color
import net.shadowfacts.cacao.util.texture.NinePatchTexture
import net.shadowfacts.cacao.util.texture.Texture
import net.shadowfacts.cacao.view.button.Button
import net.shadowfacts.kiwidsl.dsl
/**
* @author shadowfacts
*/
class DialogView(
val title: String,
val message: String,
val buttonTypes: Array<ButtonType>,
val iconTexture: Texture?,
val buttonCallback: (ButtonType, Window) -> Unit
): View() {
interface ButtonType {
val localizedName: String
}
enum class DefaultButtonType: ButtonType {
CANCEL, CONFIRM, OK, CLOSE;
override val localizedName: String
get() = name.toLowerCase().capitalize() // todo: actually localize me
}
private lateinit var background: NinePatchView
private lateinit var hStack: StackView
private var iconView: TextureView? = null
private lateinit var vStack: StackView
private lateinit var messageLabel: Label
private var buttonContainer: View? = null
private var buttonStack: StackView? = null
override fun wasAdded() {
background = addSubview(NinePatchView(NinePatchTexture.PANEL_BG).apply { zIndex = -1.0 })
hStack = addSubview(StackView(Axis.HORIZONTAL, StackView.Distribution.LEADING, spacing = 8.0))
if (iconTexture != null) {
iconView = hStack.addArrangedSubview(TextureView(iconTexture))
}
vStack = hStack.addArrangedSubview(StackView(Axis.VERTICAL, spacing = 4.0))
vStack.addArrangedSubview(Label(ChatFormat.BOLD.toString() + title, shadow = false).apply {
textColor = Color(0x404040)
})
messageLabel = vStack.addArrangedSubview(Label(message, shadow = false).apply {
textColor = Color(0x404040)
})
if (buttonTypes.isNotEmpty()) {
buttonContainer = vStack.addArrangedSubview(View())
buttonStack = buttonContainer!!.addSubview(StackView(Axis.HORIZONTAL))
for (type in buttonTypes) {
buttonStack!!.addArrangedSubview(Button(Label(type.localizedName)).apply {
handler = {
this@DialogView.buttonCallback(type, this@DialogView.window)
}
})
}
}
super.wasAdded()
}
override fun createInternalConstraints() {
super.createInternalConstraints()
solver.dsl {
centerXAnchor equalTo window.centerXAnchor
centerYAnchor equalTo window.centerYAnchor
widthAnchor greaterThanOrEqualTo 175
background.leftAnchor equalTo leftAnchor - 8
background.rightAnchor equalTo rightAnchor + 8
background.topAnchor equalTo topAnchor - 8
background.bottomAnchor equalTo bottomAnchor + 8
hStack.leftAnchor equalTo leftAnchor
hStack.rightAnchor equalTo rightAnchor
hStack.topAnchor equalTo topAnchor
hStack.bottomAnchor equalTo bottomAnchor
if (iconView != null) {
hStack.bottomAnchor greaterThanOrEqualTo iconView!!.bottomAnchor
}
hStack.bottomAnchor greaterThanOrEqualTo vStack.bottomAnchor
if (iconView != null) {
iconView!!.widthAnchor equalTo 30
iconView!!.heightAnchor equalTo 30
}
messageLabel.heightAnchor greaterThanOrEqualTo 50
if (buttonContainer != null) {
buttonStack!!.heightAnchor equalTo buttonContainer!!.heightAnchor
buttonStack!!.centerYAnchor equalTo buttonContainer!!.centerYAnchor
buttonStack!!.rightAnchor equalTo buttonContainer!!.rightAnchor
}
}
}
}

View File

@ -13,29 +13,14 @@ import net.shadowfacts.cacao.util.RenderHelper
* *
* @author shadowfacts * @author shadowfacts
* @param text The text of this label. * @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( class Label(text: String): View() {
text: String,
val shadow: Boolean = true,
val maxLines: Int = 0,
val wrappingMode: WrappingMode = WrappingMode.WRAP
): View() {
companion object { companion object {
private val textRenderer: TextRenderer private val textRenderer: TextRenderer
get() = MinecraftClient.getInstance().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. * The text of this label. Mutating this field will update the intrinsic content size and trigger a layout.
*/ */
@ -43,20 +28,9 @@ class Label(
set(value) { set(value) {
field = value field = value
updateIntrinsicContentSize() updateIntrinsicContentSize()
window.layout()
} }
private lateinit var lines: List<String>
var textColor = Color.WHITE 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() { override fun wasAdded() {
super.wasAdded() super.wasAdded()
@ -73,38 +47,7 @@ class Label(
} }
override fun drawContent(mouse: Point, delta: Float) { override fun drawContent(mouse: Point, delta: Float) {
if (!this::lines.isInitialized) { textRenderer.draw(text, 0f, 0f, textColor.argb)
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 {
wrapStringToWidthAsList(it, bounds.width)
}
}
if (0 < maxLines && maxLines < lines.size) {
lines = lines.dropLast(lines.size - maxLines)
}
this.lines = lines
}
private fun wrapStringToWidthAsList(string: String, width: Double): List<String> {
if (RenderHelper.disabled) return listOf(string)
return textRenderer.wrapStringToWidthAsList(string, width.toInt())
} }
} }

View File

@ -19,11 +19,7 @@ import java.util.*
* @param distribution The mode by which this stack lays out its children along the axis perpendicular to the * @param distribution The mode by which this stack lays out its children along the axis perpendicular to the
* primary [axis]. * primary [axis].
*/ */
class StackView( class StackView(val axis: Axis, val distribution: Distribution = Distribution.FILL): View() {
val axis: Axis,
val distribution: Distribution = Distribution.FILL,
val spacing: Double = 0.0
): View() {
// the internal mutable, list of arranged subviews // the internal mutable, list of arranged subviews
private val _arrangedSubviews = LinkedList<View>() private val _arrangedSubviews = LinkedList<View>()
@ -79,10 +75,10 @@ class StackView(
val previous = arrangedSubviews.getOrNull(index - 1) val previous = arrangedSubviews.getOrNull(index - 1)
val next = arrangedSubviews.getOrNull(index + 1) val next = arrangedSubviews.getOrNull(index + 1)
if (next != null) { if (next != null) {
arrangedSubviewConnections.add(index, anchor(TRAILING, view) equalTo (anchor(LEADING, next) + spacing)) arrangedSubviewConnections.add(index, anchor(TRAILING, view) equalTo anchor(LEADING, next))
} }
if (previous != null) { if (previous != null) {
arrangedSubviewConnections.add(index - 1, anchor(TRAILING, previous) equalTo (anchor(LEADING, view) - spacing)) arrangedSubviewConnections.add(index - 1, anchor(TRAILING, previous) equalTo anchor(LEADING, view))
} }
} }
} }

View File

@ -92,7 +92,7 @@ class DropdownButton<Value, ContentView: View>(
val dropdownBackground = dropdownWindow.addView(NinePatchView(NinePatchTexture.BUTTON_BG).apply { val dropdownBackground = dropdownWindow.addView(NinePatchView(NinePatchTexture.BUTTON_BG).apply {
zIndex = -1.0 zIndex = -1.0
}) })
val stack = dropdownWindow.addView(StackView(Axis.VERTICAL, StackView.Distribution.FILL)) val stack = dropdownWindow.addView(StackView(Axis.VERTICAL, StackView.Distribution.LEADING))
lateinit var selectedButton: View lateinit var selectedButton: View
val buttons = mutableListOf<Button>() val buttons = mutableListOf<Button>()
val last = allValues.count() - 1 val last = allValues.count() - 1
@ -113,9 +113,7 @@ class DropdownButton<Value, ContentView: View>(
} }
buttons.add(button) buttons.add(button)
dropdownWindow.solver.dsl { dropdownWindow.solver.dsl {
if (button.content.intrinsicContentSize != null) { stack.widthAnchor greaterThanOrEqualTo button.widthAnchor
button.widthAnchor greaterThanOrEqualTo button.content.intrinsicContentSize!!.width + 2 * button.padding
}
} }
} }
dropdownWindow.solver.dsl { dropdownWindow.solver.dsl {
@ -130,6 +128,12 @@ class DropdownButton<Value, ContentView: View>(
dropdownBackground.bottomAnchor equalTo stack.bottomAnchor dropdownBackground.bottomAnchor equalTo stack.bottomAnchor
} }
dropdownWindow.layout() dropdownWindow.layout()
dropdownWindow.solver.dsl {
buttons.forEach {
it.widthAnchor equalTo stack.frame.width
}
}
dropdownWindow.layout()
} }
private fun valueSelected(value: Value) { private fun valueSelected(value: Value) {

View File

@ -191,57 +191,4 @@ class StackViewLayoutTests {
assertEquals(100.0, three.rightAnchor.value) assertEquals(100.0, three.rightAnchor.value)
} }
@Test
fun testVerticalLayoutWithSpacing() {
val stack = window.addView(StackView(Axis.VERTICAL, spacing = 10.0))
val one = stack.addArrangedSubview(View().apply {
intrinsicContentSize = Size(50.0, 50.0)
})
val two = stack.addArrangedSubview(View().apply {
intrinsicContentSize = Size(75.0, 75.0)
})
val three = stack.addArrangedSubview(View().apply {
intrinsicContentSize = Size(50.0, 50.0)
})
window.solver.dsl {
stack.topAnchor equalTo 0
}
window.layout()
assertEquals(0.0, abs(one.topAnchor.value)) // sometimes -0.0, which fails the assertion but is actually ok
assertEquals(50.0, one.bottomAnchor.value)
assertEquals(60.0, two.topAnchor.value)
assertEquals(135.0, two.bottomAnchor.value)
assertEquals(145.0, three.topAnchor.value)
assertEquals(195.0, three.bottomAnchor.value)
assertEquals(195.0, stack.heightAnchor.value)
}
@Test
fun testHorizontalLayoutWithSpacing() {
val stack = window.addView(StackView(Axis.HORIZONTAL, spacing = 10.0))
val one = stack.addArrangedSubview(View().apply {
intrinsicContentSize = Size(50.0, 50.0)
})
val two = stack.addArrangedSubview(View().apply {
intrinsicContentSize = Size(75.0, 75.0)
})
val three = stack.addArrangedSubview(View().apply {
intrinsicContentSize = Size(50.0, 50.0)
})
window.solver.dsl {
stack.leftAnchor equalTo 0
}
window.layout()
assertEquals(0.0, abs(one.leftAnchor.value)) // sometimes -0.0, which fails the assertion but is actually ok
assertEquals(50.0, one.rightAnchor.value)
assertEquals(60.0, two.leftAnchor.value)
assertEquals(135.0, two.rightAnchor.value)
assertEquals(145.0, three.leftAnchor.value)
assertEquals(195.0, three.rightAnchor.value)
assertEquals(195.0, stack.widthAnchor.value)
}
} }

View File

@ -1,6 +1,5 @@
package net.shadowfacts.cacao.view package net.shadowfacts.cacao.view
import net.shadowfacts.cacao.CacaoScreen
import net.shadowfacts.cacao.Window import net.shadowfacts.cacao.Window
import net.shadowfacts.cacao.geometry.Point import net.shadowfacts.cacao.geometry.Point
import net.shadowfacts.cacao.geometry.Rect import net.shadowfacts.cacao.geometry.Rect
@ -15,13 +14,11 @@ import java.util.concurrent.CompletableFuture
*/ */
class ViewClickTests { class ViewClickTests {
lateinit var screen: CacaoScreen
lateinit var window: Window lateinit var window: Window
@BeforeEach @BeforeEach
fun setup() { fun setup() {
screen = CacaoScreen() window = Window()
window = screen.addWindow(Window())
} }
@Test @Test

View File

@ -1,6 +1,5 @@
package net.shadowfacts.cacao.view.button package net.shadowfacts.cacao.view.button
import net.shadowfacts.cacao.CacaoScreen
import net.shadowfacts.cacao.Window import net.shadowfacts.cacao.Window
import net.shadowfacts.cacao.geometry.Point import net.shadowfacts.cacao.geometry.Point
import net.shadowfacts.cacao.geometry.Size import net.shadowfacts.cacao.geometry.Size
@ -18,13 +17,11 @@ import java.util.concurrent.CompletableFuture
*/ */
class ButtonClickTests { class ButtonClickTests {
lateinit var screen: CacaoScreen
lateinit var window: Window lateinit var window: Window
@BeforeEach @BeforeEach
fun setup() { fun setup() {
screen = CacaoScreen() window = Window()
window = screen.addWindow(Window())
} }
@Test @Test

View File

@ -1,6 +1,5 @@
package net.shadowfacts.cacao.view.button package net.shadowfacts.cacao.view.button
import net.shadowfacts.cacao.CacaoScreen
import net.shadowfacts.cacao.Window import net.shadowfacts.cacao.Window
import net.shadowfacts.cacao.geometry.Point import net.shadowfacts.cacao.geometry.Point
import net.shadowfacts.cacao.geometry.Rect import net.shadowfacts.cacao.geometry.Rect
@ -29,13 +28,11 @@ class EnumButtonTests {
ONE, TWO, THREE ONE, TWO, THREE
} }
lateinit var screen: CacaoScreen
lateinit var window: Window lateinit var window: Window
@BeforeEach @BeforeEach
fun setup() { fun setup() {
screen = CacaoScreen() window = Window()
window = screen.addWindow(Window())
} }
@Test @Test