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 }