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 }