1 /**
2 * Module for a component.
3 *
4 * Authors:
5 *   Jacob Jensen
6 * License:
7 *   https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE
8 */
9 module poison.ui.component;
10 
11 import std.algorithm : filter;
12 import std.array : array;
13 import std.concurrency : thisTid;
14 
15 import poison.ui.space;
16 import poison.core : ActionArgs, Point, Size, EventArgs, ChangeEventArgs, executeUI, isUIThread, CrossThreadingExeption;
17 import poison.ui.window;
18 import poison.ui.graphics;
19 import poison.ui.paint;
20 import poison.ui.picture;
21 import poison.ui.container;
22 
23 public import dsfml.graphics : RenderWindow;
24 
25 /// The next component id.
26 private size_t nextId = 0;
27 
28 /// A component, which is the base for all controls and elements.
29 class Component : Space {
30   private:
31   /// The name.
32   string _name;
33 
34   /// The inner text.
35   dstring _innerText;
36 
37   /// The outer text.
38   dstring _outerText;
39 
40   /// Boolean determining whether it's disabled or not.
41   bool _disabled;
42 
43   /// Boolean determining whether it's hidden or not.
44   bool _hidden;
45 
46   /// The id.
47   size_t _id;
48 
49   /// The layer.
50   ptrdiff_t _layer;
51 
52   /// The parent window.
53   Window _parentWindow;
54 
55   /// The parent container.
56   Container _parentContainer;
57 
58   /// The graphics.
59   Graphics _graphics;
60 
61   /// Style selectors.
62   string[] _selectors;
63 
64   /// Selectors to render with.
65   string[] _renderSelectors;
66 
67   /// The selector name.
68   string _selectorName;
69 
70   protected:
71   /**
72   * Creates a new component.
73   * Params:
74   *   name =        The name of the component.
75   *   initialSize = The initial size of the component.
76   */
77   this(string name, Size initialSize) {
78     if (!isUIThread) {
79       throw new CrossThreadingExeption(thisTid, "Cannot create a component outside the UI thread. Consider using exeuteUI();");
80     }
81 
82     super(new Point(0, 0), initialSize);
83 
84     _name = name;
85     _layer = -1;
86     _id = nextId++;
87     _graphics = new Graphics();
88     _selectors = ["component"];
89     _selectorName = "#" ~ _name;
90   }
91 
92   /**
93   * Creates a new component.
94   * Params:
95   *   name =  The name of the component.
96   */
97   this(string name) {
98     this(name, new Size(100, 100));
99   }
100 
101   @property {
102     /// Gets the graphics of the component.
103     Graphics graphics() { return _graphics; }
104   }
105 
106   /// Renders the component. Override this!
107   void render() {
108     _graphics.size = super.size;
109     _graphics.position = super.position;
110   }
111 
112   /**
113   * Draws the component. Override this!
114   * Params:
115   *   window =  The window to draw the component to.
116   */
117   void draw(RenderWindow window) {
118     if (_graphics.displayableBackgroundRect) {
119       window.draw(_graphics.backgroundRect);
120     }
121 
122     auto picture = _graphics.backgroundPicture;
123 
124     if (picture && _graphics.displayableBackgroundPicture) {
125       if (picture.backgroundSprite) {
126         window.draw(picture.backgroundSprite);
127       }
128 
129       if (picture.drawingSprite) {
130         window.draw(picture.drawingSprite);
131       }
132     }
133   }
134 
135   /**
136   * Processes the component during application cycles.
137   * Params:
138   *   window = The render window to process.
139   */
140   void process(RenderWindow window) {
141     if (!_hidden) {
142       draw(window);
143     }
144   }
145 
146   public:
147   @property {
148     /// Gets the name of the component.
149     string name() { return _name; }
150 
151     /// Gets a boolean determining whether the component is enabled or not.
152     bool enabled() { return !_disabled; }
153 
154     /// Sets a boolean determining whether the component is enabled or not.
155     void enabled(bool isEnabled) {
156       disabled = !isEnabled;
157     }
158 
159     /// Gets a boolean determining whether the component is disabled or not.
160     bool disabled() { return _disabled; }
161 
162     /// Sets a boolean determining whether the component is disabled.
163     void disabled(bool isDisabled) {
164       executeUI({
165         _disabled = isDisabled;
166 
167         fireEvent(_disabled ? "beforeDisabled" : "beforeEnabled", EventArgs.empty);
168 
169         updateSelectors();
170         render();
171 
172         fireEvent(_disabled ? "disabled" : "enabled", EventArgs.empty);
173       });
174     }
175 
176     /// Gets a boolean determining whether the component is visible or not.
177     bool visible() { return !_hidden; }
178 
179     /// Sets a boolean determining whether the component is visible or not.
180     void visible(bool isVisible) {
181       hidden = !isVisible;
182     }
183 
184     /// Gets a boolean determining whether the component is hidden or not.
185     bool hidden() { return _hidden; }
186 
187     /// Sets a boolean determining whether the component is hidden or not.
188     void hidden(bool isHidden) {
189       executeUI({
190         _hidden = isHidden;
191 
192         fireEvent(_hidden ? "beforeHide" : "beforeShow", EventArgs.empty);
193 
194         if (!_hidden) {
195           render();
196         }
197 
198         fireEvent(_hidden ? "hide" : "show", EventArgs.empty);
199       });
200     }
201 
202     /// Gets the inner text of the component.
203     dstring innerText() { return _innerText; }
204 
205     /// Sets the inner text of the component.
206     void innerText(dstring newInnerText) {
207       executeUI({
208         auto oldInnerText = _innerText;
209         _innerText = newInnerText;
210 
211         fireEvent("innerText", new ChangeEventArgs!dstring(oldInnerText, _innerText));
212 
213         render();
214         renderSub();
215       });
216     }
217 
218     /// Gets the outer text of the component.
219     dstring outerText() { return _outerText; }
220 
221     /// Sets the outer text of the component.
222     void outerText(dstring newOuterText) {
223       executeUI({
224         auto oldOuterText = _outerText;
225         _outerText = newOuterText;
226 
227         fireEvent("outerText", new ChangeEventArgs!dstring(oldOuterText, _outerText));
228 
229         render();
230       });
231     }
232 
233     /// Ges the position of the component.
234     override Point position() { return super.position; }
235 
236     /// Sets the position of the component.
237     override void position(Point newPoint) {
238       executeUI({
239         super.position = newPoint;
240 
241         render();
242         renderSub();
243       });
244     }
245 
246     /// Gets the size of the component.
247     override Size size() { return super.size; }
248 
249     /// Sets the size of the component.
250     override void size(Size newSize) {
251       executeUI({
252         super.size = newSize;
253 
254         render();
255         renderSub();
256       });
257     }
258 
259     /// Gets the layer of the component.
260     ptrdiff_t layer() { return _layer; }
261 
262     /// Gets the parent window of the component.
263     Window parentWindow() {
264       return _parentWindow;
265     }
266 
267     /// Gets the parent container of the component.
268     Container parentContainer() {
269       return _parentContainer;
270     }
271   }
272 
273   /// Shows the component.
274   void show() {
275     visible = true;
276   }
277 
278   /// Hides the component.
279   void hide() {
280     hidden = true;
281   }
282 
283   /// Enables the component.
284   void enable() {
285     enabled = true;
286   }
287 
288   /// Disables the component.
289   void disable() {
290     disabled = true;
291   }
292 
293   /**
294   * Adds a style selector.
295   * Params:
296   *   selector =  The selector to add.
297   */
298   void addSelector(string selector) {
299     _selectors ~= selector;
300 
301     updateSelectors();
302   }
303 
304   /**
305   * Removes a style selector.
306   * Params:
307   *   The style selector to remove.
308   */
309   void removeSelector(string selector) {
310     _selectors = _selectors.filter!((s) { return s != selector; }).array;
311 
312     updateSelectors();
313   }
314 
315   /**
316   * Checks whether the component intersects with a point.
317   * Params:
318   *   p = The point to check for intersection with.
319   * Returns:
320   *   True if the component intersects with a point.
321   */
322   override bool intersect(Point p) {
323     auto pIntersects = _parentContainer ? _parentContainer.intersect(p) : true;
324 
325     return pIntersects && super.intersect(p);
326 	}
327 
328   /**
329   * Checks whether the component intersects with another space.
330   * Params:
331   *   target = The space to check for intersection with.
332   * Returns:
333   *   True if the component intersects with a space.
334   */
335   override bool intersect(Space target) {
336     auto pIntersects = _parentContainer ? _parentContainer.intersect(target) : true;
337 
338     return pIntersects && super.intersect(target);
339   }
340 
341   private:
342   /// Updates the selectors.
343   void updateSelectors() {
344     auto prefix = (_disabled ? ":disabled" : ":enabled");
345 
346     _renderSelectors ~= _selectors;
347 
348     foreach (selector; _selectors) {
349         _renderSelectors ~= selector ~ prefix;
350     }
351 
352     _renderSelectors  ~= _selectorName;
353     _renderSelectors ~= _selectorName ~ prefix;
354 
355     updateStyles();
356   }
357 
358   package(poison):
359   /// Renders the sub rectangles for the graphics of the component.
360   void renderSub() {
361     if (_graphics && _parentContainer) {
362       _graphics.renderSub(_parentContainer.position, _parentContainer.size);
363       _hidden = !intersect(_parentContainer); // We set it directly to avoid events ...
364     }
365   }
366 
367   /**
368   * Processes the component during application cycles.
369   * Params:
370   *   window = The render window to process.
371   */
372   void processInternal(RenderWindow window) {
373     process(window);
374   }
375 
376   /// Updates the styles of the component.
377   void updateStyles() {
378     import poison.ui.styles;
379 
380     if (_renderSelectors) {
381       foreach (selector; _renderSelectors) {
382         auto styleEntry = getStyleEntry(selector);
383 
384         if (styleEntry) {
385           if (styleEntry.backgroundPicture) {
386             _graphics.backgroundPicture = new Picture(styleEntry.backgroundPicture);
387             _graphics.position = super.position;
388             _graphics.backgroundPicture.finalize();
389           }
390 
391           _graphics.backgroundPaint = styleEntry.backgroundPaint;
392           _graphics.foregroundPaint = styleEntry.foregroundPaint;
393 
394           _graphics.font = styleEntry.font;
395           _graphics.fontSize = styleEntry.fontSize;
396 
397           if (styleEntry.hasSize) {
398             this.size = styleEntry.size;
399           }
400 
401           if (styleEntry.hasPosition) {
402             this.position = styleEntry.position;
403           }
404         }
405       }
406     }
407 
408     renderSub();
409   }
410 
411   @property {
412     /// Gets the id of the component.
413     size_t id() { return _id; }
414 
415     /// Sets the layer of the component.
416     void layer(ptrdiff_t newLayer) {
417       _layer = newLayer;
418     }
419 
420     /// Sets the parent window of the component.
421     void parentWindow(Window newParentWindow) {
422       _parentWindow = newParentWindow;
423     }
424 
425     /// Sets the parent container of the component.
426     void parentContainer(Container newParentContainer) {
427       _parentContainer = newParentContainer;
428     }
429   }
430 }