1 /*
2  *******************************************************************************************
3  * Dgame (a D game framework) - Copyright (c) Randy Schütt
4  *
5  * This software is provided 'as-is', without any express or implied warranty.
6  * In no event will the authors be held liable for any damages arising from
7  * the use of this software.
8  *
9  * Permission is granted to anyone to use this software for any purpose,
10  * including commercial applications, and to alter it and redistribute it
11  * freely, subject to the following restrictions:
12  *
13  * 1. The origin of this software must not be misrepresented; you must not claim
14  *    that you wrote the original software. If you use this software in a product,
15  *    an acknowledgment in the product documentation would be appreciated but is
16  *    not required.
17  *
18  * 2. Altered source versions must be plainly marked as such, and must not be
19  *    misrepresented as being the original software.
20  *
21  * 3. This notice may not be removed or altered from any source distribution.
22  *******************************************************************************************
23  */
24 module Dgame.Window.Window;
25 
26 private:
27 
28 import derelict.sdl2.sdl;
29 import derelict.opengl3.gl;
30 
31 import Dgame.Graphic.Color;
32 import Dgame.Graphic.Drawable;
33 import Dgame.Graphic.Masks;
34 import Dgame.Graphic.Surface;
35 import Dgame.Graphic.Texture;
36 
37 import Dgame.Math.Vertex;
38 import Dgame.Math.Vector2;
39 import Dgame.Math.Matrix4x4;
40 import Dgame.Math.Rect;
41 import Dgame.Math.Geometry;
42 
43 import Dgame.Window.Event;
44 import Dgame.Window.GLContextSettings;
45 import Dgame.Window.DisplayMode;
46 import Dgame.Window.Internal.Init;
47 
48 import Dgame.Internal.Error;
49 import Dgame.Internal.m3;
50 import Dgame.Internal.d2c;
51 
52 static if (!SDL_VERSION_ATLEAST(2, 0, 4)) {
53     enum int SDL_WINDOW_MOUSE_CAPTURE = 0;
54 }
55 
56 enum int DefPosX = 100;
57 enum int DefPosY = 100;
58 
59 public:
60 
61 /**
62  * Window is the rendering window where all drawable objects are drawn.
63  *
64  * Note that the default clear-color is <code>Color.White</code> and the
65  * default VerticalSync is <code>Window.VerticalSync.Disable</code>, which means the Applications runs with full FPS.
66  *
67  * Author: Randy Schuett (rswhite4@googlemail.com)
68  */
69 struct Window {
70     /**
71      * The Window syncronisation mode.
72      * Default VerticalSyncronisation is <code>VerticalSync.Enable</code>.
73      */
74     enum VerticalSync : byte {
75         Enable  = 1,    /// VerticalSync is enabled
76         Disable = 0,    /// VerticalSync is disabled
77         LateSwapTearing = -1    /// For late swap tearing
78     }
79 
80     /**
81      * The specific window styles
82      */
83     enum Style {
84         Default = Shown, /// Default is Shown
85         Fullscreen = SDL_WINDOW_FULLSCREEN, /// Window is fullscreened
86         Desktop = SDL_WINDOW_FULLSCREEN_DESKTOP,    /// Window has Desktop Fullscreen
87         Shown = SDL_WINDOW_SHOWN,   /// Show the Window immediately
88         Hidden = SDL_WINDOW_HIDDEN, /// Hide the Window immediately
89         Borderless = SDL_WINDOW_BORDERLESS, /// The Window has no border
90         Resizeable = SDL_WINDOW_RESIZABLE,  /// Window is resizeable
91         Maximized = SDL_WINDOW_MAXIMIZED,   /// Maximize the Window immediately
92         Minimized = SDL_WINDOW_MINIMIZED,   /// Minimize the Window immediately
93         InputGrabbed = SDL_WINDOW_INPUT_GRABBED,    /// Grab the input inside the window
94         InputFocus = SDL_WINDOW_INPUT_FOCUS,    /// The Window has input (keyboard) focus
95         MouseFocus = SDL_WINDOW_MOUSE_FOCUS,    /// The Window has mouse focus
96         MouseCapture = SDL_WINDOW_MOUSE_CAPTURE, /// window has mouse captured (unrelated to InputGrabbed)
97         AllowHighDPI = SDL_WINDOW_ALLOW_HIGHDPI, /// Window should be created in high-DPI mode if supported
98     }
99 
100 private:
101     SDL_Window* _window;
102     SDL_GLContext _glContext;
103 
104     static ushort _count = 0;
105 
106 public:
107     /**
108      * The current projection Matrix
109      *
110      * Note: This is intended for advanced users only.
111      *
112      * See: Matrix4x4
113      */
114     Matrix4x4 projection;
115 
116     /**
117      * CTor
118      * Position of the Window is default 100x, 100y and the VerticalSync is disabled
119      */
120     this(uint width, uint height, string title, uint style = Style.Default, const GLContextSettings gl = GLContextSettings.init) {
121         this(Rect(DefPosX, DefPosY, width, height), title, style, gl);
122     }
123 
124     /**
125     * CTor
126     * Position is at 100x, 100y and the VerticalSync is enabled, if mode.refreshRate > 0
127     */
128     this(const DisplayMode mode, string title, uint style = Style.Default, const GLContextSettings gl = GLContextSettings.init) {
129         this(Rect(DefPosX, DefPosY, mode.width, mode.height), title, style, gl);
130 
131         if (mode.refreshRate > 0)
132             this.setVerticalSync(VerticalSync.Enable);
133     }
134 
135     /**
136      * CTor
137      * Position is specifiable and the VerticalSync is disabled
138      */
139     this(const Rect view, string title, uint style = Style.Default, const GLContextSettings gl = GLContextSettings.init) {
140         if (_count == 0)
141             _initSDL();
142 
143         _initGLAttr(gl);
144 
145         _window = SDL_CreateWindow(
146             toStringz(title),
147             view.x, view.y,
148             view.width, view.height,
149             style | SDL_WINDOW_OPENGL
150         );
151         assert(_window, "SDL_Window could not be created.");
152 
153         _glContext = SDL_GL_CreateContext(_window);
154         assert(_glContext, "SDL_GLContext could not be created.");
155         assert(SDL_GL_MakeCurrent(_window, _glContext) == 0);
156 
157         _initGL();
158 
159         const Rect rect = Rect(0, 0, view.width, view.height);
160 
161         this.projection.ortho(rect);
162         this.loadProjection();
163 
164         glViewport(rect.x, rect.y, rect.width, rect.height);
165 
166         this.setClearColor(Color4b.White);
167         this.setVerticalSync(VerticalSync.Disable);
168 
169         _count++;
170     }
171 
172     /**
173      * Postblit is disabled
174      */
175     @disable
176     this(this);
177 
178     /**
179      * DTor
180      */
181     @nogc
182     ~this() nothrow {
183         SDL_GL_DeleteContext(_glContext);
184         SDL_DestroyWindow(_window);
185 
186         _count--;
187     }
188 
189     /**
190      * Load the projection Matrix, so that any change / transformation of the Matrix will now be visible
191      */
192     @nogc
193     void loadProjection() const nothrow {
194         glMatrixMode(GL_PROJECTION);
195         glLoadMatrixf(this.projection.getValues().ptr);
196         glMatrixMode(GL_MODELVIEW);
197     }
198 
199     /**
200      * Set the color which this windows use to clear the buffer.
201      * This is also the background color of the window.
202      */
203     @nogc
204     void setClearColor(const Color4b col) const nothrow {
205         immutable float[4] rgba = Color4f(col).asRGBA();
206         glClearColor(rgba[0], rgba[1], rgba[2], rgba[3]);
207     }
208 
209     /**
210      * Clears the screen with the color you specified in setClearColor
211      */
212     @nogc
213     void clear() const nothrow {
214         glClear(GL_COLOR_BUFFER_BIT);
215     }
216 
217     /**
218      * Set the VerticalSyncronisation mode of this window.
219      * Default VerticalSyncronisation is <code>VerticalSync.Enable</code>.
220      *
221      * See: VerticalSync enum
222      *
223      * Returns if the sync mode is supported.
224      */
225     @nogc
226     bool setVerticalSync(VerticalSync sync) const nothrow {
227         return SDL_GL_SetSwapInterval(sync) == 0;
228     }
229 
230     /**
231      * Returns the current syncronisation mode.
232      *
233      * See: VerticalSync enum
234      */
235     @nogc
236     VerticalSync getVerticalSync() nothrow {
237         return cast(VerticalSync) SDL_GL_GetSwapInterval();
238     }
239 
240     /**
241      * Capture the pixel data of the current window and
242      * returns a Surface with this pixel data.
243      * You can also alter the format of the pixel data.
244      * Default is <code>Texture.Format.BGRA</code>.
245      * This method is predestinated for screenshots.
246      *
247      * Example:
248      * ---
249      * Window wnd = ...
250      * ...
251      * wnd.capture().saveToFile("samples/img/screenshot.png");
252      * ---
253      */
254     @nogc
255     Surface capture(Texture.Format fmt = Texture.Format.BGRA) nothrow {
256         const Size size = this.getSize();
257         Surface my_capture = Surface(size.width, size.height, 32, Masks.Zero);
258 
259         glReadBuffer(GL_FRONT);
260         glReadPixels(0, 0, size.width, size.height, fmt, GL_UNSIGNED_BYTE, my_capture.pixels);
261 
262         immutable uint lineWidth = size.width * 4;
263         immutable uint hlw = size.height * lineWidth;
264 
265         void[] tmpLine = make!(void[])(lineWidth);
266         scope(exit) unmake(tmpLine);
267 
268         // Flip it
269         for (uint i = 0; i < size.height / 2; ++i) {
270             immutable uint tmpIdx1 = i * lineWidth;
271             immutable uint tmpIdx2 = (i + 1) * lineWidth;
272 
273             immutable uint switchIdx1 = hlw - tmpIdx2;
274             immutable uint switchIdx2 = hlw - tmpIdx1;
275 
276             tmpLine[0 .. lineWidth] = my_capture.pixels[tmpIdx1 .. tmpIdx2];
277             void[] switchLine = my_capture.pixels[switchIdx1 .. switchIdx2];
278 
279             my_capture.pixels[tmpIdx1 .. tmpIdx2] = switchLine[];
280             my_capture.pixels[switchIdx1 .. switchIdx2] = tmpLine[0 .. lineWidth];
281         }
282 
283         return my_capture;
284     }
285 
286     /**
287      * Restore the size and position, if the Window is minimized or maximized.
288      */
289     @nogc
290     void restore() nothrow {
291         SDL_RestoreWindow(_window);
292     }
293 
294     /**
295      * Raises the Window above other Windows and set the input focus.
296      */
297     @nogc
298     void raise() nothrow {
299         SDL_RaiseWindow(_window);
300     }
301 
302     /**
303      *Make the window as large as possible.
304      */
305     @nogc
306     void maximize() nothrow {
307         SDL_MaximizeWindow(_window);
308     }
309 
310     /**
311      * Minimize the Window to an iconic representation.
312      */
313     @nogc
314     void minimize() nothrow {
315         SDL_MinimizeWindow(_window);
316     }
317 
318     /**
319      * Set the border state of the Window.
320      */
321     @nogc
322     void setBorder(bool enable) nothrow {
323         SDL_SetWindowBordered(_window, enable ? SDL_TRUE : SDL_FALSE);
324     }
325 
326     /**
327      * Returns if the keyboard focus is on this window.
328      */
329     @nogc
330     bool hasKeyboardFocus() const nothrow {
331         return SDL_GetKeyboardFocus() == _window;
332     }
333 
334     /**
335      * Returns if the mouse focus is on this window.
336      */
337     @nogc
338     bool hasMouseFocus() const nothrow {
339         return SDL_GetMouseFocus() == _window;
340     }
341 
342     /**
343      * Set a new position to this window
344      */
345     @nogc
346     void setPosition(int x, int y) nothrow {
347         SDL_SetWindowPosition(_window, x, y);
348     }
349 
350     /**
351      * Set a new position to this window
352      */
353     @nogc
354     void setPosition(const Vector2i vec) nothrow {
355         this.setPosition(vec.x, vec.y);
356     }
357 
358     /**
359      * Returns the current position of the window.
360      */
361     @nogc
362     Vector2i getPosition() nothrow {
363         int x, y;
364         SDL_GetWindowPosition(_window, &x, &y);
365 
366         return Vector2i(x, y);
367     }
368 
369     /**
370      * Set a new size to this window
371      */
372     @nogc
373     void setSize(uint width, uint height) nothrow {
374         SDL_SetWindowSize(_window, width, height);
375     }
376 
377     /**
378      * Set a new size to this window
379      */
380     @nogc
381     void setSize(const Size size) nothrow {
382         this.setSize(size.width, size.height);
383     }
384 
385     /**
386      * Returns the size (width and height) of the Window
387      */
388     @nogc
389     Size getSize() nothrow {
390         int w, h;
391         SDL_GetWindowSize(_window, &w, &h);
392 
393         return Size(w, h);
394     }
395 
396     /**
397      * Returns the size of the underlying drawable area (e.g. for use with glViewport).
398      * This method may only differ from getSize if you are using High-DPI.
399      */
400     @nogc
401     Size getDrawableSize() nothrow {
402         int w, h;
403         SDL_GL_GetDrawableSize(_window, &w, &h);
404 
405         return Size(w, h);
406     }
407 
408     /**
409      * Set the minimum Size for the Window
410      */
411     @nogc
412     void setMinimumSize(uint width, uint height) nothrow {
413         SDL_SetWindowMinimumSize(_window, width, height);
414     }
415 
416     /**
417      * Set the minimum Size for the Window
418      */
419     @nogc
420     void setMinimumSize(const Size size) nothrow {
421         this.setMinimumSize(size.width, size.height);
422     }
423 
424     /**
425      * Returns the minimum Size of the Window
426      */
427     @nogc
428     Size getMinimumSize() nothrow {
429         int w, h;
430         SDL_GetWindowMinimumSize(_window, &w, &h);
431 
432         return Size(w, h);
433     }
434 
435     /**
436      * Set the maximum Size of the Window
437      */
438     @nogc
439     void setMaximumSize(uint width, uint height) nothrow {
440         SDL_SetWindowMaximumSize(_window, width, height);
441     }
442 
443     /**
444      * Set the maximum Size of the Window
445      */
446     @nogc
447     void setMaximumSize(const Size size) nothrow {
448         this.setMaximumSize(size.width, size.height);
449     }
450 
451     /**
452      * Returns the maximum Size of the Window
453      */
454     @nogc
455     Size getMaximumSize() nothrow {
456         int w, h;
457         SDL_GetWindowMaximumSize(_window, &w, &h);
458 
459         return Size(w, h);
460     }
461 
462     /**
463      * Returns the Window Style.
464      *
465      * See: Style enum
466      */
467     @nogc
468     uint getStyle() nothrow {
469         return SDL_GetWindowFlags(_window);
470     }
471 
472     /**
473      * Update the parameter event and set the data of the current event in it.
474      *
475      * Returns: true, if there was a valid event and false if not.
476      */
477     @nogc
478     bool poll(Event* event) const nothrow {
479         assert(event, "No place to store the event");
480 
481         SDL_Event sdl_event;
482         SDL_PollEvent(&sdl_event);
483 
484         return _translate(event, sdl_event);
485     }
486 
487     /**
488      * Waits for the given Event.
489      * If the second parameter is greater then -1, it waits maximal timeout milliseconds.
490      */
491     @nogc
492     bool wait(Event* event, int timeout = -1) const nothrow {
493         SDL_Event sdl_event;
494         int result;
495         if (timeout < 0)
496             result = SDL_WaitEvent(&sdl_event);
497         else
498             result = SDL_WaitEventTimeout(&sdl_event, timeout);
499 
500         if (result > 0)
501             return _translate(event, sdl_event);
502 
503         return false;
504     }
505 
506     /**
507      * Push an event of the given type inside the Event queue.
508      *
509      * Returns: if the push was successfull or not.
510      */
511     @nogc
512     bool push(Event.Type type) const nothrow {
513         SDL_Event sdl_event;
514         sdl_event.type = type;
515 
516         return SDL_PushEvent(&sdl_event) == 1;
517     }
518 
519     /**
520      * Returns: if inside of the Event Queue is an Event of the given type.
521      */
522     @nogc
523     bool hasEvent(Event.Type type) const nothrow {
524         return SDL_HasEvent(type) == SDL_TRUE;
525     }
526 
527     /**
528      * Returns: if the current Event queue has the Quit Event.
529      */
530     @nogc
531     bool hasQuitEvent() const nothrow {
532         return SDL_QuitRequested();
533     }
534 
535     /**
536      * Draw a drawable object on screen
537      */
538     @nogc
539     void draw(Drawable d) const nothrow {
540         if (d)
541             d.draw(this);
542     }
543 
544     /**
545      * Make all changes visible on screen
546      */
547     @nogc
548     void display() nothrow {
549         if (_count > 1)
550             SDL_GL_MakeCurrent(_window, _glContext);
551         SDL_GL_SwapWindow(_window);
552     }
553 
554     /**
555      * Returns the current title of the window.
556      */
557     @nogc
558     string getTitle() nothrow {
559         import core.stdc..string : strlen;
560 
561         const char* p = SDL_GetWindowTitle(_window);
562         if (!p)
563             return null;
564 
565         return cast(immutable) p[0 .. strlen(p)];
566     }
567 
568     /**
569      * Set a new title to this window
570      *
571      * Returns: the old title
572      */
573     @nogc
574     string setTitle(string title) nothrow {
575         string old_title = this.getTitle();
576         SDL_SetWindowTitle(_window, toStringz(title));
577 
578         return old_title;
579     }
580 
581     /**
582      * Set an icon for this window.
583      */
584     @nogc
585     void setIcon(ref Surface srfc) {
586         srfc.setAsIconOf(_window);
587     }
588 
589     /**
590      * Returns the index of the display which contains the center of the window
591      *
592      * Note: If something went wrong (e.g. your Window is invalid), a negative value is returned
593      */
594     @nogc
595     int getDisplayIndex() nothrow {
596         return SDL_GetWindowDisplayIndex(_window);
597     }
598 
599     /**
600      * Set the DisplayMode when the Window is visible at fullscreen.
601      */
602     @nogc
603     void setDisplayMode(const DisplayMode mode) nothrow {
604         SDL_DisplayMode sdl_mode = void;
605         immutable int result = SDL_SetWindowDisplayMode(_window, _transfer(mode, sdl_mode));
606         if (result != 0)
607             print_fmt("Could not set the display mode: %s\n", SDL_GetError());
608     }
609 
610     /**
611      * Returns the DisplayMode when the Window is visible at fullscreen.
612      */
613     @nogc
614     DisplayMode getDisplayMode() nothrow {
615         SDL_DisplayMode mode = void;
616         immutable int result = SDL_GetWindowDisplayMode(_window, &mode);
617         if (result != 0)
618             print_fmt("Could not get the display mode: %s\n", SDL_GetError());
619 
620         return DisplayMode(mode.w, mode.h, cast(ubyte) mode.refresh_rate);
621     }
622 
623     enum uint FullScreenMask = Style.Fullscreen | Style.Desktop;
624 
625     /**
626      * Use this function to (re)set Window's fullscreen states.
627      *
628      * style may be Style.Fullscreen for "real" fullscreen with a display mode change
629      * or Style.Desktop for "fake" fullscreen that takes the size of the desktop
630      * Use 0 for windowed mode.
631      *
632      * if adaptProjection is true (which is the default) the projection will automatically adapted.
633      * set it to false if you want to specify your own projection afterwards.
634      */
635     @nogc
636     bool setFullscreen(uint style, bool adaptProjection = true) nothrow {
637         if (style & this.getStyle())
638             return true;
639 
640         if (style & FullScreenMask || style == 0) {
641             immutable int result = SDL_SetWindowFullscreen(this._window, style);
642             if (result != 0) {
643                 print_fmt("Could not enable fullscreen: %s\n", SDL_GetError());
644                 return false;
645             } else if (adaptProjection) {
646                 const Size size = this.getSize();
647 
648                 this.projection.loadIdentity().ortho(Rect(0, 0, size.width, size.height));
649                 this.loadProjection();
650 
651                 glViewport(0, 0, size.width, size.height);
652             }
653 
654             return true;
655         }
656 
657         return false;
658     }
659 
660     /**
661      * Toggle between Fullscreen and windowed mode, depending on the current state.
662      *
663      * if adaptProjection is true (which is the default) the projection will automatically adapted.
664      * set it to false if you want to specify your own projection afterwards.
665      */
666     @nogc
667     void toggleFullscreen(bool adaptProjection = true) nothrow {
668         if (this.getStyle() & FullScreenMask)
669             this.setFullscreen(0, adaptProjection);
670         else
671             this.setFullscreen(Style.Fullscreen, adaptProjection);
672     }
673 
674     /**
675      * Returns, if this Window is in fullscreen mode.
676      */
677     @nogc
678     bool isFullscreen() nothrow {
679         return (this.getStyle() & FullScreenMask) != 0;
680     }
681 
682 package(Dgame):
683     @nogc
684     void draw(Geometry geo, const Texture* texture, const Vertex[] vertices) const nothrow {
685         if (vertices.length == 0)
686             return;
687 
688         if (texture)
689             texture.bind();
690 
691         glVertexPointer(2, GL_FLOAT, Vertex.sizeof, &vertices[0].position.x);
692         glColorPointer(4, GL_FLOAT, Vertex.sizeof, &vertices[0].color.red);
693         glTexCoordPointer(2, GL_FLOAT, Vertex.sizeof, &vertices[0].texCoord.x);
694 
695         // prevent 64 bit bug, because *.length is size_t and therefore on 64 bit platforms ulong
696         glDrawArrays(geo, 0, cast(uint) vertices.length);
697 
698         if (texture)
699             texture.unbind();
700     }
701 
702     @nogc
703     void draw(Geometry geo, ref const Matrix4x4 mat, const Texture* texture, const Vertex[] vertices) const nothrow {
704         glPushMatrix();
705         scope(exit) glPopMatrix();
706 
707         glLoadMatrixf(mat.getValues().ptr);
708 
709         this.draw(geo, texture, vertices);
710     }
711 }