package net.shadowfacts.asmr.ui import net.minecraft.util.Identifier import net.shadowfacts.asmr.ASMR import net.shadowfacts.asmr.program.ExecutableBlock import net.shadowfacts.asmr.program.ProgramBlock import net.shadowfacts.asmr.program.ProgramBlockInput import net.shadowfacts.asmr.program.ProgramBlockOutput import net.shadowfacts.cacao.geometry.Axis import net.shadowfacts.cacao.geometry.Point import net.shadowfacts.cacao.util.Color import net.shadowfacts.cacao.util.MouseButton import net.shadowfacts.cacao.util.texture.Texture import net.shadowfacts.cacao.view.Label import net.shadowfacts.cacao.view.StackView import net.shadowfacts.cacao.view.TextureView import net.shadowfacts.cacao.view.View import net.shadowfacts.kiwidsl.dsl import no.birkett.kiwi.Constraint import kotlin.math.max /** * @author shadowfacts */ class ProgramBlockView(val block: ProgramBlock): StackView(Axis.VERTICAL, Distribution.CENTER, spacing = 4.0) { 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) val parameterConnection = Texture(Identifier(ASMR.modid, "textures/gui/programmer/program_connections.png"), u = 14, v = 0) } var incomingExecution: View? = null var outgoingExeuction: View? = null val inputViews = mutableMapOf, View>() val outputViews = mutableMapOf, View>() var xConstraint: Constraint? = null var yConstraint: Constraint? = null var didCreateWidthConstraint = false override fun wasAdded() { super.wasAdded() respondsToDragging = true backgroundColor = Color.BLACK zIndex = 10.0 val titleStack = addArrangedSubview(StackView(Axis.HORIZONTAL, Distribution.CENTER)) val title = titleStack.addArrangedSubview(Label(block.translateName(), wrappingMode = Label.WrappingMode.NO_WRAP)) if (block is ExecutableBlock) { val incomingTexture = if (block.executionFlow.prev == null) emptyConnection else executionConnection incomingExecution = titleStack.addArrangedSubview(TextureView(incomingTexture), index = 0) val outgoingTexture = if (block.executionFlow.next == null) emptyConnection else executionConnection outgoingExeuction = titleStack.addArrangedSubview(TextureView(outgoingTexture)) } solver.dsl { // 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 if (block is ExecutableBlock) { incomingExecution!!.widthAnchor equalTo incomingExecution!!.heightAnchor incomingExecution!!.widthAnchor equalTo 7 outgoingExeuction!!.widthAnchor equalTo outgoingExeuction!!.heightAnchor outgoingExeuction!!.widthAnchor equalTo 7 } } for (i in 0 until max(block.inputs.size, block.outputs.size)) { 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)) inputViews[input] = inputView val inputLabel = hStack.addArrangedSubview(Label(input.translateName())) solver.dsl { hStack.heightAnchor equalTo inputLabel.heightAnchor inputView.widthAnchor equalTo inputView.heightAnchor inputView.widthAnchor equalTo 7 } } val spacingView = hStack.addArrangedSubview(View()) solver.dsl { spacingView.widthAnchor equalTo 8 } 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)) outputViews[output] = outputView solver.dsl { hStack.heightAnchor equalTo outputLabel.heightAnchor outputView.widthAnchor equalTo outputView.heightAnchor outputView.widthAnchor equalTo 7 } } } solver.dsl { for (view in arrangedSubviews.drop(1)) { widthAnchor.equalTo(view.widthAnchor, strength = STRONG) } } updateDraggingConstraints() } override fun didLayout() { super.didLayout() // we want to constrain this view's width to be the same as the widest view in the vertical stack, // but we don't know what view that is until after all subviews have been laid-out if (!didCreateWidthConstraint) { didCreateWidthConstraint = true arrangedSubviews.maxBy { it.bounds.width }?.let { widestSubview -> solver.dsl { widthAnchor equalTo widestSubview.widthAnchor } window!!.layout() } } } override fun mouseDragged(startPoint: Point, delta: Point, mouseButton: MouseButton): Boolean { block.position += delta updateDraggingConstraints() return true } private fun updateDraggingConstraints() { if (xConstraint != null) solver.removeConstraint(xConstraint) if (yConstraint != null) solver.removeConstraint(yConstraint) solver.dsl { xConstraint = (leftAnchor equalTo superview!!.leftAnchor + block.position.x) yConstraint = (topAnchor equalTo superview!!.topAnchor + block.position.y) } window!!.layout() } override fun drawContent(mouse: Point, delta: Float) { super.drawContent(mouse, delta) } }