Compare commits
6 Commits
971c41ed2f
...
3afc217a0b
Author | SHA1 | Date |
---|---|---|
Shadowfacts | 3afc217a0b | |
Shadowfacts | 0b72a11c70 | |
Shadowfacts | a941679197 | |
Shadowfacts | 2c2c330db6 | |
Shadowfacts | 2ffda7b4e3 | |
Shadowfacts | 233a83f368 |
|
@ -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.link(from = left.output)
|
// it.left.link(from = left.output)
|
||||||
it.right.link(from = 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.link(from = divide.output)
|
// it.input.link(from = divide.output)
|
||||||
it.position = Point(240.0, 0.0)
|
it.position = Point(240.0, 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
program.startBlock.executionFlow.link(divide)
|
program.startBlock.link(divide)
|
||||||
divide.executionFlow.link(print)
|
divide.link(print)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun activate() {
|
fun activate() {
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
package net.shadowfacts.asmr.program
|
|
||||||
|
|
||||||
import net.minecraft.util.Identifier
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author shadowfacts
|
|
||||||
*/
|
|
||||||
abstract class ExecutableBlock(identifier: Identifier): ProgramBlock(identifier) {
|
|
||||||
|
|
||||||
open val executionFlow = ExecutionFlow(this)
|
|
||||||
|
|
||||||
abstract fun execute()
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
package net.shadowfacts.asmr.program
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author shadowfacts
|
|
||||||
*/
|
|
||||||
open class ExecutionFlow(val block: ExecutableBlock) {
|
|
||||||
|
|
||||||
open var next: ExecutableBlock? = null
|
|
||||||
open var prev: ExecutableBlock? = null
|
|
||||||
|
|
||||||
fun link(next: ExecutableBlock) {
|
|
||||||
this.next = next
|
|
||||||
next.executionFlow.prev = block
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
package net.shadowfacts.asmr.program
|
package net.shadowfacts.asmr.program
|
||||||
|
|
||||||
import net.shadowfacts.asmr.program.blocks.StartBlock
|
import net.shadowfacts.asmr.program.blocks.StartBlock
|
||||||
|
import net.shadowfacts.asmr.program.execution.ExecutableBlock
|
||||||
import net.shadowfacts.cacao.geometry.Point
|
import net.shadowfacts.cacao.geometry.Point
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,11 +15,11 @@ class Program {
|
||||||
val blocks = mutableListOf<ProgramBlock>(startBlock)
|
val blocks = mutableListOf<ProgramBlock>(startBlock)
|
||||||
|
|
||||||
fun execute() {
|
fun execute() {
|
||||||
var currentBlock: ExecutableBlock? = startBlock.executionFlow.next
|
var currentBlock: ExecutableBlock? = startBlock.next()
|
||||||
while (currentBlock != null) {
|
while (currentBlock != null) {
|
||||||
currentBlock.execute()
|
currentBlock.execute()
|
||||||
|
|
||||||
currentBlock = currentBlock.executionFlow.next
|
currentBlock = currentBlock.next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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}")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package net.shadowfacts.asmr.program
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class ProgramType<Type> {
|
class ProgramType<Type: Any> {
|
||||||
companion object {
|
companion object {
|
||||||
val INT = ProgramType<Int>()
|
val INT = ProgramType<Int>()
|
||||||
val FLOAT = ProgramType<Float>()
|
val FLOAT = ProgramType<Float>()
|
||||||
|
|
|
@ -3,14 +3,14 @@ package net.shadowfacts.asmr.program.blocks
|
||||||
import net.minecraft.util.Identifier
|
import net.minecraft.util.Identifier
|
||||||
import net.shadowfacts.asmr.ASMR
|
import net.shadowfacts.asmr.ASMR
|
||||||
import net.shadowfacts.asmr.program.*
|
import net.shadowfacts.asmr.program.*
|
||||||
import net.shadowfacts.cacao.geometry.Point
|
import net.shadowfacts.asmr.program.execution.SimpleExecutableBlock
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class PrintBlock<Type: Any>(
|
class PrintBlock<Type: Any>(
|
||||||
val type: ProgramType<Type>
|
val type: ProgramType<Type>
|
||||||
): ExecutableBlock(
|
): SimpleExecutableBlock(
|
||||||
Identifier(ASMR.modid, "print")
|
Identifier(ASMR.modid, "print")
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,13 @@ package net.shadowfacts.asmr.program.blocks
|
||||||
|
|
||||||
import net.minecraft.util.Identifier
|
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.*
|
||||||
import net.shadowfacts.asmr.program.ProgramBlockInput
|
import net.shadowfacts.asmr.program.execution.SimpleExecutableBlock
|
||||||
import net.shadowfacts.asmr.program.ProgramBlockOutput
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class StartBlock: ExecutableBlock(
|
class StartBlock: SimpleExecutableBlock(
|
||||||
Identifier(ASMR.modid, "start")
|
Identifier(ASMR.modid, "start")
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,8 @@ package net.shadowfacts.asmr.program.blocks.math
|
||||||
|
|
||||||
import net.minecraft.util.Identifier
|
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.*
|
||||||
import net.shadowfacts.asmr.program.ProgramBlockInput
|
import net.shadowfacts.asmr.program.execution.SimpleExecutableBlock
|
||||||
import net.shadowfacts.asmr.program.ProgramBlockOutput
|
|
||||||
import net.shadowfacts.asmr.program.ProgramType
|
|
||||||
import java.lang.RuntimeException
|
import java.lang.RuntimeException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,7 +12,7 @@ import java.lang.RuntimeException
|
||||||
class BinaryOperatorBlock<Type: Any>(
|
class BinaryOperatorBlock<Type: Any>(
|
||||||
val type: ProgramType<Type>,
|
val type: ProgramType<Type>,
|
||||||
val operation: Operation
|
val operation: Operation
|
||||||
): ExecutableBlock(
|
): SimpleExecutableBlock(
|
||||||
Identifier(ASMR.modid, "binary_operator")
|
Identifier(ASMR.modid, "binary_operator")
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package net.shadowfacts.asmr.program.execution
|
||||||
|
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import net.shadowfacts.asmr.ASMR
|
||||||
|
import net.shadowfacts.asmr.program.ProgramBlock
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
abstract class ExecutableBlock(identifier: Identifier): ProgramBlock(identifier) {
|
||||||
|
|
||||||
|
val incoming = IncomingExecutionFlow(Identifier(ASMR.modid, "incoming"), this)
|
||||||
|
abstract val outgoing: Array<OutgoingExecutionFlow>
|
||||||
|
|
||||||
|
abstract fun execute()
|
||||||
|
|
||||||
|
abstract fun next(): ExecutableBlock?
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package net.shadowfacts.asmr.program.execution
|
||||||
|
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import net.minecraft.util.Language
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
class IncomingExecutionFlow(
|
||||||
|
val identifier: Identifier,
|
||||||
|
val block: ExecutableBlock
|
||||||
|
) {
|
||||||
|
|
||||||
|
var source: OutgoingExecutionFlow? = null
|
||||||
|
internal set
|
||||||
|
|
||||||
|
fun link(from: OutgoingExecutionFlow) {
|
||||||
|
from.link(to = this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unlink() {
|
||||||
|
source?.unlink()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun translateName(): String {
|
||||||
|
return Language.getInstance().translate("programblock.execution.${identifier.namespace}.${identifier.path}")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package net.shadowfacts.asmr.program.execution
|
||||||
|
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import net.minecraft.util.Language
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
class OutgoingExecutionFlow(
|
||||||
|
val identifier: Identifier,
|
||||||
|
val block: ExecutableBlock
|
||||||
|
) {
|
||||||
|
|
||||||
|
var destination: IncomingExecutionFlow? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun link(to: IncomingExecutionFlow) {
|
||||||
|
destination = to
|
||||||
|
to.source = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unlink() {
|
||||||
|
destination?.source = null
|
||||||
|
destination = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun translateName(): String {
|
||||||
|
return Language.getInstance().translate("programblock.execution.${identifier.namespace}.${identifier.path}")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package net.shadowfacts.asmr.program.execution
|
||||||
|
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import net.shadowfacts.asmr.ASMR
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
abstract class SimpleExecutableBlock(identifier: Identifier): ExecutableBlock(identifier) {
|
||||||
|
|
||||||
|
val outgoingFlow = OutgoingExecutionFlow(Identifier(ASMR.modid, "outgoing"), this)
|
||||||
|
override val outgoing = arrayOf(outgoingFlow)
|
||||||
|
|
||||||
|
override fun next(): ExecutableBlock? {
|
||||||
|
return outgoingFlow.destination?.block
|
||||||
|
}
|
||||||
|
|
||||||
|
fun link(to: ExecutableBlock) {
|
||||||
|
outgoingFlow.link(to.incoming)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,10 +1,16 @@
|
||||||
package net.shadowfacts.asmr.ui
|
package net.shadowfacts.asmr.ui
|
||||||
|
|
||||||
import net.shadowfacts.asmr.program.ExecutableBlock
|
import net.shadowfacts.asmr.program.*
|
||||||
import net.shadowfacts.asmr.program.Program
|
import net.shadowfacts.asmr.program.execution.ExecutableBlock
|
||||||
import net.shadowfacts.asmr.program.ProgramBlock
|
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
|
import net.shadowfacts.cacao.geometry.Point
|
||||||
import net.shadowfacts.cacao.util.Color
|
import net.shadowfacts.cacao.util.Color
|
||||||
|
import net.shadowfacts.cacao.util.MouseButton
|
||||||
import net.shadowfacts.cacao.util.RenderHelper
|
import net.shadowfacts.cacao.util.RenderHelper
|
||||||
import net.shadowfacts.cacao.view.View
|
import net.shadowfacts.cacao.view.View
|
||||||
|
|
||||||
|
@ -19,6 +25,8 @@ class ProgramCanvasView(val program: Program): View() {
|
||||||
private val executionFlowStartLinks = mutableMapOf<ExecutableBlock, Pair<Point, Point>>()
|
private val executionFlowStartLinks = mutableMapOf<ExecutableBlock, Pair<Point, Point>>()
|
||||||
private val parameterLinks = mutableMapOf<ProgramBlock, Pair<Point, Point>>()
|
private val parameterLinks = mutableMapOf<ProgramBlock, Pair<Point, Point>>()
|
||||||
|
|
||||||
|
private var currentPartialLink: PartialLink? = null
|
||||||
|
|
||||||
override fun wasAdded() {
|
override fun wasAdded() {
|
||||||
super.wasAdded()
|
super.wasAdded()
|
||||||
|
|
||||||
|
@ -32,15 +40,20 @@ class ProgramCanvasView(val program: Program): View() {
|
||||||
|
|
||||||
private fun updateBlockLinks() {
|
private fun updateBlockLinks() {
|
||||||
for ((startBlock, startView) in blocks) {
|
for ((startBlock, startView) in blocks) {
|
||||||
(startBlock as? ExecutableBlock)?.executionFlow?.next?.let { endBlock ->
|
(startBlock as? ExecutableBlock)?.let { startBlock ->
|
||||||
blocks[endBlock]?.let { endView ->
|
for (outgoing in startBlock.outgoing) {
|
||||||
val outgoing = startView.outgoingExeuction!!
|
val outgoingView = startView.outgoingViews[outgoing] ?: continue
|
||||||
val incoming = endView.incomingExecution!!
|
val start = outgoingView.convert(outgoingView.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)
|
||||||
|
|
||||||
val start = outgoing.convert(outgoing.bounds.center, to = this)
|
executionFlowStartLinks[startBlock] = start to end
|
||||||
val end = incoming.convert(outgoing.bounds.center, to = this)
|
} else {
|
||||||
|
executionFlowStartLinks.remove(startBlock)
|
||||||
executionFlowStartLinks[startBlock] = start to end
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,6 +89,202 @@ class ProgramCanvasView(val program: Program): View() {
|
||||||
for ((start, end) in parameterLinks.values) {
|
for ((start, end) in parameterLinks.values) {
|
||||||
RenderHelper.drawLine(start, end, zIndex, 5f, Color.RED)
|
RenderHelper.drawLine(start, end, zIndex, 5f, Color.RED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val currentPartialLink = currentPartialLink
|
||||||
|
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, mouseButton)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ((output, outputView) in blockView.outputViews) {
|
||||||
|
if (convert(point, to = outputView) in outputView.bounds) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.mouseClicked(point, mouseButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <Type: Any> inputViewClicked(block: ProgramBlock, input: ProgramBlockInput<Type>, inputView: ProgramBlockParamView, 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 = 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
|
||||||
|
@Suppress("NAME_SHADOWING", "UNCHECKED_CAST")
|
||||||
|
val currentPartialLink = currentPartialLink as ParamOutputPartialLink<Type>
|
||||||
|
currentPartialLink.output.link(to = input)
|
||||||
|
currentPartialLink.view.updateTexture()
|
||||||
|
inputView.updateTexture()
|
||||||
|
updateBlockLinks()
|
||||||
|
this.currentPartialLink = null
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
// otherwise, click always fails
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <Type: Any> outputViewClicked(block: ProgramBlock, output: ProgramBlockOutput<Type>, outputView: ProgramBlockParamView, 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 = 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
|
||||||
|
@Suppress("NAME_SHADOWING", "UNCHECKED_CAST")
|
||||||
|
val currentPartialLink = currentPartialLink as ParamInputPartialLink<Type>
|
||||||
|
currentPartialLink.input.link(from = output)
|
||||||
|
currentPartialLink.view.updateTexture()
|
||||||
|
outputView.updateTexture()
|
||||||
|
updateBlockLinks()
|
||||||
|
this.currentPartialLink = null
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
// otherwise, click always fails
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ParamOutputPartialLink<Type: Any>(val output: ProgramBlockOutput<Type>, view: ProgramBlockParamView, startPoint: Point): PartialLink(view, startPoint)
|
||||||
|
class IncomingExecutionPartialLink(val incoming: IncomingExecutionFlow, view: ProgramBlockExecutionView, startPoint: Point): PartialLink(view, startPoint)
|
||||||
|
class OutgoingExecutionPartialLink(val outgoing: OutgoingExecutionFlow, view: ProgramBlockExecutionView, startPoint: Point): PartialLink(view, startPoint)
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package net.shadowfacts.asmr.ui.block
|
||||||
|
|
||||||
|
import net.shadowfacts.cacao.view.View
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
interface ProgramBlockConnectionIndicatorView {
|
||||||
|
|
||||||
|
fun updateTexture()
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package net.shadowfacts.asmr.ui.block
|
||||||
|
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import net.shadowfacts.asmr.ASMR
|
||||||
|
import net.shadowfacts.asmr.program.execution.IncomingExecutionFlow
|
||||||
|
import net.shadowfacts.asmr.program.execution.OutgoingExecutionFlow
|
||||||
|
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 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)
|
||||||
|
val executionConnection = Texture(Identifier(ASMR.modid, "textures/gui/programmer/program_connections.png"), u = 7, v = 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
lateinit var textureView: TextureView
|
||||||
|
|
||||||
|
constructor(incoming: IncomingExecutionFlow?): this(incoming, null)
|
||||||
|
constructor(outgoing: OutgoingExecutionFlow?): this(null, outgoing)
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (incoming == null && outgoing == null) {
|
||||||
|
throw RuntimeException("One of incoming or outgoing 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateTexture() {
|
||||||
|
val active = if (incoming != null) incoming.source != null else outgoing!!.destination != null
|
||||||
|
textureView.texture = if (active) executionConnection else emptyConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package net.shadowfacts.asmr.ui.block
|
||||||
|
|
||||||
|
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(), ProgramBlockConnectionIndicatorView {
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateTexture() {
|
||||||
|
val active = if (input != null) input.source != null else output!!.destinations.isNotEmpty()
|
||||||
|
textureView.texture = if (active) parameterConnection else emptyConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,11 +1,12 @@
|
||||||
package net.shadowfacts.asmr.ui
|
package net.shadowfacts.asmr.ui.block
|
||||||
|
|
||||||
import net.minecraft.util.Identifier
|
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.execution.ExecutableBlock
|
||||||
import net.shadowfacts.asmr.program.ProgramBlock
|
import net.shadowfacts.asmr.program.ProgramBlock
|
||||||
import net.shadowfacts.asmr.program.ProgramBlockInput
|
import net.shadowfacts.asmr.program.ProgramBlockInput
|
||||||
import net.shadowfacts.asmr.program.ProgramBlockOutput
|
import net.shadowfacts.asmr.program.ProgramBlockOutput
|
||||||
|
import net.shadowfacts.asmr.program.execution.OutgoingExecutionFlow
|
||||||
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
|
||||||
|
@ -13,7 +14,6 @@ import net.shadowfacts.cacao.util.MouseButton
|
||||||
import net.shadowfacts.cacao.util.texture.Texture
|
import net.shadowfacts.cacao.util.texture.Texture
|
||||||
import net.shadowfacts.cacao.view.Label
|
import net.shadowfacts.cacao.view.Label
|
||||||
import net.shadowfacts.cacao.view.StackView
|
import net.shadowfacts.cacao.view.StackView
|
||||||
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
|
||||||
|
@ -30,11 +30,11 @@ class ProgramBlockView(val block: ProgramBlock): StackView(Axis.VERTICAL, Distri
|
||||||
val parameterConnection = Texture(Identifier(ASMR.modid, "textures/gui/programmer/program_connections.png"), u = 14, v = 0)
|
val parameterConnection = Texture(Identifier(ASMR.modid, "textures/gui/programmer/program_connections.png"), u = 14, v = 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
var incomingExecution: View? = null
|
var incomingView: ProgramBlockExecutionView? = null
|
||||||
var outgoingExeuction: View? = null
|
val outgoingViews = mutableMapOf<OutgoingExecutionFlow, ProgramBlockExecutionView>()
|
||||||
|
|
||||||
val inputViews = mutableMapOf<ProgramBlockInput<*>, View>()
|
val inputViews = mutableMapOf<ProgramBlockInput<*>, ProgramBlockParamView>()
|
||||||
val outputViews = mutableMapOf<ProgramBlockOutput<*>, View>()
|
val outputViews = mutableMapOf<ProgramBlockOutput<*>, ProgramBlockParamView>()
|
||||||
|
|
||||||
var xConstraint: Constraint? = null
|
var xConstraint: Constraint? = null
|
||||||
var yConstraint: Constraint? = null
|
var yConstraint: Constraint? = null
|
||||||
|
@ -49,23 +49,33 @@ class ProgramBlockView(val block: ProgramBlock): StackView(Axis.VERTICAL, Distri
|
||||||
zIndex = 10.0
|
zIndex = 10.0
|
||||||
|
|
||||||
val titleStack = addArrangedSubview(StackView(Axis.HORIZONTAL, Distribution.CENTER))
|
val titleStack = addArrangedSubview(StackView(Axis.HORIZONTAL, Distribution.CENTER))
|
||||||
val title = titleStack.addArrangedSubview(Label(block.translateName(), wrappingMode = Label.WrappingMode.NO_WRAP))
|
|
||||||
if (block is ExecutableBlock) {
|
if (block is ExecutableBlock) {
|
||||||
val incomingTexture = if (block.executionFlow.prev == null) emptyConnection else executionConnection
|
incomingView = titleStack.addArrangedSubview(ProgramBlockExecutionView(block.incoming))
|
||||||
incomingExecution = titleStack.addArrangedSubview(TextureView(incomingTexture), index = 0)
|
|
||||||
val outgoingTexture = if (block.executionFlow.next == null) emptyConnection else executionConnection
|
|
||||||
outgoingExeuction = titleStack.addArrangedSubview(TextureView(outgoingTexture))
|
|
||||||
}
|
}
|
||||||
|
val title = titleStack.addArrangedSubview(Label(block.translateName(), wrappingMode = Label.WrappingMode.NO_WRAP))
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
if (block is ExecutableBlock) {
|
if (block is ExecutableBlock) {
|
||||||
incomingExecution!!.widthAnchor equalTo incomingExecution!!.heightAnchor
|
incomingView!!.widthAnchor equalTo incomingView!!.heightAnchor
|
||||||
incomingExecution!!.widthAnchor equalTo 7
|
incomingView!!.widthAnchor equalTo 7
|
||||||
outgoingExeuction!!.widthAnchor equalTo outgoingExeuction!!.heightAnchor
|
}
|
||||||
outgoingExeuction!!.widthAnchor equalTo 7
|
}
|
||||||
|
|
||||||
|
if (block is ExecutableBlock) {
|
||||||
|
for (outgoing in block.outgoing) {
|
||||||
|
val hStack = addArrangedSubview(StackView(Axis.HORIZONTAL, Distribution.CENTER))
|
||||||
|
val outgoingLabel = hStack.addArrangedSubview(Label(outgoing.translateName(), wrappingMode = Label.WrappingMode.NO_WRAP, textAlignment = Label.TextAlignment.RIGHT))
|
||||||
|
val outgoingView = hStack.addArrangedSubview(ProgramBlockExecutionView(outgoing))
|
||||||
|
outgoingViews[outgoing] = outgoingView
|
||||||
|
solver.dsl {
|
||||||
|
hStack.heightAnchor equalTo outgoingLabel.heightAnchor
|
||||||
|
|
||||||
|
outgoingView.widthAnchor equalTo outgoingView.heightAnchor
|
||||||
|
outgoingView.widthAnchor equalTo 7
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,10 +83,9 @@ class ProgramBlockView(val block: ProgramBlock): StackView(Axis.VERTICAL, Distri
|
||||||
val hStack = addArrangedSubview(StackView(Axis.HORIZONTAL, Distribution.CENTER))
|
val hStack = addArrangedSubview(StackView(Axis.HORIZONTAL, Distribution.CENTER))
|
||||||
|
|
||||||
block.inputs.getOrNull(i)?.let { input ->
|
block.inputs.getOrNull(i)?.let { input ->
|
||||||
val inputTexture = if (input.source == null) emptyConnection else parameterConnection
|
val inputView = hStack.addArrangedSubview(ProgramBlockParamView(input))
|
||||||
val inputView = hStack.addArrangedSubview(TextureView(inputTexture))
|
|
||||||
inputViews[input] = inputView
|
inputViews[input] = inputView
|
||||||
val inputLabel = hStack.addArrangedSubview(Label(input.translateName()))
|
val inputLabel = hStack.addArrangedSubview(Label(input.translateName(), wrappingMode = Label.WrappingMode.NO_WRAP))
|
||||||
solver.dsl {
|
solver.dsl {
|
||||||
hStack.heightAnchor equalTo inputLabel.heightAnchor
|
hStack.heightAnchor equalTo inputLabel.heightAnchor
|
||||||
|
|
||||||
|
@ -91,9 +100,8 @@ class ProgramBlockView(val block: ProgramBlock): StackView(Axis.VERTICAL, Distri
|
||||||
}
|
}
|
||||||
|
|
||||||
block.outputs.getOrNull(i)?.let { output ->
|
block.outputs.getOrNull(i)?.let { output ->
|
||||||
val outputLabel = hStack.addArrangedSubview(Label(output.translateName(), textAlignment = Label.TextAlignment.RIGHT))
|
val outputLabel = hStack.addArrangedSubview(Label(output.translateName(), wrappingMode = Label.WrappingMode.NO_WRAP, textAlignment = Label.TextAlignment.RIGHT))
|
||||||
val outputTexture = if (output.destinations.isEmpty()) emptyConnection else parameterConnection
|
val outputView = hStack.addArrangedSubview(ProgramBlockParamView(output))
|
||||||
val outputView = hStack.addArrangedSubview(TextureView(outputTexture))
|
|
||||||
outputViews[output] = outputView
|
outputViews[output] = outputView
|
||||||
solver.dsl {
|
solver.dsl {
|
||||||
hStack.heightAnchor equalTo outputLabel.heightAnchor
|
hStack.heightAnchor equalTo outputLabel.heightAnchor
|
||||||
|
@ -124,7 +132,7 @@ class ProgramBlockView(val block: ProgramBlock): StackView(Axis.VERTICAL, Distri
|
||||||
|
|
||||||
arrangedSubviews.maxBy { it.bounds.width }?.let { widestSubview ->
|
arrangedSubviews.maxBy { it.bounds.width }?.let { widestSubview ->
|
||||||
solver.dsl {
|
solver.dsl {
|
||||||
widthAnchor equalTo widestSubview.widthAnchor
|
widthAnchor equalTo widestSubview.bounds.width
|
||||||
}
|
}
|
||||||
window!!.layout()
|
window!!.layout()
|
||||||
}
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
{
|
{
|
||||||
|
"programblock.execution.asmr.incoming": "Incoming",
|
||||||
|
"programblock.execution.asmr.outgoing": "Outgoing",
|
||||||
"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",
|
||||||
|
|
|
@ -20,7 +20,7 @@ class ProgramTests {
|
||||||
it.right.link(from = two.output)
|
it.right.link(from = two.output)
|
||||||
}
|
}
|
||||||
|
|
||||||
program.startBlock.executionFlow.link(multiply)
|
program.startBlock.link(multiply)
|
||||||
|
|
||||||
program.execute()
|
program.execute()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue