package nl.utwente.viskell.ui.components; import com.google.common.collect.ImmutableMap; import javafx.fxml.FXML; import javafx.geometry.Point2D; import javafx.scene.paint.Color; import javafx.scene.shape.Shape; import nl.utwente.viskell.haskell.expr.*; import nl.utwente.viskell.haskell.type.Type; import nl.utwente.viskell.haskell.type.TypeScope; import nl.utwente.viskell.ui.BlockContainer; import java.util.*; /** * Anchor that specifically functions as an output. */ public class OutputAnchor extends ConnectionAnchor implements ConnectionAnchor.Target { /** The visual representation of the OutputAnchor. */ @FXML private Shape visibleAnchor; /** The invisible part of the OutputAnchor (the touch zone). */ @FXML private Shape invisibleAnchor; /** The thing sticking out of an unconnected OutputAnchor. */ @FXML private Shape openWire; /** The thing that is shown when this anchor is used as a boolean guard. */ @FXML private Shape guardMarker; /** The connections this anchor has, can be empty for no connections. */ protected List<Connection> connections; /** The variable binder attached to the expression corresponding to this anchor */ protected final Binder binder; /** * @param block The block this Anchor is connected to * @param binder The binder to use for this */ public OutputAnchor(Block block, Binder binder) { super(block); this.loadFXML("OutputAnchor"); this.connections = new ArrayList<>(); this.binder = binder; } /** * @param targetConnection optionally which special connection the type associated with. * @return the local type of this anchor */ public Type getType(Optional<Connection> targetConnection) { return this.binder.getBoundType(); } @Override public Type getFreshType() { return this.getType(Optional.empty()).getFresh(); } @Override public ConnectionAnchor getAssociatedAnchor() { return this; } /** * @return the string representation of the in- or output type. */ public final String getStringType() { return this.getType(Optional.empty()).prettyPrint(); } /** * Refreshes the internal anchor type. * @param scope wherein the fresh type is constructed */ public void refreshType(TypeScope scope) { this.binder.refreshBinderType(scope); } /** * Sets the internal anchor type. * @param type to replace the internal type with. */ public void setExactRequiredType(Type type) { this.binder.setAnnotationAsType(type); } /** * Set a new type constraint for this anchor, and refreshes it internal type. * @param type to constrain this anchor with. * @param scope scope wherein the fresh type is constructed. */ public void setFreshRequiredType(Type type, TypeScope scope) { this.binder.setFreshAnnotation(type, scope); } /** * Get the input anchors on the other side of the Connection from this anchor. * * @return A list of each input anchor for each Connection this anchor has. */ public List<InputAnchor> getOppositeAnchors() { List<InputAnchor> list = new ArrayList<>(); for (Connection c : this.connections) { list.add(c.getEndAnchor()); } return list; } @Override public boolean hasConnection() { return !this.connections.isEmpty(); } /** * Adds the given connection to the connections this anchor has. * @param connection The connection to add. */ protected void addConnection(Connection connection) { this.connections.add(connection); this.openWire.setVisible(false); this.guardMarker.setVisible(false); } /** * Drops the connection from this anchor * @param connection Connection to disconnect from. */ protected void dropConnection(Connection connection) { if (this.connections.contains(connection)) { this.connections.remove(connection); this.openWire.setVisible(!this.hasConnection()); } } @Override public void removeConnections() { while (!this.connections.isEmpty()) { Connection connection = this.connections.remove(0); connection.remove(); } this.openWire.setVisible(true); if (this.getWireInProgress() != null) { this.getWireInProgress().remove(); } } @Override public Point2D getAttachmentPoint() { return this.getPane().sceneToLocal(this.localToScene(new Point2D(0, 7))); } /** Initiate connection changes at the Block this anchor is attached to. */ public void initiateConnectionChanges() { this.block.initiateConnectionChanges(); } /** Prepare connection changes in the block this anchor belongs to. */ public void prepareConnectionChanges() { this.block.prepareConnectionChanges(); } /** * @return The variable referring to the expression belong to this anchor. */ public Variable getVariable() { return new LocalVar(this.binder); } /** * Extends the expression graph to include all subexpression required * @param exprGraph the let expression representing the current expression graph * @param container the container to which this expression graph is constrained * @param outsideAnchors a mutable set of required OutputAnchors from a surrounding container */ protected void extendExprGraph(LetExpression exprGraph, BlockContainer container, Set<OutputAnchor> outsideAnchors) { if (block.getContainer().equals(container)) { boolean added = false; Expression expr = block.getLocalExpr(outsideAnchors); if (block instanceof MatchBlock) { added = exprGraph.addLetBinding(((MatchBlock)block).getPrimaryBinder(), expr); } else if (block instanceof ConstantMatchBlock) { added = exprGraph.addLetBinding(new ConstantBinder(((ConstantMatchBlock)block).getValue()), expr); } else if (block instanceof SplitterBlock) { added = exprGraph.addLetBinding(((SplitterBlock)block).getPrimaryBinder(), expr); } else { added = exprGraph.addLetBinding(binder, expr); } if (added) { // for a new let binding everything from the subexpression in this block needs to be included block.extendExprGraph(exprGraph, container, outsideAnchors); } } } @Override protected void setNearbyWireReaction(int goodness) { if (goodness > 2) { this.openWire.setStroke(Color.DODGERBLUE); this.openWire.setStrokeWidth(5); this.visibleAnchor.setFill(Color.DODGERBLUE); this.guardMarker.setStroke(Color.DODGERBLUE); } else if (goodness > 0) { this.openWire.setStroke(Color.DARKGREEN); this.openWire.setStrokeWidth(5); this.visibleAnchor.setFill(Color.DARKGREEN); this.guardMarker.setStroke(Color.DARKGREEN); } else if (goodness < 0) { this.openWire.setStroke(Color.RED); this.openWire.setStrokeWidth(3); this.visibleAnchor.setFill(Color.RED); this.guardMarker.setStroke(Color.RED); } else { this.openWire.setStroke(Color.BLACK); this.openWire.setStrokeWidth(3); this.visibleAnchor.setFill(Color.BLACK); this.guardMarker.setStroke(Color.BLACK); } } @Override public void setWireInProgress(DrawWire wire) { super.setWireInProgress(wire); if (wire == null) { this.invalidateVisualState(); this.invisibleAnchor.setMouseTransparent(false); } else { this.openWire.setVisible(false); this.guardMarker.setVisible(false); this.invisibleAnchor.setMouseTransparent(true); } } public void invalidateVisualState() { if ("Bool".equals(this.getStringType()) && this.getContainer() instanceof Lane) { this.guardMarker.setVisible(!this.hasConnection()); this.openWire.setVisible(false); } else { this.openWire.setVisible(!this.hasConnection()); this.guardMarker.setVisible(false); } } @Override public BlockContainer getContainer() { return this.block.getContainer(); } @Override public Map<String, Object> toBundle() { ImmutableMap.Builder<String, Object> bundle = ImmutableMap.builder(); Block block = this.block; bundle.put(BLOCK_LABEL, block.hashCode()); return bundle.build(); } }