Skip to content

Commit 69fd426

Browse files
committed
Operations are dropped in the position they are released in pipeline
Operations will now land in the location they were placed instead of just being appended to the end of the pipeline.
1 parent 4ce227b commit 69fd426

File tree

7 files changed

+312
-19
lines changed

7 files changed

+312
-19
lines changed

core/src/main/java/edu/wpi/grip/core/Pipeline.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import edu.wpi.grip.core.sockets.OutputSocket;
1515
import edu.wpi.grip.core.sockets.SocketHint;
1616

17+
import javax.annotation.Nullable;
1718
import java.util.*;
1819
import java.util.concurrent.locks.Lock;
1920
import java.util.concurrent.locks.ReadWriteLock;
@@ -230,6 +231,37 @@ public void onSourceRemoved(SourceRemovedEvent event) {
230231
}
231232
}
232233

234+
/**
235+
* Adds the step between two other steps.
236+
* @param stepToAdd The step to add to the pipeline
237+
* @param lower The step to be added above
238+
* @param higher The step to be added below
239+
*/
240+
public void addStepBetween(Step stepToAdd, @Nullable Step lower, @Nullable Step higher) {
241+
checkNotNull(stepToAdd, "The step to add cannot be null");
242+
int index = readStepsSafely(steps -> {
243+
// If not in the list these can return -1
244+
int lowerIndex = steps.indexOf(lower);
245+
int upperIndex = steps.indexOf(higher);
246+
if (lowerIndex != -1 && upperIndex != -1) {
247+
assert lowerIndex <= upperIndex : "Upper step was before lower index";
248+
int difference = Math.abs(upperIndex - lowerIndex); // Just in case
249+
// Place the step halfway between these two steps
250+
return lowerIndex + 1 + difference / 2;
251+
} else if (lowerIndex != -1) {
252+
// Place it above the lower one
253+
return lowerIndex + 1;
254+
} else if (upperIndex != -1) {
255+
// Place it below the upper one
256+
return upperIndex;
257+
} else {
258+
// Otherwise if they are both null place it at the end of the list
259+
return steps.size();
260+
}
261+
});
262+
addStep(index, stepToAdd);
263+
}
264+
233265
public void addStep(int index, Step step) {
234266
checkNotNull(step, "The step can not be null");
235267
checkArgument(!step.removed(), "The step must not have been disabled already");

core/src/test/java/edu/wpi/grip/core/PipelineTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,4 +275,64 @@ public void testCannotConnectIncompatibleTypes() {
275275

276276
assertFalse("Should not be able to connect incompatible types", pipeline.canConnect((OutputSocket) b, (InputSocket) a));
277277
}
278+
279+
@Test
280+
public void testAddBetweenSteps() {
281+
final Step
282+
stepToAdd = new MockStep(),
283+
lowerStep = new MockStep(),
284+
upperStep = new MockStep();
285+
pipeline.addStep(lowerStep);
286+
pipeline.addStep(upperStep);
287+
288+
pipeline.addStepBetween(stepToAdd, lowerStep, upperStep);
289+
assertEquals("The step was not added to the middle of the pipeline",
290+
Arrays.asList(lowerStep, stepToAdd, upperStep), pipeline.getSteps());
291+
}
292+
293+
@Test
294+
public void testAddBetweenNullAndStep() {
295+
final Step
296+
stepToAdd = new MockStep(),
297+
lowerStep = new MockStep(),
298+
upperStep = new MockStep();
299+
pipeline.addStep(lowerStep);
300+
pipeline.addStep(upperStep);
301+
pipeline.addStepBetween(stepToAdd, null, lowerStep);
302+
assertEquals("The step was not added to the begining of the pipeline",
303+
Arrays.asList(stepToAdd, lowerStep, upperStep), pipeline.getSteps());
304+
}
305+
306+
@Test
307+
public void testAddBetweenStepAndNull() {
308+
final Step
309+
stepToAdd = new MockStep(),
310+
lowerStep = new MockStep(),
311+
upperStep = new MockStep();
312+
pipeline.addStep(lowerStep);
313+
pipeline.addStep(upperStep);
314+
pipeline.addStepBetween(stepToAdd, upperStep, null);
315+
assertEquals("The step was not added to the end of the pipeline",
316+
Arrays.asList(lowerStep, upperStep, stepToAdd), pipeline.getSteps());
317+
}
318+
319+
@Test
320+
public void testAddBetweenTwoNulls() {
321+
final Step stepToAdd = new MockStep();
322+
pipeline.addStepBetween(stepToAdd, null, null);
323+
assertEquals("The step should have been added to the pipeline",
324+
Collections.singletonList(stepToAdd), pipeline.getSteps());
325+
}
326+
327+
@Test(expected = AssertionError.class)
328+
public void testAddBetweenStepsOutOfOrder() {
329+
final Step
330+
stepToAdd = new MockStep(),
331+
lowerStep = new MockStep(),
332+
upperStep = new MockStep();
333+
pipeline.addStep(lowerStep);
334+
pipeline.addStep(upperStep);
335+
336+
pipeline.addStepBetween(stepToAdd, upperStep, lowerStep);
337+
}
278338
}

ui/src/main/java/edu/wpi/grip/ui/pipeline/PipelineController.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333

3434
import javax.inject.Inject;
3535
import java.util.Collection;
36+
import java.util.Map;
37+
import java.util.NavigableMap;
38+
import java.util.TreeMap;
39+
import java.util.stream.Collectors;
3640

3741
/**
3842
* A JavaFX controller for the pipeline. This controller renders a list of steps.
@@ -97,15 +101,56 @@ public void initialize() throws Exception {
97101
});
98102

99103
stepBox.setOnDragDropped(mouseEvent -> {
104+
// If this is an operation being dropped
100105
operationDragService.getValue().ifPresent(operation -> {
106+
// Then we need to figure out where to put it in the pipeline
107+
// First create a map of every node in the steps list to its x position
108+
final Map<Double, Node> positionMapping = stepsMapManager
109+
.entrySet()
110+
.stream()
111+
.collect(
112+
Collectors
113+
.toMap(e -> calculateMiddleXPosOfNodeInParent(e.getValue()),
114+
Map.Entry::getValue));
115+
116+
// A tree map is an easy way to sort the values
117+
final NavigableMap<Double, Node> sortedPositionMapping
118+
= new TreeMap<>(positionMapping);
119+
120+
// Now we find the sockets that are to the immediate left and
121+
// immediate right of the drop point
122+
123+
// These can be null
124+
final Map.Entry<Double, Node>
125+
lowerEntry = sortedPositionMapping.floorEntry(mouseEvent.getX()),
126+
higherEntry = sortedPositionMapping.ceilingEntry(mouseEvent.getX());
127+
// These can be null
128+
final StepController
129+
lowerStepController =
130+
lowerEntry == null ?
131+
null : stepsMapManager.getWithNode(lowerEntry.getValue());
132+
final StepController
133+
higherStepController =
134+
higherEntry == null ?
135+
null : stepsMapManager.getWithNode(higherEntry.getValue());
136+
final Step
137+
lowerStep = lowerStepController == null ? null : lowerStepController.getStep(),
138+
higherStep = higherStepController == null ? null : higherStepController.getStep();
139+
140+
101141
operationDragService.completeDrag();
102-
pipeline.addStep(stepFactory.create(operation));
142+
// Add the new step to the pipeline between these two steps
143+
pipeline.addStepBetween(stepFactory.create(operation), lowerStep, higherStep);
103144
});
104145
});
105146

106147
addSourcePane.getChildren().add(addSourceView);
107148
}
108149

150+
private double calculateMiddleXPosOfNodeInParent(Node node) {
151+
return node.getLayoutX() + (node.getBoundsInParent().getWidth() / 2.);
152+
}
153+
109154
/**
110155
* @return An unmodifiable list of {@link SourceController} corresponding to all of the sources in the pipeline
111156
*/

ui/src/main/java/edu/wpi/grip/ui/util/StyleClassNameUtility.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,34 @@ public static String idNameFor(Operation operation) {
3131
* @return The CSS class for the step in the pipeline. To use as a css selector then prepend the string with a '.'
3232
*/
3333
public static String classNameFor(Step step) {
34-
return shortNameFor(step.getOperation()).append("-step").toString();
34+
return classNameForStepHolding(step.getOperation());
35+
}
36+
37+
/**
38+
* Return the CSS Class name for a {@link Step} that holds this {@link Operation}
39+
*
40+
* @param operation The operation to get the step's class name for
41+
* @return The CSS class for this step. To use as a css selector then prepend the string with a '.'
42+
*/
43+
public static String classNameForStepHolding(Operation operation) {
44+
return shortNameFor(operation).append("-step").toString();
45+
}
46+
47+
public static String cssSelectorForOutputSocketHandleOnStepHolding(Operation operation) {
48+
return ".pipeline ." + classNameForStepHolding(operation) + " .socket-handle.output";
49+
}
50+
51+
public static String cssSelectorForInputSocketHandleOnStepHolding(Operation operation) {
52+
return ".pipeline ." + classNameForStepHolding(operation) + " .socket-handle.input";
53+
}
54+
55+
56+
public static String cssSelectorForOutputSocketHandleOn(Step step) {
57+
return cssSelectorForOutputSocketHandleOnStepHolding(step.getOperation());
58+
}
59+
60+
public static String cssSelectorForInputSocketHandleOn(Step step) {
61+
return cssSelectorForInputSocketHandleOnStepHolding(step.getOperation());
3562
}
3663

3764
/**

ui/src/test/java/edu/wpi/grip/ui/MainWindowTest.java

Lines changed: 108 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,145 @@
11
package edu.wpi.grip.ui;
22

33

4-
import edu.wpi.grip.core.AdditionOperation;
5-
import edu.wpi.grip.core.PipelineRunner;
4+
import com.google.common.eventbus.EventBus;
5+
import com.google.inject.Guice;
6+
import com.google.inject.Injector;
7+
import com.google.inject.util.Modules;
8+
import edu.wpi.grip.core.*;
69
import edu.wpi.grip.core.events.OperationAddedEvent;
10+
import edu.wpi.grip.ui.util.DPIUtility;
11+
import edu.wpi.grip.ui.util.StyleClassNameUtility;
12+
import edu.wpi.grip.util.GRIPCoreTestModule;
13+
import javafx.fxml.FXMLLoader;
14+
import javafx.scene.Parent;
15+
import javafx.scene.Scene;
716
import javafx.stage.Stage;
817
import org.junit.After;
9-
import org.junit.Ignore;
1018
import org.junit.Test;
1119
import org.testfx.framework.junit.ApplicationTest;
1220
import org.testfx.matcher.base.NodeMatchers;
1321
import org.testfx.util.WaitForAsyncUtils;
1422

23+
import static org.junit.Assert.assertEquals;
1524
import static org.testfx.api.FxAssert.verifyThat;
1625

17-
26+
@SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert")
1827
public class MainWindowTest extends ApplicationTest {
19-
28+
private static final String STEP_NOT_ADDED_MSG = "Step was not added to pipeline";
29+
private final GRIPCoreTestModule testModule = new GRIPCoreTestModule();
30+
private Pipeline pipeline;
2031
private PipelineRunner pipelineRunner;
32+
private Operation addOperation;
33+
private AdditionOperation additionOperation;
2134

2235
@Override
2336
@SuppressWarnings("PMD.SignatureDeclareThrowsException")
2437
public void start(Stage stage) throws Exception {
25-
final Main main = new Main();
26-
main.start(stage);
38+
testModule.setUp();
39+
40+
Injector injector = Guice.createInjector(Modules.override(testModule).with(new GRIPUIModule()));
41+
42+
final Parent root =
43+
FXMLLoader.load(Main.class.getResource("MainWindow.fxml"), null, null, injector::getInstance);
44+
root.setStyle("-fx-font-size: " + DPIUtility.FONT_SIZE + "px");
45+
46+
pipeline = injector.getInstance(Pipeline.class);
47+
pipelineRunner = injector.getInstance(PipelineRunner.class);
48+
final EventBus eventBus = injector.getInstance(EventBus.class);
2749

28-
final OperationListController operationList = main.injector.getInstance(OperationListController.class);
29-
pipelineRunner = main.injector.getInstance(PipelineRunner.class);
30-
operationList.clearOperations();
31-
operationList.onOperationAdded(new OperationAddedEvent(new AdditionOperation()));
50+
addOperation = new AddOperation();
51+
additionOperation = new AdditionOperation();
52+
53+
eventBus.post(new OperationAddedEvent(addOperation));
54+
eventBus.post(new OperationAddedEvent(additionOperation));
55+
56+
stage.setScene(new Scene(root));
57+
stage.show();
3258
}
3359

3460
@After
3561
public void tearDown() {
62+
testModule.tearDown();
3663
pipelineRunner.stopAsync().awaitTerminated();
3764
}
3865

3966
@Test
40-
@Ignore
41-
@SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert")
4267
public void testShouldCreateNewOperationInPipelineView() {
4368
// Given:
44-
clickOn("#add-operation");
69+
clickOn(addOperation.getName());
70+
71+
WaitForAsyncUtils.waitForFxEvents();
72+
73+
// Then:
74+
final String cssSelector = "." + StyleClassNameUtility.classNameForStepHolding(addOperation);
75+
verifyThat(cssSelector, NodeMatchers.isNotNull());
76+
verifyThat(cssSelector, NodeMatchers.isVisible());
77+
78+
assertEquals(STEP_NOT_ADDED_MSG, 1, pipeline.getSteps().size());
79+
assertEquals("Step added was not this addOperation", addOperation, pipeline.getSteps().get(0).getOperation());
80+
}
81+
82+
@Test
83+
public void testDragOperationFromPaletteToPipeline() {
84+
// Given:
85+
drag(addOperation.getName())
86+
.dropTo(".steps");
4587

4688
WaitForAsyncUtils.waitForFxEvents();
4789

4890
// Then:
49-
verifyThat(".pipeline", NodeMatchers.hasChild(".add-step"));
91+
final String cssSelector = "." + StyleClassNameUtility.classNameForStepHolding(addOperation);
92+
verifyThat(cssSelector, NodeMatchers.isNotNull());
93+
verifyThat(cssSelector, NodeMatchers.isVisible());
94+
95+
assertEquals(STEP_NOT_ADDED_MSG, 1, pipeline.getSteps().size());
96+
assertEquals("Step added was not this addOperation", addOperation, pipeline.getSteps().get(0).getOperation());
97+
}
98+
99+
@Test
100+
public void testDragOperationFromPaletteToLeftOfExistingStep() {
101+
// Run the same test as before
102+
testDragOperationFromPaletteToPipeline();
103+
104+
// Now add a second step before it
105+
drag(additionOperation.getName())
106+
// We drag to the input socket hint handle because this will always be on the left side of the
107+
// step. This should cause the UI to put the new step on the left side
108+
.dropTo(StyleClassNameUtility.cssSelectorForInputSocketHandleOnStepHolding(addOperation));
109+
110+
WaitForAsyncUtils.waitForFxEvents();
111+
112+
// Then:
113+
final String cssSelector = "." + StyleClassNameUtility.classNameForStepHolding(additionOperation);
114+
verifyThat(cssSelector, NodeMatchers.isNotNull());
115+
verifyThat(cssSelector, NodeMatchers.isVisible());
116+
117+
assertEquals(STEP_NOT_ADDED_MSG, 2, pipeline.getSteps().size());
118+
assertEquals("Step added was not added in the right place in the pipeline",
119+
additionOperation, pipeline.getSteps().get(0).getOperation());
120+
}
121+
122+
@Test
123+
public void testDragOperationFromPaletteToRightOfExistingStep() {
124+
// Run the same test as before
125+
testDragOperationFromPaletteToPipeline();
126+
127+
// Now add a second step after it
128+
drag(additionOperation.getName())
129+
// We drag to the output socket hint handle because this will always be on the right side of the
130+
// step. This should cause the UI to put the new step on the right side
131+
.dropTo(StyleClassNameUtility.cssSelectorForOutputSocketHandleOnStepHolding(addOperation));
132+
133+
WaitForAsyncUtils.waitForFxEvents();
134+
135+
// Then:
136+
final String cssSelector = "." + StyleClassNameUtility.classNameForStepHolding(additionOperation);
137+
verifyThat(cssSelector, NodeMatchers.isNotNull());
138+
verifyThat(cssSelector, NodeMatchers.isVisible());
139+
140+
assertEquals(STEP_NOT_ADDED_MSG, 2, pipeline.getSteps().size());
141+
assertEquals("Step added was not added in the right place in the pipeline",
142+
additionOperation, pipeline.getSteps().get(1).getOperation());
50143
}
51144

52145

ui/src/test/java/edu/wpi/grip/ui/pipeline/PipelineUITest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ public void testConnectingTwoOperations() {
7676
Step subtractStep = addOperation(1, subtractionOperation);
7777

7878
// When
79-
drag(".pipeline ." + StyleClassNameUtility.classNameFor(addStep) + " .socket-handle.output", MouseButton.PRIMARY)
80-
.dropTo(".pipeline ." + StyleClassNameUtility.classNameFor(subtractStep) + " .socket-handle.input");
79+
drag(StyleClassNameUtility.cssSelectorForOutputSocketHandleOn(addStep), MouseButton.PRIMARY)
80+
.dropTo(StyleClassNameUtility.cssSelectorForInputSocketHandleOn(subtractStep));
8181

8282
// Then
8383
Connection connection = assertStepConnected("The add step did not connect to the subtract step", addStep, subtractStep);

0 commit comments

Comments
 (0)