1 /**
2 * Module for window handling.
3 *
4 * Authors:
5 *   Jacob Jensen
6 * License:
7 *   https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE
8 */
9 module poison.ui.window;
10 
11 import dsfmlWindow = dsfml.window;
12 import dsfml.window : Event, Keyboard, Mouse;
13 
14 private alias ContextSettings = dsfmlWindow.ContextSettings;
15 private alias VideoMode = dsfmlWindow.VideoMode;
16 private alias WindowStyle = dsfmlWindow.Window.Style;
17 
18 import poison.core : Point, Size, ActionArgs, KeyEventArgs, MouseEventArgs, TextEventArgs, executeUI;
19 import poison.ui.container;
20 import poison.ui.component;
21 
22 /// A window component.
23 class Window : Container {
24   private:
25   /// The context settings.
26   ContextSettings _context;
27 
28   /// The video mode.
29   VideoMode _videoMode;
30 
31   /// The render window.
32   RenderWindow _window;
33 
34   /// The key event.
35   KeyEventArgs _keyEvent;
36 
37   /// The mouse event.
38   MouseEventArgs _mouseEvent;
39 
40   /// The text event.
41   TextEventArgs _textEvent;
42 
43   /// The fps for the window.
44   uint _fps;
45 
46   /// Boolean determining whether the cursor is visible or not.
47   bool _cursorVisible;
48 
49   public:
50   /**
51   * Creates a new window.
52   * Params:
53   *   name =    The name of the window.
54   *   title =   The title of the window.
55   *   size =    The size of the window.
56   *   layers =  The layers of the window. (Defaults to 1)
57   */
58   this(string name, dstring title, Size size, size_t layers = 1) {
59     super(name, size, layers);
60 
61     _context.antialiasingLevel = 8;
62     _videoMode = VideoMode(cast(int)size.width, cast(int)size.height);
63     _keyEvent = new KeyEventArgs();
64     _mouseEvent = new MouseEventArgs();
65     _textEvent = new TextEventArgs();
66 
67     innerText = title;
68     _fps = 60;
69 
70     addSelector("window");
71   }
72 
73   @property {
74     /// Gets the title of the window.
75     dstring title() { return super.innerText; }
76 
77     /// Sets the title of the window.
78     void title(dstring newTitle) {
79       super.innerText = newTitle;
80 
81       if (_window) {
82         _window.setTitle(super.innerText);
83       }
84     }
85 
86     /// Gets a boolean determining whether the window is open or not.
87     bool isOpen() {
88       return _window && _window.isOpen();
89     }
90 
91     /**
92     * Gets the parent window.
93     * Returns:
94     *   Always returns "this" where "this" is the window itself.
95     */
96     override Window parentWindow() {
97       return this;
98     }
99 
100     /// Gets or sets a boolean determining whether the window is hidden or not.
101     override void hidden(bool isHidden) {
102       if (_window) {
103         _window.setVisible(!isHidden);
104       }
105 
106       super.hidden = isHidden;
107     }
108 
109     /// Gets the fps of the window.
110     uint fps() { return _fps; }
111 
112     /// Sets the fps of the window.
113     void fps(uint newFps) {
114       _fps = newFps;
115 
116       if (_window) {
117         _window.setFramerateLimit(_fps);
118       }
119     }
120 
121     /// Gets a boolean determining whether the cursor is visible or not.
122     bool cursorVisible() { return _cursorVisible; }
123 
124     /// Sets a boolean determining whether the cursor is visible or not.
125     void cursorVisible(bool isCursorVisible) {
126       _cursorVisible = isCursorVisible;
127 
128       _window.setMouseCursorVisible(_cursorVisible);
129     }
130   }
131 
132   /// Shows the window.
133   override void show() {
134     if (!_window) {
135       _window = new RenderWindow(_videoMode, title, (WindowStyle.Titlebar | WindowStyle.Resize | WindowStyle.Close), _context);
136 
137       if (_fps) {
138         _window.setFramerateLimit(_fps);
139       }
140 
141       _windowComponents[super.id] = this;
142     }
143 
144     super.show();
145   }
146 
147   /// Closes the window.
148   void close() {
149     assert(_window !is null);
150 
151     _window.close();
152   }
153 
154   /**
155   * Sends a key input through memory.
156   * Params:
157   *   key = The key to send an input of.
158   */
159   void sendKeyInput(Keyboard.Key key) {
160     executeUI({
161       _keyEvent.press(key);
162 
163       if (_windowComponents) {
164         foreach (component; _windowComponents) {
165           if (component && !component.disabled) {
166             component.fireEvent("keyDown", _keyEvent);
167           }
168         }
169       }
170 
171       _keyEvent.release(key);
172 
173       if (_windowComponents) {
174         foreach (component; _windowComponents) {
175           if (component && !component.disabled) {
176             component.fireEvent("keyUp", _keyEvent);
177           }
178         }
179       }
180     });
181   }
182 
183   /**
184   * Sends a mouse input through memory.
185   * Params:
186   *   button =    The button to send an input of.
187   *   position =  The position of the mouse input.
188   * Note:
189   *   This doesn't change the position of the cursor. Use Window.changeCursorPosition() instead.
190   */
191   void sendMouseInput(Mouse.Button button, Point position) {
192     executeUI({
193       auto lastPosition = _mouseEvent.position;
194 
195       _mouseEvent.press(button);
196       _mouseEvent.position = position;
197 
198       if (_windowComponents) {
199         foreach (component; _windowComponents) {
200           if (component && !component.disabled) {
201             component.fireEvent("mouseDown", _mouseEvent);
202           }
203         }
204       }
205 
206       _mouseEvent.release(button);
207 
208       if (_windowComponents) {
209         foreach (component; _windowComponents) {
210           if (component && !component.disabled) {
211             component.fireEvent("mouseUp", _mouseEvent);
212           }
213         }
214       }
215 
216       _mouseEvent.position = lastPosition;
217     });
218   }
219 
220   /**
221   * Changes the cursor position.
222   * Params:
223   *   position =  The position to set the cursor at.
224   * Note:
225   *   For virtual mouse presses do not use this. Use Window.sendMouseInput() instead.
226   */
227   void changeCursorPosition(Point position) {
228     executeUI({
229       _mouseEvent.position = position;
230 
231       import dsfml.system : Vector2i;
232       Mouse.setPosition(Vector2i(position.x, position.y));
233     });
234   }
235 
236   package(poison):
237   /// Components for event handling
238   Component[size_t] _windowComponents;
239 
240   /// Processes the window cycle.
241   void process() {
242     Event event;
243     while(_window.pollEvent(event)) {
244       switch (event.type) {
245         case Event.EventType.Closed: {
246           _window.close();
247           return;
248         }
249 
250         case Event.EventType.KeyPressed: {
251           _keyEvent.press(event.key.code);
252 
253           if (_windowComponents) {
254             foreach (component; _windowComponents) {
255               if (component && !component.disabled) {
256                 component.fireEvent("keyDown", _keyEvent);
257               }
258             }
259           }
260 
261           break;
262         }
263 
264         case Event.EventType.KeyReleased: {
265           _keyEvent.release(event.key.code);
266 
267           if (_windowComponents) {
268             foreach (component; _windowComponents) {
269 
270               if (component && !component.disabled) {
271                 component.fireEvent("keyUp", _keyEvent);
272               }
273             }
274           }
275 
276           break;
277         }
278 
279         case Event.EventType.MouseButtonPressed: {
280           _mouseEvent.press(event.mouseButton.button);
281 
282           if (_windowComponents) {
283             foreach (component; _windowComponents) {
284               if (component && !component.disabled && component.intersect(_mouseEvent.position)) {
285                 component.fireEvent("mouseDown", _mouseEvent);
286               }
287             }
288           }
289 
290           break;
291         }
292 
293         case Event.EventType.MouseButtonReleased: {
294           _mouseEvent.release(event.mouseButton.button);
295 
296           if (_windowComponents) {
297             foreach (component; _windowComponents) {
298               if (component && !component.disabled && component.intersect(_mouseEvent.position)) {
299                 component.fireEvent("mouseUp", _mouseEvent);
300               }
301             }
302           }
303 
304           break;
305         }
306 
307         case Event.EventType.MouseMoved: {
308           _mouseEvent.position = new Point(event.mouseMove.x, event.mouseMove.y);
309 
310           if (_windowComponents) {
311             foreach (component; _windowComponents) {
312               if (component) {
313                 component.fireEvent("mouseMoved", _mouseEvent);
314               }
315             }
316           }
317 
318           break;
319         }
320 
321         case Event.EventType.TextEntered: {
322           _textEvent.enter(event.text.unicode);
323 
324           if (_windowComponents) {
325             foreach (component; _windowComponents) {
326               if (component && !component.disabled) {
327                 component.fireEvent("textEntered", _textEvent);
328               }
329             }
330           }
331 
332           break;
333         }
334 
335         default: break;
336       }
337     }
338 
339     _window.clear(super.graphics.backgroundPaint.sfmlColor);
340     super.process(_window);
341     _window.display();
342   }
343 }