Skip to content

Commit f24c08d

Browse files
committed
Use FXCollections.sort to sort preview views
Instead of manually inserting them, which is verbose and error prone, we can use JavaFX's built-in sorting routines to make the preview order match up with the order of the things in the pipeline being previewed Closes #398
1 parent a35a1b8 commit f24c08d

1 file changed

Lines changed: 65 additions & 197 deletions

File tree

Lines changed: 65 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,22 @@
11
package edu.wpi.grip.ui.preview;
22

3-
import com.google.common.eventbus.EventBus;
43
import com.google.common.eventbus.Subscribe;
5-
import com.google.common.math.IntMath;
4+
import com.sun.javafx.application.PlatformImpl;
65
import edu.wpi.grip.core.OutputSocket;
76
import edu.wpi.grip.core.Pipeline;
87
import edu.wpi.grip.core.Source;
98
import edu.wpi.grip.core.Step;
109
import edu.wpi.grip.core.events.SocketPreviewChangedEvent;
1110
import edu.wpi.grip.core.events.StepMovedEvent;
12-
import edu.wpi.grip.ui.pipeline.PipelineController;
13-
import edu.wpi.grip.ui.pipeline.source.SourceController;
14-
import edu.wpi.grip.ui.util.GRIPPlatform;
11+
import javafx.collections.FXCollections;
12+
import javafx.collections.ObservableList;
1513
import javafx.fxml.FXML;
1614
import javafx.scene.layout.HBox;
1715

1816
import javax.inject.Inject;
1917
import javax.inject.Singleton;
20-
import java.util.ArrayList;
21-
import java.util.List;
22-
import java.util.Stack;
18+
import java.util.Arrays;
19+
import java.util.Comparator;
2320

2421
/**
2522
* Controller for a container that automatically shows previews of all sockets marked as "previewed".
@@ -29,215 +26,86 @@
2926
@Singleton
3027
public class PreviewsController {
3128

32-
@FXML
33-
private HBox previewBox;
34-
@Inject
35-
private EventBus eventBus;
36-
@Inject
37-
private PipelineController pipelineController;
38-
@Inject
39-
private Pipeline pipeline;
40-
@Inject
41-
private SocketPreviewViewFactory previewViewFactory;
42-
@Inject
43-
private GRIPPlatform platform;
29+
@FXML private HBox previewBox;
4430

45-
private final List<OutputSocket<?>> previewedSockets = new ArrayList<>();
31+
@Inject private Pipeline pipeline;
32+
@Inject private SocketPreviewViewFactory previewViewFactory;
33+
34+
private final Comparator<SocketPreviewView<?>> comparePreviews =
35+
Comparator.comparing(SocketPreviewView::getSocket, this::compareSockets);
4636

4737
/**
48-
* This function is called when a step moves in the pipeline to adjust the positions of any open previews it has
49-
* to reflect the new order of the pipeline.
38+
* Any time a step is moved in the pipeline, we have to re-sort the previews
5039
*/
5140
@Subscribe
52-
public synchronized void onPreviewOrderChanged(StepMovedEvent event) {
53-
platform.runAsSoonAsPossible(() -> {//Run this function on the main gui thread
54-
final Step movedStep = event.getStep(); //The step whose position in the pipeline has changed
55-
final int distanceMoved = event.getDistance(); //The number of indices (positive or negative) the step has been moved by
56-
final int numberOfSourcePreviews = getNumbOfSourcePreviews();//The number of previews opened that are displaying sources (NOT steps)
57-
58-
final OutputSocket<?>[] socketsMovedArray = movedStep.getOutputSockets();//Grab all the output sockets of the step that has moved
59-
60-
//Find the rightmost and leftmost position in the previews of the previewed sockets of the step that has moved
61-
int rightmostIndex = 0; //Set to minimum possible value so that the first index will overwrite it
62-
int leftmostIndex = this.previewedSockets.size();//Set to maximum possible value so that the first index will overwrite it
63-
64-
Stack<OutputSocket<?>> previewedMovedSockets = new Stack<OutputSocket<?>>();//This will hold the sockets of the step that was moved that are open for preview
65-
66-
for (OutputSocket<?> i : socketsMovedArray) {
67-
if (this.previewedSockets.indexOf(i) != -1) {//If this socket is previewed
68-
previewedMovedSockets.push(i);
69-
70-
if (rightmostIndex < this.previewedSockets.indexOf(i)) {
71-
rightmostIndex = this.previewedSockets.indexOf(i);
72-
}
73-
74-
if (leftmostIndex > this.previewedSockets.indexOf(i)) {
75-
leftmostIndex = this.previewedSockets.indexOf(i);
76-
}
77-
78-
}
79-
}
80-
81-
//Deal with each previewed socket from the step that was moved in turn
82-
while (previewedMovedSockets.size() != 0) { //While there are still sockets to deal with on the stack
83-
OutputSocket<?> current = previewedMovedSockets.pop();//Grab the top socket on the stack
84-
int oldIndex = this.previewedSockets.indexOf(current);//Get the index of this preview so we can remove the correct entry
85-
86-
int newLocation = 0;//This will hold the new index in the list of previewed sockets for this socket
87-
88-
if (distanceMoved < 0) { //If the step moved left....
89-
newLocation = leftmostIndex + distanceMoved; //Calculate the new index from the leftmost previewed socket of this step
90-
} else { //The step must have moved right....
91-
newLocation = rightmostIndex + distanceMoved;//So calculate the new index from the rightmost previewed socket of this step
92-
}
93-
94-
if (newLocation < numberOfSourcePreviews) {//If the new calculated index would put it in the midst of source previews
95-
newLocation = numberOfSourcePreviews;//Make the index the location of the first non-source preview
96-
} else { //The new index is the current location of another step (NOT a source)
97-
98-
//So we need to make sure that we jump over GROUPS of previews associated with the SAME step as a unit
99-
int count = 0;//This will hold the number of previews open from the same step in sequence, starting from the new location and going in the direction we are moving
100-
101-
if (distanceMoved < 0) {//If the step moved left....
102-
OutputSocket<?> nextSocketInDirection = this.previewedSockets.get(newLocation);//Grab the socket whose preview is open at the new location
103-
boolean zeroReached = false;//We will set this to true if we reach the beginning of the list of previews (there are no source previews open)
104-
while ((!zeroReached) &&
105-
((nextSocketInDirection.getStep().isPresent())
106-
&& (nextSocketInDirection.getStep().get() == this.previewedSockets.get(newLocation).getStep().get()))) { //While we haven't reached the beginning of the list of previews, the socket at this location is a socket from a step, and it is the SAME step as the step of the socket at the new location...
107-
count++;
108-
if ((newLocation - count) > 0) {//If we haven't reached the beginning of the list of open previews...
109-
nextSocketInDirection = this.previewedSockets.get(newLocation - count);//Grab the next previewed socket to examine in the direction we are moving
110-
} else {
111-
zeroReached = true;//Mark that we've reached the beginning of the list of previews so we know to stop looking for more
112-
}
113-
}
114-
newLocation = newLocation - (count - 1);//Since the first compare of the while loop will always be true, we subract one from the count when we use it to adjust newLocation
115-
116-
} else {//The step must have moved right....
117-
while ((newLocation + count < this.previewedSockets.size())
118-
&& (this.previewedSockets.get(newLocation + count).getStep().get() == this.previewedSockets.get(newLocation).getStep().get())) { //While there are still previewed sockets to examine, and the socket being examined is one from the SAME step of the socket at the new location....
119-
count++;
120-
}
121-
newLocation = newLocation + (count - 1);//Since the first compare of the while loop will always be true, we subract one from the count when we use it to adjust newLocation
122-
}
123-
}
124-
125-
//Remove this socket from the old point in the previews
126-
this.previewedSockets.remove(oldIndex);
127-
this.eventBus.unregister(this.previewBox.getChildren().remove(oldIndex));
128-
129-
if (newLocation > this.previewedSockets.size()) {//If the new index is now too big for the list of previews
130-
newLocation = this.previewedSockets.size();//Make it so it will be added to the end of the list of previews
131-
}
132-
this.previewedSockets.add(newLocation, current);//...add it to the correct location in the list of previews open
133-
this.previewBox.getChildren().add(newLocation, previewViewFactory.create(current));//...and display it in the correct location in the list of previews open
134-
}
135-
});
41+
public synchronized void onStepMoved(StepMovedEvent event) {
42+
PlatformImpl.runAndWait(() -> FXCollections.sort((ObservableList) previewBox.getChildren(), comparePreviews));
13643
}
13744

13845
/**
13946
* This function is called when a preview button is pushed/triggered
14047
*/
14148
@Subscribe
14249
public synchronized void onSocketPreviewChanged(SocketPreviewChangedEvent event) {
143-
platform.runAsSoonAsPossible(() -> {//Run this function on the main gui thread
144-
145-
final OutputSocket<?> socket = event.getSocket(); //The socket whose preview has changed
146-
147-
if (socket.isPreviewed()) {// If the socket was just set as previewed, add it to the list of previewed sockets and add a new view for it.
148-
149-
if (!this.previewedSockets.contains(socket)) {//If the socket is not already previewed...
150-
151-
if (socket.getStep().isPresent()) { //If this is a socket associated with a pipeline step (IE NOT a source)....
152-
153-
//Find the appropriate index to add this preview with...
154-
int indexInPreviews = getIndexInPreviewsOfAStepSocket(socket);
155-
156-
this.previewedSockets.add(indexInPreviews, socket);//...use this index to add it to the correct location in the list of previews open
157-
this.previewBox.getChildren().add(indexInPreviews, previewViewFactory.create(socket));//...and display it in the correct location in the list of previews open in the gui
158-
159-
} else {//This is a socket associated with a source and not a pipeline step...
160-
161-
//Find the appropriate index to add this preview with.
162-
int indexInSourcePreviews = getIndexInPreviewsOfASourceSocket(socket);
163-
164-
this.previewedSockets.add(indexInSourcePreviews, socket);//Add the preview to the appropriate place in the list of previewed sockets
165-
this.previewBox.getChildren().add(indexInSourcePreviews, previewViewFactory.create(socket));//Display the preview in the appropriate place
166-
}
167-
}
168-
} else {//The socket was already previewed, so the user must be requesting to not show this preview (remove both it and the corresponding control)
169-
170-
int index = this.previewedSockets.indexOf(socket);//Get the index of this preview so we can remove the correct entry
171-
if (index != -1) {//False when the preview isn't currently displayed
172-
this.previewedSockets.remove(index);
173-
this.eventBus.unregister(this.previewBox.getChildren().remove(index));
174-
}
50+
OutputSocket<?> socket = event.getSocket();
51+
52+
@SuppressWarnings("unchecked")
53+
ObservableList<SocketPreviewView<?>> previews = (ObservableList) previewBox.getChildren();
54+
55+
// This needs to run right away to avoid synchronization problems, although in practice this method only runs
56+
// in the UI thread anyways (since it fires in response to a button press)
57+
PlatformImpl.runAndWait(() -> {
58+
if (socket.isPreviewed()) {
59+
// When a socket previewed, add a new view, then sort all of the views so they stay ordered
60+
previews.add(previewViewFactory.create(socket));
61+
FXCollections.sort(previews, comparePreviews);
62+
} else {
63+
// When a socket is no longer marked as previewed, find and remove the view associated with it
64+
previews.stream()
65+
.filter(view -> view.getSocket() == socket)
66+
.findFirst()
67+
.ifPresent(previews::remove);
17568
}
17669
});
17770
}
17871

17972
/**
180-
* Find the correct index in the displayed previews for a socket associated with a source (NOT a step socket)
181-
* by comparing the indices in the pipeline.
182-
* Made to be called in {@link PreviewsController#onSocketPreviewChanged}
183-
*
184-
* @param socket An output socket associated with a source (NOT a step)
185-
* @return The correct index (an int) in the list of displayed previews for the given <code>socket</code>
186-
* @see PreviewsController#onSocketPreviewChanged(SocketPreviewChangedEvent)
73+
* Given two sockets, determine which comes first in the pipeline. This is used to sort the previews.
18774
*/
188-
private int getIndexInPreviewsOfASourceSocket(OutputSocket<?> socket) {
189-
final Source socketSource = socket.getSource().get();//The source socket associated with the socket whose preview has changed
190-
final SourceController sourceView = this.pipelineController.findSourceView(socketSource);//The gui object that displays the socketSource
191-
int indexOfSource = this.pipeline.getSources().indexOf(sourceView); //The index of the source that has the socket in the pipeline
192-
193-
//Start with the first socket in the list of previewed sockets
194-
int indexInSourcePreviews = 0;
195-
//Find the correct index in the displayed source previews by comparing the indices
196-
while (((this.previewedSockets.size() > indexInSourcePreviews)//If there are previews still to be examined AND
197-
&& (this.previewedSockets.get(indexInSourcePreviews).getSource().isPresent()))//AND If the preview at this index is a source...
198-
&& ((this.pipeline.getSources().indexOf(this.pipelineController.findSourceView(this.previewedSockets.get(indexInSourcePreviews).getSource().get()))) < indexOfSource)) {//AND the preview at this index is a source with an index in the list of sources less than this source
199-
indexInSourcePreviews++;
75+
private int compareSockets(OutputSocket<?> a, OutputSocket<?> b) {
76+
if (a.getStep().isPresent() && b.getStep().isPresent()) {
77+
final Step stepA = a.getStep().get(), stepB = b.getStep().get();
78+
79+
if (stepA == stepB) {
80+
// If both sockets are in the same step, order them based on which is first in the step
81+
return Arrays.asList(stepA.getOutputSockets()).stream()
82+
.filter(socket -> socket == a || socket == b)
83+
.findFirst().get() == a ? -1 : 1;
84+
} else {
85+
// If both sockets are in different steps, order them based on which step is first
86+
return pipeline.getSteps().stream()
87+
.filter(step -> step == stepA || step == stepB)
88+
.findFirst().get() == stepA ? -1 : 1;
89+
}
20090
}
201-
return indexInSourcePreviews;
202-
}
20391

204-
/**
205-
* Find the correct index in the displayed previews for a socket associated with a step (NOT a source socket)
206-
* by comparing the indices in the pipeline, starting with the first non-source preview displayed.
207-
* Made to be called in {@link PreviewsController#onSocketPreviewChanged}
208-
*
209-
* @param socket An output socket associated with a step (NOT a source)
210-
* @return The correct index in the list of displayed previews for the given <code>socket</code>
211-
* @see PreviewsController#onSocketPreviewChanged(SocketPreviewChangedEvent)
212-
*/
213-
private int getIndexInPreviewsOfAStepSocket(OutputSocket<?> socket) {
214-
int numbOfSourcePreviews = getNumbOfSourcePreviews();//Count how many *source* previews (not *step* previews) are currently displayed
215-
216-
final Step socketStep = socket.getStep().get();//The pipeline step associated with the socket whose preview has changed
217-
int indexOfStep = this.pipeline.getSteps().indexOf(socketStep); //The index of the step that has the socket in the pipeline
218-
219-
//Start at the first non-source socket in the list of previewed sockets
220-
long indexInPreviews =
221-
// The socket at this index in the list of displayed sockets has an index in the pipeline less than the socket passed in as "socket"
222-
this.previewedSockets.stream().filter(outSocket -> outSocket.getStep().isPresent() && this.pipeline.getSteps().indexOf(outSocket.getStep().get()) < indexOfStep).count();
223-
return IntMath.checkedAdd(Math.toIntExact(indexInPreviews), numbOfSourcePreviews);
224-
}
225-
226-
/**
227-
* Counts how many source previews (NOT step previews) are currently displayed.
228-
* Called in {@link PreviewsController#getIndexInPreviewsOfAStepSocket} and {@link PreviewsController#onPreviewOrderChanged(StepMovedEvent)}
229-
*
230-
* @return The number of source (NOT step) previews that are currently displayed
231-
* @see PreviewsController#getIndexInPreviewsOfAStepSocket(OutputSocket)
232-
* @see PreviewsController#onPreviewOrderChanged(StepMovedEvent)
233-
*/
234-
private int getNumbOfSourcePreviews() {
235-
//Start at the beginning of the list.
236-
int numbOfSourcePreviews = 0;
237-
while ((this.previewedSockets.size() > numbOfSourcePreviews) //While there are still previews to examine
238-
&& (!this.previewedSockets.get(numbOfSourcePreviews).getStep().isPresent())) { //If this is a source...
239-
numbOfSourcePreviews++;
92+
if (a.getSource().isPresent() && b.getSource().isPresent()) {
93+
final Source sourceA = a.getSource().get(), sourceB = b.getSource().get();
94+
95+
if (sourceA == sourceB) {
96+
// If both sockets are in the same source, order them based on which is first in the source
97+
return Arrays.asList(sourceA.getOutputSockets()).stream()
98+
.filter(socket -> socket == a || socket == b)
99+
.findFirst().get() == a ? -1 : 1;
100+
} else {
101+
// If both sockets are from sources, order them based on the order of the sources in the pipeline
102+
return pipeline.getSources().stream()
103+
.filter(source -> source == sourceA || source == sourceB)
104+
.findFirst().get() == sourceA ? -1 : 1;
105+
}
240106
}
241-
return numbOfSourcePreviews;
107+
108+
// Lastly, if one socket is from a step and the other is from a source, the source always comes first
109+
return b.getStep().isPresent() ? -1 : 1;
242110
}
243111
}

0 commit comments

Comments
 (0)