Redefined PinUsages; cleaned component JSONs
[Mograsim.git] / plugins / 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 static net.mograsim.logic.model.preferences.RenderPreferences.DEFAULT_LINE_WIDTH;
4 import static net.mograsim.logic.model.preferences.RenderPreferences.SUBMODEL_ZOOM_ALPHA_0;
5 import static net.mograsim.logic.model.preferences.RenderPreferences.SUBMODEL_ZOOM_ALPHA_1;
6
7 import java.util.Collections;
8 import java.util.HashMap;
9 import java.util.Map;
10 import java.util.Map.Entry;
11 import java.util.function.Consumer;
12
13 import net.haspamelodica.swt.helper.gcs.GCConfig;
14 import net.haspamelodica.swt.helper.gcs.GeneralGC;
15 import net.haspamelodica.swt.helper.gcs.TranslatedGC;
16 import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
17 import net.mograsim.logic.model.LogicUIRenderer;
18 import net.mograsim.logic.model.model.LogicModel;
19 import net.mograsim.logic.model.model.LogicModelModifiable;
20 import net.mograsim.logic.model.model.components.ModelComponent;
21 import net.mograsim.logic.model.model.wires.MovablePin;
22 import net.mograsim.logic.model.model.wires.Pin;
23 import net.mograsim.logic.model.model.wires.PinUsage;
24 import net.mograsim.logic.model.preferences.RenderPreferences;
25 import net.mograsim.logic.model.serializing.IdentifyParams;
26 import net.mograsim.logic.model.serializing.IndirectModelComponentCreator;
27 import net.mograsim.logic.model.serializing.SubmodelComponentParams;
28 import net.mograsim.logic.model.serializing.SubmodelComponentSerializer;
29 import net.mograsim.logic.model.snippets.Renderer;
30 import net.mograsim.logic.model.util.JsonHandler;
31
32 /**
33  * A {@link ModelComponent} consisting of another model. A <code>SubmodelComponent</code> can have so-called "interface pins" connecting the
34  * inner and outer models.
35  */
36 public abstract class SubmodelComponent extends ModelComponent
37 {
38         public static final String SUBMODEL_INTERFACE_NAME = "_submodelinterface";
39         /**
40          * A modifiable view of {@link #submodel}.
41          */
42         protected final LogicModelModifiable submodelModifiable;
43         /**
44          * The model this {@link SubmodelComponent} consists of.
45          */
46         public final LogicModel submodel;
47         /**
48          * The list of all submodel interface pins of this {@link SubmodelComponent} on the submodel side.
49          */
50         private final Map<String, MovablePin> submodelPins;
51         /**
52          * An unmodifiable view of {@link #submodelPins}.
53          */
54         private final Map<String, MovablePin> submodelMovablePinsUnmodifiable;
55         /**
56          * An unmodifiable view of {@link #submodelPins} where pins are not movable.
57          */
58         private final Map<String, Pin> submodelUnmovablePinsUnmodifiable;
59         /**
60          * The list of all submodel interface pins of this {@link SubmodelComponent} on the supermodel side.
61          */
62         private final Map<String, MovablePin> supermodelPins;
63         /**
64          * An unmodifiable view of {@link #supermodelPins}.
65          */
66         private final Map<String, MovablePin> supermodelMovablePinsUnmodifiable;
67         /**
68          * An unmodifiable view of {@link #supermodelPins} where pins are not movable.
69          */
70         private final Map<String, Pin> supermodelUnmovablePinsUnmodifiable;
71         /**
72          * A pseudo-component containing all submodel interface pins on the submodel side.
73          */
74         private final SubmodelInterface submodelInterface;
75         /**
76          * The factor by which the submodel is scaled when rendering.
77          */
78         private double submodelScale;
79         /**
80          * The renderer used for rendering the submodel.
81          */
82         private final LogicUIRenderer renderer;
83         /**
84          * The {@link Renderer} used to render the symbol of this SubmodelCoponent.
85          */
86         private Renderer symbolRenderer;
87         /**
88          * The {@link Renderer} used to render the outline of this SubmodelCoponent.
89          */
90         private Renderer outlineRenderer;
91
92         // creation and destruction
93
94         public SubmodelComponent(LogicModelModifiable model, String name)
95         {
96                 this(model, name, true);
97         }
98
99         protected SubmodelComponent(LogicModelModifiable model, String name, boolean callInit)
100         {
101                 super(model, name, false);
102                 this.submodelModifiable = new LogicModelModifiable();
103                 this.submodel = submodelModifiable;
104                 this.submodelPins = new HashMap<>();
105                 this.submodelMovablePinsUnmodifiable = Collections.unmodifiableMap(submodelPins);
106                 this.submodelUnmovablePinsUnmodifiable = Collections.unmodifiableMap(submodelPins);
107                 this.supermodelPins = new HashMap<>();
108                 this.supermodelMovablePinsUnmodifiable = Collections.unmodifiableMap(supermodelPins);
109                 this.supermodelUnmovablePinsUnmodifiable = Collections.unmodifiableMap(supermodelPins);
110                 this.submodelInterface = new SubmodelInterface(submodelModifiable);
111
112                 this.submodelScale = 1;
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                 if (callInit)
125                         init();
126         }
127
128         // pins
129
130         /**
131          * Adds a new submodel interface pin.
132          * 
133          * @param supermodelPin the submodel interface pin on the supermodel side
134          * 
135          * @return the submodel interface pin on the submodel side
136          * 
137          * @author Daniel Kirschten
138          */
139         protected Pin addSubmodelInterface(MovablePin supermodelPin)
140         {
141                 super.addPin(supermodelPin);// do this first to be fail-fast if the supermodel does not belong to this component
142
143                 String name = supermodelPin.name;
144                 // TODO if we upgrade to Java 12, replace with switch-expression
145                 PinUsage submodelPinUsage = supermodelPin.usage.getOpposite();
146                 MovablePin submodelPin = new MovablePin(submodelModifiable, submodelInterface, name, supermodelPin.logicWidth, submodelPinUsage,
147                                 supermodelPin.getRelX() / submodelScale, supermodelPin.getRelY() / submodelScale);
148
149                 submodelPin.addPinMovedListener(p ->
150                 {
151                         double newRelX = p.getRelX() * submodelScale;
152                         double newRelY = p.getRelY() * submodelScale;
153                         if (supermodelPin.getRelX() != newRelX || supermodelPin.getRelY() != newRelY)
154                                 supermodelPin.setRelPos(newRelX, newRelY);
155                 });
156                 supermodelPin.addPinMovedListener(p ->
157                 {
158                         double newRelX = p.getRelX() / submodelScale;
159                         double newRelY = p.getRelY() / submodelScale;
160                         if (submodelPin.getRelX() != newRelX || submodelPin.getRelY() != newRelY)
161                                 submodelPin.setRelPos(newRelX, newRelY);
162                 });
163
164                 submodelInterface.addPin(submodelPin);
165
166                 submodelPins.put(name, submodelPin);
167                 supermodelPins.put(name, supermodelPin);
168
169                 // no need to call requestRedraw() because addPin() will request a redraw
170                 return submodelPin;
171         }
172
173         /**
174          * Removes a submodel interface pin.
175          * 
176          * @author Daniel Kirschten
177          */
178         protected void removeSubmodelInterface(String name)
179         {
180                 super.removePin(name);// do this first to be fail-fast if this component doesn't have a pin with the given name
181                 Pin submodelPin = submodelPins.remove(name);
182                 submodelInterface.removePin(submodelPin.name);
183                 supermodelPins.remove(name);
184
185                 // no need to call requestRedraw() because removePin() will request a redraw
186         }
187
188         /**
189          * Returns a collection of submodel interface pins on the submodel side of this component.
190          * 
191          * @author Daniel Kirschten
192          */
193         public Map<String, Pin> getSubmodelPins()
194         {
195                 return submodelUnmovablePinsUnmodifiable;
196         }
197
198         /**
199          * Returns the submodel interface pin with the given name on the submodel side of this component.
200          * 
201          * @author Daniel Kirschten
202          */
203         public Pin getSubmodelPin(String name)
204         {
205                 return getSubmodelMovablePin(name);
206         }
207
208         /**
209          * Returns a collection of movable submodel interface pins on the submodel side of this component.
210          * 
211          * @author Daniel Kirschten
212          */
213         protected Map<String, MovablePin> getSubmodelMovablePins()
214         {
215                 return submodelMovablePinsUnmodifiable;
216         }
217
218         /**
219          * Returns the movable submodel interface pin with the given name on the submodel side of this component.
220          * 
221          * @author Daniel Kirschten
222          */
223         protected MovablePin getSubmodelMovablePin(String name)
224         {
225                 return submodelPins.get(name);
226         }
227
228         /**
229          * Returns a collection of submodel interface pins on the supermodel side of this component.
230          * 
231          * @author Daniel Kirschten
232          */
233         public Map<String, Pin> getSupermodelPins()
234         {
235                 return supermodelUnmovablePinsUnmodifiable;
236         }
237
238         /**
239          * Returns the submodel interface pin with the given name on the supermodel side of this component.
240          * 
241          * @author Daniel Kirschten
242          */
243         public Pin getSupermodelPin(String name)
244         {
245                 return getSupermodelMovablePin(name);
246         }
247
248         /**
249          * Returns a collection of movable submodel interface pins on the supermodel side of this component.
250          * 
251          * @author Daniel Kirschten
252          */
253         protected Map<String, MovablePin> getSupermodelMovablePins()
254         {
255                 return supermodelMovablePinsUnmodifiable;
256         }
257
258         /**
259          * Returns the movable submodel interface pin with the given name on the supermodel side of this component.
260          * 
261          * @author Daniel Kirschten
262          */
263         protected MovablePin getSupermodelMovablePin(String name)
264         {
265                 return supermodelPins.get(name);
266         }
267
268         // "graphical" operations
269
270         /**
271          * Sets the factor by which the submodel is scaled when rendering and calls redrawListeners. Note that the submodel interface pins will
272          * stay at their position relative to the supermodel, which means they will move relative to the submodel.
273          * 
274          * @author Daniel Kirschten
275          */
276         protected void setSubmodelScale(double submodelScale)
277         {
278                 this.submodelScale = submodelScale;
279
280                 for (Entry<String, MovablePin> e : supermodelPins.entrySet())
281                         getSubmodelMovablePin(e.getKey()).setRelPos(e.getValue().getRelX() * submodelScale, e.getValue().getRelY() * submodelScale);
282
283                 model.requestRedraw();// needed if there is no submodel interface pin
284         }
285
286         /**
287          * Returns the current factor by which the submodel is scaled when rendering.
288          * 
289          * @author Daniel Kirschten
290          */
291         public double getSubmodelScale()
292         {
293                 return submodelScale;
294         }
295
296         /**
297          * @see #renderSymbol(GeneralGC, Rectangle)
298          * 
299          * @author Daniel Kirschten
300          */
301         protected void setSymbolRenderer(Renderer symbolRenderer)
302         {
303                 this.symbolRenderer = symbolRenderer;
304                 model.requestRedraw();
305         }
306
307         /**
308          * @see #renderSymbol(GeneralGC, Rectangle)
309          * 
310          * @author Daniel Kirschten
311          */
312         public Renderer getSymbolRenderer()
313         {
314                 return symbolRenderer;
315         }
316
317         /**
318          * @see #renderOutline(GeneralGC, Rectangle)
319          * 
320          * @author Daniel Kirschten
321          */
322         protected void setOutlineRenderer(Renderer outlineRenderer)
323         {
324                 this.outlineRenderer = outlineRenderer;
325                 model.requestRedraw();
326         }
327
328         /**
329          * @see #renderOutline(GeneralGC, Rectangle)
330          * 
331          * @author Daniel Kirschten
332          */
333         public Renderer getOutlineRenderer()
334         {
335                 return outlineRenderer;
336         }
337
338         @Override
339         public boolean clicked(double x, double y)
340         {
341                 double scaledX = (x - getPosX()) / submodelScale;
342                 double scaledY = (y - getPosY()) / submodelScale;
343                 for (ModelComponent component : submodel.getComponentsByName().values())
344                         if (component.getBounds().contains(scaledX, scaledY) && component.clicked(scaledX, scaledY))
345                                 return true;
346                 return false;
347         }
348
349         @Override
350         public void render(GeneralGC gc, RenderPreferences renderPrefs, Rectangle visibleRegion)
351         {
352                 GCConfig conf = new GCConfig(gc);
353                 GeneralGC tgc = new TranslatedGC(gc, getPosX(), getPosY(), submodelScale, true);
354                 conf.reset(tgc);
355                 double visibleRegionFillRatio = getWidth() * getHeight() / (visibleRegion.width * visibleRegion.height);
356                 /**
357                  * If this {@link SubmodelComponent} fills at least this amount of the visible region vertically or horizontally, the submodel
358                  * starts to be visible.
359                  */
360                 // TODO add a listener
361                 double maxVisibleRegionFillRatioForAlpha0 = renderPrefs.getDouble(SUBMODEL_ZOOM_ALPHA_0);
362                 /**
363                  * If this {@link SubmodelComponent} fills at least this amount of the visible region vertically or horizontally, the submodel is
364                  * fully visible.
365                  */
366                 // TODO add a listener
367                 double minVisibleRegionFillRatioForAlpha1 = renderPrefs.getDouble(SUBMODEL_ZOOM_ALPHA_1);
368                 double alphaFactor = map(visibleRegionFillRatio, maxVisibleRegionFillRatioForAlpha0, minVisibleRegionFillRatioForAlpha1, 0, 1);
369                 alphaFactor = Math.max(0, Math.min(1, alphaFactor));
370                 // we need to take the old alpha into account to support nested submodules better.
371                 int oldAlpha = gc.getAlpha();
372                 int submodelAlpha = Math.max(0, Math.min(255, (int) (oldAlpha * alphaFactor)));
373                 int labelAlpha = Math.max(0, Math.min(255, (int) (oldAlpha * (1 - alphaFactor))));
374                 if (submodelAlpha != 0)
375                 {
376                         gc.setAlpha(submodelAlpha);
377                         renderer.render(tgc, renderPrefs,
378                                         visibleRegion.translate(getPosX() / submodelScale, getPosY() / submodelScale, 1 / submodelScale));
379                 }
380                 if (labelAlpha != 0)
381                 {
382                         gc.setAlpha(labelAlpha);
383                         renderSymbol(gc, renderPrefs, visibleRegion);
384                 }
385                 conf.reset(gc);
386                 // reset line width explicitly to avoid rounding errors causing weird glitches
387                 gc.setLineWidth(renderPrefs.getDouble(DEFAULT_LINE_WIDTH));
388                 // draw the outline after all other operations to make interface pins look better
389                 renderOutline(gc, renderPrefs, 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         private void renderSymbol(GeneralGC gc, RenderPreferences renderPrefs, Rectangle visibleRegion)
398         {
399                 if (symbolRenderer != null)
400                         symbolRenderer.render(gc, renderPrefs, 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         private void renderOutline(GeneralGC gc, RenderPreferences renderPrefs, Rectangle visibleRegion)
409         {
410                 if (outlineRenderer != null)
411                         outlineRenderer.render(gc, renderPrefs, 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         /**
422          * {@link SubmodelComponent}'s implementation of {@link ModelComponent#getIDForSerializing(IdentifyParams)} returns "submodel". It is
423          * recommended to override this behaviour.
424          * 
425          * @see ModelComponent#getIDForSerializing(IdentifyParams)
426          * @see ModelComponent#getParamsForSerializing(IdentifyParams)
427          */
428         @Override
429         public String getIDForSerializing(IdentifyParams idParams)
430         {
431                 return "submodel";// TODO what ID?
432         }
433
434         /**
435          * {@link SubmodelComponent}'s implementation of {@link ModelComponent#getParamsForSerializing(IdentifyParams)} returns an instance of
436          * {@link SubmodelComponentParams}. It is recommended to override this behaviour.
437          * 
438          * @see ModelComponent#getIDForSerializing(IdentifyParams)
439          * @see ModelComponent#getParamsForSerializing(IdentifyParams)
440          */
441         @Override
442         public Object getParamsForSerializing(IdentifyParams idParams)
443         {
444                 return SubmodelComponentSerializer.serialize(this, idParams);
445         }
446
447         // operations no longer supported
448
449         @Override
450         protected void addPin(Pin pin)
451         {
452                 throw new UnsupportedOperationException("Can't add pins to a SubmodelComponent directly, call addSubmodelInterface instead");
453         }
454
455         @Override
456         protected void removePin(String name)
457         {
458                 throw new UnsupportedOperationException("Can't remove pins of a SubmodelComponent directly, call removeSubmodelInterface instead");
459         }
460
461         static
462         {
463                 IndirectModelComponentCreator.setComponentSupplier(SubmodelComponent.class.getCanonicalName(),
464                                 (m, p, n) -> SubmodelComponentSerializer.deserialize(m, JsonHandler.fromJsonTree(p, SubmodelComponentParams.class), n));
465         }
466 }