Merge branch 'fusebug' into development
[Mograsim.git] / net.mograsim.logic.model / src / net / mograsim / logic / model / model / components / submodels / SubmodelComponent.java
1 package net.mograsim.logic.model.model.components.submodels;
2
3 import java.util.Collections;
4 import java.util.HashMap;
5 import java.util.Map;
6 import java.util.Map.Entry;
7 import java.util.function.Consumer;
8
9 import net.haspamelodica.swt.helper.gcs.GCConfig;
10 import net.haspamelodica.swt.helper.gcs.GeneralGC;
11 import net.haspamelodica.swt.helper.gcs.TranslatedGC;
12 import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
13 import net.mograsim.logic.model.LogicUIRenderer;
14 import net.mograsim.logic.model.model.ViewModel;
15 import net.mograsim.logic.model.model.ViewModelModifiable;
16 import net.mograsim.logic.model.model.components.GUIComponent;
17 import net.mograsim.logic.model.model.wires.MovablePin;
18 import net.mograsim.logic.model.model.wires.Pin;
19 import net.mograsim.logic.model.model.wires.PinUsage;
20 import net.mograsim.logic.model.snippets.Renderer;
21
22 /**
23  * A {@link GUIComponent} consisting of another model. A <code>SubmodelComponent</code> can have so-called "interface pins" connecting the
24  * inner and outer models.
25  */
26 //TODO override getParams
27 public abstract class SubmodelComponent extends GUIComponent
28 {
29         public static final String SUBMODEL_INTERFACE_NAME = "_submodelinterface";
30         /**
31          * A modifiable view of {@link #submodel}.
32          */
33         protected final ViewModelModifiable submodelModifiable;
34         /**
35          * The model this {@link SubmodelComponent} consists of.
36          */
37         public final ViewModel submodel;
38         /**
39          * The list of all submodel interface pins of this {@link SubmodelComponent} on the submodel side.
40          */
41         private final Map<String, MovablePin> submodelPins;
42         /**
43          * An unmodifiable view of {@link #submodelPins}.
44          */
45         private final Map<String, MovablePin> submodelMovablePinsUnmodifiable;
46         /**
47          * An unmodifiable view of {@link #submodelPins} where pins are not movable.
48          */
49         private final Map<String, Pin> submodelUnmovablePinsUnmodifiable;
50         /**
51          * The list of all submodel interface pins of this {@link SubmodelComponent} on the supermodel side.
52          */
53         private final Map<String, MovablePin> supermodelPins;
54         /**
55          * An unmodifiable view of {@link #supermodelPins}.
56          */
57         private final Map<String, MovablePin> supermodelMovablePinsUnmodifiable;
58         /**
59          * An unmodifiable view of {@link #supermodelPins} where pins are not movable.
60          */
61         private final Map<String, Pin> supermodelUnmovablePinsUnmodifiable;
62         /**
63          * A pseudo-component containing all submodel interface pins on the submodel side.
64          */
65         private final SubmodelInterface submodelInterface;
66
67         /**
68          * The factor by which the submodel is scaled when rendering.
69          */
70         private double submodelScale;
71         /**
72          * If this {@link SubmodelComponent} fills at least this amount of the visible region vertically or horizontally, the submodel starts to
73          * be visible.
74          */
75         private double maxVisibleRegionFillRatioForAlpha0;
76         /**
77          * If this {@link SubmodelComponent} fills at least this amount of the visible region vertically or horizontally, the submodel is fully
78          * visible.
79          */
80         private double minVisibleRegionFillRatioForAlpha1;
81         /**
82          * The renderer used for rendering the submodel.
83          */
84         private final LogicUIRenderer renderer;
85
86         /**
87          * The {@link Renderer} used to render the symbol of this SubmodelCoponent.
88          */
89         private Renderer symbolRenderer;
90         /**
91          * The {@link Renderer} used to render the outline of this SubmodelCoponent.
92          */
93         private Renderer outlineRenderer;
94
95         // creation and destruction
96
97         public SubmodelComponent(ViewModelModifiable model, String name)
98         {
99                 super(model, name);
100                 this.submodelModifiable = new ViewModelModifiable();
101                 this.submodel = submodelModifiable;
102                 this.submodelPins = new HashMap<>();
103                 this.submodelMovablePinsUnmodifiable = Collections.unmodifiableMap(submodelPins);
104                 this.submodelUnmovablePinsUnmodifiable = Collections.unmodifiableMap(submodelPins);
105                 this.supermodelPins = new HashMap<>();
106                 this.supermodelMovablePinsUnmodifiable = Collections.unmodifiableMap(supermodelPins);
107                 this.supermodelUnmovablePinsUnmodifiable = Collections.unmodifiableMap(supermodelPins);
108                 this.submodelInterface = new SubmodelInterface(submodelModifiable, SUBMODEL_INTERFACE_NAME);
109
110                 this.submodelScale = 1;
111                 this.maxVisibleRegionFillRatioForAlpha0 = 0.8;
112                 this.minVisibleRegionFillRatioForAlpha1 = 0.9;
113                 this.renderer = new LogicUIRenderer(submodelModifiable);
114
115                 Consumer<Runnable> redrawHandlerChangedListener = submodelModifiable::setRedrawHandler;
116                 model.addRedrawHandlerChangedListener(redrawHandlerChangedListener);
117                 model.addComponentRemovedListener(c ->
118                 {
119                         if (c == this)
120                                 model.removeRedrawHandlerChangedListener(redrawHandlerChangedListener);
121                 });
122                 submodelModifiable.setRedrawHandler(model.getRedrawHandler());
123         }
124
125         // pins
126
127         /**
128          * Adds a new submodel interface pin.
129          * 
130          * @param supermodelPin the submodel interface pin on the supermodel side
131          * 
132          * @return the submodel interface pin on the submodel side
133          * 
134          * @author Daniel Kirschten
135          */
136         protected Pin addSubmodelInterface(MovablePin supermodelPin)
137         {
138                 super.addPin(supermodelPin);// do this first to be fail-fast if the supermodel does not belong to this component
139
140                 String name = supermodelPin.name;
141                 // TODO if we upgrade to Java 12, replace with switch-expression
142                 PinUsage submodelPinUsage;
143                 switch (supermodelPin.usage)
144                 {
145                 case INPUT:
146                         submodelPinUsage = PinUsage.OUTPUT;
147                         break;
148                 case OUTPUT:
149                         submodelPinUsage = PinUsage.INPUT;
150                         break;
151                 case TRISTATE:
152                         submodelPinUsage = PinUsage.TRISTATE;
153                         break;
154                 default:
155                         throw new IllegalArgumentException("Unknown enum constant: " + supermodelPin.usage);
156                 }
157                 MovablePin submodelPin = new MovablePin(submodelInterface, name, supermodelPin.logicWidth, submodelPinUsage,
158                                 supermodelPin.getRelX() / submodelScale, supermodelPin.getRelY() / submodelScale);
159
160                 submodelPin.addPinMovedListener(p ->
161                 {
162                         double newRelX = p.getRelX() * submodelScale;
163                         double newRelY = p.getRelY() * submodelScale;
164                         if (supermodelPin.getRelX() != newRelX || supermodelPin.getRelY() != newRelY)
165                                 supermodelPin.setRelPos(newRelX, newRelY);
166                 });
167                 supermodelPin.addPinMovedListener(p ->
168                 {
169                         double newRelX = p.getRelX() / submodelScale;
170                         double newRelY = p.getRelY() / submodelScale;
171                         if (submodelPin.getRelX() != newRelX || submodelPin.getRelY() != newRelY)
172                                 submodelPin.setRelPos(newRelX, newRelY);
173                 });
174
175                 submodelInterface.addPin(submodelPin);
176
177                 submodelPins.put(name, submodelPin);
178                 supermodelPins.put(name, supermodelPin);
179
180                 // no need to call requestRedraw() because addPin() will request a redraw
181                 return submodelPin;
182         }
183
184         /**
185          * Removes a submodel interface pin.
186          * 
187          * @author Daniel Kirschten
188          */
189         protected void removeSubmodelInterface(String name)
190         {
191                 super.removePin(name);// do this first to be fail-fast if this component doesn't have a pin with the given name
192                 Pin submodelPin = submodelPins.remove(name);
193                 submodelInterface.removePin(submodelPin.name);
194                 supermodelPins.remove(name);
195
196                 // no need to call requestRedraw() because removePin() will request a redraw
197         }
198
199         /**
200          * Returns a collection of submodel interface pins on the submodel side of this component.
201          * 
202          * @author Daniel Kirschten
203          */
204         public Map<String, Pin> getSubmodelPins()
205         {
206                 return submodelUnmovablePinsUnmodifiable;
207         }
208
209         /**
210          * Returns the submodel interface pin with the given name on the submodel side of this component.
211          * 
212          * @author Daniel Kirschten
213          */
214         public Pin getSubmodelPin(String name)
215         {
216                 return getSubmodelMovablePin(name);
217         }
218
219         /**
220          * Returns a collection of movable submodel interface pins on the submodel side of this component.
221          * 
222          * @author Daniel Kirschten
223          */
224         protected Map<String, MovablePin> getSubmodelMovablePins()
225         {
226                 return submodelMovablePinsUnmodifiable;
227         }
228
229         /**
230          * Returns the movable submodel interface pin with the given name on the submodel side of this component.
231          * 
232          * @author Daniel Kirschten
233          */
234         protected MovablePin getSubmodelMovablePin(String name)
235         {
236                 return submodelPins.get(name);
237         }
238
239         /**
240          * Returns a collection of submodel interface pins on the supermodel side of this component.
241          * 
242          * @author Daniel Kirschten
243          */
244         public Map<String, Pin> getSupermodelPins()
245         {
246                 return supermodelUnmovablePinsUnmodifiable;
247         }
248
249         /**
250          * Returns the submodel interface pin with the given name on the supermodel side of this component.
251          * 
252          * @author Daniel Kirschten
253          */
254         public Pin getSupermodelPin(String name)
255         {
256                 return getSupermodelMovablePin(name);
257         }
258
259         /**
260          * Returns a collection of movable submodel interface pins on the supermodel side of this component.
261          * 
262          * @author Daniel Kirschten
263          */
264         protected Map<String, MovablePin> getSupermodelMovablePins()
265         {
266                 return supermodelMovablePinsUnmodifiable;
267         }
268
269         /**
270          * Returns the movable submodel interface pin with the given name on the supermodel side of this component.
271          * 
272          * @author Daniel Kirschten
273          */
274         protected MovablePin getSupermodelMovablePin(String name)
275         {
276                 return supermodelPins.get(name);
277         }
278
279         // "graphical" operations
280
281         /**
282          * Sets the factor by which the submodel is scaled when rendering and calls redrawListeners. Note that the submodel interface pins will
283          * stay at their position relative to the supermodel, which means they will move relative to the submodel.
284          * 
285          * @author Daniel Kirschten
286          */
287         protected void setSubmodelScale(double submodelScale)
288         {
289                 this.submodelScale = submodelScale;
290
291                 for (Entry<String, MovablePin> e : supermodelPins.entrySet())
292                         getSubmodelMovablePin(e.getKey()).setRelPos(e.getValue().getRelX() * submodelScale, e.getValue().getRelY() * submodelScale);
293
294                 model.requestRedraw();// needed if there is no submodel interface pin
295         }
296
297         /**
298          * Returns the current factor by which the submodel is scaled when rendering.
299          * 
300          * @author Daniel Kirschten
301          */
302         public double getSubmodelScale()
303         {
304                 return submodelScale;
305         }
306
307         /**
308          * @see #renderSymbol(GeneralGC, Rectangle)
309          * 
310          * @author Daniel Kirschten
311          */
312         protected void setSymbolRenderer(Renderer symbolRenderer)
313         {
314                 this.symbolRenderer = symbolRenderer;
315                 model.requestRedraw();
316         }
317
318         /**
319          * @see #renderSymbol(GeneralGC, Rectangle)
320          * 
321          * @author Daniel Kirschten
322          */
323         public Renderer getSymbolRenderer()
324         {
325                 return symbolRenderer;
326         }
327
328         /**
329          * @see #renderOutline(GeneralGC, Rectangle)
330          * 
331          * @author Daniel Kirschten
332          */
333         protected void setOutlineRenderer(Renderer outlineRenderer)
334         {
335                 this.outlineRenderer = outlineRenderer;
336                 model.requestRedraw();
337         }
338
339         /**
340          * @see #renderOutline(GeneralGC, Rectangle)
341          * 
342          * @author Daniel Kirschten
343          */
344         public Renderer getOutlineRenderer()
345         {
346                 return outlineRenderer;
347         }
348
349         @Override
350         public void render(GeneralGC gc, Rectangle visibleRegion)
351         {
352                 GCConfig conf = new GCConfig(gc);
353                 TranslatedGC tgc = new TranslatedGC(gc, getPosX(), getPosY(), submodelScale, true);
354                 conf.reset(tgc);
355                 double visibleRegionFillRatio = Math.max(getWidth() / visibleRegion.width, getHeight() / visibleRegion.height);
356                 double alphaFactor = map(visibleRegionFillRatio, maxVisibleRegionFillRatioForAlpha0, minVisibleRegionFillRatioForAlpha1, 0, 1);
357                 alphaFactor = Math.max(0, Math.min(1, alphaFactor));
358                 // we need to take the old alpha into account to support nested submodules better.
359                 int oldAlpha = gc.getAlpha();
360                 int submodelAlpha = Math.max(0, Math.min(255, (int) (oldAlpha * alphaFactor)));
361                 int labelAlpha = Math.max(0, Math.min(255, (int) (oldAlpha * (1 - alphaFactor))));
362                 if (submodelAlpha != 0)
363                 {
364                         gc.setAlpha(submodelAlpha);
365                         renderer.render(tgc, visibleRegion.translate(getPosX() / submodelScale, getPosY() / submodelScale, 1 / submodelScale));
366                 }
367                 if (labelAlpha != 0)
368                 {
369                         gc.setAlpha(labelAlpha);
370                         renderSymbol(gc, visibleRegion);
371                 }
372                 conf.reset(gc);
373                 // draw the outline after all other operations to make interface pins look better
374                 renderOutline(gc, visibleRegion);
375         }
376
377         /**
378          * Render the symbol of this {@link SubmodelComponent}, e.g. the things that should be hidden if the submodel is drawn.
379          * 
380          * @author Daniel Kirschten
381          */
382         protected void renderSymbol(GeneralGC gc, Rectangle visibleRegion)
383         {
384                 if (symbolRenderer != null)
385                         symbolRenderer.render(gc, visibleRegion);
386         }
387
388         /**
389          * Render the outline of this {@link SubmodelComponent}, e.g. the graphical elements that should stay visible if the submodel is drawn.
390          * 
391          * @author Daniel Kirschten
392          */
393         protected void renderOutline(GeneralGC gc, Rectangle visibleRegion)
394         {
395                 if (outlineRenderer != null)
396                         outlineRenderer.render(gc, visibleRegion);
397         }
398
399         private static double map(double val, double valMin, double valMax, double mapMin, double mapMax)
400         {
401                 return mapMin + (val - valMin) * (mapMax - mapMin) / (valMax - valMin);
402         }
403
404         @Override
405         public boolean clicked(double x, double y)
406         {
407                 double scaledX = (x - getPosX()) / submodelScale;
408                 double scaledY = (y - getPosY()) / submodelScale;
409                 for (GUIComponent component : submodel.getComponentsByName().values())
410                         if (component.getBounds().contains(scaledX, scaledY) && component.clicked(scaledX, scaledY))
411                                 return true;
412                 return false;
413         }
414
415         // operations no longer supported
416
417         @Override
418         protected void addPin(Pin pin)
419         {
420                 throw new UnsupportedOperationException("Can't add pins to a SubmodelComponent directly, call addSubmodelInterface instead");
421         }
422
423         @Override
424         protected void removePin(String name)
425         {
426                 throw new UnsupportedOperationException("Can't remove pins of a SubmodelComponent directly, call removeSubmodelInterface instead");
427         }
428 }