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