6398817d1cb1cf917185dc9b1c7c8996a28f0f52
[Mograsim.git] / net.mograsim.logic.ui / src / net / mograsim / logic / ui / model / wires / GUIWire.java
1 package net.mograsim.logic.ui.model.wires;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.List;
6
7 import org.eclipse.swt.SWT;
8
9 import net.haspamelodica.swt.helper.gcs.GeneralGC;
10 import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
11 import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
12 import net.mograsim.logic.core.LogicObserver;
13 import net.mograsim.logic.core.types.BitVectorFormatter;
14 import net.mograsim.logic.core.wires.Wire.ReadEnd;
15 import net.mograsim.logic.ui.ColorHelper;
16 import net.mograsim.logic.ui.model.ViewModelModifiable;
17
18 /**
19  * A wire connecting exactly two {@link Pin}s.
20  * 
21  * @author Daniel Kirschten
22  */
23 public class GUIWire
24 {
25         /**
26          * The model this wire is a part of.
27          */
28         private final ViewModelModifiable model;
29         /**
30          * The logical width of this wire. Is equal to the logical with of {@link #pin1} and {@link #pin2}.
31          */
32         public final int logicWidth;
33         /**
34          * The {@link Pin} on one side of this wire, usually the signal source.
35          */
36         private Pin pin1;
37         /**
38          * The {@link Pin} on one side of this wire, usually the signal target.
39          */
40         private Pin pin2;
41         /**
42          * The user-defined path between {@link #pin1} and {@link #pin2}.<br>
43          * Special cases: <code>null</code> means "choose an interpolation as fits", and an empty array means "direct connection without any
44          * interpolation".
45          */
46         private Point[] path;
47         /**
48          * The bounds of this wire, excluding line width (and line joins, if the line join is {@link SWT#JOIN_MITER})
49          */
50         private final Rectangle bounds;
51         /**
52          * The effective path of this wire, including automatic interpolation and the position of both {@link Pin}s. Is never null.
53          */
54         private double[] effectivePath;
55
56         private final List<Runnable> redrawListeners;
57
58         /**
59          * A LogicObserver calling redrawListeners. Used for logic model bindings.
60          */
61         private final LogicObserver logicObs;
62         /**
63          * A ReadEnd of the logic wire this GUI wire currently is bound to.
64          */
65         private ReadEnd end;
66
67         // creation and destruction
68
69         /**
70          * Creates a new {@link GUIWire} with automatic interpolation.
71          * 
72          * @author Daniel Kirschten
73          */
74         public GUIWire(ViewModelModifiable model, WireCrossPoint pin1, WireCrossPoint pin2)
75         {
76                 this(model, pin1, pin2, (Point[]) null);
77         }
78
79         /**
80          * Creates a new {@link GUIWire} with automatic interpolation.
81          * 
82          * @author Daniel Kirschten
83          */
84         public GUIWire(ViewModelModifiable model, WireCrossPoint pin1, Pin pin2)
85         {
86                 this(model, pin1, pin2, (Point[]) null);
87         }
88
89         /**
90          * Creates a new {@link GUIWire} with automatic interpolation.
91          * 
92          * @author Daniel Kirschten
93          */
94         public GUIWire(ViewModelModifiable model, Pin pin1, WireCrossPoint pin2)
95         {
96                 this(model, pin1, pin2, (Point[]) null);
97         }
98
99         /**
100          * Creates a new {@link GUIWire} with automatic interpolation.
101          * 
102          * @author Daniel Kirschten
103          */
104         public GUIWire(ViewModelModifiable model, Pin pin1, Pin pin2)
105         {
106                 this(model, pin1, pin2, (Point[]) null);
107         }
108
109         /**
110          * Creates a new {@link GUIWire} without automatic interpolation.
111          * 
112          * @author Daniel Kirschten
113          */
114         public GUIWire(ViewModelModifiable model, WireCrossPoint pin1, WireCrossPoint pin2, Point... path)
115         {
116                 this(model, pin1.getPin(), pin2.getPin(), path);
117         }
118
119         /**
120          * Creates a new {@link GUIWire} without automatic interpolation.
121          * 
122          * @author Daniel Kirschten
123          */
124         public GUIWire(ViewModelModifiable model, WireCrossPoint pin1, Pin pin2, Point... path)
125         {
126                 this(model, pin1.getPin(), pin2, path);
127         }
128
129         /**
130          * Creates a new {@link GUIWire} without automatic interpolation.
131          * 
132          * @author Daniel Kirschten
133          */
134         public GUIWire(ViewModelModifiable model, Pin pin1, WireCrossPoint pin2, Point... path)
135         {
136                 this(model, pin1, pin2.getPin(), path);
137         }
138
139         /**
140          * Creates a new {@link GUIWire} without automatic interpolation.
141          * 
142          * @author Daniel Kirschten
143          */
144         public GUIWire(ViewModelModifiable model, Pin pin1, Pin pin2, Point... path)
145         {
146                 logicObs = (i) -> callRedrawListeners();
147                 this.model = model;
148                 this.logicWidth = pin1.logicWidth;
149                 if (pin2.logicWidth != pin1.logicWidth)
150                         throw new IllegalArgumentException("Can't connect pins of different logic width");
151
152                 this.pin1 = pin1;
153                 this.pin2 = pin2;
154
155                 this.path = path == null ? null : Arrays.copyOf(path, path.length);
156                 this.bounds = new Rectangle(0, 0, -1, -1);
157
158                 redrawListeners = new ArrayList<>();
159
160                 pin1.addPinMovedListener(p -> pinMoved());
161                 pin2.addPinMovedListener(p -> pinMoved());
162
163                 recalculateEffectivePath();
164
165                 model.wireCreated(this);
166         }
167
168         /**
169          * Destroys this wire. This method implicitly calls {@link ViewModelModifiable#wireDestroyed(GUIWire) wireDestroyed()} for the model
170          * this component is a part of.
171          * 
172          * @author Daniel Kirschten
173          */
174         public void destroy()
175         {
176                 model.wireDestroyed(this);
177         }
178
179         // pins
180
181         /**
182          * Returns the {@link Pin} on one side of this wire, usually the signal source.
183          * 
184          * @author Daniel Kirschten
185          */
186         public Pin getPin1()
187         {
188                 return pin1;
189         }
190
191         /**
192          * Returns the {@link Pin} on one side of this wire, usually the signal target.
193          * 
194          * @author Daniel Kirschten
195          */
196         public Pin getPin2()
197         {
198                 return pin2;
199         }
200
201         /**
202          * Called when {@link #pin1} or {@link #pin2} were moved.
203          * 
204          * @author Daniel Kirschten
205          */
206         private void pinMoved()
207         {
208                 recalculateEffectivePath();
209                 callRedrawListeners();
210         }
211
212         // "graphical" operations
213
214         /**
215          * Recalculates {@link #effectivePath} "from scratch". Also updates {@link #bounds}.
216          * 
217          * @author Daniel Kirschten
218          */
219         private void recalculateEffectivePath()
220         {
221                 Point pos1 = pin1.getPos(), pos2 = pin2.getPos();
222
223                 double boundsX1 = Math.min(pos1.x, pos2.x);
224                 double boundsY1 = Math.min(pos1.y, pos2.y);
225                 double boundsX2 = Math.max(pos1.x, pos2.x);
226                 double boundsY2 = Math.max(pos1.y, pos2.y);
227
228                 if (path == null)
229                         effectivePath = new double[] { pos1.x, pos1.y, (pos1.x + pos2.x) / 2, pos1.y, (pos1.x + pos2.x) / 2, pos2.y, pos2.x, pos2.y };
230                 else
231                 {
232                         effectivePath = new double[path.length * 2 + 4];
233                         effectivePath[0] = pos1.x;
234                         effectivePath[1] = pos1.y;
235                         for (int srcI = 0, dstI = 2; srcI < path.length; srcI++, dstI += 2)
236                         {
237                                 double pathX = path[srcI].x;
238                                 double pathY = path[srcI].y;
239                                 effectivePath[dstI + 0] = pathX;
240                                 effectivePath[dstI + 1] = pathY;
241                                 if (pathX < boundsX1)
242                                         boundsX1 = pathX;
243                                 if (pathX > boundsX2)
244                                         boundsX2 = pathX;
245                                 if (pathY < boundsY1)
246                                         boundsY1 = pathY;
247                                 if (pathY > boundsY2)
248                                         boundsY2 = pathY;
249                         }
250                         effectivePath[effectivePath.length - 2] = pos2.x;
251                         effectivePath[effectivePath.length - 1] = pos2.y;
252                 }
253
254                 bounds.x = boundsX1;
255                 bounds.y = boundsY1;
256                 bounds.width = boundsX2 - boundsX1;
257                 bounds.height = boundsY2 - boundsY1;
258         }
259
260         /**
261          * Returns the bounds of this wire, excluding line width (and line joins, if the line join is {@link SWT#JOIN_MITER})
262          * 
263          * @author Daniel Kirschten
264          */
265         public Rectangle getBounds()
266         {
267                 return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
268         }
269
270         /**
271          * Render this wire to the given gc, in absoulute coordinates.
272          * 
273          * @author Daniel Kirschten
274          */
275         public void render(GeneralGC gc)
276         {
277                 ColorHelper.executeWithDifferentForeground(gc, BitVectorFormatter.formatAsColor(end), () -> gc.drawPolyline(effectivePath));
278         }
279
280         /**
281          * The user-defined path between {@link #pin1} and {@link #pin2}. Note that this is not neccessarily equal to the effective path drawn
282          * in {@link #render(GeneralGC)}.<br>
283          * Special cases: <code>null</code> means "choose an interpolation as fits", and an empty array means "direct connection without any
284          * interpolation".
285          * 
286          * @author Daniel Kirschten
287          */
288         public Point[] getPath()
289         {
290                 return path == null ? null : path.clone();
291         }
292
293         // logic model binding
294
295         /**
296          * Binds this {@link GUIWire} to the given {@link ReadEnd}: The color of this {@link GUIWire} will now depend on the state of the given
297          * {@link ReadEnd}, and further changes of the given {@link ReadEnd} will result in readrawListeners being called.<br>
298          * The argument can be null, in which case the old binding is stopped.
299          * 
300          * @author Daniel Kirschten
301          */
302         public void setLogicModelBinding(ReadEnd end)
303         {
304                 if (this.end != null)
305                         this.end.deregisterObserver(logicObs);
306                 this.end = end;
307                 if (end != null)
308                         end.registerObserver(logicObs);
309         }
310
311         // listeners
312
313         // @formatter:off
314         public void addRedrawListener   (Runnable listener) {redrawListeners         .add   (listener);}
315
316         public void removeRedrawListener(Runnable listener) {redrawListeners         .remove(listener);}
317
318         private void callRedrawListeners() {redrawListeners.forEach(l -> l.run());}
319         // @formatter:on
320
321 }