From d1877d42c6d1f219721f90b91236f5d489853c01 Mon Sep 17 00:00:00 2001 From: Jonathan Leitschuh Date: Sun, 1 May 2016 16:51:13 -0400 Subject: [PATCH] Adds Dragging and Dropping Steps in the Pipeline --- .../main/java/edu/wpi/grip/core/Pipeline.java | 37 ++++-- .../java/edu/wpi/grip/core/PipelineTest.java | 30 +++++ .../edu/wpi/grip/ui/OperationController.java | 19 +-- .../edu/wpi/grip/ui/dragging/DragService.java | 21 +++- .../wpi/grip/ui/dragging/StepDragService.java | 15 +++ .../grip/ui/pipeline/PipelineController.java | 111 ++++++++++++------ .../grip/ui/pipeline/SocketHandleView.java | 10 +- .../wpi/grip/ui/pipeline/StepController.java | 15 +++ 8 files changed, 191 insertions(+), 67 deletions(-) create mode 100644 ui/src/main/java/edu/wpi/grip/ui/dragging/StepDragService.java diff --git a/core/src/main/java/edu/wpi/grip/core/Pipeline.java b/core/src/main/java/edu/wpi/grip/core/Pipeline.java index d2c318c629..ed9c157ab2 100644 --- a/core/src/main/java/edu/wpi/grip/core/Pipeline.java +++ b/core/src/main/java/edu/wpi/grip/core/Pipeline.java @@ -232,14 +232,13 @@ public void onSourceRemoved(SourceRemovedEvent event) { } /** - * Adds the step between two other steps. - * @param stepToAdd The step to add to the pipeline - * @param lower The step to be added above - * @param higher The step to be added below + * Finds the index between the two steps + * @param lower The lower step + * @param higher The higher step + * @return The index that is in between the two of these steps */ - public void addStepBetween(Step stepToAdd, @Nullable Step lower, @Nullable Step higher) { - checkNotNull(stepToAdd, "The step to add cannot be null"); - int index = readStepsSafely(steps -> { + private int indexBetween(@Nullable Step lower, @Nullable Step higher) { + return readStepsSafely(steps -> { // If not in the list these can return -1 int lowerIndex = steps.indexOf(lower); int upperIndex = steps.indexOf(higher); @@ -259,9 +258,33 @@ public void addStepBetween(Step stepToAdd, @Nullable Step lower, @Nullable Step return steps.size(); } }); + } + + /** + * Adds the step between two other steps. + * @param stepToAdd The step to add to the pipeline + * @param lower The step to be added above + * @param higher The step to be added below + */ + public void addStepBetween(Step stepToAdd, @Nullable Step lower, @Nullable Step higher) { + checkNotNull(stepToAdd, "The step to add cannot be null"); + final int index = indexBetween(lower, higher); addStep(index, stepToAdd); } + /** + * + * @param toMove The step to move + * @param lower The lower step + * @param higher The upper step + */ + public void moveStepBetween(Step toMove, @Nullable Step lower, @Nullable Step higher) { + checkNotNull(toMove, "The step to move cannot be null"); + final int index = indexBetween(lower, higher); + final int currentIndex = readStepsSafely(steps -> steps.indexOf(toMove)); + moveStep(toMove, index > currentIndex ? index - (currentIndex + 1 ) : index - currentIndex); + } + public void addStep(int index, Step step) { checkNotNull(step, "The step can not be null"); checkArgument(!step.removed(), "The step must not have been disabled already"); diff --git a/core/src/test/java/edu/wpi/grip/core/PipelineTest.java b/core/src/test/java/edu/wpi/grip/core/PipelineTest.java index 36b71757b2..b03f681106 100644 --- a/core/src/test/java/edu/wpi/grip/core/PipelineTest.java +++ b/core/src/test/java/edu/wpi/grip/core/PipelineTest.java @@ -335,4 +335,34 @@ public void testAddBetweenStepsOutOfOrder() { pipeline.addStepBetween(stepToAdd, upperStep, lowerStep); } + + @Test + public void testMoveStepToLeft() { + final Step + stepToMove = new MockStep(), + lowerStep = new MockStep(), + upperStep = new MockStep(); + pipeline.addStep(lowerStep); + pipeline.addStep(upperStep); + pipeline.addStep(stepToMove); + pipeline.moveStepBetween(stepToMove, lowerStep, upperStep); + + assertEquals("The step should have been moved within the pipeline", + Arrays.asList(lowerStep, stepToMove, upperStep), pipeline.getSteps()); + } + + @Test + public void testMoveStepToRight() { + final Step + stepToMove = new MockStep(), + lowerStep = new MockStep(), + upperStep = new MockStep(); + pipeline.addStep(stepToMove); + pipeline.addStep(lowerStep); + pipeline.addStep(upperStep); + pipeline.moveStepBetween(stepToMove, lowerStep, upperStep); + + assertEquals("The step should have been moved within the pipeline", + Arrays.asList(lowerStep, stepToMove, upperStep), pipeline.getSteps()); + } } diff --git a/ui/src/main/java/edu/wpi/grip/ui/OperationController.java b/ui/src/main/java/edu/wpi/grip/ui/OperationController.java index 7de3e7719c..5c63451fdf 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/OperationController.java +++ b/ui/src/main/java/edu/wpi/grip/ui/OperationController.java @@ -13,13 +13,8 @@ import javafx.scene.control.Tooltip; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.input.DataFormat; -import javafx.scene.input.Dragboard; -import javafx.scene.input.TransferMode; import javafx.scene.layout.GridPane; -import java.util.Collections; - /** * A JavaFX control that renders information about an {@link Operation}. This is used in the palette view to present * the user with information on the various operations to choose from. @@ -76,19 +71,15 @@ public void initialize() { root.setOnDragDetected(mouseEvent -> { - // Create a snapshot to use as the cursor - final ImageView preview = new ImageView(root.snapshot(null, null)); - - final Dragboard db = root.startDragAndDrop(TransferMode.ANY); - db.setContent(Collections.singletonMap(DataFormat.PLAIN_TEXT, operation.getName())); - db.setDragView(preview.getImage()); - // Begin the dragging - root.startFullDrag(); // Tell the drag service that this is the operation that will be received - operationDragService.beginDrag(operation); + operationDragService.beginDrag(operation, root, operation.getName()); mouseEvent.consume(); }); + + root.setOnDragDone(mouseEvent -> { + operationDragService.completeDrag(); + }); } @FXML diff --git a/ui/src/main/java/edu/wpi/grip/ui/dragging/DragService.java b/ui/src/main/java/edu/wpi/grip/ui/dragging/DragService.java index 2fc4302f4d..cb8e1981d4 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/dragging/DragService.java +++ b/ui/src/main/java/edu/wpi/grip/ui/dragging/DragService.java @@ -4,7 +4,13 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.Node; +import javafx.scene.image.ImageView; +import javafx.scene.input.DataFormat; +import javafx.scene.input.Dragboard; +import javafx.scene.input.TransferMode; +import java.util.Collections; import java.util.Optional; /** @@ -39,11 +45,22 @@ public Optional getValue() { } /** - * Begins the drag action + * Begins the drag action. + * Creates the dragboard on the root node and adds a + * snapshot of the root node as the view. * * @param value The value to be transferred during the drag. + * @param root The root node to drag + * @param name The name to set as the content of the dragboard */ - public void beginDrag(T value) { + public void beginDrag(T value, Node root, String name) { + // Create a snapshot to use as the cursor + final ImageView preview = new ImageView(root.snapshot(null, null)); + + final Dragboard db = root.startDragAndDrop(TransferMode.ANY); + db.setContent(Collections.singletonMap(DataFormat.PLAIN_TEXT, name)); + db.setDragView(preview.getImage()); + this.dragProperty.set(value); } diff --git a/ui/src/main/java/edu/wpi/grip/ui/dragging/StepDragService.java b/ui/src/main/java/edu/wpi/grip/ui/dragging/StepDragService.java new file mode 100644 index 0000000000..41c2e1155b --- /dev/null +++ b/ui/src/main/java/edu/wpi/grip/ui/dragging/StepDragService.java @@ -0,0 +1,15 @@ +package edu.wpi.grip.ui.dragging; + +import com.google.inject.Singleton; +import edu.wpi.grip.core.Step; + +/** + * Service for dragging and dropping a step + */ +@Singleton +public class StepDragService extends DragService { + + public StepDragService() { + super("step"); + } +} diff --git a/ui/src/main/java/edu/wpi/grip/ui/pipeline/PipelineController.java b/ui/src/main/java/edu/wpi/grip/ui/pipeline/PipelineController.java index 46f187e4b4..cca52b703c 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/pipeline/PipelineController.java +++ b/ui/src/main/java/edu/wpi/grip/ui/pipeline/PipelineController.java @@ -13,6 +13,7 @@ import edu.wpi.grip.core.sockets.OutputSocket; import edu.wpi.grip.ui.annotations.ParametrizedController; import edu.wpi.grip.ui.dragging.OperationDragService; +import edu.wpi.grip.ui.dragging.StepDragService; import edu.wpi.grip.ui.pipeline.input.InputSocketController; import edu.wpi.grip.ui.pipeline.source.SourceController; import edu.wpi.grip.ui.pipeline.source.SourceControllerFactory; @@ -31,6 +32,7 @@ import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; +import javax.annotation.Nullable; import javax.inject.Inject; import java.util.Collection; import java.util.Map; @@ -70,6 +72,8 @@ public final class PipelineController { private AddSourceView addSourceView; @Inject private OperationDragService operationDragService; + @Inject + private StepDragService stepDragService; private ControllerMap stepsMapManager; private ControllerMap sourceMapManager; @@ -98,55 +102,90 @@ public void initialize() throws Exception { dragEvent.acceptTransferModes(TransferMode.ANY); }); + stepDragService.getValue().ifPresent(step -> { + dragEvent.acceptTransferModes(TransferMode.ANY); + }); + }); stepBox.setOnDragDropped(mouseEvent -> { // If this is an operation being dropped operationDragService.getValue().ifPresent(operation -> { - // Then we need to figure out where to put it in the pipeline - // First create a map of every node in the steps list to its x position - final Map positionMapping = stepsMapManager - .entrySet() - .stream() - .collect( - Collectors - .toMap(e -> calculateMiddleXPosOfNodeInParent(e.getValue()), - Map.Entry::getValue)); - - // A tree map is an easy way to sort the values - final NavigableMap sortedPositionMapping - = new TreeMap<>(positionMapping); - - // Now we find the sockets that are to the immediate left and - // immediate right of the drop point - - // These can be null - final Map.Entry - lowerEntry = sortedPositionMapping.floorEntry(mouseEvent.getX()), - higherEntry = sortedPositionMapping.ceilingEntry(mouseEvent.getX()); - // These can be null - final StepController - lowerStepController = - lowerEntry == null ? - null : stepsMapManager.getWithNode(lowerEntry.getValue()); - final StepController - higherStepController = - higherEntry == null ? - null : stepsMapManager.getWithNode(higherEntry.getValue()); - final Step - lowerStep = lowerStepController == null ? null : lowerStepController.getStep(), - higherStep = higherStepController == null ? null : higherStepController.getStep(); - - operationDragService.completeDrag(); + final StepPair pair = lowerAndHigherStep(mouseEvent.getX()); // Add the new step to the pipeline between these two steps - pipeline.addStepBetween(stepFactory.create(operation), lowerStep, higherStep); + pipeline.addStepBetween(stepFactory.create(operation), pair.lower, pair.higher); + }); + + // If this is a step being dropped + stepDragService.getValue().ifPresent(step -> { + stepDragService.completeDrag(); + final StepPair pair = lowerAndHigherStep(mouseEvent.getX()); + // Move the new step to the pipeline between these two steps + pipeline.moveStepBetween(step, pair.lower, pair.higher); }); }); addSourcePane.getChildren().add(addSourceView); } + /** + * Simple class for returning two steps + */ + private static final class StepPair { + final Step lower; + final Step higher; + + StepPair(@Nullable Step lower, @Nullable Step higher) { + this.lower = lower; + this.higher = higher; + } + } + + /** + * Determines the steps (via the {@link StepController} that are above and below the given + * {@code x} value in the list of steps. + * + * @param x The x value to find what steps this is between + * @return The pair of steps that are above and below this x position. + */ + private StepPair lowerAndHigherStep(double x) { + // Then we need to figure out where to put it in the pipeline + // First create a map of every node in the steps list to its x position + final Map positionMapping = stepsMapManager + .entrySet() + .stream() + .collect( + Collectors + .toMap(e -> calculateMiddleXPosOfNodeInParent(e.getValue()), + Map.Entry::getValue)); + + // A tree map is an easy way to sort the values + final NavigableMap sortedPositionMapping + = new TreeMap<>(positionMapping); + + // Now we find the sockets that are to the immediate left and + // immediate right of the drop point + + // These can be null + final Map.Entry + lowerEntry = sortedPositionMapping.floorEntry(x), + higherEntry = sortedPositionMapping.ceilingEntry(x); + // These can be null + final StepController + lowerStepController = + lowerEntry == null ? + null : stepsMapManager.getWithNode(lowerEntry.getValue()); + final StepController + higherStepController = + higherEntry == null ? + null : stepsMapManager.getWithNode(higherEntry.getValue()); + return new StepPair( + lowerStepController == null ? null : lowerStepController.getStep(), + higherStepController == null ? null : higherStepController.getStep() + ); + } + private double calculateMiddleXPosOfNodeInParent(Node node) { return node.getLayoutX() + (node.getBoundsInParent().getWidth() / 2.); } diff --git a/ui/src/main/java/edu/wpi/grip/ui/pipeline/SocketHandleView.java b/ui/src/main/java/edu/wpi/grip/ui/pipeline/SocketHandleView.java index 3b3eddc161..9ec43e0ff4 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/pipeline/SocketHandleView.java +++ b/ui/src/main/java/edu/wpi/grip/ui/pipeline/SocketHandleView.java @@ -19,11 +19,8 @@ import javafx.css.PseudoClass; import javafx.scene.control.Button; import javafx.scene.control.Tooltip; -import javafx.scene.input.DataFormat; -import javafx.scene.input.Dragboard; import javafx.scene.input.TransferMode; -import java.util.Collections; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -95,12 +92,9 @@ public interface Factory { // When the user starts dragging a socket handle, starting forming a connection. This involves keeping a // reference to the SocketView that the drag started at. this.setOnDragDetected(mouseEvent -> { - Dragboard db = this.startDragAndDrop(TransferMode.ANY); - db.setContent(Collections.singletonMap(DataFormat.PLAIN_TEXT, "socket")); - mouseEvent.consume(); - this.connectingProperty.set(true); - socketDragService.beginDrag(this.socket); + socketDragService.beginDrag(this.socket, this, "socket"); + mouseEvent.consume(); }); // Remove the "connecting" property (which changes the appearance of the handle) when the user moves the cursor diff --git a/ui/src/main/java/edu/wpi/grip/ui/pipeline/StepController.java b/ui/src/main/java/edu/wpi/grip/ui/pipeline/StepController.java index 412a7d6e08..1a97c6ff63 100644 --- a/ui/src/main/java/edu/wpi/grip/ui/pipeline/StepController.java +++ b/ui/src/main/java/edu/wpi/grip/ui/pipeline/StepController.java @@ -8,6 +8,7 @@ import edu.wpi.grip.ui.Controller; import edu.wpi.grip.ui.annotations.ParametrizedController; import edu.wpi.grip.ui.components.ExceptionWitnessResponderButton; +import edu.wpi.grip.ui.dragging.StepDragService; import edu.wpi.grip.ui.pipeline.input.InputSocketController; import edu.wpi.grip.ui.pipeline.input.InputSocketControllerFactory; import edu.wpi.grip.ui.util.ControllerMap; @@ -47,6 +48,7 @@ public class StepController implements Controller { private final InputSocketControllerFactory inputSocketControllerFactory; private final OutputSocketController.Factory outputSocketControllerFactory; private final ExceptionWitnessResponderButton.Factory exceptionWitnessResponderButtonFactory; + private final StepDragService stepDragService; private final Step step; private ControllerMap inputSocketMapManager; @@ -67,11 +69,13 @@ public interface Factory { InputSocketControllerFactory inputSocketControllerFactory, OutputSocketController.Factory outputSocketControllerFactory, ExceptionWitnessResponderButton.Factory exceptionWitnessResponderButtonFactory, + StepDragService stepDragService, @Assisted Step step) { this.pipeline = pipeline; this.inputSocketControllerFactory = inputSocketControllerFactory; this.outputSocketControllerFactory = outputSocketControllerFactory; this.exceptionWitnessResponderButtonFactory = exceptionWitnessResponderButtonFactory; + this.stepDragService = stepDragService; this.step = step; } @@ -93,6 +97,17 @@ private void initialize() { for (OutputSocket outputSocket : step.getOutputSockets()) { outputSocketMapManager.add(outputSocketControllerFactory.create(outputSocket)); } + + root.setOnDragDetected(event -> { + stepDragService.beginDrag(this.step, root, "step"); + event.consume(); + }); + + root.setOnDragDone(event -> { + stepDragService.completeDrag(); + event.consume(); + }); + } /**