diff --git a/src/main/kotlin/net/shadowfacts/asmr/program/ProgramBlockOutput.kt b/src/main/kotlin/net/shadowfacts/asmr/program/ProgramBlockOutput.kt index 19029f3..25c31f6 100644 --- a/src/main/kotlin/net/shadowfacts/asmr/program/ProgramBlockOutput.kt +++ b/src/main/kotlin/net/shadowfacts/asmr/program/ProgramBlockOutput.kt @@ -27,6 +27,13 @@ class ProgramBlockOutput( _destinations.remove(to) } + fun unlinkAll() { + for (dest in destinations) { + dest.source = null + } + _destinations.clear() + } + fun translateName(): String { return Language.getInstance().translate("programblock.param.${identifier.namespace}.${identifier.path}") } diff --git a/src/main/kotlin/net/shadowfacts/asmr/ui/ProgramCanvasView.kt b/src/main/kotlin/net/shadowfacts/asmr/ui/ProgramCanvasView.kt index 75186c8..0b0880f 100644 --- a/src/main/kotlin/net/shadowfacts/asmr/ui/ProgramCanvasView.kt +++ b/src/main/kotlin/net/shadowfacts/asmr/ui/ProgramCanvasView.kt @@ -2,6 +2,10 @@ package net.shadowfacts.asmr.ui import net.shadowfacts.asmr.program.* import net.shadowfacts.asmr.program.execution.ExecutableBlock +import net.shadowfacts.asmr.program.execution.IncomingExecutionFlow +import net.shadowfacts.asmr.program.execution.OutgoingExecutionFlow +import net.shadowfacts.asmr.ui.block.ProgramBlockConnectionIndicatorView +import net.shadowfacts.asmr.ui.block.ProgramBlockExecutionView import net.shadowfacts.asmr.ui.block.ProgramBlockParamView import net.shadowfacts.asmr.ui.block.ProgramBlockView import net.shadowfacts.cacao.geometry.Point @@ -40,12 +44,16 @@ class ProgramCanvasView(val program: Program): View() { for (outgoing in startBlock.outgoing) { val outgoingView = startView.outgoingViews[outgoing] ?: continue val start = outgoingView.convert(outgoingView.bounds.center, to = this) - val incoming = outgoing.destination ?: continue - val incomingBlockView = blocks[incoming.block] ?: continue - val incomingView = incomingBlockView.incomingView!! - val end = incomingView.convert(incomingView.bounds.center, to = this) + val incoming = outgoing.destination + if (incoming != null) { + val incomingBlockView = blocks[incoming.block] ?: continue + val incomingView = incomingBlockView.incomingView!! + val end = incomingView.convert(incomingView.bounds.center, to = this) - executionFlowStartLinks[startBlock] = start to end + executionFlowStartLinks[startBlock] = start to end + } else { + executionFlowStartLinks.remove(startBlock) + } } } @@ -83,25 +91,40 @@ class ProgramCanvasView(val program: Program): View() { } val currentPartialLink = currentPartialLink - when (currentPartialLink) { - is ParamInputPartialLink<*>, is ParamOutputPartialLink<*> -> { - RenderHelper.drawLine(currentPartialLink.startPoint, mouse, zIndex, 5f, Color.RED) - } + if (currentPartialLink != null) { + val color = if (currentPartialLink is ParamInputPartialLink<*> || currentPartialLink is ParamOutputPartialLink<*>) Color.RED else Color.GREEN + RenderHelper.drawLine(currentPartialLink.startPoint, mouse, zIndex, 5f, color) } } override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean { + if (mouseButton == MouseButton.RIGHT && currentPartialLink != null) { + this.currentPartialLink = null + return true + } + 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) + return inputViewClicked(block, input, inputView, mouseButton) } } for ((output, outputView) in blockView.outputViews) { if (convert(point, to = outputView) in outputView.bounds) { - return outputViewClicked(block, output, outputView) + return outputViewClicked(block, output, outputView, mouseButton) + } + } + + if (block is ExecutableBlock) { + if (convert(point, to = blockView.incomingView!!) in blockView.incomingView!!.bounds) { + return incomingExecutionViewClicked(block, block.incoming, blockView.incomingView!!, mouseButton) + } + for ((outgoing, outgoingView) in blockView.outgoingViews) { + if (convert(point, to = outgoingView) in outgoingView.bounds) { + return outgoingExecutionViewClicked(block, outgoing, outgoingView, mouseButton) + } } } } @@ -109,12 +132,28 @@ class ProgramCanvasView(val program: Program): View() { return super.mouseClicked(point, mouseButton) } - private fun inputViewClicked(block: ProgramBlock, input: ProgramBlockInput, inputView: ProgramBlockParamView): Boolean { + private fun inputViewClicked(block: ProgramBlock, input: ProgramBlockInput, inputView: ProgramBlockParamView, mouseButton: MouseButton): 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 + return if (currentPartialLink == null) { + // no partial link in progress + when (mouseButton) { + MouseButton.LEFT -> { + // begin a new one + this.currentPartialLink = ParamInputPartialLink(input, inputView, inputView.convert(inputView.bounds.center, to = this)) + true + } + MouseButton.RIGHT -> { + // clear clicked connection + val source = input.source ?: return false + val outputView = blocks[source.block]?.outputViews?.get(source) ?: return false + input.unlink() + inputView.updateTexture() + outputView.updateTexture() + updateBlockLinks() + true + } + else -> false + } } 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 @@ -123,24 +162,36 @@ class ProgramCanvasView(val program: Program): View() { currentPartialLink.output.link(to = input) currentPartialLink.view.updateTexture() inputView.updateTexture() + updateBlockLinks() 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 { + private fun outputViewClicked(block: ProgramBlock, output: ProgramBlockOutput, outputView: ProgramBlockParamView, mouseButton: MouseButton): Boolean { val currentPartialLink = currentPartialLink - val result = if (currentPartialLink == null) { - this.currentPartialLink = ParamOutputPartialLink(output, outputView, outputView.convert(outputView.bounds.center, to = this)) - true + return if (currentPartialLink == null) { + // no partial link in progress + when (mouseButton) { + MouseButton.LEFT -> { + // begin a new one + this.currentPartialLink = ParamOutputPartialLink(output, outputView, outputView.convert(outputView.bounds.center, to = this)) + true + } + MouseButton.RIGHT -> { + // clear clicked connection + val inputViews = output.destinations.mapNotNull { blocks[it.block]?.inputViews?.get(it) } + output.unlinkAll() + outputView.updateTexture() + inputViews.forEach(ProgramBlockParamView::updateTexture) + updateBlockLinks() + true + } + else -> false + } } 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 @@ -149,23 +200,91 @@ class ProgramCanvasView(val program: Program): View() { currentPartialLink.input.link(from = output) currentPartialLink.view.updateTexture() outputView.updateTexture() + updateBlockLinks() this.currentPartialLink = null true } else { // otherwise, click always fails false } - - if (result) { - updateBlockLinks() - } - return result } - open class PartialLink(val view: ProgramBlockParamView, val startPoint: Point) + private fun incomingExecutionViewClicked(block: ProgramBlock, incoming: IncomingExecutionFlow, incomingView: ProgramBlockExecutionView, mouseButton: MouseButton): Boolean { + val currentPartialLink = currentPartialLink + return if (currentPartialLink == null) { + // no partial link in progress + when (mouseButton) { + MouseButton.LEFT -> { + // being a new one + this.currentPartialLink = IncomingExecutionPartialLink(incoming, incomingView, incomingView.convert(incomingView.bounds.center, to = this)) + true + } + MouseButton.RIGHT -> { + // clear clicked connection + val outgoing = incoming.source ?: return false + val outgoingView = blocks[outgoing.block]?.outgoingViews?.get(outgoing) ?: return false + incoming.unlink() + incomingView.updateTexture() + outgoingView.updateTexture() + updateBlockLinks() + true + } + else -> false + } + } else if (currentPartialLink is OutgoingExecutionPartialLink) { + // current link in progress originated with an outgoing execution connection, so complete it + currentPartialLink.outgoing.link(incoming) + currentPartialLink.view.updateTexture() + incomingView.updateTexture() + updateBlockLinks() + this.currentPartialLink = null + true + } else { + // otherwise, click always fails + false + } + } + + private fun outgoingExecutionViewClicked(block: ProgramBlock, outgoing: OutgoingExecutionFlow, outgoingView: ProgramBlockExecutionView, mouseButton: MouseButton): Boolean { + val currentPartialLink = currentPartialLink + return if (currentPartialLink == null) { + // no partial link in progress + when (mouseButton) { + MouseButton.LEFT -> { + // begin a new one + this.currentPartialLink = OutgoingExecutionPartialLink(outgoing, outgoingView, outgoingView.convert(outgoingView.bounds.center, to = this)) + true + } + MouseButton.RIGHT -> { + // clear clicked connection + val incoming = outgoing.destination ?: return false + val incomingView = blocks[incoming.block]?.incomingView ?: return false + outgoing.unlink() + outgoingView.updateTexture() + incomingView.updateTexture() + updateBlockLinks() + true + } + else -> false + } + } else if (currentPartialLink is IncomingExecutionPartialLink) { + // current link in progress originated with an incoming execution connection, so complete it + currentPartialLink.incoming.link(outgoing) + currentPartialLink.view.updateTexture() + outgoingView.updateTexture() + updateBlockLinks() + this.currentPartialLink = null + true + } else { + // otherwise, click always fails + false + } + } + + open class PartialLink(val view: ProgramBlockConnectionIndicatorView, 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 + class IncomingExecutionPartialLink(val incoming: IncomingExecutionFlow, view: ProgramBlockExecutionView, startPoint: Point): PartialLink(view, startPoint) + class OutgoingExecutionPartialLink(val outgoing: OutgoingExecutionFlow, view: ProgramBlockExecutionView, startPoint: Point): PartialLink(view, startPoint) } \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/asmr/ui/block/ProgramBlockConnectionIndicatorView.kt b/src/main/kotlin/net/shadowfacts/asmr/ui/block/ProgramBlockConnectionIndicatorView.kt new file mode 100644 index 0000000..1cbd369 --- /dev/null +++ b/src/main/kotlin/net/shadowfacts/asmr/ui/block/ProgramBlockConnectionIndicatorView.kt @@ -0,0 +1,12 @@ +package net.shadowfacts.asmr.ui.block + +import net.shadowfacts.cacao.view.View + +/** + * @author shadowfacts + */ +interface ProgramBlockConnectionIndicatorView { + + fun updateTexture() + +} \ No newline at end of file diff --git a/src/main/kotlin/net/shadowfacts/asmr/ui/block/ProgramBlockExecutionView.kt b/src/main/kotlin/net/shadowfacts/asmr/ui/block/ProgramBlockExecutionView.kt index 09135ae..ba810a4 100644 --- a/src/main/kotlin/net/shadowfacts/asmr/ui/block/ProgramBlockExecutionView.kt +++ b/src/main/kotlin/net/shadowfacts/asmr/ui/block/ProgramBlockExecutionView.kt @@ -13,7 +13,7 @@ import java.lang.RuntimeException /** * @author shadowfacts */ -class ProgramBlockExecutionView(val incoming: IncomingExecutionFlow?, val outgoing: OutgoingExecutionFlow?): View() { +class ProgramBlockExecutionView(val incoming: IncomingExecutionFlow?, val outgoing: OutgoingExecutionFlow?): View(), ProgramBlockConnectionIndicatorView { companion object { val emptyConnection = Texture(Identifier(ASMR.modid, "textures/gui/programmer/program_connections.png"), u = 0, v = 0) @@ -45,7 +45,7 @@ class ProgramBlockExecutionView(val incoming: IncomingExecutionFlow?, val outgoi } } - fun updateTexture() { + override fun updateTexture() { val active = if (incoming != null) incoming.source != null else outgoing!!.destination != null textureView.texture = if (active) executionConnection else emptyConnection } diff --git a/src/main/kotlin/net/shadowfacts/asmr/ui/block/ProgramBlockParamView.kt b/src/main/kotlin/net/shadowfacts/asmr/ui/block/ProgramBlockParamView.kt index f9dff52..6e2acda 100644 --- a/src/main/kotlin/net/shadowfacts/asmr/ui/block/ProgramBlockParamView.kt +++ b/src/main/kotlin/net/shadowfacts/asmr/ui/block/ProgramBlockParamView.kt @@ -13,7 +13,7 @@ import java.lang.RuntimeException /** * @author shadowfacts */ -class ProgramBlockParamView(val input: ProgramBlockInput<*>?, val output: ProgramBlockOutput<*>?): View() { +class ProgramBlockParamView(val input: ProgramBlockInput<*>?, val output: ProgramBlockOutput<*>?): View(), ProgramBlockConnectionIndicatorView { companion object { val emptyConnection = Texture(Identifier(ASMR.modid, "textures/gui/programmer/program_connections.png"), u = 0, v = 0) @@ -45,7 +45,7 @@ class ProgramBlockParamView(val input: ProgramBlockInput<*>?, val output: Progra } } - fun updateTexture() { + override fun updateTexture() { val active = if (input != null) input.source != null else output!!.destinations.isNotEmpty() textureView.texture = if (active) parameterConnection else emptyConnection } diff --git a/src/main/resources/assets/asmr/lang/en_us.json b/src/main/resources/assets/asmr/lang/en_us.json index 4e29096..8aaab6c 100644 --- a/src/main/resources/assets/asmr/lang/en_us.json +++ b/src/main/resources/assets/asmr/lang/en_us.json @@ -1,6 +1,6 @@ { - "programblock.execution.asmr.incoming": "Incoming", - "programblock.execution.asmr.outgoing": "Outgoing", + "programblock.execution.asmr.incoming": "Previous", + "programblock.execution.asmr.outgoing": "Next", "programblock.asmr.start": "Start", "programblock.asmr.constant": "Constant", "programblock.param.asmr.constant.output": "Output",