package net.shadowfacts.asmr.ui import net.shadowfacts.asmr.program.* import net.shadowfacts.asmr.program.execution.ExecutableBlock import net.shadowfacts.asmr.ui.block.ProgramBlockParamView import net.shadowfacts.asmr.ui.block.ProgramBlockView 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 /** * @author shadowfacts */ class ProgramCanvasView(val program: Program): View() { private lateinit var blocks: Map // map of starting block to (start, end) points private val executionFlowStartLinks = mutableMapOf>() private val parameterLinks = mutableMapOf>() private var currentPartialLink: PartialLink? = null override fun wasAdded() { super.wasAdded() zIndex = 5.0 blocks = program.blocks.associateWith { ProgramBlockView(it) } blocks.values.forEach { addSubview(it) } } private fun updateBlockLinks() { for ((startBlock, startView) in blocks) { (startBlock as? ExecutableBlock)?.let { startBlock -> 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) executionFlowStartLinks[startBlock] = start to end } } for (output in startBlock.outputs) { val outputView = startView.outputViews[output] ?: continue val start = outputView.convert(outputView.bounds.center, to = this) for (destination in output.destinations) { val endView = blocks[destination.block] ?: continue val inputView = endView.inputViews[destination] ?: continue val end = inputView.convert(inputView.bounds.center, to = this) parameterLinks[startBlock] = start to end } } } } override fun didLayout() { super.didLayout() // we can't calculate the link points until layout is completed and all of the views have concrete positions/sizes updateBlockLinks() } override fun drawContent(mouse: Point, delta: Float) { super.drawContent(mouse, delta) for ((start, end) in executionFlowStartLinks.values) { RenderHelper.drawLine(start, end, zIndex, 5f, Color.GREEN) } 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 }