package nl.utwente.viskell.ui.components;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TouchEvent;
import javafx.scene.input.TouchPoint;
import javafx.scene.paint.Color;
import javafx.scene.shape.ArcTo;
import javafx.scene.shape.ClosePath;
import javafx.scene.shape.CubicCurve;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.StrokeType;
import javafx.scene.transform.Transform;
import javafx.util.Duration;
import nl.utwente.viskell.haskell.env.DataTypeInfo;
import nl.utwente.viskell.haskell.env.HaskellCatalog;
import nl.utwente.viskell.haskell.type.*;
import nl.utwente.viskell.ui.BlockContainer;
import nl.utwente.viskell.ui.ComponentLoader;
import nl.utwente.viskell.ui.ToplevelPane;
import nl.utwente.viskell.ui.WireMenu;
/**
* A DrawWire represents the UI for a new incomplete connection is the process of being drawn.
* It is linked to a single Anchor as starting point, and with a second anchor it produces a new Connection.
*/
public class DrawWire extends CubicCurve implements ChangeListener<Transform>, ComponentLoader {
/** The Anchor this wire is connected to */
protected final ConnectionAnchor anchor;
private final TouchArea toucharea;
private WireMenu menu;
/**
* @param anchor the connected side of this new wire.
* @param startingPoint the position where this wire was initiated from.
* @param touchPoint that initiated this wire, or null if it was by mouse.
*/
private DrawWire(ConnectionAnchor anchor, Point2D startingPoint, TouchPoint touchPoint) {
this.setMouseTransparent(true);
this.anchor = anchor;
this.anchor.setWireInProgress(this);
ToplevelPane pane = anchor.getPane();
pane.addWire(this);
this.setFreePosition(startingPoint);
anchor.localToSceneTransformProperty().addListener(this);
this.toucharea = new TouchArea(touchPoint);
pane.addUpperTouchArea(this.toucharea);
}
protected static DrawWire initiate(ConnectionAnchor anchor, TouchPoint touchPoint) {
if (anchor instanceof InputAnchor && anchor.hasConnection()) {
Connection conn = ((InputAnchor)anchor).getConnection().get();
OutputAnchor startAnchor = conn.getStartAnchor();
if (startAnchor.getWireInProgress() == null) {
// make room for a new connection by removing existing one
conn.remove();
// keep the other end of old connection to initiate the new one
return new DrawWire(startAnchor, anchor.getAttachmentPoint(), touchPoint);
} else {
return null;
}
} else if (anchor instanceof OutputAnchor && anchor.hasConnection() && ((OutputAnchor)anchor).connections.get(0).hasTypeError()) {
Connection conn = ((OutputAnchor)anchor).connections.get(0);
InputAnchor endAnchor = conn.getEndAnchor();
if (endAnchor.getWireInProgress() == null) {
// trying to solve the type error by changing the connection
conn.remove();
return new DrawWire(endAnchor, anchor.getAttachmentPoint(), touchPoint);
}
}
return new DrawWire(anchor, anchor.getAttachmentPoint(), touchPoint);
}
public ConnectionAnchor getAnchor() {
return this.anchor;
}
private void showMenu(boolean byMouse) {
if (this.menu == null) {
this.menu = new WireMenu(this, byMouse);
this.menu.relocate(this.toucharea.getLayoutX() + 50 , this.toucharea.getLayoutY() - 50);
this.anchor.block.getToplevel().addMenu(this.menu);
}
}
protected void handleMouseDrag(MouseEvent event) {
if (this.menu == null && !event.isSynthesized()) {
Point2D localPos = this.anchor.getPane().sceneToLocal(event.getSceneX(), event.getSceneY());
this.toucharea.dragTo(localPos.getX(), localPos.getY(), event.getPickResult().getIntersectedNode());
}
event.consume();
}
protected void handleMouseRelease(MouseEvent event) {
this.toucharea.handleMouseRelease(event);
}
/** @return the ConnectionAnchor related to the picked Node, or null of none. */
private static ConnectionAnchor findPickedAnchor(Node picked) {
Node next = picked;
while (next != null) {
if (next instanceof ConnectionAnchor.Target) {
return ((ConnectionAnchor.Target)next).getAssociatedAnchor();
}
next = next.getParent();
}
return null;
}
private void handleReleaseOn(Node picked) {
ConnectionAnchor target = findPickedAnchor(picked);
if (target == this.anchor) {
this.toucharea.handleReleaseOnSelf();
} else if (target != null && target.getWireInProgress() == null) {
Connection connection = this.buildConnectionTo(target);
if (connection != null) {
connection.getStartAnchor().initiateConnectionChanges();
this.remove();
} else {
this.toucharea.handleReleaseOnNothing();
}
} else if (Connection.lengthSquared(this) < 900) {
this.toucharea.handleReleaseOnSelf();
} else {
this.toucharea.handleReleaseOnNothing();
}
}
/**
* Constructs a new Connection from this partial wire and another anchor
* @param target the Anchor to which the other end of this should be connection to.
* @return the newly build Connection or null if it's not possible
*/
public Connection buildConnectionTo(ConnectionAnchor target) {
InputAnchor sink;
OutputAnchor source;
if (this.anchor instanceof InputAnchor) {
if (target instanceof InputAnchor) {
return null;
}
sink = (InputAnchor)this.anchor;
source = (OutputAnchor)target;
} else {
if (target instanceof OutputAnchor) {
return null;
}
sink = (InputAnchor)target;
source = (OutputAnchor)this.anchor;
if (sink.hasConnection()) {
sink.removeConnections(); // push out the existing connection
}
}
if (sink.block == source.block && !(sink instanceof ResultAnchor && source instanceof BinderAnchor)) {
// self recursive wires are not allowed
return null;
}
return new Connection(source, sink);
}
/** Removes this wire from its pane, and its listener. */
public final void remove() {
if (this.menu != null) {
this.menu.close();
this.menu = null;
}
if (this.toucharea != null) {
this.toucharea.remove();
}
this.anchor.setWireInProgress(null);
this.anchor.localToSceneTransformProperty().removeListener(this);
this.anchor.getPane().removeWire(this);
}
@Override
public void changed(ObservableValue<? extends Transform> observable, Transform oldValue, Transform newValue) {
this.invalidateAnchorPosition();
}
/** Update the UI position of the anchor. */
private void invalidateAnchorPosition() {
Point2D point = this.anchor.getAttachmentPoint();
if (this.anchor instanceof InputAnchor) {
this.setEndX(point.getX());
this.setEndY(point.getY());
} else {
this.setStartX(point.getX());
this.setStartY(point.getY());
}
Connection.updateBezierControlPoints(this);
}
/**
* Sets the free end coordinates for this wire.
* @param point coordinates local to this wire's parent.
*/
public void setFreePosition(Point2D point) {
if (this.anchor instanceof InputAnchor) {
this.setStartX(point.getX());
this.setStartY(point.getY());
} else {
this.setEndX(point.getX());
this.setEndY(point.getY());
}
this.invalidateAnchorPosition();
ToplevelPane pane = this.anchor.block.getToplevel();
Point2D scenePoint = pane.localToScene(point, false);
BlockContainer anchorContainer = this.anchor.getContainer();
boolean scopeOK = true;
if (this.anchor instanceof OutputAnchor) {
scopeOK = anchorContainer.containmentBoundsInScene().contains(scenePoint);
} else if (this.anchor instanceof InputAnchor) {
scopeOK = pane.getAllBlockContainers().
filter(con -> con.containmentBoundsInScene().contains(scenePoint)).
allMatch(con -> anchorContainer.isContainedWithin(con));
}
if (scopeOK) {
this.getStrokeDashArray().clear();
} else if (this.getStrokeDashArray().isEmpty()) {
this.getStrokeDashArray().addAll(15.0, 15.0);
}
}
/** A circular area at the open end of the draw wire for handling multi finger touch actions.
* This area has as a workaround a hole in the middle to be able to pick the thing behind it on release.
*/
private class TouchArea extends Path {
/** The ID of finger that spawned this touch area. */
private int touchID;
/** Whether this touch area has been dragged further than the drag threshold. */
private boolean dragStarted;
/** Timed animation for toucharea and drawwire self removal */
private final Timeline disapperance;
/** List of nearby anchors that have visually reacted to this wire. */
private List<ConnectionAnchor> nearbyAnchors;
private Point2D lastNearbyUpdate;
/**
* @param touchPoint that is the center of new active touch area, or null if the mouse
*/
private TouchArea(TouchPoint touchPoint) {
super();
this.setLayoutX(DrawWire.this.getEndX());
this.setLayoutY(DrawWire.this.getEndY());
this.touchID = touchPoint == null ? -1 : touchPoint.getId();
this.dragStarted = true;
this.nearbyAnchors = new ArrayList<>();
this.lastNearbyUpdate = Point2D.ZERO;
this.disapperance = new Timeline(new KeyFrame(Duration.millis(2000),
e -> DrawWire.this.remove(),
new KeyValue(this.opacityProperty(), 0.3),
new KeyValue(DrawWire.this.opacityProperty(), 0.2)));
// a circle with hole is built from a path of round arcs with a very thick stroke
ArcTo arc1 = new ArcTo(100, 100, 0, 100, 0, true, true);
ArcTo arc2 = new ArcTo(100, 100, 0, -100, 0, true, true);
this.getElements().addAll(new MoveTo(-100, 0), arc1, arc2, new ClosePath());
this.setStroke(Color.web("#0066FF"));
this.setStrokeType(StrokeType.INSIDE);
this.setStrokeWidth(90);
this.setStroke(Color.web("#0066FF"));
this.setStrokeType(StrokeType.INSIDE);
this.setOpacity(0);
if (touchPoint != null) {
touchPoint.grab(this);
}
this.addEventHandler(TouchEvent.TOUCH_PRESSED, this::handleTouchPress);
this.addEventHandler(TouchEvent.TOUCH_MOVED, this::handleTouchDrag);
this.addEventHandler(TouchEvent.TOUCH_RELEASED, this::handleTouchRelease);
this.addEventHandler(MouseEvent.MOUSE_PRESSED, this::handleMousePress);
this.addEventHandler(MouseEvent.MOUSE_DRAGGED, this::handleMouseDrag);
this.addEventHandler(MouseEvent.MOUSE_RELEASED, this::handleMouseRelease);
}
private void handleReleaseOnNothing() {
this.makeVisible();
disapperance.playFromStart();
}
private void handleReleaseOnSelf() {
this.makeVisible();
disapperance.playFrom(disapperance.getTotalDuration().multiply(0.9));
}
private void makeVisible() {
this.clearWireReactions();
this.setScaleX(0.25);
this.setScaleY(0.25);
this.setOpacity(0.6);
this.setStrokeWidth(99);
DrawWire.this.setOpacity(1);
// avoid clicking the hole with a non moving mouse
this.setLayoutY(this.getLayoutY() + (DrawWire.this.anchor instanceof InputAnchor ? -2 : 2));
}
private void clearWireReactions() {
for (ConnectionAnchor anchor : this.nearbyAnchors) {
anchor.setNearbyWireReaction(0);
}
}
private void remove() {
this.clearWireReactions();
ToplevelPane pane = DrawWire.this.anchor.getPane();
pane.removeUpperTouchArea(this);
}
private void handleTouchPress(TouchEvent event) {
if (!this.dragStarted) {
this.touchID = event.getTouchPoint().getId();
this.disapperance.stop();
this.makeVisible();
}
event.consume();
}
private void handleMousePress(MouseEvent event) {
if (event.isSynthesized()) {
// don't react on synthesized events
} else if (event.getButton() == MouseButton.PRIMARY) {
this.touchID = -1;
this.handleDragStart();
this.disapperance.stop();
DrawWire.this.setOpacity(1);
} else {
DrawWire.this.remove();
}
event.consume();
}
private void handleTouchRelease(TouchEvent event) {
this.dragStarted = false;
long fingerCount = event.getTouchPoints().stream().filter(tp -> tp.belongsTo(this)).count();
if (fingerCount == 1 && DrawWire.this.menu == null) {
Node picked = event.getTouchPoint().getPickResult().getIntersectedNode();
DrawWire.this.handleReleaseOn(picked);
} else if (DrawWire.this.menu != null || this.touchID < 0) {
// avoid accidental creation of (more) menus
} else if (fingerCount == 2) {
DrawWire.this.showMenu(false);
// a delay to avoid the background picking up jitter from this event
Timeline delay = new Timeline(new KeyFrame(Duration.millis(250), e -> this.makeVisible()));
delay.play();
}
event.consume();
}
private void handleMouseRelease(MouseEvent event) {
if (event.isSynthesized()) {
// don't react on synthesized events
} else if (DrawWire.this.menu != null) {
// release has no effect if there is a menu
} else if (event.getButton() == MouseButton.PRIMARY) {
DrawWire.this.handleReleaseOn(event.getPickResult().getIntersectedNode());
this.dragStarted = false;
} else {
DrawWire.this.showMenu(true);
this.dragStarted = false;
this.makeVisible();
}
event.consume();
}
private void handleTouchDrag(TouchEvent event) {
double scaleFactor = this.getScaleX();
double deltaX = event.getTouchPoint().getX() * scaleFactor;
double deltaY = event.getTouchPoint().getY() * scaleFactor;
if (event.getTouchPoint().getId() != this.touchID) {
// we use only primary finger for drag movement
if (this.dragStarted && Math.abs(deltaX) > 175) {
this.horizontalSplittingAction(event);
} else if (this.dragStarted && Math.abs(deltaY) > 150){
this.verticalSplittingAction(event);
}
} else {
if ((deltaX*deltaX + deltaY*deltaY) > 10000) {
// FIXME: ignore too large movements
} else if (this.dragStarted || (deltaX*deltaX + deltaY*deltaY) > 63) {
if (!this.dragStarted) {
this.handleDragStart();
}
double newX = this.getLayoutX() + deltaX;
double newY = this.getLayoutY() + deltaY;
this.dragTo(newX, newY, event.getTouchPoint().getPickResult().getIntersectedNode());
}
}
event.consume();
}
private void handleMouseDrag(MouseEvent event) {
if (DrawWire.this.menu == null && !event.isSynthesized()) {
double scaleFactor = this.getScaleX();
double newX = this.getLayoutX() + event.getX() * scaleFactor;
double newY = this.getLayoutY() + event.getY() * scaleFactor;
this.dragTo(newX, newY, event.getPickResult().getIntersectedNode());
}
event.consume();
}
private void dragTo(double newX, double newY) {
this.dragTo(newX, newY, null);
}
private void dragTo(double newX, double newY, Node picked) {
this.setLayoutX(newX);
this.setLayoutY(newY);
Point2D newPos = new Point2D(newX, newY);
DrawWire.this.setFreePosition(newPos);
// threshold to avoid doing a quite expensive computation too often
if (this.lastNearbyUpdate.distance(newPos) > 10) {
this.lastNearbyUpdate = newPos;
List<ConnectionAnchor> targetAnchors = anchor.block.getToplevel().allNearbyFreeAnchors(newPos, 166);
List<ConnectionAnchor> newNearby = new ArrayList<>();
// trial unification on all nearby opposite free anchor so see if they could fit
if (DrawWire.this.anchor instanceof InputAnchor) {
InputAnchor anchor = (InputAnchor)DrawWire.this.anchor;
ConnectionAnchor releaseAnchor = DrawWire.findPickedAnchor(picked);
if (releaseAnchor == anchor) {
releaseAnchor = null;
}
for (ConnectionAnchor target : targetAnchors) {
if (target instanceof OutputAnchor) {
target.setNearbyWireReaction(determineWireReaction((OutputAnchor)target, anchor, releaseAnchor));
newNearby.add(target);
}
}
} else {
OutputAnchor anchor = (OutputAnchor)DrawWire.this.anchor;
ConnectionAnchor releaseAnchor = DrawWire.findPickedAnchor(picked);
if (releaseAnchor == anchor) {
releaseAnchor = null;
}
for (ConnectionAnchor target : targetAnchors) {
if (target instanceof InputAnchor) {
newNearby.add(target);
target.setNearbyWireReaction(determineWireReaction(anchor, (InputAnchor)target, releaseAnchor));
}
}
}
// reset all anchors that are not nearby anymore
for (ConnectionAnchor oldNear : this.nearbyAnchors) {
if (! newNearby.contains(oldNear)) {
oldNear.setNearbyWireReaction(0);
}
}
this.nearbyAnchors = newNearby;
}
}
private int determineWireReaction(OutputAnchor source, InputAnchor sink, ConnectionAnchor releaseAnchor) {
if (sink.block == source.block && !(sink instanceof ResultAnchor && source instanceof BinderAnchor)) {
return 0;
}
try {
TypeChecker.unify("wire reaction", source.getType(Optional.empty()).getFresh(), sink.getType().getFresh());
if (source == releaseAnchor || sink == releaseAnchor) {
return 3;
} else {
return 1;
}
} catch (HaskellTypeError e) {
return -1;
}
}
private void handleDragStart() {
this.dragStarted = true;
if (DrawWire.this.menu != null) {
// resume dragging the wire
DrawWire.this.menu.close();
DrawWire.this.menu = null;
}
this.setScaleX(1);
this.setScaleY(1);
this.setOpacity(0);
this.setStrokeWidth(90);
}
private void horizontalSplittingAction(TouchEvent event) {
ToplevelPane toplevel = DrawWire.this.anchor.block.getToplevel();
TouchPoint tpA = event.getTouchPoint();
Point2D posA = toplevel.sceneToLocal(new Point2D(tpA.getSceneX(), tpA.getSceneY()));
List<TouchPoint> tpis = event.getTouchPoints().stream().filter(tp -> tp.getId() == this.touchID).collect(Collectors.toList());
if (tpis.isEmpty()) {
return; // something is wrong with primary touchpoint, give up
}
TouchPoint tpB = tpis.get(0);
Point2D posB = new Point2D(this.getLayoutX(), this.getLayoutY());
this.dragStarted = false;
this.touchID = -1;
int tupleArity = 2;
List<DataTypeInfo.Constructor> constrs = new ArrayList<>();
Type type = DrawWire.this.anchor.getFreshType().getConcrete();
if (type instanceof TypeApp) {
List<Type> ts = ((TypeApp)type).asFlattenedAppChain();
if (ts.get(0) instanceof TupleTypeCon) {
tupleArity = ts.size()-1;
} else if (ts.get(0) instanceof TypeCon) {
HaskellCatalog catalog = toplevel.getGhciSession().getCatalog();
DataTypeInfo datatype = catalog.getDataType(((TypeCon)ts.get(0)).getName());
if (datatype != null && !datatype.isBuiltin()) {
constrs = datatype.getConstructors();
}
}
} else if (type instanceof TypeCon) {
HaskellCatalog catalog = toplevel.getGhciSession().getCatalog();
DataTypeInfo datatype = catalog.getDataType(((TypeCon)type).getName());
if (datatype != null && !datatype.isBuiltin()) {
constrs = datatype.getConstructors();
}
}
if (constrs.size() >= 2 && constrs.size() <= 5) {
ChoiceBlock choice = new ChoiceBlock(toplevel);
for (int i = 2; i < constrs.size(); i++) {
choice.addLane();
}
toplevel.addBlock(choice);
double offsetX = tpA.getX() < 0 ? -300 : -150;
if (DrawWire.this.anchor instanceof OutputAnchor) {
double posX = DrawWire.this.getEndX() + offsetX;
double posY = DrawWire.this.getEndY()-100;
choice.relocate(posX, posY);
choice.addExtraInput();
InputAnchor input = choice.getAllInputs().get(0);
Connection connection = DrawWire.this.buildConnectionTo(input);
if (connection != null) {
connection.getStartAnchor().initiateConnectionChanges();
}
List<Block> decons = new ArrayList<>();
List<Lane> lanes = choice.getLanes();
for (int i = 0; i < constrs.size(); i++) {
Lane lane = lanes.get(i);
MatchBlock mblock = new MatchBlock(toplevel, toplevel.getEnvInstance().lookupFun(constrs.get(i).getName()));
decons.add(mblock);
toplevel.addBlock(mblock);
OutputAnchor arg = (OutputAnchor)lane.getAllAnchors().get(0);
mblock.relocate(posX+125 + (i*250), posY + 75);
DrawWire tempWire = DrawWire.initiate(arg, null);
tempWire.buildConnectionTo(mblock.input);
tempWire.remove();
}
Platform.runLater(() -> {
for (Block block : decons) {
block.refreshContainer();
block.initiateConnectionChanges();
}
});
DrawWire.this.remove();
} else {
double posX = DrawWire.this.getEndX() + offsetX;
double posY = DrawWire.this.getEndY() - 450;
choice.relocate(posX, posY);
OutputAnchor output = choice.getAllOutputs().get(0);
Connection connection = DrawWire.this.buildConnectionTo(output);
if (connection != null) {
connection.getStartAnchor().initiateConnectionChanges();
}
List<Block> conses = new ArrayList<>();
List<Lane> lanes = choice.getLanes();
for (int i = 0; i < constrs.size(); i++) {
Lane lane = lanes.get(i);
FunApplyBlock cblock = new FunApplyBlock(toplevel, new LibraryFunUse(toplevel.getEnvInstance().lookupFun(constrs.get(i).getName())));
conses.add(cblock);
toplevel.addBlock(cblock);
cblock.initiateConnectionChanges();
InputAnchor res = (InputAnchor)lane.getAllAnchors().get(0);
cblock.relocate(posX+125 + (i*250), posY + 250);
DrawWire tempWire = DrawWire.initiate(res, null);
tempWire.buildConnectionTo(cblock.getAllOutputs().get(0));
tempWire.remove();
}
Platform.runLater(() -> {
for (Block block : conses) {
block.refreshContainer();
block.initiateConnectionChanges();
}
});
DrawWire.this.remove();
}
} else {
if (DrawWire.this.anchor instanceof OutputAnchor) {
Block block = new SplitterBlock(toplevel, tupleArity);
toplevel.addBlock(block);
double offsetX = tpA.getX() < 0 ? -75 : 75;
block.relocate(DrawWire.this.getEndX() + offsetX, DrawWire.this.getEndY()-100);
block.refreshContainer();
block.initiateConnectionChanges();
InputAnchor input = block.getAllInputs().get(0);
Connection connection = DrawWire.this.buildConnectionTo(input);
if (connection != null) {
connection.getStartAnchor().initiateConnectionChanges();
}
if (tpA.getX() < 0) {
DrawWire wireA = DrawWire.initiate(block.getAllOutputs().get(0), tpA);
wireA.toucharea.dragTo(posA.getX(), posA.getY());
DrawWire wireB = DrawWire.initiate(block.getAllOutputs().get(1), tpB);
wireB.toucharea.dragTo(posB.getX(), posB.getY());
} else {
DrawWire wireA = DrawWire.initiate(block.getAllOutputs().get(1), tpA);
wireA.toucharea.dragTo(posA.getX(), posA.getY());
DrawWire wireB = DrawWire.initiate(block.getAllOutputs().get(0), tpB);
wireB.toucharea.dragTo(posB.getX(), posB.getY());
}
DrawWire.this.remove();
} else {
Block block = new JoinerBlock(toplevel, tupleArity);
toplevel.addBlock(block);
double offsetX = tpA.getX() < 0 ? -75 : 75;
block.relocate(DrawWire.this.getStartX() + offsetX, DrawWire.this.getStartY()+100);
block.refreshContainer();
block.initiateConnectionChanges();
OutputAnchor input = block.getAllOutputs().get(0);
Connection connection = DrawWire.this.buildConnectionTo(input);
if (connection != null) {
connection.getStartAnchor().initiateConnectionChanges();
}
if (tpA.getX() < 0) {
DrawWire wireA = DrawWire.initiate(block.getAllInputs().get(0), tpA);
wireA.toucharea.dragTo(posA.getX(), posA.getY());
DrawWire wireB = DrawWire.initiate(block.getAllInputs().get(1), tpB);
wireB.toucharea.dragTo(posB.getX(), posB.getY());
} else {
DrawWire wireA = DrawWire.initiate(block.getAllInputs().get(1), tpA);
wireA.toucharea.dragTo(posA.getX(), posA.getY());
DrawWire wireB = DrawWire.initiate(block.getAllInputs().get(0), tpB);
wireB.toucharea.dragTo(posB.getX(), posB.getY());
}
DrawWire.this.remove();
}
}
}
private void verticalSplittingAction(TouchEvent event) {
ToplevelPane toplevel = DrawWire.this.anchor.block.getToplevel();
TouchPoint tpA = event.getTouchPoint();
Point2D posA = toplevel.sceneToLocal(new Point2D(tpA.getSceneX(), tpA.getSceneY()));
List<TouchPoint> tpis = event.getTouchPoints().stream().filter(tp -> tp.getId() == this.touchID).collect(Collectors.toList());
if (tpis.isEmpty()) {
return; // something is wrong with primary touchpoint, give up
}
TouchPoint tpB = tpis.get(0);
Point2D posB = new Point2D(this.getLayoutX(), this.getLayoutY());
this.dragStarted = false;
this.touchID = -1;
Type type = DrawWire.this.anchor.getFreshType();
int arity = type.countArguments();
if (arity < 1) {
return; // we only we with functions for now
}
if (DrawWire.this.anchor instanceof OutputAnchor) {
Block block = new FunApplyBlock(toplevel, new ApplyAnchor(arity));
toplevel.addBlock(block);
double offsetY = tpA.getY() < 0 ? -100 : 50;
block.relocate(DrawWire.this.getEndX() - 40, DrawWire.this.getEndY()+offsetY);
block.refreshContainer();
block.initiateConnectionChanges();
InputAnchor input = block.getAllInputs().get(0);
Connection connection = DrawWire.this.buildConnectionTo(input);
if (connection != null) {
connection.getStartAnchor().initiateConnectionChanges();
}
if (tpA.getY() < 0) {
DrawWire wireA = DrawWire.initiate(block.getAllInputs().get(1), tpA);
wireA.toucharea.dragTo(posA.getX(), posA.getY());
DrawWire wireB = DrawWire.initiate(block.getAllOutputs().get(0), tpB);
wireB.toucharea.dragTo(posB.getX(), posB.getY());
} else {
DrawWire wireA = DrawWire.initiate(block.getAllOutputs().get(0), tpA);
wireA.toucharea.dragTo(posA.getX(), posA.getY());
DrawWire wireB = DrawWire.initiate(block.getAllInputs().get(1), tpB);
wireB.toucharea.dragTo(posB.getX(), posB.getY());
}
DrawWire.this.remove();
} else {
LambdaBlock block = new LambdaBlock(toplevel, arity);
toplevel.addBlock(block);
double offsetY = tpA.getY() < 0 ? -240 : -100;
block.relocate(DrawWire.this.getStartX()-150, DrawWire.this.getStartY() + offsetY);
block.initiateConnectionChanges();
OutputAnchor input = block.getAllOutputs().get(0);
Connection connection = DrawWire.this.buildConnectionTo(input);
if (connection != null) {
connection.getStartAnchor().initiateConnectionChanges();
}
LambdaContainer body = block.getBody();
if (tpA.getY() < 0) {
DrawWire wireA = DrawWire.initiate(body.getAllAnchors().get(0), tpA);
wireA.toucharea.dragTo(posA.getX(), posA.getY());
DrawWire wireB = DrawWire.initiate(body.getAllAnchors().get(arity), tpB);
wireB.toucharea.dragTo(posB.getX(), posB.getY());
} else {
DrawWire wireA = DrawWire.initiate(body.getAllAnchors().get(arity), tpA);
wireA.toucharea.dragTo(posA.getX(), posA.getY());
DrawWire wireB = DrawWire.initiate(body.getAllAnchors().get(0), tpB);
wireB.toucharea.dragTo(posB.getX(), posB.getY());
}
DrawWire.this.remove();
}
}
}
}