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