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 }