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