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 	import derelict.sdl2.sdl;
28 	import derelict.opengl3.gl;
29 
30 	import Dgame.Internal.Log;
31 	import Dgame.Internal.Unique;
32 	
33 	import Dgame.Graphics.Color;
34 	import Dgame.Graphics.Drawable;
35 	import Dgame.Graphics.Surface;
36 	import Dgame.Graphics.Texture;
37 	import Dgame.Graphics.Renderer;
38 	import Dgame.Graphics.TileMap;
39 	import Dgame.Math.Vector2;
40 	import Dgame.Window.VideoMode;
41 	import Dgame.System.Clock;
42 }
43 
44 private Window[] _WndFinalizer;
45 
46 static ~this() {
47 	debug Log.info("Close open Windows.");
48 	
49 	for (size_t i = 0; i < _WndFinalizer.length; ++i) {
50 		if (_WndFinalizer[i])
51 			_WndFinalizer[i].close();
52 	}
53 	
54 	debug Log.info("Open Windows closed.");
55 	
56 	_WndFinalizer = null;
57 }
58 
59 /**
60  * Window is a rendering window where all drawable objects are drawn.
61  *
62  * Note that the default clear-color is <code>Color.White</code> and the
63  * default Sync is <code>Window.Sync.Enable</code>.
64  *
65  * Author: rschuett
66  */
67 final class Window {
68 	/**
69 	 * The Window syncronisation mode.
70 	 * Default Syncronisation is <code>Sync.Enable</code>.
71 	 */
72 	enum Sync : byte {
73 		Enable  = 1,	/** Sync is enabled */
74 		Disable = 0,	/** Sync is disabled */
75 		LateSwapTearing = -1	/** For late swap tearing */
76 	}
77 	
78 	/**
79 	 * The specific window styles
80 	 */
81 	enum Style {
82 		Fullscreen = SDL_WINDOW_FULLSCREEN, /** Window is fullscreened */
83 		Desktop = SDL_WINDOW_FULLSCREEN_DESKTOP, /** Window has Desktop Fullscreen */
84 		OpenGL	= SDL_WINDOW_OPENGL,	 /** OpenGL support */
85 		Shown	= SDL_WINDOW_SHOWN,		 /** Show the Window immediately */
86 		Borderless = SDL_WINDOW_BORDERLESS, /** Hide the Window immediately */
87 		Resizeable = SDL_WINDOW_RESIZABLE,  /** Window is resizeable */
88 		Maximized = SDL_WINDOW_MAXIMIZED,  /** Maximize the Window immediately */
89 		Minimized = SDL_WINDOW_MINIMIZED,  /** Minimize the Window immediately */
90 		InputGrabbed = SDL_WINDOW_INPUT_GRABBED, /** Grab the input inside the window */
91 		InputFocus = SDL_WINDOW_INPUT_FOCUS, /** The Window has input (keyboard) focus */
92 		MouseFocus = SDL_WINDOW_MOUSE_FOCUS, /** The Window has mouse focus */
93 		HighDPI = SDL_WINDOW_ALLOW_HIGHDPI, /** Window should be created in high-DPI mode if supported (>= SDL 2.0.1) */
94 		Foreign = SDL_WINDOW_FOREIGN, /** The window was created by some other framework. */
95 		
96 		Default = Shown | OpenGL | HighDPI /** Default mode is Shown | OpenGL | HighDPI */
97 	}
98 	
99 private:
100 	SDL_Window* _window;
101 	SDL_GLContext _glContext;
102 	
103 	VideoMode _vMode = void;
104 	Style _style;
105 	
106 	string _title;
107 	ubyte _framerateLimit;
108 	
109 	static int _winCount;
110 	
111 public:
112 final:
113 	static immutable string DefaultTitle = "App";
114 	enum DefaultXPos = SDL_WINDOWPOS_CENTERED;
115 	enum DefaultYPos = SDL_WINDOWPOS_CENTERED;
116 	
117 	/**
118 	 * CTor
119 	 */
120 	this(VideoMode vMode, string title = DefaultTitle,
121 	     Style style = Style.Default,
122 	     int x = DefaultXPos, int y = DefaultYPos)
123 	{
124 		// Create an application window with the following settings:
125 		this._window = SDL_CreateWindow(title.ptr,	// const char* title
126 		                                x,	// int x: initial x position
127 		                                y,	// int y: initial y position
128 		                                vMode.width,	// int w: width, in pixels
129 		                                vMode.height,	// int h: height, in pixels
130 		                                style);	// Uint32 flags: window options
131 		
132 		if (this._window is null)
133 			Log.error("Error by creating a SDL2 window: " ~ to!string(SDL_GetError()));
134 		
135 		if (style & Style.OpenGL) {
136 			this._glContext = SDL_GL_CreateContext(this._window);
137 			if (this._glContext is null)
138 				Log.error("Error while creating gl context: " ~ to!string(SDL_GetError()));
139 			
140 			const GLVersion glver = DerelictGL.reload();
141 			debug Log.info("Derelict loaded GL version: %s (%s), available GL version: %s",
142 			               DerelictGL.loadedVersion, glver, to!(string)(glGetString(GL_VERSION)));
143 			if (glver < GLVersion.GL30)
144 				Log.error("Your OpenGL version (%d) is too low. Need at least GL 3.0.", glver);
145 			
146 			glMatrixMode(GL_PROJECTION);
147 			glLoadIdentity();
148 			
149 			glEnable(GL_CULL_FACE);
150 			glCullFace(GL_FRONT);
151 			
152 			glShadeModel(GL_FLAT);
153 			glDisable(GL_DITHER);
154 			
155 			glEnable(GL_BLEND);
156 			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
157 			
158 			glDisable(GL_DEPTH_TEST);
159 			
160 			// Hints
161 			glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
162 			glHint(GL_GENERATE_MIPMAP_HINT, GL_FASTEST);
163 			glHint(GL_TEXTURE_COMPRESSION_HINT, GL_FASTEST);
164 			
165 			glOrtho(0, vMode.width, vMode.height, 0, 1, -1);
166 			
167 			this.setVerticalSync(Sync.Enable);
168 			this.setClearColor(Color.White);
169 			
170 			SDL_GL_MakeCurrent(this._window, this._glContext);
171 		}
172 		
173 		this._title = title;
174 		this._vMode = vMode;
175 		this._style = style;
176 		
177 		_WndFinalizer ~= this;
178 		
179 		_winCount += 1;
180 	}
181 
182 	/**
183 	 * Close and destroy this window.
184 	 */
185 	void close() {
186 		// Once finished with OpenGL functions, the SDL_GLContext can be deleted.
187 		SDL_GL_DeleteContext(this._glContext);  
188 		// Close and destroy the window
189 		SDL_DestroyWindow(this._window);
190 		
191 		this._glContext = null;
192 		this._window = null;
193 		
194 		_winCount--;
195 	}
196 	
197 	/**
198 	 * Returns how many windows exist
199 	 */
200 	static int count() {
201 		return _winCount;
202 	}
203 	
204 	/**
205 	 * Returns the current VideoMode which hold the current width, the current height and the refresh rate.
206 	 * The first two can also be accessed with the 'width' and 'height' property.
207 	 */
208 	ref const(VideoMode) getVideoMode() const {
209 		return this._vMode;
210 	}
211 	
212 	/**
213 	 * Every window has an unique Id.
214 	 * This method returns this Id.
215 	 */
216 	@property
217 	uint id() {
218 		return SDL_GetWindowID(this._window);
219 	}
220 	
221 	/**
222 	 * Set the Syncronisation mode of this window.
223 	 * Default Syncronisation is <code>Sync.Enable</code>.
224 	 *
225 	 * See: Sync enum
226 	 *
227 	 * Returns if the sync mode is supported.
228 	 */
229 	bool setVerticalSync(Sync sync) const {
230 		if (sync == Sync.Enable || sync == Sync.Disable)
231 			return SDL_GL_SetSwapInterval(sync) == 0;
232 		else
233 			Log.error("Unknown sync mode. Sync mode must be one of Sync.Enable, Sync.Disable.");
234 		
235 		return false;
236 	}
237 
238 	/**
239 	 * Returns the current syncronisation mode.
240 	 *
241 	 * See: Sync enum
242 	 */
243 	Sync getVerticalSync() {
244 		return cast(Sync) SDL_GL_GetSwapInterval();
245 	}
246 	
247 	/**
248 	 * Returns the Window Style.
249 	 * 
250 	 * See: Style enum
251 	 */
252 	Style getStyle() const pure nothrow {
253 		return this._style;
254 	}
255 	
256 	/**
257 	 * Capture the pixel data of the current window and
258 	 * returns a Surface with this pixel data.
259 	 * You can also alter the format of the pixel data.
260 	 * Default is <code>Texture.Format.BGRA</code>.
261 	 * This method is predestinated for screenshots.
262 	 * 
263 	 * Example:
264 	 * ---
265 	 * Window wnd = ...
266 	 * ...
267 	 * wnd.capture().saveToFile("samples/img/screenshot.png");
268 	 * ---
269 	 * 
270 	 * If you want to use the Screenshot more than only to save it,
271 	 * it could be better to wrap it into an Image.
272 	 * 
273 	 * Example:
274 	 * ----
275 	 * Window wnd = ...
276 	 * ...
277 	 * Image screen = new Image(wnd.capture());
278 	 * ----
279 	 */
280 	Surface capture(Texture.Format fmt = Texture.Format.BGRA) const {
281 		Surface mycapture = Surface.make(this.width, this.height);
282 		
283 		glReadBuffer(GL_FRONT);
284 		
285 		ubyte* pixels = cast(ubyte*) mycapture.pixels;
286 		glReadPixels(0, 0, this.width, this.height, fmt, GL_UNSIGNED_BYTE, pixels);
287 		
288 		const uint lineWidth = this.width * 4;
289 		const uint hlw = this.height * lineWidth;
290 
291 		unique_ptr!(ubyte) tmpLine = allocate_unique!(ubyte)(lineWidth);
292 //		ubyte[] tmpLine = new ubyte[lineWidth];
293 
294 		for (ushort i = 0; i < this.height / 2; ++i) {
295 			const uint tmpIdx1 = i * lineWidth;
296 			const uint tmpIdx2 = (i + 1) * lineWidth;
297 			
298 			const uint switchIdx1 = hlw - tmpIdx2;
299 			const uint switchIdx2 = hlw - tmpIdx1;
300 			
301 			tmpLine[0 .. lineWidth] = pixels[tmpIdx1 .. tmpIdx2];
302 			ubyte[] switchLine = pixels[switchIdx1 .. switchIdx2];
303 			
304 			pixels[tmpIdx1 .. tmpIdx2] = switchLine[];
305 			pixels[switchIdx1 .. switchIdx2] = tmpLine[0 .. lineWidth];
306 		}
307 		
308 		return mycapture;
309 	}
310 	
311 	/**
312 	 * Returns if the keyboard focus is on this window.
313 	 */
314 	bool hasKeyboardFocus() const {
315 		return SDL_GetKeyboardFocus() == this._window;
316 	}
317 	
318 	/**
319 	 * Returns if the mouse focus is on this window.
320 	 */
321 	bool hasMouseFocus() const {
322 		return SDL_GetMouseFocus() == this._window;
323 	}
324 	
325 	/**
326 	 * Set the framerate limit for this window.
327 	 */
328 	void setFramerateLimit(ubyte fps) pure nothrow {
329 		this._framerateLimit = fps;
330 	}
331 	
332 	/**
333 	 * Returns the framerate limit for this window.
334 	 */
335 	ubyte getFramerateLimit() const pure nothrow {
336 		return this._framerateLimit;
337 	}
338 	
339 	/**
340 	 * Check if this window is still opened.
341 	 */
342 	@property
343 	bool isOpen() const pure nothrow {
344 		return this._window !is null;
345 	}
346 	
347 	/**
348 	 * Set the color with which this windows clear his buffer.
349 	 * This is also the background color of the window.
350 	 */
351 	void setClearColor(ref const Color col) {
352 		const float[4] rgba = col.asGLColor();
353 		glClearColor(rgba[0], rgba[1], rgba[2], rgba[3]);
354 	}
355 	
356 	/**
357 	 * Rvalue version
358 	 */
359 	void setClearColor(const Color col) {
360 		this.setClearColor(col);
361 	}
362 	
363 	/**
364 	 * Set the color with which this windows clear his buffer.
365 	 * This is also the background color of the window.
366 	 */
367 	void setClearColor(float red, float green, float blue, float alpha = 0.0) {
368 		glClearColor(red, green, blue, alpha);
369 	}
370 	
371 	/**
372 	 * Clears the buffer.
373 	 */
374 	void clear() const {
375 		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
376 	}
377 	
378 	/**
379 	 * Draw a drawable object on screen.
380 	 */
381 	void draw(Drawable draw) const in {
382 		assert(draw !is null, "Drawable object is null.");
383 	} body {
384 		draw.render();
385 	}
386 	
387 	/**
388 	 * Draw a Renderer on the screen.
389 	 */
390 	void draw(ref Renderer rtarget) const {
391 		rtarget.present();
392 	}
393 	
394 	/**
395 	 * Make all changes visible on screen.
396 	 * If the framerate limit is not 0, it waits for (1000 / framerate limit) milliseconds.
397 	 */
398 	void display() {
399 		if (!this.isOpen())
400 			return;
401 		
402 		if (this._framerateLimit != 0 && this.getVerticalSync() != Sync.Enable)
403 			Clock.wait(1000 / this._framerateLimit);
404 
405 		if (this._style & Style.OpenGL) {
406 			if (_winCount > 1)
407 				SDL_GL_MakeCurrent(this._window, this._glContext);
408 			SDL_GL_SwapWindow(this._window);
409 		} else
410 			SDL_UpdateWindowSurface(this._window);
411 	}
412 	
413 	/**
414 	 * Set a new position to this window
415 	 */
416 	void setPosition(short x, short y) {
417 		SDL_SetWindowPosition(this._window, x, y);
418 	}
419 	
420 	/**
421 	 * Set a new position to this window
422 	 */
423 	void setPosition(ref const Vector2s vec) {
424 		this.setPosition(vec.x, vec.y);
425 	}
426 	
427 	/**
428 	 * Returns the current position of the window.
429 	 */
430 	Vector2s getPosition() {
431 		int x, y;
432 		this.fetchPosition(&x, &y);
433 		
434 		return Vector2s(x, y);
435 	}
436 	
437 	/**
438 	 * Fetch the current position if this window.
439 	 * The position is stored inside of the pointer.
440 	 * The pointer don't have to be null.
441 	 */
442 	void fetchPosition(int* x, int* y) in {
443 		assert(x !is null && y !is null, "x or y pointer is null.");
444 	} body {
445 		SDL_GetWindowPosition(this._window, x, y);
446 	}
447 	
448 	/**
449 	 * Returns the current title of the window.
450 	 */
451 	string getTitle() const pure nothrow {
452 		return this._title; /// SDL_GetWindowTitle(this._window);
453 	}
454 	
455 	/**
456 	 * Set a new title to this window
457 	 *
458 	 * Returns: the old title
459 	 */
460 	string setTitle(string title) {
461 		string old_title = this.getTitle();
462 		SDL_SetWindowTitle(this._window, title.ptr);
463 		
464 		return old_title;
465 	}
466 	
467 	/**
468 	 * Set a new size to this window
469 	 */
470 	void setSize(ushort width, ushort height) {
471 		SDL_SetWindowSize(this._window, width, height);
472 		
473 		this._vMode.width  = width;
474 		this._vMode.height = height;
475 	}
476 	
477 	void fetchSize(int* w, int* h) in {
478 		assert(w !is null && h !is null, "w or h pointer is null.");
479 	} body {
480 		SDL_GetWindowSize(this._window, w, h);
481 	}
482 	
483 	int[2] getSize() {
484 		int[2] size = void;
485 		this.fetchSize(&size[0], &size[1]);
486 		
487 		return size;
488 	}
489 	
490 	@property
491 	ushort width() const pure nothrow {
492 		return this._vMode.width;
493 	}
494 	
495 	@property
496 	ushort height() const pure nothrow {
497 		return this._vMode.height;
498 	}
499 	
500 	/**
501 	 * Raise the window.
502 	 * The window has after this call focus.
503 	 */
504 	void raise() {
505 		SDL_RaiseWindow(this._window);
506 	}
507 	
508 	/**
509 	 * Restore the window.
510 	 */
511 	void restore() {
512 		SDL_RestoreWindow(this._window);
513 	}
514 	
515 	/**
516 	 * Enable or Disable the screen saver.
517 	 */
518 	void setScreenSaver(bool enable) {
519 		if (enable)
520 			SDL_EnableScreenSaver();
521 		else
522 			SDL_DisableScreenSaver();
523 	}
524 	
525 	/**
526 	 * Returns if the screen saver is currently enabled, or not.
527 	 */
528 	bool isScreenSaverEnabled() const {
529 		return SDL_IsScreenSaverEnabled() == SDL_TRUE;
530 	}
531 	
532 	/**
533 	 * Show or hide the window.
534 	 * true shows, false hides.
535 	 */
536 	void show(bool show) {
537 		if (show)
538 			SDL_ShowWindow(this._window);
539 		else
540 			SDL_HideWindow(this._window);
541 	}
542 	
543 	/**
544 	 * When input is grabbed the mouse is confined to the window.
545 	 */
546 	void setGrabbed(bool enable) {
547 		SDL_SetWindowGrab(this._window, enable ? SDL_TRUE : SDL_FALSE);
548 	}
549 	
550 	/**
551 	 * Returns true, if input is grabbed.
552 	 */
553 	bool isGrabbed() {
554 		return SDL_GetWindowGrab(this._window) == SDL_TRUE;
555 	}
556 	
557 	/**
558 	 * Returns the brightness (gamma correction) for the window
559 	 * where 0.0 is completely dark and 1.0 is normal brightness.
560 	 */
561 	float getBrightness() {
562 		return SDL_GetWindowBrightness(this._window);
563 	}
564 	
565 	/**
566 	 * Set the brightness (gamma correction) for the window.
567 	 */
568 	bool setBrightness(float bright) {
569 		return SDL_SetWindowBrightness(this._window, bright) == 0;
570 	}
571 	
572 	enum FullScreenMask = Style.Fullscreen | Style.Desktop;
573 	
574 	/**
575 	 * Use this function to (re)set Window's fullscreen states.
576 	 * style may be Style.Fullscreen for "real" fullscreen with a videomode change
577 	 * or Style.Desktop for "fake" fullscreen that takes the size of the desktop
578 	 * Set 0 for windowed mode.
579 	 */
580 	void setFullscreen(int style) {
581 		if (style & this._style)
582 			return;
583 		
584 		if (style & FullScreenMask || style == 0) {
585 			if (SDL_SetWindowFullscreen(this._window, style) == 0) {
586 				this._style &= ~FullScreenMask;
587 				if (style != 0)
588 					this._style |= style;
589 			}
590 		}
591 	}
592 	
593 	/**
594 	 * Toggle between Fullscreen and windowed mode, depending on the current state.
595 	 */
596 	void toggleFullscreen() {
597 		if (this._style & FullScreenMask)
598 			this.setFullscreen(0);
599 		else
600 			this.setFullscreen(Style.Fullscreen);
601 	}
602 	
603 	/**
604 	 * Returns, if this Window is in fullscreen mode.
605 	 */
606 	bool isFullscreen() const pure nothrow {
607 		return (this._style & FullScreenMask) != 0;
608 	}
609 	
610 	/**
611 	 * Set an icon for this window.
612 	 */
613 	void setIcon(ref Surface icon) {
614 		SDL_SetWindowIcon(this._window, icon.ptr);
615 	}
616 }