Skip to content

Commit 4ce227b

Browse files
committed
Adds Basic Drag & Drop Support for Operations
1 parent 95c28f1 commit 4ce227b

File tree

8 files changed

+164
-37
lines changed

8 files changed

+164
-37
lines changed

ui/src/main/java/edu/wpi/grip/ui/OperationController.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,20 @@
66
import edu.wpi.grip.core.Pipeline;
77
import edu.wpi.grip.core.Step;
88
import edu.wpi.grip.ui.annotations.ParametrizedController;
9+
import edu.wpi.grip.ui.dragging.OperationDragService;
910
import edu.wpi.grip.ui.util.StyleClassNameUtility;
1011
import javafx.fxml.FXML;
1112
import javafx.scene.control.Label;
1213
import javafx.scene.control.Tooltip;
1314
import javafx.scene.image.Image;
1415
import javafx.scene.image.ImageView;
16+
import javafx.scene.input.DataFormat;
17+
import javafx.scene.input.Dragboard;
18+
import javafx.scene.input.TransferMode;
1519
import javafx.scene.layout.GridPane;
1620

21+
import java.util.Collections;
22+
1723
/**
1824
* A JavaFX control that renders information about an {@link Operation}. This is used in the palette view to present
1925
* the user with information on the various operations to choose from.
@@ -35,16 +41,18 @@ public class OperationController implements Controller {
3541

3642
private final Pipeline pipeline;
3743
private final Step.Factory stepFactory;
44+
private final OperationDragService operationDragService;
3845
private final Operation operation;
3946

4047
public interface Factory {
4148
OperationController create(Operation operation);
4249
}
4350

4451
@Inject
45-
OperationController(Pipeline pipeline, Step.Factory stepFactory, @Assisted Operation operation) {
52+
OperationController(Pipeline pipeline, Step.Factory stepFactory, OperationDragService operationDragService, @Assisted Operation operation) {
4653
this.pipeline = pipeline;
4754
this.stepFactory = stepFactory;
55+
this.operationDragService = operationDragService;
4856
this.operation = operation;
4957
}
5058

@@ -65,6 +73,22 @@ public void initialize() {
6573

6674
// Ensures that when this element is hidden that it also removes its size calculations
6775
root.managedProperty().bind(root.visibleProperty());
76+
77+
78+
root.setOnDragDetected(mouseEvent -> {
79+
// Create a snapshot to use as the cursor
80+
final ImageView preview = new ImageView(root.snapshot(null, null));
81+
82+
final Dragboard db = root.startDragAndDrop(TransferMode.ANY);
83+
db.setContent(Collections.singletonMap(DataFormat.PLAIN_TEXT, operation.getName()));
84+
db.setDragView(preview.getImage());
85+
// Begin the dragging
86+
root.startFullDrag();
87+
// Tell the drag service that this is the operation that will be received
88+
operationDragService.beginDrag(operation);
89+
90+
mouseEvent.consume();
91+
});
6892
}
6993

7094
@FXML
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package edu.wpi.grip.ui.dragging;
2+
3+
4+
import javafx.beans.property.ObjectProperty;
5+
import javafx.beans.property.ReadOnlyObjectProperty;
6+
import javafx.beans.property.SimpleObjectProperty;
7+
8+
import java.util.Optional;
9+
10+
/**
11+
* A service to provide data transfer capabilities between two controllers.
12+
* Concrete versions of the service are usually {@link com.google.inject.Singleton Singletons}
13+
* so that they can be injected into the two controllers.
14+
*
15+
* @param <T> The value that the object property holds.
16+
*/
17+
public abstract class DragService<T> {
18+
private final ObjectProperty<T> dragProperty;
19+
20+
/**
21+
* @param name The name for the {@link SimpleObjectProperty}
22+
*/
23+
public DragService(String name) {
24+
this.dragProperty = new SimpleObjectProperty<>(this, name);
25+
}
26+
27+
/**
28+
* @return The read only version of this object property.
29+
*/
30+
public ReadOnlyObjectProperty<T> getDragProperty() {
31+
return dragProperty;
32+
}
33+
34+
/**
35+
* @return The value stored in the object property.
36+
*/
37+
public Optional<T> getValue() {
38+
return Optional.ofNullable(dragProperty.get());
39+
}
40+
41+
/**
42+
* Begins the drag action
43+
*
44+
* @param value The value to be transferred during the drag.
45+
*/
46+
public void beginDrag(T value) {
47+
this.dragProperty.set(value);
48+
}
49+
50+
/**
51+
* This should be called when the drag action is complete.
52+
*/
53+
public void completeDrag() {
54+
dragProperty.setValue(null);
55+
}
56+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package edu.wpi.grip.ui.dragging;
2+
3+
4+
import com.google.inject.Singleton;
5+
import edu.wpi.grip.core.Operation;
6+
7+
/**
8+
* Service for dragging an {@link Operation} from the {@link edu.wpi.grip.ui.pipeline.PipelineController}
9+
* to the {@link edu.wpi.grip.ui.pipeline.PipelineController}.
10+
*/
11+
@Singleton
12+
public class OperationDragService extends DragService<Operation> {
13+
14+
public OperationDragService() {
15+
super("operation");
16+
}
17+
}

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@
44
import com.google.common.eventbus.Subscribe;
55
import com.google.inject.Singleton;
66
import com.sun.javafx.application.PlatformImpl;
7-
import edu.wpi.grip.core.*;
7+
import edu.wpi.grip.core.Connection;
8+
import edu.wpi.grip.core.Pipeline;
9+
import edu.wpi.grip.core.Source;
10+
import edu.wpi.grip.core.Step;
811
import edu.wpi.grip.core.events.*;
912
import edu.wpi.grip.core.sockets.InputSocket;
1013
import edu.wpi.grip.core.sockets.OutputSocket;
1114
import edu.wpi.grip.ui.annotations.ParametrizedController;
15+
import edu.wpi.grip.ui.dragging.OperationDragService;
1216
import edu.wpi.grip.ui.pipeline.input.InputSocketController;
1317
import edu.wpi.grip.ui.pipeline.source.SourceController;
1418
import edu.wpi.grip.ui.pipeline.source.SourceControllerFactory;
@@ -22,6 +26,7 @@
2226
import javafx.scene.Group;
2327
import javafx.scene.Node;
2428
import javafx.scene.Parent;
29+
import javafx.scene.input.TransferMode;
2530
import javafx.scene.layout.HBox;
2631
import javafx.scene.layout.Pane;
2732
import javafx.scene.layout.VBox;
@@ -54,9 +59,13 @@ public final class PipelineController {
5459
@Inject
5560
private SourceControllerFactory sourceControllerFactory;
5661
@Inject
62+
private Step.Factory stepFactory;
63+
@Inject
5764
private StepController.Factory stepControllerFactory;
5865
@Inject
5966
private AddSourceView addSourceView;
67+
@Inject
68+
private OperationDragService operationDragService;
6069

6170
private ControllerMap<StepController, Node> stepsMapManager;
6271
private ControllerMap<SourceController, Node> sourceMapManager;
@@ -80,6 +89,20 @@ public void initialize() throws Exception {
8089
stepsMapManager.add(stepController);
8190
});
8291

92+
stepBox.setOnDragOver(dragEvent -> {
93+
operationDragService.getValue().ifPresent(operation -> {
94+
dragEvent.acceptTransferModes(TransferMode.ANY);
95+
});
96+
97+
});
98+
99+
stepBox.setOnDragDropped(mouseEvent -> {
100+
operationDragService.getValue().ifPresent(operation -> {
101+
operationDragService.completeDrag();
102+
pipeline.addStep(stepFactory.create(operation));
103+
});
104+
});
105+
83106
addSourcePane.getChildren().add(addSourceView);
84107
}
85108

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

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@
55
import com.google.inject.Inject;
66
import com.google.inject.Singleton;
77
import com.google.inject.assistedinject.Assisted;
8-
import edu.wpi.grip.core.*;
8+
import edu.wpi.grip.core.Connection;
9+
import edu.wpi.grip.core.Pipeline;
910
import edu.wpi.grip.core.events.ConnectionAddedEvent;
1011
import edu.wpi.grip.core.events.ConnectionRemovedEvent;
1112
import edu.wpi.grip.core.events.SocketConnectedChangedEvent;
1213
import edu.wpi.grip.core.sockets.InputSocket;
1314
import edu.wpi.grip.core.sockets.OutputSocket;
1415
import edu.wpi.grip.core.sockets.Socket;
16+
import edu.wpi.grip.ui.dragging.DragService;
1517
import javafx.beans.property.BooleanProperty;
16-
import javafx.beans.property.ObjectProperty;
1718
import javafx.beans.property.SimpleBooleanProperty;
18-
import javafx.beans.property.SimpleObjectProperty;
1919
import javafx.css.PseudoClass;
2020
import javafx.scene.control.Button;
2121
import javafx.scene.control.Tooltip;
@@ -24,6 +24,7 @@
2424
import javafx.scene.input.TransferMode;
2525

2626
import java.util.Collections;
27+
import java.util.Optional;
2728
import java.util.Set;
2829
import java.util.stream.Collectors;
2930

@@ -46,8 +47,10 @@ public class SocketHandleView extends Button {
4647
* connection to be made.
4748
*/
4849
@Singleton
49-
protected static final class DragService {
50-
private final ObjectProperty<Socket> socket = new SimpleObjectProperty<>(this, "socket");
50+
protected static final class SocketDragService extends DragService<Socket> {
51+
public SocketDragService() {
52+
super("socket");
53+
}
5154
}
5255

5356
final private BooleanProperty connectingProperty = new SimpleBooleanProperty(this, "connecting", false);
@@ -61,7 +64,7 @@ public interface Factory {
6164
SocketHandleView(EventBus eventBus,
6265
Pipeline pipeline,
6366
Connection.Factory<Object> connectionFactory,
64-
DragService dragService,
67+
SocketDragService socketDragService,
6568
@Assisted Socket socket) {
6669
this.eventBus = eventBus;
6770
this.socket = socket;
@@ -97,27 +100,27 @@ public interface Factory {
97100
mouseEvent.consume();
98101

99102
this.connectingProperty.set(true);
100-
dragService.socket.setValue(this.socket);
103+
socketDragService.beginDrag(this.socket);
101104
});
102105

103106
// Remove the "connecting" property (which changes the appearance of the handle) when the user moves the cursor
104107
// out of the socket handle or completes the drag. If the user moves the cursor away, it only disables the
105108
// connecting property if it's not the socket that the user started dragging from, since that one is supposed
106109
// to be dragged away.
107110
this.setOnDragExited(dragEvent -> {
108-
Socket<?> other = dragService.socket.getValue();
109-
if (other != null) this.connectingProperty.set(this.socket == other);
111+
socketDragService.getValue().ifPresent( other -> {
112+
this.connectingProperty.set(this.socket == other);
113+
});
110114
});
111115

112116
this.setOnDragDone(dragEvent -> {
113117
this.connectingProperty.set(false);
114-
dragService.socket.setValue(null);
118+
socketDragService.completeDrag();
115119
});
116120

117121
// When the user drops the connection onto another socket, add a new connection.
118122
this.setOnDragDropped(dragEvent -> {
119-
Socket<?> other = dragService.socket.getValue();
120-
if (other != null) {
123+
socketDragService.getValue().ifPresent(other -> {
121124
InputSocket inputSocket;
122125
OutputSocket outputSocket;
123126

@@ -139,23 +142,24 @@ public interface Factory {
139142
}
140143
final Connection connection = connectionFactory.create(outputSocket, inputSocket);
141144
eventBus.post(new ConnectionAddedEvent(connection));
142-
}
145+
});
143146
});
144147

145148
// Accept a drag event if it's possible to connect the two sockets
146149
this.setOnDragOver(dragEvent -> {
147-
Socket<?> other = dragService.socket.getValue();
148-
if (other != null && pipeline.canConnect(this.socket, other)) {
149-
dragEvent.acceptTransferModes(TransferMode.ANY);
150-
this.connectingProperty.set(true);
151-
}
150+
socketDragService.getValue().ifPresent(other -> {
151+
if (pipeline.canConnect(this.socket, other)) {
152+
dragEvent.acceptTransferModes(TransferMode.ANY);
153+
this.connectingProperty.set(true);
154+
}
155+
});
152156
});
153157

154158
// While dragging, disable any socket that we can't drag onto, providing visual feedback to the user about
155159
// what can be connected.
156-
dragService.socket.addListener(observable -> {
157-
Socket<?> other = dragService.socket.getValue();
158-
pseudoClassStateChanged(DISABLED_PSEUDO_CLASS, other != null && !pipeline.canConnect(socket, other));
160+
socketDragService.getDragProperty().addListener(observable -> {
161+
Optional<Socket> other = socketDragService.getValue();
162+
pseudoClassStateChanged(DISABLED_PSEUDO_CLASS, other.isPresent() && !pipeline.canConnect(socket, other.get()));
159163
});
160164
}
161165

ui/src/main/resources/edu/wpi/grip/ui/MainWindow.fxml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@
139139
<fx:include source="preview/Previews.fxml"/>
140140
<fx:include source="Palette.fxml"/>
141141
</SplitPane>
142-
<ScrollPane fx:id="bottomPane">
142+
<ScrollPane fx:id="bottomPane" fitToHeight="true" fitToWidth="true">
143143
<fx:include source="pipeline/Pipeline.fxml" fx:id="pipelineView"/>
144144
</ScrollPane>
145145
</items>
Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,30 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22

3+
<?import java.lang.*?>
4+
<?import javafx.scene.*?>
5+
<?import javafx.scene.control.*?>
6+
<?import javafx.scene.shape.*?>
37
<?import javafx.scene.control.Label?>
48
<?import javafx.scene.control.Separator?>
59
<?import javafx.scene.Group?>
610
<?import javafx.scene.layout.*?>
711
<?import javafx.scene.shape.Rectangle?>
8-
<StackPane maxWidth="Infinity" maxHeight="Infinity" fx:id="root"
9-
fx:controller="edu.wpi.grip.ui.pipeline.PipelineController"
10-
styleClass="pipeline" xmlns:fx="http://javafx.com/fxml/1">
12+
13+
<StackPane fx:id="root" maxHeight="Infinity" maxWidth="Infinity" styleClass="pipeline" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="edu.wpi.grip.ui.pipeline.PipelineController">
1114
<children>
12-
<HBox>
13-
<VBox fillWidth="true">
14-
<Label styleClass="pane-title" text="Sources" maxWidth="Infinity"/>
15-
<Separator orientation="HORIZONTAL"/>
16-
<Pane fx:id="addSourcePane"/>
17-
<VBox fx:id="sourcesBox" styleClass="sources"/>
15+
<HBox maxWidth="1.7976931348623157E308">
16+
<VBox fillWidth="true" maxWidth="1.7976931348623157E308">
17+
<Label maxWidth="Infinity" styleClass="pane-title" text="Sources" />
18+
<Separator orientation="HORIZONTAL" />
19+
<Pane fx:id="addSourcePane" />
20+
<VBox fx:id="sourcesBox" styleClass="sources" />
1821
</VBox>
19-
<Separator orientation="VERTICAL"/>
20-
<HBox fx:id="stepBox" styleClass="steps" fillHeight="false"/>
22+
<Separator orientation="VERTICAL" />
23+
<HBox fx:id="stepBox" fillHeight="false" maxWidth="1.7976931348623157E308" styleClass="steps" HBox.hgrow="ALWAYS" />
2124
</HBox>
2225
<Group fx:id="connections" mouseTransparent="true">
2326
<StackPane.alignment>TOP_LEFT</StackPane.alignment>
24-
<Rectangle x="0" y="0" width="1" height="1" opacity="0"/>
27+
<Rectangle height="1" opacity="0" width="1" x="0" y="0" />
2528
</Group>
2629
</children>
2730
</StackPane>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public void start(Stage stage) {
3434
initiallyPreviewedOutputSocket = new InitiallyPreviewedOutputSocket("Initially previewed");
3535

3636
final SocketHandleView.Factory socketHandleFactory
37-
= socket -> new SocketHandleView(new EventBus(), null, null, new SocketHandleView.DragService(), socket);
37+
= socket -> new SocketHandleView(new EventBus(), null, null, new SocketHandleView.SocketDragService(), socket);
3838
defaultOutputSocketController =
3939
new OutputSocketController(socketHandleFactory, outputSocket);
4040
initiallyPreviewedOutputSocketController =

0 commit comments

Comments
 (0)