Add program parameter linking UI

This commit is contained in:
Shadowfacts 2019-08-11 18:44:22 -04:00
parent 971c41ed2f
commit 233a83f368
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
4 changed files with 153 additions and 12 deletions

View File

@ -25,12 +25,12 @@ class ManagerBlockEntity: BlockEntity(ASMR.managerEntityType) {
it.position = Point(0.0, 120.0)
}
val divide = program.addBlock(BinaryOperatorBlock(INT, DIVIDE)) {
it.left.link(from = left.output)
it.right.link(from = right.output)
// it.left.link(from = left.output)
// it.right.link(from = right.output)
it.position = Point(120.0, 0.0)
}
val print = program.addBlock(PrintBlock(INT)) {
it.input.link(from = divide.output)
// it.input.link(from = divide.output)
it.position = Point(240.0, 0.0)
}

View File

@ -0,0 +1,53 @@
package net.shadowfacts.asmr.ui
import net.minecraft.util.Identifier
import net.shadowfacts.asmr.ASMR
import net.shadowfacts.asmr.program.ProgramBlockInput
import net.shadowfacts.asmr.program.ProgramBlockOutput
import net.shadowfacts.cacao.util.texture.Texture
import net.shadowfacts.cacao.view.TextureView
import net.shadowfacts.cacao.view.View
import net.shadowfacts.kiwidsl.dsl
import java.lang.RuntimeException
/**
* @author shadowfacts
*/
class ProgramBlockParamView(val input: ProgramBlockInput<*>?, val output: ProgramBlockOutput<*>?): View() {
companion object {
val emptyConnection = Texture(Identifier(ASMR.modid, "textures/gui/programmer/program_connections.png"), u = 0, v = 0)
val parameterConnection = Texture(Identifier(ASMR.modid, "textures/gui/programmer/program_connections.png"), u = 14, v = 0)
}
lateinit var textureView: TextureView
constructor(input: ProgramBlockInput<*>): this(input, null)
constructor(output: ProgramBlockOutput<*>): this(null, output)
init {
if (input == null && output == null) {
throw RuntimeException("One of input or output must be non-null")
}
}
override fun wasAdded() {
super.wasAdded()
textureView = addSubview(TextureView(emptyConnection))
updateTexture()
solver.dsl {
textureView.leftAnchor equalTo leftAnchor
textureView.rightAnchor equalTo rightAnchor
textureView.topAnchor equalTo topAnchor
textureView.bottomAnchor equalTo bottomAnchor
}
}
fun updateTexture() {
val active = if (input != null) input.source != null else output!!.destinations.isNotEmpty()
textureView.texture = if (active) parameterConnection else emptyConnection
}
}

View File

@ -33,8 +33,8 @@ class ProgramBlockView(val block: ProgramBlock): StackView(Axis.VERTICAL, Distri
var incomingExecution: View? = null
var outgoingExeuction: View? = null
val inputViews = mutableMapOf<ProgramBlockInput<*>, View>()
val outputViews = mutableMapOf<ProgramBlockOutput<*>, View>()
val inputViews = mutableMapOf<ProgramBlockInput<*>, ProgramBlockParamView>()
val outputViews = mutableMapOf<ProgramBlockOutput<*>, ProgramBlockParamView>()
var xConstraint: Constraint? = null
var yConstraint: Constraint? = null
@ -73,8 +73,9 @@ class ProgramBlockView(val block: ProgramBlock): StackView(Axis.VERTICAL, Distri
val hStack = addArrangedSubview(StackView(Axis.HORIZONTAL, Distribution.CENTER))
block.inputs.getOrNull(i)?.let { input ->
val inputTexture = if (input.source == null) emptyConnection else parameterConnection
val inputView = hStack.addArrangedSubview(TextureView(inputTexture))
// val inputTexture = if (input.source == null) emptyConnection else parameterConnection
// val inputView = hStack.addArrangedSubview(TextureView(inputTexture))
val inputView = hStack.addArrangedSubview(ProgramBlockParamView(input))
inputViews[input] = inputView
val inputLabel = hStack.addArrangedSubview(Label(input.translateName()))
solver.dsl {
@ -92,8 +93,9 @@ class ProgramBlockView(val block: ProgramBlock): StackView(Axis.VERTICAL, Distri
block.outputs.getOrNull(i)?.let { output ->
val outputLabel = hStack.addArrangedSubview(Label(output.translateName(), textAlignment = Label.TextAlignment.RIGHT))
val outputTexture = if (output.destinations.isEmpty()) emptyConnection else parameterConnection
val outputView = hStack.addArrangedSubview(TextureView(outputTexture))
// val outputTexture = if (output.destinations.isEmpty()) emptyConnection else parameterConnection
// val outputView = hStack.addArrangedSubview(TextureView(outputTexture))
val outputView = hStack.addArrangedSubview(ProgramBlockParamView(output))
outputViews[output] = outputView
solver.dsl {
hStack.heightAnchor equalTo outputLabel.heightAnchor

View File

@ -1,10 +1,9 @@
package net.shadowfacts.asmr.ui
import net.shadowfacts.asmr.program.ExecutableBlock
import net.shadowfacts.asmr.program.Program
import net.shadowfacts.asmr.program.ProgramBlock
import net.shadowfacts.asmr.program.*
import net.shadowfacts.cacao.geometry.Point
import net.shadowfacts.cacao.util.Color
import net.shadowfacts.cacao.util.MouseButton
import net.shadowfacts.cacao.util.RenderHelper
import net.shadowfacts.cacao.view.View
@ -19,6 +18,8 @@ class ProgramCanvasView(val program: Program): View() {
private val executionFlowStartLinks = mutableMapOf<ExecutableBlock, Pair<Point, Point>>()
private val parameterLinks = mutableMapOf<ProgramBlock, Pair<Point, Point>>()
private var currentPartialLink: PartialLink? = null
override fun wasAdded() {
super.wasAdded()
@ -76,6 +77,91 @@ class ProgramCanvasView(val program: Program): View() {
for ((start, end) in parameterLinks.values) {
RenderHelper.drawLine(start, end, zIndex, 5f, Color.RED)
}
val currentPartialLink = currentPartialLink
when (currentPartialLink) {
is ParamInputPartialLink<*>, is ParamOutputPartialLink<*> -> {
RenderHelper.drawLine(currentPartialLink.startPoint, mouse, zIndex, 5f, Color.RED)
}
}
}
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
for ((block, blockView) in blocks) {
if (convert(point, to = blockView) !in blockView.bounds) continue
for ((input, inputView) in blockView.inputViews) {
if (convert(point, to = inputView) in inputView.bounds) {
return inputViewClicked(block, input, inputView)
}
}
for ((output, outputView) in blockView.outputViews) {
if (convert(point, to = outputView) in outputView.bounds) {
return outputViewClicked(block, output, outputView)
}
}
}
return super.mouseClicked(point, mouseButton)
}
private fun <Type: Any> inputViewClicked(block: ProgramBlock, input: ProgramBlockInput<Type>, inputView: ProgramBlockParamView): Boolean {
val currentPartialLink = currentPartialLink
val result = if (currentPartialLink == null) {
// no partial link in progress, begin a new one
this.currentPartialLink = ParamInputPartialLink(input, inputView, inputView.convert(inputView.bounds.center, to = this))
true
} else if (currentPartialLink is ParamOutputPartialLink<*> && currentPartialLink.output.type == input.type) {
// current link in progress originated with an output param of same type as clicked input, so complete it
// we can ignore the unchecked cast because we checked it ourselves above by comparing ProgramTypes, but the compiler doesn't know that
@Suppress("NAME_SHADOWING", "UNCHECKED_CAST")
val currentPartialLink = currentPartialLink as ParamOutputPartialLink<Type>
currentPartialLink.output.link(to = input)
currentPartialLink.view.updateTexture()
inputView.updateTexture()
this.currentPartialLink = null
true
} else {
// otherwise, click always fails
false
}
if (result) {
updateBlockLinks()
}
return result
}
private fun <Type: Any> outputViewClicked(block: ProgramBlock, output: ProgramBlockOutput<Type>, outputView: ProgramBlockParamView): Boolean {
val currentPartialLink = currentPartialLink
val result = if (currentPartialLink == null) {
this.currentPartialLink = ParamOutputPartialLink(output, outputView, outputView.convert(outputView.bounds.center, to = this))
true
} else if (currentPartialLink is ParamInputPartialLink<*> && currentPartialLink.input.type == output.type) {
// current link in progress originated with an input param of same type as clicked output, so complete it
// we can ignore the unchecked cast because we checked it ourselves above by comparing ProgramTypes, but the compiler doesn't know that
@Suppress("NAME_SHADOWING", "UNCHECKED_CAST")
val currentPartialLink = currentPartialLink as ParamInputPartialLink<Type>
currentPartialLink.input.link(from = output)
currentPartialLink.view.updateTexture()
outputView.updateTexture()
this.currentPartialLink = null
true
} else {
// otherwise, click always fails
false
}
if (result) {
updateBlockLinks()
}
return result
}
open class PartialLink(val view: ProgramBlockParamView, val startPoint: Point)
class ParamInputPartialLink<Type: Any>(val input: ProgramBlockInput<Type>, view: ProgramBlockParamView, startPoint: Point): PartialLink(view, startPoint)
class ParamOutputPartialLink<Type: Any>(val output: ProgramBlockOutput<Type>, view: ProgramBlockParamView, startPoint: Point): PartialLink(view, startPoint)
// class ExecutionStartPartialLink<Type: Any>(val start: ExecutableBlock): PartialLink
// class ExecutionEndPartialLink<Type: Any>(val end: ExecutableBlock): PartialLink
}