Speedup by changing redraw listener system
[Mograsim.git] / net.mograsim.logic.model / src / net / mograsim / logic / model / model / wires / GUIWire.java
1 package net.mograsim.logic.model.model.wires;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.List;
6 import java.util.function.Consumer;
7
8 import org.eclipse.swt.SWT;
9
10 import net.haspamelodica.swt.helper.gcs.GeneralGC;
11 import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
12 import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
13 import net.mograsim.logic.core.LogicObserver;
14 import net.mograsim.logic.core.types.BitVector;
15 import net.mograsim.logic.core.types.BitVectorFormatter;
16 import net.mograsim.logic.core.wires.Wire;
17 import net.mograsim.logic.core.wires.Wire.ReadEnd;
18 import net.mograsim.logic.model.model.ViewModelModifiable;
19 import net.mograsim.preferences.ColorDefinition;
20 import net.mograsim.preferences.ColorManager;
21
22 /**
23  * A wire connecting exactly two {@link Pin}s.
24  * 
25  * @author Daniel Kirschten
26  */
27 public class GUIWire
28 {
29         /**
30          * The model this wire is a part of.
31          */
32         private final ViewModelModifiable model;
33         /**
34          * The name of this wire. Is unique for all wires in its model.
35          */
36         public final String name;
37         /**
38          * The logical width of this wire. Is equal to the logical with of {@link #pin1} and {@link #pin2}.
39          */
40         public final int logicWidth;
41         /**
42          * The {@link Pin} on one side of this wire, usually the signal source.
43          */
44         private Pin pin1;
45         /**
46          * The {@link Pin} on one side of this wire, usually the signal target.
47          */
48         private Pin pin2;
49         /**
50          * The user-defined path between {@link #pin1} and {@link #pin2}.<br>
51          * Special cases: <code>null</code> means "choose an interpolation as fits", and an empty array means "direct connection without any
52          * interpolation".
53          */
54         private Point[] path;
55         /**
56          * The bounds of this wire, excluding line width (and line joins, if the line join is {@link SWT#JOIN_MITER})
57          */
58         private final Rectangle bounds;
59         /**
60          * The effective path of this wire, including automatic interpolation and the position of both {@link Pin}s. Is never null.
61          */
62         private double[] effectivePath;
63
64         private final List<Consumer<GUIWire>> pathChangedListeners;
65
66         /**
67          * A LogicObserver calling redrawListeners. Used for logic model bindings.
68          */
69         private final LogicObserver logicObs;
70         /**
71          * A ReadEnd of the logic wire this GUI wire currently is bound to.
72          */
73         private ReadEnd end;
74
75         // creation and destruction
76
77         /**
78          * Creates a new {@link GUIWire} with automatic interpolation and using the default name.
79          * 
80          * @author Daniel Kirschten
81          */
82         public GUIWire(ViewModelModifiable model, WireCrossPoint pin1, WireCrossPoint pin2)
83         {
84                 this(model, null, pin1, pin2);
85         }
86
87         /**
88          * Creates a new {@link GUIWire} with automatic interpolation and using the default name.
89          * 
90          * @author Daniel Kirschten
91          */
92         public GUIWire(ViewModelModifiable model, WireCrossPoint pin1, Pin pin2)
93         {
94                 this(model, null, pin1, pin2);
95         }
96
97         /**
98          * Creates a new {@link GUIWire} with automatic interpolation and using the default name.
99          * 
100          * @author Daniel Kirschten
101          */
102         public GUIWire(ViewModelModifiable model, Pin pin1, WireCrossPoint pin2)
103         {
104                 this(model, null, pin1, pin2);
105         }
106
107         /**
108          * Creates a new {@link GUIWire} with automatic interpolation and using the default name.
109          * 
110          * @author Daniel Kirschten
111          */
112         public GUIWire(ViewModelModifiable model, Pin pin1, Pin pin2)
113         {
114                 this(model, null, pin1, pin2);
115         }
116
117         /**
118          * Creates a new {@link GUIWire} without automatic interpolation and using the default name.
119          * 
120          * @author Daniel Kirschten
121          */
122         public GUIWire(ViewModelModifiable model, WireCrossPoint pin1, WireCrossPoint pin2, Point... path)
123         {
124                 this(model, null, pin1, pin2, path);
125         }
126
127         /**
128          * Creates a new {@link GUIWire} without automatic interpolation and using the default name.
129          * 
130          * @author Daniel Kirschten
131          */
132         public GUIWire(ViewModelModifiable model, WireCrossPoint pin1, Pin pin2, Point... path)
133         {
134                 this(model, null, pin1, pin2, path);
135         }
136
137         /**
138          * Creates a new {@link GUIWire} without automatic interpolation and using the default name.
139          * 
140          * @author Daniel Kirschten
141          */
142         public GUIWire(ViewModelModifiable model, Pin pin1, WireCrossPoint pin2, Point... path)
143         {
144                 this(model, null, pin1, pin2, path);
145         }
146
147         /**
148          * Creates a new {@link GUIWire} without automatic interpolation and using the default name.
149          * 
150          * @author Daniel Kirschten
151          */
152         public GUIWire(ViewModelModifiable model, Pin pin1, Pin pin2, Point... path)
153         {
154                 this(model, null, pin1, pin2, path);
155         }
156
157         /**
158          * Creates a new {@link GUIWire} with automatic interpolation.
159          * 
160          * @author Daniel Kirschten
161          */
162         public GUIWire(ViewModelModifiable model, String name, WireCrossPoint pin1, WireCrossPoint pin2)
163         {
164                 this(model, name, pin1, pin2, (Point[]) null);
165         }
166
167         /**
168          * Creates a new {@link GUIWire} with automatic interpolation.
169          * 
170          * @author Daniel Kirschten
171          */
172         public GUIWire(ViewModelModifiable model, String name, WireCrossPoint pin1, Pin pin2)
173         {
174                 this(model, name, pin1, pin2, (Point[]) null);
175         }
176
177         /**
178          * Creates a new {@link GUIWire} with automatic interpolation.
179          * 
180          * @author Daniel Kirschten
181          */
182         public GUIWire(ViewModelModifiable model, String name, Pin pin1, WireCrossPoint pin2)
183         {
184                 this(model, name, pin1, pin2, (Point[]) null);
185         }
186
187         /**
188          * Creates a new {@link GUIWire} with automatic interpolation.
189          * 
190          * @author Daniel Kirschten
191          */
192         public GUIWire(ViewModelModifiable model, String name, Pin pin1, Pin pin2)
193         {
194                 this(model, name, pin1, pin2, (Point[]) null);
195         }
196
197         /**
198          * Creates a new {@link GUIWire} without automatic interpolation.
199          * 
200          * @author Daniel Kirschten
201          */
202         public GUIWire(ViewModelModifiable model, String name, WireCrossPoint pin1, WireCrossPoint pin2, Point... path)
203         {
204                 this(model, name, pin1.getPin(), pin2.getPin(), path);
205         }
206
207         /**
208          * Creates a new {@link GUIWire} without automatic interpolation.
209          * 
210          * @author Daniel Kirschten
211          */
212         public GUIWire(ViewModelModifiable model, String name, WireCrossPoint pin1, Pin pin2, Point... path)
213         {
214                 this(model, name, pin1.getPin(), pin2, path);
215         }
216
217         /**
218          * Creates a new {@link GUIWire} without automatic interpolation.
219          * 
220          * @author Daniel Kirschten
221          */
222         public GUIWire(ViewModelModifiable model, String name, Pin pin1, WireCrossPoint pin2, Point... path)
223         {
224                 this(model, name, pin1, pin2.getPin(), path);
225         }
226
227         /**
228          * Creates a new {@link GUIWire} without automatic interpolation.
229          * 
230          * @author Daniel Kirschten
231          */
232         public GUIWire(ViewModelModifiable model, String name, Pin pin1, Pin pin2, Point... path)
233         {
234                 this.model = model;
235                 this.name = name == null ? model.getDefaultWireName() : name;
236                 this.logicWidth = pin1.logicWidth;
237                 if (pin2.logicWidth != pin1.logicWidth)
238                         throw new IllegalArgumentException("Can't connect pins of different logic width");
239
240                 this.pin1 = pin1;
241                 this.pin2 = pin2;
242
243                 this.path = path == null ? null : Arrays.copyOf(path, path.length);
244                 this.bounds = new Rectangle(0, 0, -1, -1);
245
246                 pathChangedListeners = new ArrayList<>();
247
248                 logicObs = (i) -> model.requestRedraw();
249
250                 pin1.addPinMovedListener(p -> pinMoved());
251                 pin2.addPinMovedListener(p -> pinMoved());
252
253                 recalculateEffectivePath();
254
255                 model.wireCreated(this);
256         }
257
258         /**
259          * Destroys this wire. This method implicitly calls {@link ViewModelModifiable#wireDestroyed(GUIWire) wireDestroyed()} for the model
260          * this component is a part of.
261          * 
262          * @author Daniel Kirschten
263          */
264         public void destroy()
265         {
266                 model.wireDestroyed(this);
267         }
268
269         // pins
270
271         /**
272          * Returns the {@link Pin} on one side of this wire, usually the signal source.
273          * 
274          * @author Daniel Kirschten
275          */
276         public Pin getPin1()
277         {
278                 return pin1;
279         }
280
281         /**
282          * Returns the {@link Pin} on one side of this wire, usually the signal target.
283          * 
284          * @author Daniel Kirschten
285          */
286         public Pin getPin2()
287         {
288                 return pin2;
289         }
290
291         /**
292          * Called when {@link #pin1} or {@link #pin2} were moved.
293          * 
294          * @author Daniel Kirschten
295          */
296         private void pinMoved()
297         {
298                 recalculateEffectivePath();
299                 model.requestRedraw();
300         }
301
302         // "graphical" operations
303
304         /**
305          * Recalculates {@link #effectivePath} "from scratch". Also updates {@link #bounds}.
306          * 
307          * @author Daniel Kirschten
308          */
309         private void recalculateEffectivePath()
310         {
311                 Point pos1 = pin1.getPos(), pos2 = pin2.getPos();
312
313                 double boundsX1 = Math.min(pos1.x, pos2.x);
314                 double boundsY1 = Math.min(pos1.y, pos2.y);
315                 double boundsX2 = Math.max(pos1.x, pos2.x);
316                 double boundsY2 = Math.max(pos1.y, pos2.y);
317
318                 if (path == null)
319                         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 };
320                 else
321                 {
322                         effectivePath = new double[path.length * 2 + 4];
323                         effectivePath[0] = pos1.x;
324                         effectivePath[1] = pos1.y;
325                         for (int srcI = 0, dstI = 2; srcI < path.length; srcI++, dstI += 2)
326                         {
327                                 double pathX = path[srcI].x;
328                                 double pathY = path[srcI].y;
329                                 effectivePath[dstI + 0] = pathX;
330                                 effectivePath[dstI + 1] = pathY;
331                                 if (pathX < boundsX1)
332                                         boundsX1 = pathX;
333                                 if (pathX > boundsX2)
334                                         boundsX2 = pathX;
335                                 if (pathY < boundsY1)
336                                         boundsY1 = pathY;
337                                 if (pathY > boundsY2)
338                                         boundsY2 = pathY;
339                         }
340                         effectivePath[effectivePath.length - 2] = pos2.x;
341                         effectivePath[effectivePath.length - 1] = pos2.y;
342                 }
343
344                 bounds.x = boundsX1;
345                 bounds.y = boundsY1;
346                 bounds.width = boundsX2 - boundsX1;
347                 bounds.height = boundsY2 - boundsY1;
348         }
349
350         /**
351          * Returns the bounds of this wire, excluding line width (and line joins, if the line join is {@link SWT#JOIN_MITER})
352          * 
353          * @author Daniel Kirschten
354          */
355         public Rectangle getBounds()
356         {
357                 return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
358         }
359
360         /**
361          * Render this wire to the given gc, in absoulute coordinates.
362          * 
363          * @author Daniel Kirschten
364          */
365         public void render(GeneralGC gc)
366         {
367                 ColorDefinition wireColor = BitVectorFormatter.formatAsColor(end);
368                 if (wireColor != null)
369                         gc.setForeground(ColorManager.current().toColor(wireColor));
370                 gc.drawPolyline(effectivePath);
371         }
372
373         // operations concerning the path
374
375         /**
376          * The user-defined path between {@link #pin1} and {@link #pin2}. Note that this is not neccessarily equal to the effective path drawn
377          * in {@link #render(GeneralGC)}.<br>
378          * Special cases: <code>null</code> means "choose an interpolation as fits", and an empty array means "direct connection without any
379          * interpolation".
380          * 
381          * @author Daniel Kirschten
382          */
383         public Point[] getPath()
384         {
385                 return deepPathCopy(path);
386         }
387
388         public void setPath(Point... path)
389         {
390                 this.path = deepPathCopy(path);
391                 recalculateEffectivePath();
392                 callPathChangedListeners();
393                 model.requestRedraw();
394         }
395
396         public Point getPathPoint(int index)
397         {
398                 return pointCopy(path[index]);
399         }
400
401         public void setPathPoint(Point p, int index)
402         {
403                 path[index] = pointCopy(p);
404                 recalculateEffectivePath();
405                 callPathChangedListeners();
406                 model.requestRedraw();
407         }
408
409         public void insertPathPoint(Point p, int index)
410         {
411                 if (path == null)
412                         path = new Point[] { pointCopy(p) };
413                 else
414                 {
415                         Point[] oldPath = path;
416                         path = new Point[oldPath.length + 1];
417                         System.arraycopy(oldPath, 0, path, 0, index);
418                         if (index < oldPath.length)
419                                 System.arraycopy(oldPath, index, path, index + 1, oldPath.length - index);
420                         path[index] = pointCopy(p);
421                 }
422                 recalculateEffectivePath();
423                 callPathChangedListeners();
424         }
425
426         public void removePathPoint(int index)
427         {
428                 if (path.length == 0)
429                         path = null;
430                 else
431                 {
432                         Point[] oldPath = path;
433                         path = new Point[oldPath.length - 1];
434                         System.arraycopy(oldPath, 0, path, 0, index);
435                         if (index < oldPath.length - 1)
436                                 System.arraycopy(oldPath, index + 1, path, index, oldPath.length - index - 1);
437                 }
438                 recalculateEffectivePath();
439                 callPathChangedListeners();
440         }
441
442         public double[] getEffectivePath()
443         {
444                 return Arrays.copyOf(effectivePath, effectivePath.length);
445         }
446
447         private static Point[] deepPathCopy(Point[] path)
448         {
449                 if (path == null)
450                         return null;
451                 Point[] copy = new Point[path.length];
452                 for (int i = 0; i < path.length; i++)
453                         copy[i] = pointCopy(path[i]);
454                 return copy;
455         }
456
457         private static Point pointCopy(Point p)
458         {
459                 return new Point(p.x, p.y);
460         }
461
462         // logic model binding
463
464         /**
465          * Binds this {@link GUIWire} to the given {@link ReadEnd}: The color of this {@link GUIWire} will now depend on the state of the given
466          * {@link ReadEnd}, and further changes of the given {@link ReadEnd} will result in readrawListeners being called.<br>
467          * The argument can be null, in which case the old binding is stopped.
468          * 
469          * @author Daniel Kirschten
470          */
471         public void setLogicModelBinding(ReadEnd end)
472         {
473                 if (this.end != null)
474                         this.end.deregisterObserver(logicObs);
475                 this.end = end;
476                 if (end != null)
477                         end.registerObserver(logicObs);
478         }
479
480         /**
481          * Returns whether this {@link GUIWire} has a logic model binding or not.
482          * 
483          * @author Daniel Kirschten
484          */
485         public boolean hasLogicModelBinding()
486         {
487                 return end != null;
488         }
489
490         /**
491          * If this {@link GUIWire} has a logic model binding, delegates to {@link Wire#forceValues(BitVector)} for the {@link Wire}
492          * corresponding to this {@link GUIWire}.
493          * 
494          * @author Daniel Kirschten
495          */
496         public void forceWireValues(BitVector values)
497         {
498                 end.getWire().forceValues(values);
499         }
500
501         /**
502          * If this {@link GUIWire} has a logic model binding, delegates to {@link ReadEnd#getValues()} for the {@link ReadEnd} corresponding to
503          * this {@link GUIWire}.
504          * 
505          * @author Daniel Kirschten
506          */
507         public BitVector getWireValues()
508         {
509                 return end.getValues();
510         }
511
512         // listeners
513
514         // @formatter:off
515         public void addPathChangedListener   (Consumer<GUIWire> listener) {pathChangedListeners.add    (listener);}
516
517         public void removePathChangedListener(Consumer<GUIWire> listener) {pathChangedListeners.remove(listener);}
518
519         private void callPathChangedListeners() {pathChangedListeners.forEach(l -> l.accept(this));}
520         // @formatter:on
521
522         @Override
523         public String toString()
524         {
525                 return "GUIWire [" + pin1 + "---" + pin2 + ", value=" + (end == null ? "null" : end.getValues()) + "]";
526         }
527 }