Link program block parameters bidirectionally and add parameter link UI

This commit is contained in:
Shadowfacts 2019-08-10 00:03:25 -04:00
parent a61a67cbf3
commit 6045800d11
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
11 changed files with 146 additions and 42 deletions

View File

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

View File

@ -5,13 +5,8 @@ package net.shadowfacts.asmr.program
*/ */
abstract class ExecutableBlock: ProgramBlock() { abstract class ExecutableBlock: ProgramBlock() {
open val executionFlow = ExecutionFlow() open val executionFlow = ExecutionFlow(this)
abstract fun execute() abstract fun execute()
fun linkNext(next: ExecutableBlock) {
executionFlow.next = next
next.executionFlow.prev = this
}
} }

View File

@ -3,9 +3,14 @@ package net.shadowfacts.asmr.program
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
open class ExecutionFlow { open class ExecutionFlow(val block: ExecutableBlock) {
open var next: ExecutableBlock? = null open var next: ExecutableBlock? = null
open var prev: ExecutableBlock? = null open var prev: ExecutableBlock? = null
fun link(next: ExecutableBlock) {
this.next = next
next.executionFlow.prev = block
}
} }

View File

@ -11,7 +11,7 @@ class Program {
var startBlock = StartBlock().apply { var startBlock = StartBlock().apply {
position = Point.ORIGIN position = Point.ORIGIN
} }
val blocks = mutableListOf<ProgramBlock>() val blocks = mutableListOf<ProgramBlock>(startBlock)
fun execute() { fun execute() {
var currentBlock: ExecutableBlock? = startBlock.executionFlow.next var currentBlock: ExecutableBlock? = startBlock.executionFlow.next

View File

@ -9,10 +9,21 @@ class ProgramBlockInput<Type: Any>(
val identifier: Identifier, val identifier: Identifier,
val type: ProgramType<Type>, val type: ProgramType<Type>,
val block: ProgramBlock, val block: ProgramBlock,
var source: ProgramBlockOutput<Type>? = null source: ProgramBlockOutput<Type>? = null
) { ) {
var source: ProgramBlockOutput<Type>? = source
internal set
val value: Type? val value: Type?
get() = source?.value get() = source?.value
fun link(from: ProgramBlockOutput<Type>) {
from.link(to = this)
}
fun unlink() {
source?.unlink(to = this)
}
} }

View File

@ -13,4 +13,17 @@ class ProgramBlockOutput<Type: Any>(
lateinit var value: Type lateinit var value: Type
private val _destinations = mutableListOf<ProgramBlockInput<Type>>()
val destinations: List<ProgramBlockInput<Type>> = _destinations
fun link(to: ProgramBlockInput<Type>) {
to.source = this
_destinations.add(to)
}
fun unlink(to: ProgramBlockInput<Type>) {
to.source = null
_destinations.remove(to)
}
} }

View File

@ -4,6 +4,8 @@ import net.minecraft.util.Identifier
import net.shadowfacts.asmr.ASMR import net.shadowfacts.asmr.ASMR
import net.shadowfacts.asmr.program.ExecutableBlock import net.shadowfacts.asmr.program.ExecutableBlock
import net.shadowfacts.asmr.program.ProgramBlock import net.shadowfacts.asmr.program.ProgramBlock
import net.shadowfacts.asmr.program.ProgramBlockInput
import net.shadowfacts.asmr.program.ProgramBlockOutput
import net.shadowfacts.cacao.geometry.Axis import net.shadowfacts.cacao.geometry.Axis
import net.shadowfacts.cacao.geometry.Point import net.shadowfacts.cacao.geometry.Point
import net.shadowfacts.cacao.util.Color import net.shadowfacts.cacao.util.Color
@ -15,6 +17,7 @@ import net.shadowfacts.cacao.view.TextureView
import net.shadowfacts.cacao.view.View import net.shadowfacts.cacao.view.View
import net.shadowfacts.kiwidsl.dsl import net.shadowfacts.kiwidsl.dsl
import no.birkett.kiwi.Constraint import no.birkett.kiwi.Constraint
import kotlin.math.max
/** /**
* @author shadowfacts * @author shadowfacts
@ -22,13 +25,17 @@ import no.birkett.kiwi.Constraint
class ProgramBlockView(val block: ProgramBlock): StackView(Axis.VERTICAL, Distribution.CENTER, spacing = 4.0) { class ProgramBlockView(val block: ProgramBlock): StackView(Axis.VERTICAL, Distribution.CENTER, spacing = 4.0) {
companion object { companion object {
val executionFlowInactiveTexture = Texture(Identifier(ASMR.modid, "textures/gui/programmer/execution_flow.png"), u = 0, v = 0) val emptyConnection = Texture(Identifier(ASMR.modid, "textures/gui/programmer/program_connections.png"), u = 0, v = 0)
val executionFlowActiveTexture = Texture(Identifier(ASMR.modid, "textures/gui/programmer/execution_flow.png"), u = 7, 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 incomingExecution: View? = null var incomingExecution: View? = null
var outgoingExeuction: View? = null var outgoingExeuction: View? = null
val inputViews = mutableMapOf<ProgramBlockInput<*>, View>()
val outputViews = mutableMapOf<ProgramBlockOutput<*>, View>()
var xConstraint: Constraint? = null var xConstraint: Constraint? = null
var yConstraint: Constraint? = null var yConstraint: Constraint? = null
@ -43,27 +50,12 @@ class ProgramBlockView(val block: ProgramBlock): StackView(Axis.VERTICAL, Distri
val titleStack = addArrangedSubview(StackView(Axis.HORIZONTAL, Distribution.CENTER)) val titleStack = addArrangedSubview(StackView(Axis.HORIZONTAL, Distribution.CENTER))
val title = titleStack.addArrangedSubview(Label(block.javaClass.simpleName)) val title = titleStack.addArrangedSubview(Label(block.javaClass.simpleName))
if (block is ExecutableBlock) { if (block is ExecutableBlock) {
val incomingTexture = if (block.executionFlow.prev == null) executionFlowInactiveTexture else executionFlowActiveTexture val incomingTexture = if (block.executionFlow.prev == null) emptyConnection else executionConnection
incomingExecution = titleStack.addArrangedSubview(TextureView(incomingTexture), index = 0) incomingExecution = titleStack.addArrangedSubview(TextureView(incomingTexture), index = 0)
val outgoingTexture = if (block.executionFlow.next == null) executionFlowInactiveTexture else executionFlowActiveTexture val outgoingTexture = if (block.executionFlow.next == null) emptyConnection else executionConnection
outgoingExeuction = titleStack.addArrangedSubview(TextureView(outgoingTexture)) outgoingExeuction = titleStack.addArrangedSubview(TextureView(outgoingTexture))
} }
val pairs = block.inputs.zip(block.outputs)
val remainingInputs = if (block.inputs.size > block.outputs.size) block.inputs.drop(block.outputs.size) else listOf()
val remainingOutputs = if (block.outputs.size > block.inputs.size) block.outputs.drop(block.inputs.size) else listOf()
for ((input, output) in pairs) {
val hStack = addArrangedSubview(StackView(Axis.HORIZONTAL, spacing = 8.0))
hStack.addArrangedSubview(Label(input.identifier.toString()))
hStack.addArrangedSubview(Label(output.identifier.toString()))
}
for (input in remainingInputs) {
addArrangedSubview(Label(input.identifier.toString()))
}
for (output in remainingOutputs) {
addArrangedSubview(Label(output.identifier.toString(), textAlignment = Label.TextAlignment.RIGHT))
}
solver.dsl { solver.dsl {
// we have to constrain the titleStack height, because it's distribution is set to CENTER, so it doesn't do it itself // 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 titleStack.heightAnchor equalTo title.heightAnchor
@ -76,11 +68,82 @@ class ProgramBlockView(val block: ProgramBlock): StackView(Axis.VERTICAL, Distri
outgoingExeuction!!.widthAnchor equalTo 7 outgoingExeuction!!.widthAnchor equalTo 7
} }
// for (paramView in inputViews.values) {
// paramView.widthAnchor equalTo paramView.heightAnchor
// paramView.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(TextureView(if (input.source == null) emptyConnection else parameterConnection))
inputViews[input] = inputView
val inputLabel = hStack.addArrangedSubview(Label(input.identifier.toString()))
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.identifier.toString(), textAlignment = Label.TextAlignment.RIGHT))
val outputView = hStack.addArrangedSubview(TextureView(if (output.destinations.isEmpty()) emptyConnection else parameterConnection))
outputViews[output] = outputView
solver.dsl {
hStack.heightAnchor equalTo outputLabel.heightAnchor
outputView.widthAnchor equalTo outputView.heightAnchor
outputView.widthAnchor equalTo 7
}
}
}
// val pairs = block.inputs.zip(block.outputs)
// val remainingInputs = if (block.inputs.size > block.outputs.size) block.inputs.drop(block.outputs.size) else listOf()
// val remainingOutputs = if (block.outputs.size > block.inputs.size) block.outputs.drop(block.inputs.size) else listOf()
// for ((input, output) in pairs) {
// val hStack = addArrangedSubview(StackView(Axis.HORIZONTAL, Distribution.CENTER))
// val inputView = hStack.addArrangedSubview(TextureView(if (input.source == null) emptyConnection else parameterConnection))
// inputViews[input] = inputView
// val inputLabel = hStack.addArrangedSubview(Label(input.identifier.toString()))
// val spacingView = hStack.addArrangedSubview(View())
// hStack.addArrangedSubview(Label(output.identifier.toString()))
//// outputViews[output] = hStack.addArrangedSubview(TextureView(if (output.destinations.isEmpty) emptyConnection else parameterConnection))
//
// solver.dsl {
// hStack.heightAnchor equalTo inputLabel.heightAnchor
//
// inputView.widthAnchor equalTo inputView.heightAnchor
// inputView.widthAnchor equalTo 7
//
// spacingView.widthAnchor equalTo 8
// }
// }
// for (input in remainingInputs) {
// addArrangedSubview(Label(input.identifier.toString()))
// }
// for (output in remainingOutputs) {
// addArrangedSubview(Label(output.identifier.toString(), textAlignment = Label.TextAlignment.RIGHT))
// }
solver.dsl {
for (view in arrangedSubviews.drop(1)) { for (view in arrangedSubviews.drop(1)) {
widthAnchor.equalTo(view.widthAnchor, strength = STRONG) widthAnchor.equalTo(view.widthAnchor, strength = STRONG)
} }
} }
updateDraggingConstraints() updateDraggingConstraints()
} }

View File

@ -39,19 +39,34 @@ class ProgramCanvasView(val program: Program): View() {
val start = outgoing.convert(outgoing.bounds.center, to = this) val start = outgoing.convert(outgoing.bounds.center, to = this)
val end = incoming.convert(outgoing.bounds.center, to = this) val end = incoming.convert(outgoing.bounds.center, to = this)
RenderHelper.drawLine(start, end, zIndex, 5f, Color(0x00ff00)) RenderHelper.drawLine(start, end, zIndex, 5f, Color.GREEN)
}
}
for (input in block.inputs) {
if (input.source == null) continue
val source = input.source!!
blocks[source.block]?.let { sourceView ->
view.inputViews[input]?.let { inputView ->
sourceView.outputViews[source]?.let { outputView ->
val start = inputView.convert(inputView.bounds.center, to = this)
val end = outputView.convert(outputView.bounds.center, to = this)
RenderHelper.drawLine(start, end, zIndex, 5f, Color.RED)
}
} }
} }
// for (input in block.inputs) {
// if (input.source == null) continue
// val start = block.position // val start = block.position
// val source = input.source!!
// val sourcePosition = source.block.position // val sourcePosition = source.block.position
// val end = Point(sourcePosition.x + blocks[source.block]!!.bounds.width, sourcePosition.y) // val end = Point(sourcePosition.x + blocks[source.block]!!.bounds.width, sourcePosition.y)
// val inputView = view.inputViews[input]
// val outputView =
// //
// RenderHelper.drawLine(start, end, zIndex, 5f, Color.WHITE) // RenderHelper.drawLine(start, end, zIndex, 5f, Color.RED)
// } }
} }
} }

View File

@ -26,7 +26,9 @@ data class Color(val red: Int, val green: Int, val blue: Int, val alpha: Int = 2
val CLEAR = Color(0, alpha = 0) val CLEAR = Color(0, alpha = 0)
val WHITE = Color(0xffffff) val WHITE = Color(0xffffff)
val BLACK = Color(0) val BLACK = Color(0)
val RED = Color(0xff0000)
val GREEN = Color(0x00ff00) val GREEN = Color(0x00ff00)
val BLUE = Color(0x0000ff)
} }
} }

View File

@ -16,11 +16,11 @@ class ProgramTests {
val one = program.addBlock(ConstantBlock(ProgramType.INT, 3)) val one = program.addBlock(ConstantBlock(ProgramType.INT, 3))
val two = program.addBlock(ConstantBlock(ProgramType.INT, 7)) val two = program.addBlock(ConstantBlock(ProgramType.INT, 7))
val multiply = program.addBlock(BinaryOperatorBlock(ProgramType.INT, BinaryOperatorBlock.Operation.MULTIPLY)) { val multiply = program.addBlock(BinaryOperatorBlock(ProgramType.INT, BinaryOperatorBlock.Operation.MULTIPLY)) {
it.left.source = one.output it.left.link(from = one.output)
it.right.source = two.output it.right.link(from = two.output)
} }
program.startBlock.linkNext(multiply) program.startBlock.executionFlow.link(multiply)
program.execute() program.execute()