58c725dfea4fb2d323f379c142a24221926c255b
[Mograsim.git] / net.mograsim.logic.ui / src / net / mograsim / logic / ui / model / components / GUIComponent.java
1 package net.mograsim.logic.ui.model.components;
2
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.HashMap;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.function.Consumer;
9
10 import com.google.gson.JsonElement;
11 import com.google.gson.JsonNull;
12
13 import net.haspamelodica.swt.helper.gcs.GeneralGC;
14 import net.haspamelodica.swt.helper.swtobjectwrappers.Rectangle;
15 import net.mograsim.logic.ui.model.ViewModelModifiable;
16 import net.mograsim.logic.ui.model.wires.Pin;
17
18 /**
19  * The base class for all GUI components.<br>
20  * A <code>GUIComponent</code> has a position and size. The size can only be modified by subclasses.<br>
21  * 
22  * @author Daniel Kirschten
23  */
24 public abstract class GUIComponent
25 {
26         /**
27          * The model this component is a part of.
28          */
29         protected final ViewModelModifiable model;
30         private final Rectangle bounds;
31         /**
32          * The list of all pins of this component by name.
33          */
34         private final Map<String, Pin> pinsByName;
35         /**
36          * An unmodifiable view of {@link #pinsByName}.
37          */
38         protected final Map<String, Pin> pinsUnmodifiable;
39
40         private final List<Consumer<? super GUIComponent>> componentMovedListeners;
41         private final List<Consumer<? super Pin>> pinAddedListeners;
42         private final List<Consumer<? super Pin>> pinRemovedListeners;
43         private final List<Runnable> redrawListeners;
44
45         private final Runnable redrawListenerForSubcomponents;
46
47         // creation and destruction
48
49         public GUIComponent(ViewModelModifiable model)
50         {
51                 this.model = model;
52                 this.bounds = new Rectangle(0, 0, 0, 0);
53                 this.pinsByName = new HashMap<>();
54                 this.pinsUnmodifiable = Collections.unmodifiableMap(pinsByName);
55
56                 this.componentMovedListeners = new ArrayList<>();
57                 this.pinAddedListeners = new ArrayList<>();
58                 this.pinRemovedListeners = new ArrayList<>();
59                 this.redrawListeners = new ArrayList<>();
60
61                 redrawListenerForSubcomponents = this::requestRedraw;
62
63                 model.componentCreated(this);
64         }
65
66         /**
67          * Destroys this component. This method implicitly calls {@link ViewModelModifiable#componentDestroyed(GUIComponent)
68          * componentDestroyed()} for the model this component is a part of.
69          * 
70          * @author Daniel Kirschten
71          */
72         public void destroy()
73         {
74                 pinsByName.values().forEach(p -> pinRemovedListeners.forEach(l -> l.accept(p)));
75                 model.componentDestroyed(this);
76         }
77
78         // pins
79
80         /**
81          * Adds the given pin to this component and calls pinAddedListeners and redrawListeners.
82          * 
83          * @throws IllegalArgumentException if the pin doesn't belong to this component
84          * @throws IllegalArgumentException if there already is a pin with the given name
85          * 
86          * @author Daniel Kirschten
87          */
88         protected void addPin(Pin pin)
89         {
90                 if (pin.component != this)
91                         throw new IllegalArgumentException("Can't add a pin not belonging to this component!");
92                 if (pinsByName.containsKey(pin.name))
93                         throw new IllegalArgumentException("Duplicate pin name: " + pin.name);
94                 pinsByName.put(pin.name, pin);
95                 callPinAddedListeners(pin);
96                 pin.addRedrawListener(redrawListenerForSubcomponents);
97                 requestRedraw();
98         }
99
100         /**
101          * Removes the given pin from this component and calls pinAddedListeners and redrawListeners.
102          * 
103          * @throws NullPointerException if there was no pin with this name
104          * 
105          * @author Daniel Kirschten
106          */
107         protected void removePin(String name)
108         {
109                 Pin pin = pinsByName.remove(name);
110                 callPinRemovedListeners(pin);
111                 pin.removeRedrawListener(redrawListenerForSubcomponents);
112                 requestRedraw();
113         }
114
115         /**
116          * Returns a collection of pins of this component.
117          * 
118          * @author Daniel Kirschten
119          */
120         public Map<String, Pin> getPins()
121         {
122                 return pinsUnmodifiable;
123         }
124
125         /**
126          * Returns the pin with the given name of this component.
127          * 
128          * @throws IllegalArgumentException if there is no pin with the given name
129          * 
130          * @author Daniel Kirschten
131          */
132         public Pin getPin(String name)
133         {
134                 Pin pin = pinsByName.get(name);
135                 if (pin == null)
136                         throw new IllegalArgumentException("No pin with the name " + name);
137                 return pin;
138         }
139
140         // high-level access
141
142         /**
143          * Sets the given high-level state to the given value. <br>
144          * A high level state ID consists of parts separated by dots ('.').<br>
145          * The last part (the part after the last dot) is called "atomic high level state ID". The parts before that part are called
146          * "subcomponent ID"s.<br>
147          * If there is no dot in a high level state ID, the whole high level state ID is called atomic.<br>
148          * Note that subcomponent IDs don't have to correspond to actual subcomponents. For example, a RAM component may supply subcomponent IDs
149          * "c0000", "c0001" ... "cFFFF" without actually having a subcomponent for each cell. It also is allowed for an atomic high level state
150          * ID to be delegated to a subcomponent.
151          * 
152          * @author Daniel Kirschten
153          */
154         @SuppressWarnings({ "static-method", "unused" }) // this method is intended to be overridden
155         public void setHighLevelState(String stateID, Object newState)
156         {
157                 throw new IllegalArgumentException("No high level state with ID " + stateID);
158         }
159
160         /**
161          * Gets the current value of the given high-level state. <br>
162          * See {@link #setHighLevelState(String, Object)} for an explanation of high-level state IDs.
163          * 
164          * @author Daniel Kirschten
165          */
166         @SuppressWarnings("static-method") // this method is intended to be overridden
167         public Object getHighLevelState(String stateID)
168         {
169                 throw new IllegalArgumentException("No high level state with ID " + stateID);
170         }
171
172         // "graphical" operations
173
174         /**
175          * Sets the position of this component and calls componentMovedListeners and redrawListeners.
176          * 
177          * @author Daniel Kirschten
178          */
179         public void moveTo(double x, double y)
180         {
181                 bounds.x = x;
182                 bounds.y = y;
183                 callComponentMovedListeners();
184                 requestRedraw();
185         }
186
187         /**
188          * Sets the size of this component and calls redrawListeners.
189          * 
190          * @author Daniel Kirschten
191          */
192         protected void setSize(double width, double height)
193         {
194                 bounds.width = width;
195                 bounds.height = height;
196                 requestRedraw();
197         }
198
199         /**
200          * Returns the bounds of this component. Is a bit slower than {@link #getPosX()}, {@link #getPosY()}, {@link #getWidth},
201          * {@link #getHeight}, because new objects are created.
202          * 
203          * @author Daniel Kirschten
204          */
205         public final Rectangle getBounds()
206         {
207                 return new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height);
208         }
209
210         /**
211          * Returns the x coordinate of the position of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
212          * 
213          * @author Daniel Kirschten
214          */
215         public double getPosX()
216         {
217                 return bounds.x;
218         }
219
220         /**
221          * Returns the y coordinate of the position of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
222          * 
223          * @author Daniel Kirschten
224          */
225         public double getPosY()
226         {
227                 return bounds.y;
228         }
229
230         /**
231          * Returns the (graphical) width of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
232          * 
233          * @author Daniel Kirschten
234          */
235         public double getWidth()
236         {
237                 return bounds.width;
238         }
239
240         /**
241          * Returns the height of this component. Is a bit faster than {@link #getBounds()} because no objects are created.
242          * 
243          * @author Daniel Kirschten
244          */
245         public double getHeight()
246         {
247                 return bounds.height;
248         }
249
250         /**
251          * Called when this component is clicked. Absolute coordinates of the click are given. Returns true if this component consumed this
252          * click.
253          * 
254          * @author Daniel Kirschten
255          */
256         @SuppressWarnings({ "static-method", "unused" }) // this method is inteded to be overridden
257         public boolean clicked(double x, double y)
258         {
259                 return false;
260         }
261
262         /**
263          * Render this component to the given gc, in absoulute coordinates.
264          * 
265          * @author Daniel Kirschten
266          */
267         public abstract void render(GeneralGC gc, Rectangle visibleRegion);
268
269         // serializing
270
271         @SuppressWarnings("static-method") // this method is intended to be overridden
272         public JsonElement getParams()
273         {
274                 return JsonNull.INSTANCE;
275         }
276
277         // listeners
278
279         /**
280          * Calls redraw listeners.
281          * 
282          * @author Daniel Kirschten
283          */
284         protected void requestRedraw()
285         {
286                 callRedrawListeners();
287         }
288
289         // @formatter:off
290         public void addComponentMovedListener   (Consumer<? super GUIComponent> listener) {componentMovedListeners.add   (listener);}
291         public void addPinAddedListener         (Consumer<? super Pin         > listener) {pinAddedListeners      .add   (listener);}
292         public void addPinRemovedListener       (Consumer<? super Pin         > listener) {pinRemovedListeners    .add   (listener);}
293         public void addRedrawListener           (Runnable                       listener) {redrawListeners        .add   (listener);}
294
295         public void removeComponentMovedListener(Consumer<? super GUIComponent> listener) {componentMovedListeners .remove(listener);}
296         public void removePinAddedListener      (Consumer<? super Pin         > listener) {pinAddedListeners       .remove(listener);}
297         public void removePinRemovedListener    (Consumer<? super Pin         > listener) {pinRemovedListeners     .remove(listener);}
298         public void removeRedrawListener        (Runnable                       listener) {redrawListeners         .remove(listener);}
299
300         private void callComponentMovedListeners(     ) {componentMovedListeners.forEach(l -> l.accept(this));}
301         private void callPinAddedListeners      (Pin p) {pinAddedListeners      .forEach(l -> l.accept(p   ));}
302         private void callPinRemovedListeners    (Pin p) {pinRemovedListeners    .forEach(l -> l.accept(p   ));}
303         private void callRedrawListeners        (     ) {redrawListeners        .forEach(l -> l.run(       ));}
304         // @formatter:on
305 }