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