package net.shadowfacts.asmr.ui.block import net.minecraft.util.Identifier import net.shadowfacts.asmr.ASMR import net.shadowfacts.asmr.program.execution.ExecutableBlock import net.shadowfacts.asmr.program.ProgramBlock import net.shadowfacts.asmr.program.ProgramBlockInput import net.shadowfacts.asmr.program.ProgramBlockOutput import net.shadowfacts.asmr.program.execution.OutgoingExecutionFlow 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.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 incomingView: ProgramBlockExecutionView? = null val outgoingViews = mutableMapOf() val inputViews = mutableMapOf, ProgramBlockParamView>() val outputViews = mutableMapOf, ProgramBlockParamView>() 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)) if (block is ExecutableBlock) { incomingView = titleStack.addArrangedSubview(ProgramBlockExecutionView(block.incoming)) } val title = titleStack.addArrangedSubview(Label(block.translateName(), wrappingMode = Label.WrappingMode.NO_WRAP)) 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) { incomingView!!.widthAnchor equalTo incomingView!!.heightAnchor incomingView!!.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(), 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 } } } 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 inputView = hStack.addArrangedSubview(ProgramBlockParamView(input)) 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 outputView = hStack.addArrangedSubview(ProgramBlockParamView(output)) 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() window!!.layout() 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) } } override fun drawContent(mouse: Point, delta: Float) { super.drawContent(mouse, delta) } }