package nl.utwente.viskell.ui.components; import javafx.geometry.Point2D; import javafx.scene.input.MouseEvent; import javafx.scene.input.TouchEvent; import javafx.scene.layout.StackPane; import nl.utwente.viskell.haskell.type.Type; import nl.utwente.viskell.ui.BlockContainer; import nl.utwente.viskell.ui.ComponentLoader; import nl.utwente.viskell.ui.ToplevelPane; import nl.utwente.viskell.ui.serialize.Bundleable; /** * Represents an anchor of a Block that can connect to (1 or more) Connections. * * A ConnectionAnchor has an invisible part that acts as an enlargement of the touch zone. */ public abstract class ConnectionAnchor extends StackPane implements ComponentLoader, Bundleable { protected static final String BLOCK_LABEL = "block"; protected static final String ANCHOR_LABEL = "anchor"; /** Helper interface for finding the associated connection anchor on release a wire onto something. */ public interface Target { /** @return the connection anchor directly related to the Target object. */ ConnectionAnchor getAssociatedAnchor(); } /** The connection being drawn starting from this anchor, or null if none. */ private DrawWire wireInProgress; /** The wire we temporarily redirect mouse events to, or null if that isn't required */ private DrawWire eventRedirectionTarget; /** The block this ConnectionAnchor belongs to. */ protected Block block; /** * @param block The block this ConnectionAnchor belongs to. */ public ConnectionAnchor(Block block) { this.block = block; this.wireInProgress = null; this.eventRedirectionTarget = null; this.addEventHandler(MouseEvent.MOUSE_PRESSED, this::handleMousePress); this.addEventHandler(MouseEvent.MOUSE_DRAGGED, event -> { if (this.eventRedirectionTarget != null && !event.isSynthesized()) { this.eventRedirectionTarget.handleMouseDrag(event); } event.consume(); }); this.addEventHandler(MouseEvent.MOUSE_RELEASED, event -> { if (this.eventRedirectionTarget != null && !event.isSynthesized()) { this.eventRedirectionTarget.handleMouseRelease(event); this.eventRedirectionTarget = null; } event.consume(); }); this.addEventHandler(TouchEvent.TOUCH_PRESSED, this::handleTouchPress); } /** * Removes all the connections this anchor has. */ public abstract void removeConnections(); /** * @return True if this anchor has 1 or more connections. */ public abstract boolean hasConnection(); /** @return the location of where to attach wire in the coordinates of the toplevel pane. */ public abstract Point2D getAttachmentPoint(); /** * Make this anchor visually react to a draw wire getting nearby. * @param goodness 0 is neutral, negative is error causing, and positive is an connectable wire. */ protected abstract void setNearbyWireReaction(int goodness); /** * @return the wire is being drawn from this connection anchor, or null if none. */ public DrawWire getWireInProgress() { return this.wireInProgress; } /** * @param wire is being drawn from this connection anchor, or null if the drawing has finished/failed. */ protected void setWireInProgress(DrawWire wire) { this.wireInProgress = wire; } /** * @return The inner most block container associated with this anchor */ public abstract BlockContainer getContainer(); /** * Handle the Connection changes for the Block this anchor is attached to. * @param finalPhase whether the change propagation is in the second (final) phase. */ protected void handleConnectionChanges(boolean finalPhase) { this.block.handleConnectionChanges(finalPhase); } public abstract Type getFreshType(); private void handleMousePress(MouseEvent event) { if (this.wireInProgress == null && this.eventRedirectionTarget == null && !event.isSynthesized()) { this.eventRedirectionTarget = DrawWire.initiate(this, null); } event.consume(); } private void handleTouchPress(TouchEvent event) { if (this.wireInProgress == null) { DrawWire.initiate(this, event.getTouchPoint()); } event.consume(); } @Override public String toString() { return String.format("%s belonging to %s", this.getClass().getSimpleName(), this.block); } /** @return the UIPane of the attached block. */ public ToplevelPane getPane() { return this.block.getToplevel(); } }