Add execution flow editing and connection removal

This commit is contained in:
Shadowfacts 2019-08-11 23:15:20 -04:00
parent 0b72a11c70
commit 34dba86788
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
6 changed files with 177 additions and 39 deletions

View File

@ -27,6 +27,13 @@ class ProgramBlockOutput<Type: Any>(
_destinations.remove(to) _destinations.remove(to)
} }
fun unlinkAll() {
for (dest in destinations) {
dest.source = null
}
_destinations.clear()
}
fun translateName(): String { fun translateName(): String {
return Language.getInstance().translate("programblock.param.${identifier.namespace}.${identifier.path}") return Language.getInstance().translate("programblock.param.${identifier.namespace}.${identifier.path}")
} }

View File

@ -2,6 +2,10 @@ package net.shadowfacts.asmr.ui
import net.shadowfacts.asmr.program.* import net.shadowfacts.asmr.program.*
import net.shadowfacts.asmr.program.execution.ExecutableBlock 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.ProgramBlockParamView
import net.shadowfacts.asmr.ui.block.ProgramBlockView import net.shadowfacts.asmr.ui.block.ProgramBlockView
import net.shadowfacts.cacao.geometry.Point import net.shadowfacts.cacao.geometry.Point
@ -40,12 +44,16 @@ class ProgramCanvasView(val program: Program): View() {
for (outgoing in startBlock.outgoing) { for (outgoing in startBlock.outgoing) {
val outgoingView = startView.outgoingViews[outgoing] ?: continue val outgoingView = startView.outgoingViews[outgoing] ?: continue
val start = outgoingView.convert(outgoingView.bounds.center, to = this) val start = outgoingView.convert(outgoingView.bounds.center, to = this)
val incoming = outgoing.destination ?: continue val incoming = outgoing.destination
val incomingBlockView = blocks[incoming.block] ?: continue if (incoming != null) {
val incomingView = incomingBlockView.incomingView!! val incomingBlockView = blocks[incoming.block] ?: continue
val end = incomingView.convert(incomingView.bounds.center, to = this) 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 val currentPartialLink = currentPartialLink
when (currentPartialLink) { if (currentPartialLink != null) {
is ParamInputPartialLink<*>, is ParamOutputPartialLink<*> -> { val color = if (currentPartialLink is ParamInputPartialLink<*> || currentPartialLink is ParamOutputPartialLink<*>) Color.RED else Color.GREEN
RenderHelper.drawLine(currentPartialLink.startPoint, mouse, zIndex, 5f, Color.RED) RenderHelper.drawLine(currentPartialLink.startPoint, mouse, zIndex, 5f, color)
}
} }
} }
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean { override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
if (mouseButton == MouseButton.RIGHT && currentPartialLink != null) {
this.currentPartialLink = null
return true
}
for ((block, blockView) in blocks) { for ((block, blockView) in blocks) {
if (convert(point, to = blockView) !in blockView.bounds) continue if (convert(point, to = blockView) !in blockView.bounds) continue
for ((input, inputView) in blockView.inputViews) { for ((input, inputView) in blockView.inputViews) {
if (convert(point, to = inputView) in inputView.bounds) { 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) { for ((output, outputView) in blockView.outputViews) {
if (convert(point, to = outputView) in outputView.bounds) { 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) return super.mouseClicked(point, mouseButton)
} }
private fun <Type: Any> inputViewClicked(block: ProgramBlock, input: ProgramBlockInput<Type>, inputView: ProgramBlockParamView): Boolean { private fun <Type: Any> inputViewClicked(block: ProgramBlock, input: ProgramBlockInput<Type>, inputView: ProgramBlockParamView, mouseButton: MouseButton): Boolean {
val currentPartialLink = currentPartialLink val currentPartialLink = currentPartialLink
val result = if (currentPartialLink == null) { return if (currentPartialLink == null) {
// no partial link in progress, begin a new one // no partial link in progress
this.currentPartialLink = ParamInputPartialLink(input, inputView, inputView.convert(inputView.bounds.center, to = this)) when (mouseButton) {
true 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) { } 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 // 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 // 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.output.link(to = input)
currentPartialLink.view.updateTexture() currentPartialLink.view.updateTexture()
inputView.updateTexture() inputView.updateTexture()
updateBlockLinks()
this.currentPartialLink = null this.currentPartialLink = null
true true
} else { } else {
// otherwise, click always fails // otherwise, click always fails
false false
} }
if (result) {
updateBlockLinks()
}
return result
} }
private fun <Type: Any> outputViewClicked(block: ProgramBlock, output: ProgramBlockOutput<Type>, outputView: ProgramBlockParamView): Boolean { private fun <Type: Any> outputViewClicked(block: ProgramBlock, output: ProgramBlockOutput<Type>, outputView: ProgramBlockParamView, mouseButton: MouseButton): Boolean {
val currentPartialLink = currentPartialLink val currentPartialLink = currentPartialLink
val result = if (currentPartialLink == null) { return if (currentPartialLink == null) {
this.currentPartialLink = ParamOutputPartialLink(output, outputView, outputView.convert(outputView.bounds.center, to = this)) // no partial link in progress
true 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) { } 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 // 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 // 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.input.link(from = output)
currentPartialLink.view.updateTexture() currentPartialLink.view.updateTexture()
outputView.updateTexture() outputView.updateTexture()
updateBlockLinks()
this.currentPartialLink = null this.currentPartialLink = null
true true
} else { } else {
// otherwise, click always fails // otherwise, click always fails
false 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<Type: Any>(val input: ProgramBlockInput<Type>, view: ProgramBlockParamView, startPoint: Point): PartialLink(view, startPoint) 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 ParamOutputPartialLink<Type: Any>(val output: ProgramBlockOutput<Type>, view: ProgramBlockParamView, startPoint: Point): PartialLink(view, startPoint)
// class ExecutionStartPartialLink<Type: Any>(val start: ExecutableBlock): PartialLink class IncomingExecutionPartialLink(val incoming: IncomingExecutionFlow, view: ProgramBlockExecutionView, startPoint: Point): PartialLink(view, startPoint)
// class ExecutionEndPartialLink<Type: Any>(val end: ExecutableBlock): PartialLink class OutgoingExecutionPartialLink(val outgoing: OutgoingExecutionFlow, view: ProgramBlockExecutionView, startPoint: Point): PartialLink(view, startPoint)
} }

View File

@ -0,0 +1,12 @@
package net.shadowfacts.asmr.ui.block
import net.shadowfacts.cacao.view.View
/**
* @author shadowfacts
*/
interface ProgramBlockConnectionIndicatorView {
fun updateTexture()
}

View File

@ -13,7 +13,7 @@ import java.lang.RuntimeException
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class ProgramBlockExecutionView(val incoming: IncomingExecutionFlow?, val outgoing: OutgoingExecutionFlow?): View() { class ProgramBlockExecutionView(val incoming: IncomingExecutionFlow?, val outgoing: OutgoingExecutionFlow?): View(), ProgramBlockConnectionIndicatorView {
companion object { companion object {
val emptyConnection = Texture(Identifier(ASMR.modid, "textures/gui/programmer/program_connections.png"), u = 0, v = 0) 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 val active = if (incoming != null) incoming.source != null else outgoing!!.destination != null
textureView.texture = if (active) executionConnection else emptyConnection textureView.texture = if (active) executionConnection else emptyConnection
} }

View File

@ -13,7 +13,7 @@ import java.lang.RuntimeException
/** /**
* @author shadowfacts * @author shadowfacts
*/ */
class ProgramBlockParamView(val input: ProgramBlockInput<*>?, val output: ProgramBlockOutput<*>?): View() { class ProgramBlockParamView(val input: ProgramBlockInput<*>?, val output: ProgramBlockOutput<*>?): View(), ProgramBlockConnectionIndicatorView {
companion object { companion object {
val emptyConnection = Texture(Identifier(ASMR.modid, "textures/gui/programmer/program_connections.png"), u = 0, v = 0) 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() val active = if (input != null) input.source != null else output!!.destinations.isNotEmpty()
textureView.texture = if (active) parameterConnection else emptyConnection textureView.texture = if (active) parameterConnection else emptyConnection
} }

View File

@ -1,6 +1,6 @@
{ {
"programblock.execution.asmr.incoming": "Incoming", "programblock.execution.asmr.incoming": "Previous",
"programblock.execution.asmr.outgoing": "Outgoing", "programblock.execution.asmr.outgoing": "Next",
"programblock.asmr.start": "Start", "programblock.asmr.start": "Start",
"programblock.asmr.constant": "Constant", "programblock.asmr.constant": "Constant",
"programblock.param.asmr.constant.output": "Output", "programblock.param.asmr.constant.output": "Output",