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   /**
107   * Draws the component. Override this!
108   * Params:
109   *   window =  The window to draw the component to.
110   */
111   void draw(RenderWindow window) {
112     if (_graphics.displayableBackgroundRect) {
113       window.draw(_graphics.backgroundRect);
114     }
115 
116     auto picture = _graphics.backgroundPicture;
117 
118     if (picture && _graphics.displayableBackgroundPicture) {
119       if (picture.backgroundSprite) {
120         window.draw(picture.backgroundSprite);
121       }
122 
123       if (picture.drawingSprite) {
124         window.draw(picture.drawingSprite);
125       }
126     }
127   }
128 
129   /**
130   * Processes the component during application cycles.
131   * Params:
132   *   window = The render window to process.
133   */
134   void process(RenderWindow window) {
135     if (!_hidden) {
136       draw(window);
137     }
138   }
139 
140   public:
141   @property {
142     /// Gets the name of the component.
143     string name() { return _name; }
144 
145     /// Gets a boolean determining whether the component is enabled or not.
146     bool enabled() { return !_disabled; }
147 
148     /// Sets a boolean determining whether the component is enabled or not.
149     void enabled(bool isEnabled) {
150       disabled = !isEnabled;
151     }
152 
153     /// Gets a boolean determining whether the component is disabled or not.
154     bool disabled() { return _disabled; }
155 
156     /// Sets a boolean determining whether the component is disabled.
157     void disabled(bool isDisabled) {
158       executeUI({
159         _disabled = isDisabled;
160 
161         fireEvent(_disabled ? "beforeDisabled" : "beforeEnabled", EventArgs.empty);
162 
163         updateSelectors();
164         render();
165 
166         fireEvent(_disabled ? "disabled" : "enabled", EventArgs.empty);
167       });
168     }
169 
170     /// Gets a boolean determining whether the component is visible or not.
171     bool visible() { return !_hidden; }
172 
173     /// Sets a boolean determining whether the component is visible or not.
174     void visible(bool isVisible) {
175       hidden = !isVisible;
176     }
177 
178     /// Gets a boolean determining whether the component is hidden or not.
179     bool hidden() { return _hidden; }
180 
181     /// Sets a boolean determining whether the component is hidden or not.
182     void hidden(bool isHidden) {
183       executeUI({
184         _hidden = isHidden;
185 
186         fireEvent(_hidden ? "beforeHide" : "beforeShow", EventArgs.empty);
187 
188         if (!_hidden) {
189           render();
190         }
191 
192         fireEvent(_hidden ? "hide" : "show", EventArgs.empty);
193       });
194     }
195 
196     /// Gets the inner text of the component.
197     dstring innerText() { return _innerText; }
198 
199     /// Sets the inner text of the component.
200     void innerText(dstring newInnerText) {
201       executeUI({
202         auto oldInnerText = _innerText;
203         _innerText = newInnerText;
204 
205         fireEvent("innerText", new ChangeEventArgs!dstring(oldInnerText, _innerText));
206 
207         render();
208       });
209     }
210 
211     /// Gets the outer text of the component.
212     dstring outerText() { return _outerText; }
213 
214     /// Sets the outer text of the component.
215     void outerText(dstring newOuterText) {
216       executeUI({
217         auto oldOuterText = _outerText;
218         _outerText = newOuterText;
219 
220         fireEvent("outerText", new ChangeEventArgs!dstring(oldOuterText, _outerText));
221 
222         render();
223       });
224     }
225 
226     /// Ges the position of the component.
227     override Point position() { return super.position; }
228 
229     /// Sets the position of the component.
230     override void position(Point newPoint) {
231       executeUI({
232         super.position = newPoint;
233 
234         render();
235       });
236     }
237 
238     /// Gets the size of the component.
239     override Size size() { return super.size; }
240 
241     /// Sets the size of the component.
242     override void size(Size newSize) {
243       executeUI({
244         super.size = newSize;
245 
246         render();
247       });
248     }
249 
250     /// Gets the layer of the component.
251     ptrdiff_t layer() { return _layer; }
252 
253     /// Gets the parent window of the component.
254     Window parentWindow() {
255       return _parentWindow;
256     }
257 
258     /// Gets the parent container of the component.
259     Container parentContainer() {
260       return _parentContainer;
261     }
262   }
263 
264   /// Renders the component. Override this!
265   void render() {
266     _graphics.size = super.size;
267     _graphics.position = super.position;
268 
269     renderSub();
270   }
271 
272   /// Shows the component.
273   void show() {
274     visible = true;
275   }
276 
277   /// Hides the component.
278   void hide() {
279     hidden = true;
280   }
281 
282   /// Enables the component.
283   void enable() {
284     enabled = true;
285   }
286 
287   /// Disables the component.
288   void disable() {
289     disabled = true;
290   }
291 
292   /**
293   * Adds a style selector.
294   * Params:
295   *   selector =  The selector to add.
296   */
297   void addSelector(string selector) {
298     _selectors ~= selector;
299 
300     updateSelectors();
301   }
302 
303   /**
304   * Removes a style selector.
305   * Params:
306   *   The style selector to remove.
307   */
308   void removeSelector(string selector) {
309     _selectors = _selectors.filter!((s) { return s != selector; }).array;
310 
311     updateSelectors();
312   }
313 
314   /**
315   * Checks whether the component intersects with a point.
316   * Params:
317   *   p = The point to check for intersection with.
318   * Returns:
319   *   True if the component intersects with a point.
320   */
321   override bool intersect(Point p) {
322     auto pIntersects = _parentContainer ? _parentContainer.intersect(p) : true;
323 
324     return pIntersects && super.intersect(p);
325 	}
326 
327   /**
328   * Checks whether the component intersects with another space.
329   * Params:
330   *   target = The space to check for intersection with.
331   * Returns:
332   *   True if the component intersects with a space.
333   */
334   override bool intersect(Space target) {
335     auto pIntersects = _parentContainer ? _parentContainer.intersect(target) : true;
336 
337     return pIntersects && super.intersect(target);
338   }
339 
340   private:
341   /// Updates the selectors.
342   void updateSelectors() {
343     auto prefix = (_disabled ? ":disabled" : ":enabled");
344 
345     _renderSelectors ~= _selectors;
346 
347     foreach (selector; _selectors) {
348         _renderSelectors ~= selector ~ prefix;
349     }
350 
351     _renderSelectors  ~= _selectorName;
352     _renderSelectors ~= _selectorName ~ prefix;
353 
354     updateStyles();
355   }
356 
357   package(poison):
358   /// Renders the sub rectangles for the graphics of the component.
359   void renderSub() {
360     if (_graphics && _parentContainer) {
361       _graphics.renderSub(_parentContainer.position, _parentContainer.size);
362       _hidden = !super.intersect(_parentContainer); // We set it directly to avoid events ...
363     }
364   }
365 
366   /**
367   * Processes the component during application cycles.
368   * Params:
369   *   window = The render window to process.
370   */
371   void processInternal(RenderWindow window) {
372     process(window);
373   }
374 
375   /// Updates the styles of the component.
376   void updateStyles() {
377     import poison.ui.styles;
378 
379     if (_renderSelectors) {
380       Size newSize;
381       Point newPosition;
382 
383       foreach (selector; _renderSelectors) {
384         auto styleEntry = getStyleEntry(selector);
385 
386         if (styleEntry) {
387           if (styleEntry.backgroundPicture) {
388             _graphics.backgroundPicture = new Picture(styleEntry.backgroundPicture);
389             _graphics.position = super.position;
390             _graphics.backgroundPicture.finalize();
391           }
392 
393           _graphics.backgroundPaint = styleEntry.backgroundPaint;
394           _graphics.foregroundPaint = styleEntry.foregroundPaint;
395 
396           _graphics.font = styleEntry.font;
397           _graphics.fontSize = styleEntry.fontSize;
398 
399           if (styleEntry.hasSize) {
400             newSize = styleEntry.size;
401           }
402 
403           if (styleEntry.hasPosition) {
404             newPosition = styleEntry.position;
405           }
406         }
407       }
408 
409       if (newSize && !_graphics.hasSize) {
410         this.size = newSize;
411         _graphics.hasSize = true;
412       }
413 
414       if (newPosition && !_graphics.hasPosition) {
415         this.position = newPosition;
416         _graphics.hasPosition = true;
417       }
418     }
419 
420     render();
421   }
422 
423   @property {
424     /// Gets the id of the component.
425     size_t id() { return _id; }
426 
427     /// Sets the layer of the component.
428     void layer(ptrdiff_t newLayer) {
429       _layer = newLayer;
430     }
431 
432     /// Sets the parent window of the component.
433     void parentWindow(Window newParentWindow) {
434       _parentWindow = newParentWindow;
435     }
436 
437     /// Sets the parent container of the component.
438     void parentContainer(Container newParentContainer) {
439       _parentContainer = newParentContainer;
440     }
441   }
442 }