1 package net.mograsim.logic.ui.model.components;
3 import java.util.Collections;
4 import java.util.HashMap;
5 import java.util.HashSet;
6 import java.util.Iterator;
9 import java.util.Map.Entry;
12 import net.haspamelodica.swt.helper.gcs.GCConfig;
13 import net.haspamelodica.swt.helper.gcs.GeneralGC;
14 import net.haspamelodica.swt.helper.gcs.TranslatedGC;
15 import net.haspamelodica.swt.helper.swtobjectwrappers.Point;
16 import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
17 import net.mograsim.logic.ui.LogicUIRenderer;
18 import net.mograsim.logic.ui.model.ViewModel;
19 import net.mograsim.logic.ui.model.ViewModelModifiable;
20 import net.mograsim.logic.ui.model.components.SubmodelComponentParams.ComponentCompositionParams;
21 import net.mograsim.logic.ui.model.components.SubmodelComponentParams.ComponentCompositionParams.InnerComponentParams;
22 import net.mograsim.logic.ui.model.components.SubmodelComponentParams.InnerPinParams;
23 import net.mograsim.logic.ui.model.components.SubmodelComponentParams.InnerWireParams;
24 import net.mograsim.logic.ui.model.components.SubmodelComponentParams.InterfacePinParams;
25 import net.mograsim.logic.ui.model.wires.GUIWire;
26 import net.mograsim.logic.ui.model.wires.MovablePin;
27 import net.mograsim.logic.ui.model.wires.Pin;
30 * A {@link GUIComponent} consisting of another model. A <code>SubmodelComponent</code> can have so-called "interface pins" connecting the
31 * inner and outer models.
33 public abstract class SubmodelComponent extends GUIComponent
36 * A modifiable view of {@link #submodel}.
38 protected final ViewModelModifiable submodelModifiable;
40 * The model this {@link SubmodelComponent} consists of.
42 public final ViewModel submodel;
44 * The list of all submodel interface pins of this {@link SubmodelComponent} on the submodel side.
46 private final Map<String, MovablePin> submodelPins;
48 * An unmodifiable view of {@link #submodelPins}.
50 private final Map<String, MovablePin> submodelMovablePinsUnmodifiable;
52 * An unmodifiable view of {@link #submodelPins} where pins are not movable.
54 private final Map<String, Pin> submodelUnmovablePinsUnmodifiable;
56 * The list of all submodel interface pins of this {@link SubmodelComponent} on the supermodel side.
58 private final Map<String, MovablePin> supermodelPins;
60 * An unmodifiable view of {@link #supermodelPins}.
62 private final Map<String, MovablePin> supermodelMovablePinsUnmodifiable;
64 * An unmodifiable view of {@link #supermodelPins} where pins are not movable.
66 private final Map<String, Pin> supermodelUnmovablePinsUnmodifiable;
68 * A pseudo-component containing all submodel interface pins on the submodel side.
70 private final SubmodelInterface submodelInterface;
73 * The list of all high level state IDs this component supports without delegating to subcomponents.
75 private final Set<String> highLevelAtomicStates;
77 * A map of high level state subcomponent IDs to the {@link GUIComponent} high level state access requests are delegated to.
79 private final Map<String, GUIComponent> subcomponentsByHighLevelStateSubcomponentID;
82 * The factor by which the submodel is scaled when rendering.
84 private double submodelScale;
86 * If this {@link SubmodelComponent} fills at least this amount of the visible region vertically or horizontally, the submodel starts to
89 private double maxVisibleRegionFillRatioForAlpha0;
91 * If this {@link SubmodelComponent} fills at least this amount of the visible region vertically or horizontally, the submodel is fully
94 private double minVisibleRegionFillRatioForAlpha1;
96 * The renderer used for rendering the submodel.
98 private final LogicUIRenderer renderer;
100 // creation and destruction
102 public SubmodelComponent(ViewModelModifiable model)
105 this.submodelModifiable = new ViewModelModifiable();
106 this.submodel = submodelModifiable;
107 this.submodelPins = new HashMap<>();
108 this.submodelMovablePinsUnmodifiable = Collections.unmodifiableMap(submodelPins);
109 this.submodelUnmovablePinsUnmodifiable = Collections.unmodifiableMap(submodelPins);
110 this.supermodelPins = new HashMap<>();
111 this.supermodelMovablePinsUnmodifiable = Collections.unmodifiableMap(supermodelPins);
112 this.supermodelUnmovablePinsUnmodifiable = Collections.unmodifiableMap(supermodelPins);
113 this.submodelInterface = new SubmodelInterface(submodelModifiable);
115 this.highLevelAtomicStates = new HashSet<>();
116 this.subcomponentsByHighLevelStateSubcomponentID = new HashMap<>();
118 this.submodelScale = 1;
119 this.maxVisibleRegionFillRatioForAlpha0 = 0.4;
120 this.minVisibleRegionFillRatioForAlpha1 = 0.8;
121 this.renderer = new LogicUIRenderer(submodelModifiable);
123 submodelModifiable.addRedrawListener(this::requestRedraw);
129 * Adds a new submodel interface pin.
131 * @param supermodelPin the submodel interface pin on the supermodel side
133 * @return the submodel interface pin on the submodel side
135 * @author Daniel Kirschten
137 protected Pin addSubmodelInterface(MovablePin supermodelPin)
139 super.addPin(supermodelPin);// do this first to be fail-fast if the supermodel does not belong to this component
141 String name = supermodelPin.name;
142 MovablePin submodelPin = new MovablePin(submodelInterface, name, supermodelPin.logicWidth, supermodelPin.getRelX() / submodelScale,
143 supermodelPin.getRelY() / submodelScale);
145 submodelPin.addPinMovedListener(p ->
147 double newRelX = p.getRelX() * submodelScale;
148 double newRelY = p.getRelY() * submodelScale;
149 if (supermodelPin.getRelX() != newRelX || supermodelPin.getRelY() != newRelY)
150 supermodelPin.setRelPos(newRelX, newRelY);
152 supermodelPin.addPinMovedListener(p ->
154 double newRelX = p.getRelX() / submodelScale;
155 double newRelY = p.getRelY() / submodelScale;
156 if (submodelPin.getRelX() != newRelX || submodelPin.getRelY() != newRelY)
157 submodelPin.setRelPos(newRelX, newRelY);
160 submodelInterface.addPin(submodelPin);
162 submodelPins.put(name, submodelPin);
163 supermodelPins.put(name, supermodelPin);
165 // no need to call requestRedraw() because addPin() will request a redraw
170 * Removes a submodel interface pin.
172 * @author Daniel Kirschten
174 protected void removeSubmodelInterface(String name)
176 super.removePin(name);// do this first to be fail-fast if this component doesn't have a pin with the given name
177 Pin submodelPin = submodelPins.remove(name);
178 submodelInterface.removePin(submodelPin.name);
179 supermodelPins.remove(name);
181 // no need to call requestRedraw() because removePin() will request a redraw
185 * Returns a collection of submodel interface pins on the submodel side of this component.
187 * @author Daniel Kirschten
189 public Map<String, Pin> getSubmodelPins()
191 return submodelUnmovablePinsUnmodifiable;
195 * Returns the submodel interface pin with the given name on the submodel side of this component.
197 * @author Daniel Kirschten
199 public Pin getSubmodelPin(String name)
201 return getSubmodelMovablePin(name);
205 * Returns a collection of movable submodel interface pins on the submodel side of this component.
207 * @author Daniel Kirschten
209 protected Map<String, MovablePin> getSubmodelMovablePins()
211 return submodelMovablePinsUnmodifiable;
215 * Returns the movable submodel interface pin with the given name on the submodel side of this component.
217 * @author Daniel Kirschten
219 protected MovablePin getSubmodelMovablePin(String name)
221 return submodelPins.get(name);
225 * Returns a collection of submodel interface pins on the supermodel side of this component.
227 * @author Daniel Kirschten
229 public Map<String, Pin> getSupermodelPins()
231 return supermodelUnmovablePinsUnmodifiable;
235 * Returns the submodel interface pin with the given name on the supermodel side of this component.
237 * @author Daniel Kirschten
239 public Pin getSupermodelPin(String name)
241 return getSupermodelMovablePin(name);
245 * Returns a collection of movable submodel interface pins on the supermodel side of this component.
247 * @author Daniel Kirschten
249 protected Map<String, MovablePin> getSupermodelMovablePins()
251 return supermodelMovablePinsUnmodifiable;
255 * Returns the movable submodel interface pin with the given name on the supermodel side of this component.
257 * @author Daniel Kirschten
259 protected MovablePin getSupermodelMovablePin(String name)
261 return supermodelPins.get(name);
267 * Adds the given subcomponent ID to the set of allowed subcomponent IDs and links the given {@link GUIComponent} as the delegate target
268 * for this subcomponent ID. <br>
269 * Note that this method does not affect whether {@link #setSubcomponentHighLevelState(String, String, Object)
270 * set}/{@link #getSubcomponentHighLevelState(String, String)} will be called. <br>
271 * See {@link GUIComponent#setHighLevelState(String, Object)} for details about subcomponent IDs.
273 * @author Daniel Kirschten
275 protected void addHighLevelStateSubcomponentID(String subcomponentID, GUIComponent subcomponent)
277 checkHighLevelStateIDPart(subcomponentID);
278 subcomponentsByHighLevelStateSubcomponentID.put(subcomponentID, subcomponent);
282 * Removes the given subcomponent ID from the set of allowed subcomponent IDs. <br>
283 * Note that this method does not affect whether {@link #setSubcomponentHighLevelState(String, String, Object)
284 * set}/{@link #getSubcomponentHighLevelState(String, String)} will be called.<br>
285 * See {@link GUIComponent#setHighLevelState(String, Object)} for details about subcomponent IDs.
287 * @author Daniel Kirschten
289 protected void removeHighLevelStateSubcomponentID(String subcomponentID)
291 subcomponentsByHighLevelStateSubcomponentID.remove(subcomponentID);
295 * Adds the given atomic state ID to the set of allowed atomic state IDs. <br>
296 * See {@link GUIComponent#setHighLevelState(String, Object)} for details about atomic state IDs.
298 * @author Daniel Kirschten
300 protected void addAtomicHighLevelStateID(String stateID)
302 checkHighLevelStateIDPart(stateID);
303 highLevelAtomicStates.add(stateID);
307 * Removes the given atomic state ID from the set of allowed atomic state IDs. <br>
308 * See {@link GUIComponent#setHighLevelState(String, Object)} for details about atomic state IDs.
310 * @author Daniel Kirschten
312 protected void removeAtomicHighLevelStateID(String stateID)
314 highLevelAtomicStates.remove(stateID);
318 public final void setHighLevelState(String stateID, Object newState)
320 int indexOfDot = stateID.indexOf('.');
321 if (indexOfDot == -1)
322 if (highLevelAtomicStates.contains(stateID))
323 setAtomicHighLevelState(stateID, newState);
325 super.setHighLevelState(stateID, newState);
327 setSubcomponentHighLevelState(stateID.substring(0, indexOfDot), stateID.substring(indexOfDot + 1), newState);
331 * This method is called in {@link #setHighLevelState(String, Object)} when the state ID is not atomic. The default implementation uses
332 * the information given to {@link #addHighLevelStateSubcomponentID(String, GUIComponent)
333 * add}/{@link #removeHighLevelStateSubcomponentID(String)} to decide which subcomponent to delegate to.<br>
334 * Note that {@link #addHighLevelStateSubcomponentID(String, GUIComponent) add}/{@link #removeHighLevelStateSubcomponentID(String)}
335 * don't affect whether this method will be called.
337 * @author Daniel Kirschten
339 protected void setSubcomponentHighLevelState(String subcomponentID, String subcomponentHighLevelStateID, Object newState)
341 GUIComponent subcomponent = subcomponentsByHighLevelStateSubcomponentID.get(subcomponentID);
342 if (subcomponent != null)
343 subcomponent.setHighLevelState(subcomponentHighLevelStateID, newState);
345 super.setHighLevelState(subcomponentID + "." + subcomponentHighLevelStateID, newState);
349 * This method is called in {@link #setHighLevelState(String, Object)} when the state ID is atomic and in the set of allowed atomic
351 * See {@link GUIComponent#setHighLevelState(String, Object)} for details about atomic state IDs.
353 * @author Daniel Kirschten
355 @SuppressWarnings({ "static-method", "unused" }) // this method is intended to be overridden
356 protected void setAtomicHighLevelState(String stateID, Object newState)
358 throw new IllegalStateException("Unknown high level state ID: " + stateID);
362 public final Object getHighLevelState(String stateID)
364 int indexOfDot = stateID.indexOf('.');
365 if (indexOfDot == -1)
367 if (highLevelAtomicStates.contains(stateID))
368 return getAtomicHighLevelState(stateID);
369 return super.getHighLevelState(stateID);
371 return getSubcomponentHighLevelState(stateID.substring(0, indexOfDot), stateID.substring(indexOfDot + 1));
375 * This method is called in {@link #getHighLevelState(String, Object)} when the state ID is not atomic. The default implementation uses
376 * the information given to {@link #addHighLevelStateSubcomponentID(String, GUIComponent)
377 * add}/{@link #removeHighLevelStateSubcomponentID(String)} to decide which subcomponent to delegate to. <br>
378 * Note that {@link #addHighLevelStateSubcomponentID(String, GUIComponent) add}/{@link #removeHighLevelStateSubcomponentID(String)}
379 * don't affect whether this method will be called.
381 * @author Daniel Kirschten
383 protected Object getSubcomponentHighLevelState(String subcomponentID, String subcomponentHighLevelStateID)
385 GUIComponent subcomponent = subcomponentsByHighLevelStateSubcomponentID.get(subcomponentID);
386 if (subcomponent != null)
387 return subcomponent.getHighLevelState(subcomponentHighLevelStateID);
388 return super.getHighLevelState(subcomponentID + "." + subcomponentHighLevelStateID);
392 * This method is called in {@link SubmodelComponent#getHighLevelState(String)} when the state ID is in the set of allowed atomic state
394 * See {@link GUIComponent#setHighLevelState(String, Object)} for details about atomic state IDs.
396 * @author Daniel Kirschten
398 @SuppressWarnings("static-method") // this method is intended to be overridden
399 protected Object getAtomicHighLevelState(String stateID)
401 throw new IllegalStateException("Unknown high level state ID: " + stateID);
404 private static void checkHighLevelStateIDPart(String stateIDPart)
406 if (stateIDPart.indexOf('.') != -1)
407 throw new IllegalArgumentException("Illegal high level state ID part (contains dot): " + stateIDPart);
411 // "graphical" operations
414 * Sets the factor by which the submodel is scaled when rendering and calls redrawListeners. Note that the submodel interface pins will
415 * stay at their position relative to the supermodel, which means they will move relative to the submodel.
417 * @author Daniel Kirschten
419 protected void setSubmodelScale(double submodelScale)
421 this.submodelScale = submodelScale;
423 for (Entry<String, MovablePin> e : supermodelPins.entrySet())
424 getSubmodelMovablePin(e.getKey()).setRelPos(e.getValue().getRelX() * submodelScale, e.getValue().getRelY() * submodelScale);
426 requestRedraw();// needed if there is no submodel interface pin
430 * Returns the current factor by which the submodel is scaled when rendering.
432 * @author Daniel Kirschten
434 protected double getSubmodelScale()
436 return submodelScale;
440 public void render(GeneralGC gc, Rectangle visibleRegion)
442 GCConfig conf = new GCConfig(gc);
443 TranslatedGC tgc = new TranslatedGC(gc, getPosX(), getPosY(), submodelScale, true);
445 double visibleRegionFillRatio = Math.max(getWidth() / visibleRegion.width, getHeight() / visibleRegion.height);
446 double alphaFactor = map(visibleRegionFillRatio, maxVisibleRegionFillRatioForAlpha0, minVisibleRegionFillRatioForAlpha1, 0, 1);
447 alphaFactor = Math.max(0, Math.min(1, alphaFactor));
448 // we need to take the old alpha into account to support nested submodules better.
449 int oldAlpha = gc.getAlpha();
450 int submodelAlpha = Math.max(0, Math.min(255, (int) (oldAlpha * alphaFactor)));
451 int labelAlpha = Math.max(0, Math.min(255, (int) (oldAlpha * (1 - alphaFactor))));
452 if (submodelAlpha != 0)
454 gc.setAlpha(submodelAlpha);
455 renderer.render(tgc, visibleRegion.translate(getPosX() / submodelScale, getPosY() / submodelScale, 1 / submodelScale));
459 gc.setAlpha(labelAlpha);
460 renderSymbol(gc, visibleRegion);
463 // draw the outline after all other operations to make interface pins look better
464 renderOutline(gc, visibleRegion);
468 * Render the outline of this {@link SubmodelComponent}, e.g. the graphical elements that should stay visible if the submodel is drawn.
470 * @author Daniel Kirschten
472 protected abstract void renderOutline(GeneralGC gc, Rectangle visibleRegion);
475 * Render the symbol of this {@link SubmodelComponent}, e.g. the things that should be hidden if the submodel is drawn.
477 * @author Daniel Kirschten
479 protected abstract void renderSymbol(GeneralGC gc, Rectangle visibleRegion);
481 private static double map(double val, double valMin, double valMax, double mapMin, double mapMax)
483 return mapMin + (val - valMin) * (mapMax - mapMin) / (valMax - valMin);
487 public boolean clicked(double x, double y)
489 double scaledX = (x - getPosX()) / submodelScale;
490 double scaledY = (y - getPosY()) / submodelScale;
491 for (GUIComponent component : submodel.getComponents())
492 if (component.getBounds().contains(scaledX, scaledY) && component.clicked(scaledX, scaledY))
500 * @return {@link SubmodelComponentParams}, which describe this {@link SubmodelComponent}.
502 public SubmodelComponentParams calculateParams()
504 SubmodelComponentParams params = new SubmodelComponentParams();
505 params.name = getIdentifier();
506 params.type = SubmodelComponent.class.getSimpleName();
507 params.composition = calculateCompositionParams();
509 params.width = getWidth();
510 params.height = getHeight();
512 InterfacePinParams[] iPins = new InterfacePinParams[getPins().size()];
514 for (Pin p : getPins().values())
516 InterfacePinParams iPinParams = new InterfacePinParams();
517 iPins[i] = iPinParams;
518 iPinParams.location = p.getRelPos();
519 iPinParams.name = p.name;
520 iPinParams.logicWidth = p.logicWidth;
523 params.interfacePins = iPins;
527 protected ComponentCompositionParams calculateCompositionParams()
529 ComponentCompositionParams params = new ComponentCompositionParams();
530 params.innerScale = getSubmodelScale();
532 List<GUIComponent> compList = submodelModifiable.getComponents();
533 Iterator<GUIComponent> componentIt = compList.iterator();
534 componentIt.next(); // Skip inner SubmodelInterface
535 InnerComponentParams[] comps = new InnerComponentParams[compList.size() - 1];
537 while (componentIt.hasNext())
539 GUIComponent component = componentIt.next();
540 InnerComponentParams inner = new InnerComponentParams();
542 inner.params = component.getInstantiationParameters();
543 inner.pos = new Point(component.getPosX(), component.getPosY());
544 inner.name = component.getIdentifier();
547 params.subComps = comps;
549 List<GUIWire> wireList = submodelModifiable.getWires();
550 InnerWireParams wires[] = new InnerWireParams[wireList.size()];
552 for (GUIWire wire : wireList)
554 InnerWireParams inner = new InnerWireParams();
556 InnerPinParams pin1Params = new InnerPinParams(), pin2Params = new InnerPinParams();
558 pin1Params.pinName = wire.getPin1().name;
559 pin1Params.compId = compList.indexOf(wire.getPin1().component);
560 pin2Params.pinName = wire.getPin2().name;
561 pin2Params.compId = compList.indexOf(wire.getPin2().component);
562 inner.pin1 = pin1Params;
563 inner.pin2 = pin2Params;
564 inner.path = wire.getPath();
567 params.innerWires = wires;
571 // operations no longer supported
574 protected void addPin(Pin pin)
576 throw new UnsupportedOperationException("Can't add pins to a SubmodelComponent directly, call addSubmodelInterface instead");
580 protected void removePin(String name)
582 throw new UnsupportedOperationException("Can't remove pins of a SubmodelComponent directly, call removeSubmodelInterface instead");