ASMR/src/main/kotlin/net/shadowfacts/asmr/ui/block/ProgramBlockView.kt

162 lines
5.6 KiB
Kotlin

package net.shadowfacts.asmr.ui.block
import net.minecraft.util.Identifier
import net.shadowfacts.asmr.ASMR
import net.shadowfacts.asmr.program.execution.ExecutableBlock
import net.shadowfacts.asmr.program.ProgramBlock
import net.shadowfacts.asmr.program.ProgramBlockInput
import net.shadowfacts.asmr.program.ProgramBlockOutput
import net.shadowfacts.asmr.program.execution.OutgoingExecutionFlow
import net.shadowfacts.cacao.geometry.Axis
import net.shadowfacts.cacao.geometry.Point
import net.shadowfacts.cacao.util.Color
import net.shadowfacts.cacao.util.MouseButton
import net.shadowfacts.cacao.util.texture.Texture
import net.shadowfacts.cacao.view.Label
import net.shadowfacts.cacao.view.StackView
import net.shadowfacts.cacao.view.View
import net.shadowfacts.kiwidsl.dsl
import no.birkett.kiwi.Constraint
import kotlin.math.max
/**
* @author shadowfacts
*/
class ProgramBlockView(val block: ProgramBlock): StackView(Axis.VERTICAL, Distribution.CENTER, spacing = 4.0) {
companion object {
val emptyConnection = Texture(Identifier(ASMR.modid, "textures/gui/programmer/program_connections.png"), u = 0, v = 0)
val executionConnection = Texture(Identifier(ASMR.modid, "textures/gui/programmer/program_connections.png"), u = 7, v = 0)
val parameterConnection = Texture(Identifier(ASMR.modid, "textures/gui/programmer/program_connections.png"), u = 14, v = 0)
}
var incomingView: ProgramBlockExecutionView? = null
val outgoingViews = mutableMapOf<OutgoingExecutionFlow, ProgramBlockExecutionView>()
val inputViews = mutableMapOf<ProgramBlockInput<*>, ProgramBlockParamView>()
val outputViews = mutableMapOf<ProgramBlockOutput<*>, ProgramBlockParamView>()
var xConstraint: Constraint? = null
var yConstraint: Constraint? = null
var didCreateWidthConstraint = false
override fun wasAdded() {
super.wasAdded()
respondsToDragging = true
backgroundColor = Color.BLACK
zIndex = 10.0
val titleStack = addArrangedSubview(StackView(Axis.HORIZONTAL, Distribution.CENTER))
if (block is ExecutableBlock) {
incomingView = titleStack.addArrangedSubview(ProgramBlockExecutionView(block.incoming))
}
val title = titleStack.addArrangedSubview(Label(block.translateName(), wrappingMode = Label.WrappingMode.NO_WRAP))
solver.dsl {
// we have to constrain the titleStack height, because it's distribution is set to CENTER, so it doesn't do it itself
titleStack.heightAnchor equalTo title.heightAnchor
if (block is ExecutableBlock) {
incomingView!!.widthAnchor equalTo incomingView!!.heightAnchor
incomingView!!.widthAnchor equalTo 7
}
}
if (block is ExecutableBlock) {
for (outgoing in block.outgoing) {
val hStack = addArrangedSubview(StackView(Axis.HORIZONTAL, Distribution.CENTER))
val outgoingLabel = hStack.addArrangedSubview(Label(outgoing.translateName(), wrappingMode = Label.WrappingMode.NO_WRAP, textAlignment = Label.TextAlignment.RIGHT))
val outgoingView = hStack.addArrangedSubview(ProgramBlockExecutionView(outgoing))
outgoingViews[outgoing] = outgoingView
solver.dsl {
hStack.heightAnchor equalTo outgoingLabel.heightAnchor
outgoingView.widthAnchor equalTo outgoingView.heightAnchor
outgoingView.widthAnchor equalTo 7
}
}
}
for (i in 0 until max(block.inputs.size, block.outputs.size)) {
val hStack = addArrangedSubview(StackView(Axis.HORIZONTAL, Distribution.CENTER))
block.inputs.getOrNull(i)?.let { input ->
val inputView = hStack.addArrangedSubview(ProgramBlockParamView(input))
inputViews[input] = inputView
val inputLabel = hStack.addArrangedSubview(Label(input.translateName(), wrappingMode = Label.WrappingMode.NO_WRAP))
solver.dsl {
hStack.heightAnchor equalTo inputLabel.heightAnchor
inputView.widthAnchor equalTo inputView.heightAnchor
inputView.widthAnchor equalTo 7
}
}
val spacingView = hStack.addArrangedSubview(View())
solver.dsl {
spacingView.widthAnchor equalTo 8
}
block.outputs.getOrNull(i)?.let { output ->
val outputLabel = hStack.addArrangedSubview(Label(output.translateName(), wrappingMode = Label.WrappingMode.NO_WRAP, textAlignment = Label.TextAlignment.RIGHT))
val outputView = hStack.addArrangedSubview(ProgramBlockParamView(output))
outputViews[output] = outputView
solver.dsl {
hStack.heightAnchor equalTo outputLabel.heightAnchor
outputView.widthAnchor equalTo outputView.heightAnchor
outputView.widthAnchor equalTo 7
}
}
}
solver.dsl {
for (view in arrangedSubviews.drop(1)) {
widthAnchor.equalTo(view.widthAnchor, strength = STRONG)
}
}
updateDraggingConstraints()
}
override fun didLayout() {
super.didLayout()
// we want to constrain this view's width to be the same as the widest view in the vertical stack,
// but we don't know what view that is until after all subviews have been laid-out
if (!didCreateWidthConstraint) {
didCreateWidthConstraint = true
arrangedSubviews.maxBy { it.bounds.width }?.let { widestSubview ->
solver.dsl {
widthAnchor equalTo widestSubview.bounds.width
}
window!!.layout()
}
}
}
override fun mouseDragged(startPoint: Point, delta: Point, mouseButton: MouseButton): Boolean {
block.position += delta
updateDraggingConstraints()
window!!.layout()
return true
}
private fun updateDraggingConstraints() {
if (xConstraint != null) solver.removeConstraint(xConstraint)
if (yConstraint != null) solver.removeConstraint(yConstraint)
solver.dsl {
xConstraint = (leftAnchor equalTo superview!!.leftAnchor + block.position.x)
yConstraint = (topAnchor equalTo superview!!.topAnchor + block.position.y)
}
}
override fun drawContent(mouse: Point, delta: Float) {
super.drawContent(mouse, delta)
}
}