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