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