1 module d2d.window.window;
2 
3 import d2d;
4 
5 import std.stdio : stderr;
6 
7 deprecated("Use BindSDL_Mixer, BindSDL_TTF, BindSDL_Image") enum DynLibs
8 {
9 	none
10 }
11 
12 /// Single-Window class wrapping SDL_Window.
13 class Window : IVerifiable, IDisposable, IRenderTarget
14 {
15 private:
16 	SDL_Window* _handle;
17 	int _id;
18 	uint _fbo, _drb;
19 	Texture _texture;
20 	RectangleShape _displayPlane;
21 	bool _direct = false;
22 	mat4 _postMatrix;
23 
24 public:
25 	/// Static variable to a SDL GL Context.
26 	static SDL_GLContext glContext = null;
27 
28 	/// Creates a new centered window with specified title and flags on a 800x480 resolution.
29 	this()(string title = "D2DGame", SDL_WindowFlags flags = WindowFlags.Default)
30 	{
31 		this(800, 480, title, flags);
32 	}
33 
34 	deprecated this()(string title, SDL_WindowFlags flags, DynLibs dynamicLibs)
35 	{
36 		static assert(false,
37 				"Use BindSDL_Mixer, BindSDL_TTF, BindSDL_Image versions instead of DynLibs constructor");
38 	}
39 
40 	/// Creates a new centered window with specified dimensions, title and flags.
41 	this(int width, int height, string title = "D2DGame", SDL_WindowFlags flags = WindowFlags.Default)
42 	{
43 		this(SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, title, flags);
44 	}
45 
46 	/// Creates a new window with specified parameters.
47 	this(int x, int y, int width, int height, string title,
48 			SDL_WindowFlags flags = WindowFlags.Default)
49 	{
50 		SDLSupport sdl = loadSDL();
51 		loadErrorCheck("SDL");
52 		if (sdl == SDLSupport.noLibrary)
53 		{
54 			stderr.writeln("SDL failed to load (no library found)");
55 			throw new Exception("SDL not found");
56 		}
57 		else if (sdl == SDLSupport.badLibrary)
58 		{
59 			stderr.writeln("SDL failed to load some symbols");
60 			throw new Exception("SDL version invalid");
61 		}
62 		else if (sdl != sdlSupport)
63 		{
64 			stderr.writeln("SDL version failed to match");
65 			throw new Exception("SDL version invalid");
66 		}
67 
68 		version (BindSDL_Image)
69 		{
70 			auto img = loadSDLImage();
71 			loadErrorCheck("SDL_Image");
72 			if (img != sdlImageSupport)
73 			{
74 				stderr.writeln("Failed loading SDL_Image");
75 				throw new Exception("Failed to load SDL_Image");
76 			}
77 		}
78 
79 		version (BindSDL_Mixer)
80 		{
81 			auto mixer = loadSDLMixer();
82 			loadErrorCheck("SDL_Mixer");
83 			if (mixer != sdlMixerSupport)
84 			{
85 				stderr.writeln("Failed loading SDL_Mixer");
86 				throw new Exception("Failed to load SDL_Mixer");
87 			}
88 		}
89 
90 		version (BindSDL_TTF)
91 		{
92 			auto ttf = loadSDLTTF();
93 			loadErrorCheck("SDL_TTF");
94 			if (ttf != sdlTTFSupport)
95 			{
96 				stderr.writeln("Failed loading SDL_TTF");
97 				throw new Exception("Failed to load SDL_TTF");
98 			}
99 		}
100 
101 		SDL_Init(SDL_INIT_EVERYTHING);
102 
103 		version (BindSDL_TTF)
104 			if (TTF_Init() == -1)
105 			{
106 				throw new Exception("Error Initializing SDL_TTF: " ~ TTF_GetError().fromStringz.idup);
107 			}
108 
109 		_handle = SDL_CreateWindow(title.toStringz(), x, y, width, height, flags | SDL_WINDOW_OPENGL);
110 		if (!valid)
111 			throw new Exception("Couldn't create window!");
112 		_id = SDL_GetWindowID(_handle);
113 
114 		SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
115 		SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);
116 
117 		SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
118 		SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
119 		SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
120 
121 		glContext = SDL_GL_CreateContext(_handle);
122 
123 		SDL_GL_SetSwapInterval(0);
124 
125 		auto gl = loadOpenGL();
126 		loadErrorCheck("OpenGL");
127 		if (gl == GLSupport.noLibrary)
128 		{
129 			stderr.writeln("OpenGL failed to load (no library found)");
130 			throw new Exception("OpenGL not found");
131 		}
132 		else if (gl == GLSupport.badLibrary)
133 		{
134 			stderr.writeln("OpenGL failed to load some symbols");
135 			throw new Exception("OpenGL could not be initialized");
136 		}
137 		else if (gl != glSupport)
138 			throw new Exception("Needs at least OpenGL 3.2 to run");
139 
140 		if (SDL_GL_MakeCurrent(_handle, glContext) < 0)
141 			throw new Exception(cast(string) fromStringz(SDL_GetError()));
142 
143 		Texture.supportsAnisotropy = hasARBTextureFilterAnisotropic;
144 
145 		glEnable(GL_BLEND);
146 		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
147 
148 		ShaderProgram.load();
149 		Texture.load();
150 		version (BindSDL_Mixer)
151 			if (!Music.load())
152 				throw new Exception(Music.error);
153 
154 		create(width, height);
155 
156 		_displayPlane = new RectangleShape();
157 		_displayPlane.size = vec2(1, 1);
158 		_displayPlane.create();
159 		_displayPlane.texture = _texture;
160 
161 		_postMatrix = mat4.orthographic(0, 1, 0, 1, -1, 1);
162 
163 		foreach (info; soloader.errors)
164 			stderr.writeln(info.error, info.message);
165 	}
166 
167 	~this()
168 	{
169 		if (valid)
170 			dispose();
171 	}
172 
173 	/// Polls a event from the stack and returns `true` if one was found.
174 	bool pollEvent(ref WindowEvent event)
175 	{
176 		SDL_Event evt;
177 		if (SDL_PollEvent(&evt))
178 		{
179 			event = WindowEvent();
180 			event.fromSDL(evt);
181 			return true;
182 		}
183 		return false;
184 	}
185 
186 	/// Pushes `WindowEvent.Quit` to the event stack.
187 	void quit()
188 	{
189 		SDL_Event sdlevent;
190 		sdlevent.type = SDL_QUIT;
191 		SDL_PushEvent(&sdlevent);
192 	}
193 
194 	void bind()
195 	{
196 		if (!_direct)
197 		{
198 			glBindFramebuffer(GL_FRAMEBUFFER, _fbo);
199 			glViewport(0, 0, _texture.width, _texture.height);
200 		}
201 	}
202 
203 	void resize(int width, int height)
204 	{
205 		glDeleteFramebuffers(1, &_fbo);
206 		_texture.dispose();
207 		create(width, height);
208 		_displayPlane.texture = _texture;
209 		projectionStack.set(mat4.orthographic(0, width, height, 0, -1, 1));
210 	}
211 
212 	void create(int width, int height)
213 	{
214 		glGenFramebuffers(1, &_fbo);
215 		glBindFramebuffer(GL_FRAMEBUFFER, _fbo);
216 
217 		_texture = new Texture();
218 		_texture.minFilter = TextureFilterMode.Nearest;
219 		_texture.magFilter = TextureFilterMode.Nearest;
220 		_texture.create(width, height, GL_RGB, null);
221 
222 		glGenRenderbuffers(1, &_drb);
223 		glBindRenderbuffer(GL_RENDERBUFFER, _drb);
224 		glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height);
225 		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _drb);
226 
227 		glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, _texture.id, 0);
228 
229 		glDrawBuffers(1, [GL_COLOR_ATTACHMENT0].ptr);
230 		projectionStack.set(mat4.orthographic(0, width, height, 0, -1, 1));
231 		if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
232 			throw new Exception("Invalid Framebuffer");
233 	}
234 
235 	/// Texture containing rendered content.
236 	@property Texture texture()
237 	{
238 		return _texture;
239 	}
240 
241 	/// Displays rendered content to the window.
242 	void display(ShaderProgram post = null)
243 	{
244 		glBindFramebuffer(GL_FRAMEBUFFER, 0);
245 		int x, y;
246 		SDL_GetWindowSize(_handle, &x, &y);
247 		glViewport(0, 0, x, y);
248 		glClearColor(Color3.BlueViolet, 1);
249 		glClear(GL_COLOR_BUFFER_BIT);
250 		_direct = true;
251 		projectionStack.push();
252 		projectionStack.set(_postMatrix);
253 		matrixStack.push();
254 		matrixStack.set(mat4.identity);
255 		draw(_displayPlane, post);
256 		matrixStack.pop();
257 		projectionStack.pop();
258 		_direct = false;
259 		SDL_GL_SwapWindow(_handle);
260 	}
261 
262 	/// Dynamically sets the title of the window.
263 	@property void title(string title)
264 	{
265 		SDL_SetWindowTitle(_handle, title.toStringz());
266 	}
267 
268 	/// Dynamically gets the title of the window.
269 	@property string title()
270 	{
271 		string title = SDL_GetWindowTitle(_handle).fromStringz().dup;
272 		return title;
273 	}
274 
275 	/// Dynamically sets the width of the window.
276 	@property void width(int width)
277 	{
278 		SDL_SetWindowSize(_handle, width, height);
279 		resize(width, height);
280 	}
281 
282 	/// Dynamically gets the width of the window.
283 	@property int width()
284 	{
285 		int x, y;
286 		SDL_GetWindowSize(_handle, &x, &y);
287 		return x;
288 	}
289 
290 	/// Dynamically sets the height of the window.
291 	@property void height(int height)
292 	{
293 		SDL_SetWindowSize(_handle, width, height);
294 		resize(width, height);
295 	}
296 
297 	/// Dynamically gets the height of the window.
298 	@property int height()
299 	{
300 		int x, y;
301 		SDL_GetWindowSize(_handle, &x, &y);
302 		return y;
303 	}
304 
305 	/// Dynamically sets the maximum width of the window.
306 	@property void maxWidth(int maxWidth)
307 	{
308 		SDL_SetWindowMaximumSize(_handle, maxWidth, maxHeight);
309 	}
310 
311 	/// Dynamically gets the maximum width of the window.
312 	@property int maxWidth()
313 	{
314 		int x, y;
315 		SDL_GetWindowMaximumSize(_handle, &x, &y);
316 		return x;
317 	}
318 
319 	/// Dynamically sets the maximum height of the window.
320 	@property void maxHeight(int maxHeight)
321 	{
322 		SDL_SetWindowMaximumSize(_handle, maxWidth, maxHeight);
323 	}
324 
325 	/// Dynamically gets the maximum height of the window.
326 	@property int maxHeight()
327 	{
328 		int x, y;
329 		SDL_GetWindowMaximumSize(_handle, &x, &y);
330 		return y;
331 	}
332 
333 	/// Dynamically sets the minimum width of the window.
334 	@property void minWidth(int minWidth)
335 	{
336 		SDL_SetWindowMinimumSize(_handle, minWidth, minHeight);
337 	}
338 
339 	/// Dynamically gets the minimum width of the window.
340 	@property int minWidth()
341 	{
342 		int x, y;
343 		SDL_GetWindowMinimumSize(_handle, &x, &y);
344 		return x;
345 	}
346 
347 	/// Dynamically sets the minimum height of the window.
348 	@property void minHeight(int minHeight)
349 	{
350 		SDL_SetWindowMinimumSize(_handle, minWidth, minHeight);
351 	}
352 
353 	/// Dynamically gets the minimum height of the window.
354 	@property int minHeight()
355 	{
356 		int x, y;
357 		SDL_GetWindowMinimumSize(_handle, &x, &y);
358 		return y;
359 	}
360 
361 	/// Dynamically sets the x position of the window.
362 	@property void x(int x)
363 	{
364 		SDL_SetWindowPosition(_handle, x, y);
365 	}
366 
367 	/// Dynamically gets the x position of the window.
368 	@property int x()
369 	{
370 		int x, y;
371 		SDL_GetWindowPosition(_handle, &x, &y);
372 		return x;
373 	}
374 
375 	/// Dynamically sets the y position of the window.
376 	@property void y(int y)
377 	{
378 		SDL_SetWindowPosition(_handle, x, y);
379 	}
380 
381 	/// Dynamically gets the y position of the window.
382 	@property int y()
383 	{
384 		int x, y;
385 		SDL_GetWindowPosition(_handle, &x, &y);
386 		return y;
387 	}
388 
389 	/// Shows the window if hidden.
390 	void show()
391 	{
392 		SDL_ShowWindow(_handle);
393 	}
394 
395 	/// Hides the window.
396 	void hide()
397 	{
398 		SDL_HideWindow(_handle);
399 	}
400 
401 	/// Minimizes the window.
402 	void minimize()
403 	{
404 		SDL_MinimizeWindow(_handle);
405 	}
406 
407 	/// Maximizes the window.
408 	void maximize()
409 	{
410 		SDL_MaximizeWindow(_handle);
411 	}
412 
413 	/// Restores the window state from minimized or maximized.
414 	void restore()
415 	{
416 		SDL_RestoreWindow(_handle);
417 	}
418 
419 	/// Raises the window to top and focuses it for input.
420 	void focus()
421 	{
422 		SDL_RaiseWindow(_handle);
423 	}
424 
425 	/// Sets the icon to a Btimap.
426 	void setIcon(Bitmap icon)
427 	{
428 		SDL_SetWindowIcon(_handle, icon.surface);
429 	}
430 
431 	/// Closes the window and invalidates it.
432 	/// See_Also: Window.close
433 	void dispose()
434 	{
435 		glDeleteFramebuffers(1, &_fbo);
436 		_texture.dispose();
437 		if (valid)
438 		{
439 			SDL_DestroyWindow(_handle);
440 			_handle = null;
441 		}
442 	}
443 
444 	/// Closes the window and invalidates it.
445 	/// See_Also: Window.dispose
446 	void close()
447 	{
448 		dispose();
449 	}
450 
451 	/// Returns if the is still open.
452 	/// See_Also: Window.valid
453 	@property bool open()
454 	{
455 		return valid;
456 	}
457 
458 	/// Returns if the window is still open.
459 	/// See_Also: Window.open
460 	@property bool valid()
461 	{
462 		return _handle !is null;
463 	}
464 }
465 
466 ///
467 unittest
468 {
469 	Window window = new Window(800, 600, "Unittest");
470 
471 	assert(window.valid);
472 
473 	assert(window.title == "Unittest");
474 	window.title = "Window Title";
475 	assert(window.title == "Window Title");
476 	// Automatic conversion from c-strings to D-strings
477 
478 	assert(window.width == 800);
479 
480 	window.close();
481 	assert(!window.valid);
482 }
483 
484 void loadErrorCheck(string what)
485 {
486 	if (soloader.errorCount > 0)
487 	{
488 		stderr.writeln("Errors loading ", what, ":");
489 		foreach (info; soloader.errors)
490 			stderr.writeln(info.error, info.message);
491 		soloader.resetErrors();
492 	}
493 }