Restructured the Preferences system
[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;
146                 switch (supermodelPin.usage)
147                 {
148                 case INPUT:
149                         submodelPinUsage = PinUsage.OUTPUT;
150                         break;
151                 case OUTPUT:
152                         submodelPinUsage = PinUsage.INPUT;
153                         break;
154                 case TRISTATE:
155                         submodelPinUsage = PinUsage.TRISTATE;
156                         break;
157                 default:
158                         throw new IllegalArgumentException("Unknown enum constant: " + supermodelPin.usage);
159                 }
160                 MovablePin submodelPin = new MovablePin(submodelModifiable, submodelInterface, name, supermodelPin.logicWidth, submodelPinUsage,
161                                 supermodelPin.getRelX() / submodelScale, supermodelPin.getRelY() / submodelScale);
162
163                 submodelPin.addPinMovedListener(p ->
164                 {
165                         double newRelX = p.getRelX() * submodelScale;
166                         double newRelY = p.getRelY() * submodelScale;
167                         if (supermodelPin.getRelX() != newRelX || supermodelPin.getRelY() != newRelY)
168                                 supermodelPin.setRelPos(newRelX, newRelY);
169                 });
170                 supermodelPin.addPinMovedListener(p ->
171                 {
172                         double newRelX = p.getRelX() / submodelScale;
173                         double newRelY = p.getRelY() / submodelScale;
174                         if (submodelPin.getRelX() != newRelX || submodelPin.getRelY() != newRelY)
175                                 submodelPin.setRelPos(newRelX, newRelY);
176                 });
177
178                 submodelInterface.addPin(submodelPin);
179
180                 submodelPins.put(name, submodelPin);
181                 supermodelPins.put(name, supermodelPin);
182
183                 // no need to call requestRedraw() because addPin() will request a redraw
184                 return submodelPin;
185         }
186
187         /**
188          * Removes a submodel interface pin.
189          * 
190          * @author Daniel Kirschten
191          */
192         protected void removeSubmodelInterface(String name)
193         {
194                 super.removePin(name);// do this first to be fail-fast if this component doesn't have a pin with the given name
195                 Pin submodelPin = submodelPins.remove(name);
196                 submodelInterface.removePin(submodelPin.name);
197                 supermodelPins.remove(name);
198
199                 // no need to call requestRedraw() because removePin() will request a redraw
200         }
201
202         /**
203          * Returns a collection of submodel interface pins on the submodel side of this component.
204          * 
205          * @author Daniel Kirschten
206          */
207         public Map<String, Pin> getSubmodelPins()
208         {
209                 return submodelUnmovablePinsUnmodifiable;
210         }
211
212         /**
213          * Returns the submodel interface pin with the given name on the submodel side of this component.
214          * 
215          * @author Daniel Kirschten
216          */
217         public Pin getSubmodelPin(String name)
218         {
219                 return getSubmodelMovablePin(name);
220         }
221
222         /**
223          * Returns a collection of movable submodel interface pins on the submodel side of this component.
224          * 
225          * @author Daniel Kirschten
226          */
227         protected Map<String, MovablePin> getSubmodelMovablePins()
228         {
229                 return submodelMovablePinsUnmodifiable;
230         }
231
232         /**
233          * Returns the movable submodel interface pin with the given name on the submodel side of this component.
234          * 
235          * @author Daniel Kirschten
236          */
237         protected MovablePin getSubmodelMovablePin(String name)
238         {
239                 return submodelPins.get(name);
240         }
241
242         /**
243          * Returns a collection of submodel interface pins on the supermodel side of this component.
244          * 
245          * @author Daniel Kirschten
246          */
247         public Map<String, Pin> getSupermodelPins()
248         {
249                 return supermodelUnmovablePinsUnmodifiable;
250         }
251
252         /**
253          * Returns the submodel interface pin with the given name on the supermodel side of this component.
254          * 
255          * @author Daniel Kirschten
256          */
257         public Pin getSupermodelPin(String name)
258         {
259                 return getSupermodelMovablePin(name);
260         }
261
262         /**
263          * Returns a collection of movable submodel interface pins on the supermodel side of this component.
264          * 
265          * @author Daniel Kirschten
266          */
267         protected Map<String, MovablePin> getSupermodelMovablePins()
268         {
269                 return supermodelMovablePinsUnmodifiable;
270         }
271
272         /**
273          * Returns the movable submodel interface pin with the given name on the supermodel side of this component.
274          * 
275          * @author Daniel Kirschten
276          */
277         protected MovablePin getSupermodelMovablePin(String name)
278         {
279                 return supermodelPins.get(name);
280         }
281
282         // "graphical" operations
283
284         /**
285          * Sets the factor by which the submodel is scaled when rendering and calls redrawListeners. Note that the submodel interface pins will
286          * stay at their position relative to the supermodel, which means they will move relative to the submodel.
287          * 
288          * @author Daniel Kirschten
289          */
290         protected void setSubmodelScale(double submodelScale)
291         {
292                 this.submodelScale = submodelScale;
293
294                 for (Entry<String, MovablePin> e : supermodelPins.entrySet())
295                         getSubmodelMovablePin(e.getKey()).setRelPos(e.getValue().getRelX() * submodelScale, e.getValue().getRelY() * submodelScale);
296
297                 model.requestRedraw();// needed if there is no submodel interface pin
298         }
299
300         /**
301          * Returns the current factor by which the submodel is scaled when rendering.
302          * 
303          * @author Daniel Kirschten
304          */
305         public double getSubmodelScale()
306         {
307                 return submodelScale;
308         }
309
310         /**
311          * @see #renderSymbol(GeneralGC, Rectangle)
312          * 
313          * @author Daniel Kirschten
314          */
315         protected void setSymbolRenderer(Renderer symbolRenderer)
316         {
317                 this.symbolRenderer = symbolRenderer;
318                 model.requestRedraw();
319         }
320
321         /**
322          * @see #renderSymbol(GeneralGC, Rectangle)
323          * 
324          * @author Daniel Kirschten
325          */
326         public Renderer getSymbolRenderer()
327         {
328                 return symbolRenderer;
329         }
330
331         /**
332          * @see #renderOutline(GeneralGC, Rectangle)
333          * 
334          * @author Daniel Kirschten
335          */
336         protected void setOutlineRenderer(Renderer outlineRenderer)
337         {
338                 this.outlineRenderer = outlineRenderer;
339                 model.requestRedraw();
340         }
341
342         /**
343          * @see #renderOutline(GeneralGC, Rectangle)
344          * 
345          * @author Daniel Kirschten
346          */
347         public Renderer getOutlineRenderer()
348         {
349                 return outlineRenderer;
350         }
351
352         @Override
353         public boolean clicked(double x, double y)
354         {
355                 double scaledX = (x - getPosX()) / submodelScale;
356                 double scaledY = (y - getPosY()) / submodelScale;
357                 for (ModelComponent component : submodel.getComponentsByName().values())
358                         if (component.getBounds().contains(scaledX, scaledY) && component.clicked(scaledX, scaledY))
359                                 return true;
360                 return false;
361         }
362
363         @Override
364         public void render(GeneralGC gc, RenderPreferences renderPrefs, Rectangle visibleRegion)
365         {
366                 GCConfig conf = new GCConfig(gc);
367                 GeneralGC tgc = new TranslatedGC(gc, getPosX(), getPosY(), submodelScale, true);
368                 conf.reset(tgc);
369                 double visibleRegionFillRatio = getWidth() * getHeight() / (visibleRegion.width * visibleRegion.height);
370                 /**
371                  * If this {@link SubmodelComponent} fills at least this amount of the visible region vertically or horizontally, the submodel
372                  * starts to be visible.
373                  */
374                 // TODO add a listener
375                 double maxVisibleRegionFillRatioForAlpha0 = renderPrefs.getDouble(SUBMODEL_ZOOM_ALPHA_0);
376                 /**
377                  * If this {@link SubmodelComponent} fills at least this amount of the visible region vertically or horizontally, the submodel is
378                  * fully visible.
379                  */
380                 // TODO add a listener
381                 double minVisibleRegionFillRatioForAlpha1 = renderPrefs.getDouble(SUBMODEL_ZOOM_ALPHA_1);
382                 double alphaFactor = map(visibleRegionFillRatio, maxVisibleRegionFillRatioForAlpha0, minVisibleRegionFillRatioForAlpha1, 0, 1);
383                 alphaFactor = Math.max(0, Math.min(1, alphaFactor));
384                 // we need to take the old alpha into account to support nested submodules better.
385                 int oldAlpha = gc.getAlpha();
386                 int submodelAlpha = Math.max(0, Math.min(255, (int) (oldAlpha * alphaFactor)));
387                 int labelAlpha = Math.max(0, Math.min(255, (int) (oldAlpha * (1 - alphaFactor))));
388                 if (submodelAlpha != 0)
389                 {
390                         gc.setAlpha(submodelAlpha);
391                         renderer.render(tgc, renderPrefs,
392                                         visibleRegion.translate(getPosX() / submodelScale, getPosY() / submodelScale, 1 / submodelScale));
393                 }
394                 if (labelAlpha != 0)
395                 {
396                         gc.setAlpha(labelAlpha);
397                         renderSymbol(gc, renderPrefs, visibleRegion);
398                 }
399                 conf.reset(gc);
400                 // reset line width explicitly to avoid rounding errors causing weird glitches
401                 gc.setLineWidth(renderPrefs.getDouble(DEFAULT_LINE_WIDTH));
402                 // draw the outline after all other operations to make interface pins look better
403                 renderOutline(gc, renderPrefs, visibleRegion);
404         }
405
406         /**
407          * Render the symbol of this {@link SubmodelComponent}, e.g. the things that should be hidden if the submodel is drawn.
408          * 
409          * @author Daniel Kirschten
410          */
411         private void renderSymbol(GeneralGC gc, RenderPreferences renderPrefs, Rectangle visibleRegion)
412         {
413                 if (symbolRenderer != null)
414                         symbolRenderer.render(gc, renderPrefs, visibleRegion);
415         }
416
417         /**
418          * Render the outline of this {@link SubmodelComponent}, e.g. the graphical elements that should stay visible if the submodel is drawn.
419          * 
420          * @author Daniel Kirschten
421          */
422         private void renderOutline(GeneralGC gc, RenderPreferences renderPrefs, Rectangle visibleRegion)
423         {
424                 if (outlineRenderer != null)
425                         outlineRenderer.render(gc, renderPrefs, visibleRegion);
426         }
427
428         private static double map(double val, double valMin, double valMax, double mapMin, double mapMax)
429         {
430                 return mapMin + (val - valMin) * (mapMax - mapMin) / (valMax - valMin);
431         }
432
433         // serializing
434
435         /**
436          * {@link SubmodelComponent}'s implementation of {@link ModelComponent#getIDForSerializing(IdentifyParams)} returns "submodel". It is
437          * recommended to override this behaviour.
438          * 
439          * @see ModelComponent#getIDForSerializing(IdentifyParams)
440          * @see ModelComponent#getParamsForSerializing(IdentifyParams)
441          */
442         @Override
443         public String getIDForSerializing(IdentifyParams idParams)
444         {
445                 return "submodel";// TODO what ID?
446         }
447
448         /**
449          * {@link SubmodelComponent}'s implementation of {@link ModelComponent#getParamsForSerializing(IdentifyParams)} returns an instance of
450          * {@link SubmodelComponentParams}. It is recommended to override this behaviour.
451          * 
452          * @see ModelComponent#getIDForSerializing(IdentifyParams)
453          * @see ModelComponent#getParamsForSerializing(IdentifyParams)
454          */
455         @Override
456         public Object getParamsForSerializing(IdentifyParams idParams)
457         {
458                 return SubmodelComponentSerializer.serialize(this, idParams);
459         }
460
461         // operations no longer supported
462
463         @Override
464         protected void addPin(Pin pin)
465         {
466                 throw new UnsupportedOperationException("Can't add pins to a SubmodelComponent directly, call addSubmodelInterface instead");
467         }
468
469         @Override
470         protected void removePin(String name)
471         {
472                 throw new UnsupportedOperationException("Can't remove pins of a SubmodelComponent directly, call removeSubmodelInterface instead");
473         }
474
475         static
476         {
477                 IndirectModelComponentCreator.setComponentSupplier(SubmodelComponent.class.getCanonicalName(),
478                                 (m, p, n) -> SubmodelComponentSerializer.deserialize(m, JsonHandler.fromJsonTree(p, SubmodelComponentParams.class), n));
479         }
480 }