diff --git a/src/main/kotlin/net/shadowfacts/asmr/manager/ManagerBlockEntity.kt b/src/main/kotlin/net/shadowfacts/asmr/manager/ManagerBlockEntity.kt index fdd5491..f8bf87f 100644 --- a/src/main/kotlin/net/shadowfacts/asmr/manager/ManagerBlockEntity.kt +++ b/src/main/kotlin/net/shadowfacts/asmr/manager/ManagerBlockEntity.kt @@ -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) } diff --git a/src/main/kotlin/net/shadowfacts/asmr/ui/ProgramBlockParamView.kt b/src/main/kotlin/net/shadowfacts/asmr/ui/ProgramBlockParamView.kt new file mode 100644 index 0000000..a676d9f --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/asmr/ui/ProgramBlockParamView.kt @@ -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 + } + +} \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/asmr/ui/ProgramBlockView.kt b/src/main/kotlin/net/shadowfacts/asmr/ui/ProgramBlockView.kt index de1887d..8da430a 100644 --- a/src/main/kotlin/net/shadowfacts/asmr/ui/ProgramBlockView.kt +++ b/src/main/kotlin/net/shadowfacts/asmr/ui/ProgramBlockView.kt @@ -33,8 +33,8 @@ class ProgramBlockView(val block: ProgramBlock): StackView(Axis.VERTICAL, Distri var incomingExecution: View? = null var outgoingExeuction: View? = null - val inputViews = mutableMapOf, View>() - val outputViews = mutableMapOf, View>() + val inputViews = mutableMapOf, ProgramBlockParamView>() + val outputViews = mutableMapOf, 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 diff --git a/src/main/kotlin/net/shadowfacts/asmr/ui/ProgramCanvasView.kt b/src/main/kotlin/net/shadowfacts/asmr/ui/ProgramCanvasView.kt index 6407e28..8ad9132 100644 --- a/src/main/kotlin/net/shadowfacts/asmr/ui/ProgramCanvasView.kt +++ b/src/main/kotlin/net/shadowfacts/asmr/ui/ProgramCanvasView.kt @@ -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>() private val parameterLinks = mutableMapOf>() + 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 inputViewClicked(block: ProgramBlock, input: ProgramBlockInput, 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 + 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 outputViewClicked(block: ProgramBlock, output: ProgramBlockOutput, 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 + 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(val input: ProgramBlockInput, view: ProgramBlockParamView, startPoint: Point): PartialLink(view, startPoint) + class ParamOutputPartialLink(val output: ProgramBlockOutput, view: ProgramBlockParamView, startPoint: Point): PartialLink(view, startPoint) +// class ExecutionStartPartialLink(val start: ExecutableBlock): PartialLink +// class ExecutionEndPartialLink(val end: ExecutableBlock): PartialLink + } \ No newline at end of file