|
1 | 1 | package edu.wpi.grip.ui.preview; |
2 | 2 |
|
3 | | -import com.google.common.eventbus.EventBus; |
4 | 3 | import com.google.common.eventbus.Subscribe; |
5 | | -import com.google.common.math.IntMath; |
| 4 | +import com.sun.javafx.application.PlatformImpl; |
6 | 5 | import edu.wpi.grip.core.OutputSocket; |
7 | 6 | import edu.wpi.grip.core.Pipeline; |
8 | 7 | import edu.wpi.grip.core.Source; |
9 | 8 | import edu.wpi.grip.core.Step; |
10 | 9 | import edu.wpi.grip.core.events.SocketPreviewChangedEvent; |
11 | 10 | 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; |
15 | 13 | import javafx.fxml.FXML; |
16 | 14 | import javafx.scene.layout.HBox; |
17 | 15 |
|
18 | 16 | import javax.inject.Inject; |
19 | 17 | 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; |
23 | 20 |
|
24 | 21 | /** |
25 | 22 | * Controller for a container that automatically shows previews of all sockets marked as "previewed". |
|
29 | 26 | @Singleton |
30 | 27 | public class PreviewsController { |
31 | 28 |
|
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; |
44 | 30 |
|
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); |
46 | 36 |
|
47 | 37 | /** |
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 |
50 | 39 | */ |
51 | 40 | @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)); |
136 | 43 | } |
137 | 44 |
|
138 | 45 | /** |
139 | 46 | * This function is called when a preview button is pushed/triggered |
140 | 47 | */ |
141 | 48 | @Subscribe |
142 | 49 | 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); |
175 | 68 | } |
176 | 69 | }); |
177 | 70 | } |
178 | 71 |
|
179 | 72 | /** |
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. |
187 | 74 | */ |
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 | + } |
200 | 90 | } |
201 | | - return indexInSourcePreviews; |
202 | | - } |
203 | 91 |
|
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 | + } |
240 | 106 | } |
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; |
242 | 110 | } |
243 | 111 | } |
0 commit comments