1 /** 2 * Copyright: Copyright (C) 2018 Gabriel Gheorghe, All Rights Reserved 3 * Authors: $(Gabriel Gheorghe) 4 * License: $(LINK2 https://www.gnu.org/licenses/gpl-3.0.txt, GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007) 5 * Source 6 * Documentation: 7 * Coverage: 8 */ 9 module liberty.core.system; 10 import liberty.core.input; 11 import derelict.util.exception; 12 import derelict.util.loader; 13 import derelict.sdl2.sdl; 14 import liberty.core.memory : ensureNotInGC; 15 import std.string : format, fromStringz, toStringz; 16 /// A failing Platform function should <b>always</b> throw a $(D PlatformException). 17 final class PlatformException : Exception { 18 /// Default constructor. 19 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) pure nothrow @safe { 20 super(msg, file, line, next); 21 } 22 } 23 /// Root object for SDL2 functionality. 24 /// It's passed around to other SDL wapper objects to ensure library loading. 25 final class Platform { 26 private { 27 bool _initialized; 28 SDL2Window[uint] _knownWindows; 29 bool _shouldQuit; 30 } 31 package void throwPlatformException(string call_name) { 32 string message = format("%s failed: %s", call_name, errorString()); 33 throw new PlatformException(message); 34 } 35 /// Returns last SDL error and clears it. 36 const(char)[] errorString() { 37 const(char)* message = SDL_GetError(); 38 SDL_ClearError(); 39 return fromStringz(message); 40 } 41 /// Load SDL2 library. 42 /// Param1: You can specify a minimum version of SDL2 for your application. 43 this(SharedLibVersion sld2_version = SharedLibVersion(2, 0, 6)) { 44 try { 45 DerelictSDL2.load(sld2_version); 46 } catch (DerelictException e) { 47 throw new PlatformException(e.msg); 48 } 49 if (SDL_Init(0)) { 50 throwPlatformException("SDL_Init"); 51 } 52 Input.get.startService(); 53 } 54 /// Releases all resourceas and the SDL library. 55 ~this() { 56 if (_initialized) { 57 debug ensureNotInGC("Platform"); 58 SDL_Quit(); 59 _initialized = false; 60 } 61 } 62 /// Returns available SDL video drivers. 63 alias videoDrivers = drivers!(SDL_GetNumVideoDrivers, SDL_GetVideoDriver); 64 /// Returns available SDL audio drivers. 65 alias audioDrivers = drivers!(SDL_GetNumAudioDrivers, SDL_GetAudioDriver); 66 /// Returns true if a subsystem is initiated. 67 bool subSystemInitialized(int sub_system) { 68 int inited = SDL_WasInit(SDL_INIT_EVERYTHING); 69 return (inited & sub_system) != 0; 70 } 71 /// Initialize a subsystem. 72 void subSystemInit(int flag) { 73 if (!subSystemInitialized(flag)) { 74 if (SDL_InitSubSystem(flag)) { 75 throwPlatformException("SDL_InitSubSystem"); 76 } 77 } 78 } 79 /// Returns available display information. 80 /// Throws $(D PlatformException) on error. 81 SDL2VideoDisplay[] displays() { 82 int displatyCount = SDL_GetNumVideoDisplays(); 83 SDL2VideoDisplay[] availableDisplays; 84 for (int displayIndex = 0; displayIndex < displatyCount; displayIndex++) { 85 SDL_Rect rect; 86 int res = SDL_GetDisplayBounds(displayIndex, &rect); 87 if (res) { 88 throwPlatformException("SDL_GetDisplayBounds"); 89 } 90 SDL2DisplayMode[] availableModes; 91 int modeCount = SDL_GetNumDisplayModes(displayIndex); 92 for (int modeIndex = 0; modeIndex < modeCount; modeIndex++) { 93 SDL_DisplayMode mode; 94 if (SDL_GetDisplayMode(displayIndex, modeIndex, &mode)) { 95 throwPlatformException("SDL_GetDisplayMode"); 96 } 97 availableModes ~= new SDL2DisplayMode(modeIndex, mode); 98 } 99 availableDisplays ~= new SDL2VideoDisplay(displayIndex, rect, availableModes); 100 } 101 return availableDisplays; 102 } 103 /// Returns resolution of the first display. 104 /// Throws $(D PlatformException) on error. 105 SDL_Point firstDisplayResolution() { 106 auto displays = displays(); 107 if (displays.length == 0) { 108 throw new PlatformException("No display"); 109 } 110 return displays[0].dimension(); 111 } 112 /// Returns resolution of the second display. 113 /// Throws $(D PlatformException) on error. 114 SDL_Point secondDisplayResolution() { 115 auto displays = displays(); 116 if (displays.length == 0) { 117 throw new PlatformException("No display"); 118 } else if (displays.length == 1) { 119 return SDL_Point(0, 0); 120 } 121 return displays[1].dimension(); 122 } 123 /// Get next SDL2 event. 124 /// Input state gets updated and window callbacks are called too. 125 /// Returns true if an event is returned. 126 bool pollEvent(SDL_Event* event) { 127 if (!SDL_PollEvent(event)) { 128 updateState(event); 129 return true; 130 } 131 return false; 132 } 133 /// Wait for next SDL2 event. 134 /// Input state gets updated and window callbacks are called too. 135 /// Throws $(D PlatformException) on error. 136 void waitEvent(SDL_Event* event) { 137 if (!SDL_WaitEvent(event)) { 138 throwPlatformException("SDL_WaitEvent"); 139 } 140 updateState(event); 141 } 142 /// Wait for next SDL2 event, with a timeout. 143 /// Input state gets updated and window callbacks are called too. 144 /// Returns true if an event is returned. 145 /// Throws $(D PlatformException) on error. 146 bool waitEventTimeout(SDL_Event* event, int timeout_ms) { 147 if (SDL_WaitEventTimeout(event, timeout_ms) == 1) { 148 updateState(event); 149 return true; 150 } 151 return false; 152 } 153 /// Process all pending SDL2 events. 154 /// Input state gets updated. 155 void processEvents() { 156 SDL_Event event; 157 while(SDL_PollEvent(&event)) { 158 updateState(&event); 159 if (event.type == SDL_QUIT) { 160 _shouldQuit = true; 161 } 162 } 163 } 164 /// Returns true if application should quit. 165 bool shouldQuit () pure nothrow const { 166 return _shouldQuit; 167 } 168 /// Start text input. 169 void startTextInput() { 170 SDL_StartTextInput(); 171 } 172 /// Stops text input. 173 void stopTextInput() { 174 SDL_StopTextInput(); 175 } 176 /// Sets clipboard content. 177 /// Throws $(D PlatformException) on error. 178 string clipboard(string s) { 179 if (SDL_SetClipboardText(toStringz(s))) { 180 throwPlatformException("SDL_SetClipboardText"); 181 } 182 return s; 183 } 184 /// Returns clipboard content. 185 /// Throws $(D PlatformException) on error. 186 const(char)[] clipboard() { 187 if (SDL_HasClipboardText() == SDL_FALSE) { 188 return null; 189 } 190 const(char)* s = SDL_GetClipboardText(); 191 if (s is null) { 192 throwPlatformException("SDL_GetClipboardText"); 193 } 194 return fromStringz(s); 195 } 196 /// Returns available audio device names. 197 const(char)[][] audioDevices() { 198 enum type = 0; 199 const(int) devicesCount = SDL_GetNumAudioDevices(type); 200 const(char)[][] ret; 201 foreach (i; 0 .. devicesCount) { 202 ret ~= fromStringz(SDL_GetAudioDeviceName(i, type)); 203 } 204 return ret; 205 } 206 /// Returns platform name. 207 const(char)[] platformName() { 208 return fromStringz(SDL_GetPlatform()); 209 } 210 /// Returns L1 cacheline size in bytes. 211 int l1LineSize() { 212 int ret = SDL_GetCPUCacheLineSize(); 213 if (ret <= 0) { 214 ret = 64; 215 } 216 return ret; 217 } 218 /// Returns number of CPUs 219 int cpuCount() { 220 int ret = SDL_GetCPUCount(); 221 if (ret <= 0) { 222 ret = 1; 223 } 224 return ret; 225 } 226 /// Returns a path suitable for writing configuration files. 227 /// Throws $(D PlatformException) on error. 228 const(char)[] prefPath(string org_name, string application_name) { 229 char* basePath = SDL_GetPrefPath(toStringz(org_name), toStringz(application_name)); 230 if (basePath !is null) { 231 const(char)[] result = fromStringz(basePath); 232 SDL_free(basePath); 233 return result; 234 } 235 throwPlatformException("SDL_GetPrefPath"); 236 return null; 237 } 238 private const(char)[][] drivers(alias numFn, alias elemFn)() { 239 const(int) numDrivers = numFn(); 240 const(char)[][] res; 241 res.length = numDrivers; 242 foreach (i; 0..numDrivers) { 243 res[i] = fromStringz(elemFn(i)); 244 } 245 return res; 246 } 247 private void updateState(const(SDL_Event*) event) { 248 switch(event.type) { 249 case SDL_QUIT: 250 _shouldQuit = true; 251 break; 252 case SDL_KEYDOWN: 253 case SDL_KEYUP: 254 updateKeyboard(&event.key); 255 break; 256 case SDL_MOUSEMOTION: 257 Input.get.updateMouseMotion(&event.motion); 258 Input.get._isMouseMoving = true; 259 break; 260 case SDL_MOUSEBUTTONUP: 261 case SDL_MOUSEBUTTONDOWN: 262 Input.get.updateMouseButtons(&event.button); 263 break; 264 case SDL_MOUSEWHEEL: 265 Input.get.updateMouseWheel(&event.wheel); 266 Input.get._isMouseWheeling = true; 267 break; 268 default: 269 break; 270 } 271 } 272 273 private void updateKeyboard(const(SDL_KeyboardEvent*) event) { 274 if (event.repeat != 0) { 275 return; 276 } 277 switch (event.type) { 278 case SDL_KEYDOWN: 279 assert(event.state == SDL_PRESSED); 280 Input.get.markKeyAsPressed(event.keysym.scancode); 281 break; 282 case SDL_KEYUP: 283 assert(event.state == SDL_RELEASED); 284 Input.get.markKeyAsJustReleased(event.keysym.scancode); 285 break; 286 default: 287 break; 288 } 289 } 290 } 291 /// 292 final class SDL2DisplayMode { 293 private int _modeIndex; 294 private SDL_DisplayMode _mode; 295 /// 296 this(int index, SDL_DisplayMode mode) { 297 _modeIndex = index; 298 _mode = mode; 299 } 300 /// 301 override string toString() { 302 return format("mode #%s (width = %spx, height = %spx, rate = %shz, format = %s)", 303 _modeIndex, _mode.w, _mode.h, _mode.refresh_rate, _mode.format); 304 } 305 } 306 /// 307 final class SDL2VideoDisplay { 308 private { 309 int _displayIndex; 310 SDL2DisplayMode[] _availableModes; 311 SDL_Rect _bounds; 312 } 313 /// 314 this(int index, SDL_Rect bounds, SDL2DisplayMode[] available_mods) { 315 _displayIndex = index; 316 _bounds = bounds; 317 _availableModes = _availableModes; 318 } 319 /// 320 const(SDL2DisplayMode[]) availableModes() pure const nothrow { 321 return _availableModes; 322 } 323 /// 324 SDL_Point dimension() pure const nothrow { 325 return SDL_Point(_bounds.w, _bounds.h); 326 } 327 SDL_Rect bounds() pure const nothrow { 328 return _bounds; 329 } 330 override string toString() { 331 string res = format("display #%s (start = %s,%s - dimension = %s x %s)\n", _displayIndex, 332 _bounds.x, _bounds.y, _bounds.w, _bounds.h); 333 foreach (mode; _availableModes) { 334 res ~= format(" - %s\n", mode); 335 } 336 return res; 337 } 338 } 339 /// SDL2Window class. 340 final class SDL2Window { 341 private { 342 Surface _surface; 343 SDL2GLContext _glContext; 344 uint _id; 345 bool _surfaceNeedRenew; 346 bool _hasValidSurface() { return !_surfaceNeedRenew && _surface !is null; } 347 } 348 SDL2GLContext glContext() { 349 return _glContext; 350 } 351 //package { 352 Platform _platform; 353 SDL_Window* _window; 354 //} 355 /// Construct a window using an Platform reference and. 356 this(Platform platform, int x, int y, int width, int height, SDL_WindowFlags flags) { 357 _platform = platform; 358 _surface = null; 359 _glContext = null; 360 _surfaceNeedRenew = false; 361 bool open_gl = (flags & SDL_WINDOW_OPENGL) != 0; 362 //// 363 width = 1600; 364 height = 900; 365 //flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; 366 //flags |= SDL_WINDOW_BORDERLESS; 367 flags |= SDL_WINDOW_ALLOW_HIGHDPI; 368 flags |= SDL_WINDOW_RESIZABLE; 369 // TODO: Try removing SDL_HWSURFACE from your SDL_SetVideoMode() flags. 370 //// 371 _window = SDL_CreateWindow(toStringz(""), x, y, width, height, flags); 372 if (_window == null) { 373 string message = "SDL_CreateWindow failed: " ~ _platform.errorString.idup; 374 throw new PlatformException(message); 375 } 376 _id = SDL_GetWindowID(_window); 377 if (open_gl) { 378 _glContext = new SDL2GLContext(this); 379 } 380 381 //SDL_Surface *surface; // Declare an SDL_Surface to be filled in with pixel data from an image file 382 //import std.conv: to; 383 //Uint16[16*16] pixels = windowIcon.ptr; 384 //surface = SDL_CreateRGBSurfaceFrom(pixels.ptr,16,16,16,16*2,0x0f00,0x00f0,0x000f,0xf000); 385 //SDL_SetWindowIcon(_window, surface); 386 //SDL_FreeSurface(surface); 387 388 389 } 390 /// Construct a window using an Platform reference and. 391 this(Platform platform, void* windowData) { 392 _platform = platform; 393 _surface = null; 394 _glContext = null; 395 _surfaceNeedRenew = false; 396 _window = SDL_CreateWindowFrom(windowData); 397 if (_window == null) { 398 string message = "SDL_CreateWindowFrom failed: " ~ _platform.errorString.idup; 399 throw new PlatformException(message); 400 } 401 _id = SDL_GetWindowID(_window); 402 } 403 /// Releases the SDL2 resource. 404 ~this() { 405 if (_glContext !is null) { 406 debug import liberty.core.memory : ensureNotInGC; 407 debug ensureNotInGC("SDL2Window"); 408 _glContext.destroy(); 409 _glContext = null; 410 } 411 if (_window !is null) 412 { 413 debug import liberty.core.memory : ensureNotInGC; 414 debug ensureNotInGC("SDL2Window"); 415 SDL_DestroyWindow(_window); 416 _window = null; 417 } 418 } 419 420 /// See_also: $(LINK http://wiki.libsdl.org/SDL_SetWindowFullscreen) 421 /// Throws: $(D PlatformException) on error. 422 final void setFullscreenSetting(uint flags) { 423 if (SDL_SetWindowFullscreen(_window, flags) != 0) { 424 _platform.throwPlatformException("SDL_SetWindowFullscreen"); 425 } 426 } 427 428 /// Returns: The flags associated with the window. 429 /// See_also: $(LINK https://wiki.libsdl.org/SDL_GetWindowFlags) 430 final uint windowFlags() { 431 return SDL_GetWindowFlags(_window); 432 } 433 /// Returns: X window coordinate. 434 /// See_also: $(LINK http://wiki.libsdl.org/SDL_GetWindowPosition) 435 final int x() { 436 return position.x; 437 } 438 /// Returns: Y window coordinate. 439 /// See_also: $(LINK http://wiki.libsdl.org/SDL_GetWindowPosition) 440 final int y() { 441 return position.y; 442 } 443 /// Gets information about the window's display mode 444 /// See_also: $(LINK https://wiki.libsdl.org/SDL_GetWindowDisplayMode) 445 final SDL_DisplayMode windowDisplayMode() { 446 SDL_DisplayMode mode; 447 if (0 != SDL_GetWindowDisplayMode(_window, &mode)) { 448 _platform.throwPlatformException("SDL_GetWindowDisplayMode"); 449 } 450 return mode; 451 } 452 /// Returns: Window coordinates. 453 /// See_also: $(LINK http://wiki.libsdl.org/SDL_GetWindowPosition) 454 final SDL_Point position() { 455 int x, y; 456 SDL_GetWindowPosition(_window, &x, &y); 457 return SDL_Point(x, y); 458 } 459 /// See_also: $(LINK http://wiki.libsdl.org/SDL_SetWindowPosition) 460 final void position(int positionX, int positionY) { 461 SDL_SetWindowPosition(_window, positionX, positionY); 462 } 463 /// See_also: $(LINK http://wiki.libsdl.org/SDL_SetWindowSize) 464 final void size(int width, int height) { 465 SDL_SetWindowSize(_window, width, height); 466 } 467 /// Get the minimum size setting for the window 468 /// See_also: $(LINK https://wiki.libsdl.org/SDL_GetWindowMinimumSize) 469 final SDL_Point minimumSize() { 470 SDL_Point p; 471 SDL_GetWindowMinimumSize(_window, &p.x, &p.y); 472 return p; 473 } 474 /// Get the minimum size setting for the window 475 /// See_also: $(LINK https://wiki.libsdl.org/SDL_SetWindowMinimumSize) 476 final void minimumSize(int width, int height) { 477 SDL_SetWindowMinimumSize(_window, width, height); 478 } 479 /// Get the minimum size setting for the window 480 /// See_also: $(LINK https://wiki.libsdl.org/SDL_GetWindowMaximumSize) 481 final SDL_Point maximumSize() { 482 SDL_Point p; 483 SDL_GetWindowMaximumSize(_window, &p.x, &p.y); 484 return p; 485 } 486 /// Get the minimum size setting for the window 487 /// See_also: $(LINK https://wiki.libsdl.org/SDL_SetWindowMaximumSize) 488 final void maximumSize(int width, int height) { 489 SDL_SetWindowMaximumSize(_window, width, height); 490 } 491 492 /// See_also: $(LINK http://wiki.libsdl.org/SDL_GetWindowSize) 493 /// Returns: Window size in pixels. 494 import liberty.math.vector; 495 final Vector2I size() { 496 int w, h; 497 SDL_GetWindowSize(_window, &w, &h); 498 return Vector2I(w, h); 499 } 500 /// See_also: $(LINK http://wiki.libsdl.org/SDL_SetWindowIcon) 501 final void icon(Surface icon) { 502 SDL_SetWindowIcon(_window, icon.handle()); 503 } 504 /// See_also: $(LINK http://wiki.libsdl.org/SDL_SetWindowBordered) 505 final void isBordered(bool bordered) { 506 SDL_SetWindowBordered(_window, bordered ? SDL_TRUE : SDL_FALSE); 507 } 508 /// See_also: $(LINK http://wiki.libsdl.org/SDL_GetWindowSize) 509 /// Returns: Window width in pixels. 510 final int width() { 511 int w, h; 512 SDL_GetWindowSize(_window, &w, &h); 513 return w; 514 } 515 /// See_also: $(LINK http://wiki.libsdl.org/SDL_GetWindowSize) 516 /// Returns: Window height in pixels. 517 final int height() { 518 int w, h; 519 SDL_GetWindowSize(_window, &w, &h); 520 return h; 521 } 522 /// See_also: $(LINK http://wiki.libsdl.org/SDL_SetWindowTitle) 523 final void title(string title) { 524 SDL_SetWindowTitle(_window, toStringz(title)); 525 } 526 /// See_also: $(LINK http://wiki.libsdl.org/SDL_ShowWindow) 527 final void show() { 528 SDL_ShowWindow(_window); 529 } 530 /// See_also: $(LINK http://wiki.libsdl.org/SDL_HideWindow) 531 final void hide() { 532 SDL_HideWindow(_window); 533 } 534 /// See_also: $(LINK http://wiki.libsdl.org/SDL_MinimizeWindow) 535 final void minimize() { 536 SDL_MinimizeWindow(_window); 537 } 538 /// See_also: $(LINK http://wiki.libsdl.org/SDL_MaximizeWindow) 539 final void maximize() { 540 SDL_MaximizeWindow(_window); 541 } 542 /// Returns: Window surface. 543 /// See_also: $(LINK http://wiki.libsdl.org/SDL_GetWindowSurface) 544 /// Throws: $(D PlatformException) on error. 545 final Surface surface() { 546 if (!_hasValidSurface()) { 547 SDL_Surface* internalSurface = SDL_GetWindowSurface(_window); 548 if (internalSurface is null) { 549 _platform.throwPlatformException("SDL_GetWindowSurface"); 550 } 551 _surfaceNeedRenew = false; 552 _surface = new Surface(_platform, internalSurface, Surface.Owned.No); 553 } 554 return _surface; 555 } 556 /// Submit changes to the window surface. 557 /// See_also: $(LINK http://wiki.libsdl.org/SDL_UpdateWindowSurface) 558 /// Throws: $(D PlatformException) on error. 559 final void updateSurface() { 560 if (!_hasValidSurface()) { 561 surface(); 562 } 563 int res = SDL_UpdateWindowSurface(_window); 564 if (res != 0) { 565 _platform.throwPlatformException("SDL_UpdateWindowSurface"); 566 } 567 } 568 /// Returns: Window ID. 569 /// See_also: $(LINK http://wiki.libsdl.org/SDL_GetWindowID) 570 final int id() { 571 return _id; 572 } 573 /// Returns: System-specific window information, useful to use a third-party rendering library. 574 /// See_also: $(LINK http://wiki.libsdl.org/SDL_GetWindowWMInfo) 575 /// Throws: $(D PlatformException) on error. 576 SDL_SysWMinfo windowInfo() { 577 SDL_SysWMinfo info; 578 SDL_VERSION(&info.version_); 579 int res = SDL_GetWindowWMInfo(_window, &info); 580 if (res != SDL_TRUE) { 581 _platform.throwPlatformException("SDL_GetWindowWMInfo"); 582 } 583 return info; 584 } 585 } 586 /// 587 final class SDL2GLContext { 588 package { 589 SDL_GLContext _context; 590 SDL2Window _window; 591 } 592 private { 593 bool _initialized; 594 } 595 /// Creates an OpenGL context for a given SDL window. 596 this(SDL2Window window) { 597 _window = window; 598 _context = SDL_GL_CreateContext(window._window); 599 _initialized = true; 600 } 601 /// 602 ~this() { 603 close(); 604 } 605 606 /// Release the associated SDL ressource. 607 void close() { 608 if (_initialized) { 609 _initialized = false; 610 } 611 } 612 /// Makes this OpenGL context current. 613 /// Throws: $(D PlatformException) on error. 614 void makeCurrent() { 615 if (0 != SDL_GL_MakeCurrent(_window._window, _context)) { 616 _window._platform.throwPlatformException("SDL_GL_MakeCurrent"); 617 } 618 } 619 } 620 /// 621 final class Surface { 622 package { 623 Platform _platform; 624 SDL_Surface* _surface; 625 Owned _handleOwned; 626 } 627 /// 628 enum Owned : byte { 629 /// 630 No = 0x00, 631 /// 632 Yes = 0x01 633 } 634 /// 635 this(Platform platform, SDL_Surface* surface, Owned owned) { 636 assert(surface !is null); 637 _platform = platform; 638 _surface = surface; 639 _handleOwned = owned; 640 } 641 /// 642 this(Platform platform, int width, int height, int depth, uint Rmask, uint Gmask, uint Bmask, uint Amask) { 643 _platform = platform; 644 _surface = SDL_CreateRGBSurface(0, width, height, depth, Rmask, Gmask, Bmask, Amask); 645 if (_surface is null) 646 _platform.throwPlatformException("SDL_CreateRGBSurface"); 647 _handleOwned = Owned.Yes; 648 } 649 /// 650 this(Platform platform, void* pixels, int width, int height, int depth, int pitch, uint Rmask, uint Gmask, uint Bmask, uint Amask) { 651 _platform = platform; 652 _surface = SDL_CreateRGBSurfaceFrom(pixels, width, height, depth, pitch, Rmask, Gmask, Bmask, Amask); 653 if (_surface is null) 654 _platform.throwPlatformException("SDL_CreateRGBSurfaceFrom"); 655 _handleOwned = Owned.Yes; 656 } 657 /// 658 ~this(){ 659 if (_surface !is null) { 660 debug import liberty.core.memory : ensureNotInGC; 661 debug ensureNotInGC("Surface"); 662 if (_handleOwned == Owned.Yes) 663 SDL_FreeSurface(_surface); 664 _surface = null; 665 } 666 } 667 /// 668 Surface convert(const(SDL_PixelFormat)* newFormat) { 669 SDL_Surface* surface = SDL_ConvertSurface(_surface, newFormat, 0); 670 if (surface is null) 671 _platform.throwPlatformException("SDL_ConvertSurface"); 672 assert(surface != _surface); // should not be the same handle 673 return new Surface(_platform, surface, Owned.Yes); 674 } 675 /// 676 Surface clone() { 677 return convert(pixelFormat()); 678 } 679 680 /// 681 @property int width() const { 682 return _surface.w; 683 } 684 685 /// 686 @property int height() const { 687 return _surface.h; 688 } 689 /// 690 ubyte* pixels() { 691 return cast(ubyte*) _surface.pixels; 692 } 693 /// 694 size_t pitch() { 695 return _surface.pitch; 696 } 697 /// 698 void lock() { 699 if (SDL_LockSurface(_surface) != 0) 700 _platform.throwPlatformException("SDL_LockSurface"); 701 } 702 /// 703 void unlock() { 704 SDL_UnlockSurface(_surface); 705 } 706 707 /// 708 SDL_Surface* handle() { 709 return _surface; 710 } 711 712 /// 713 SDL_PixelFormat* pixelFormat() 714 { 715 return _surface.format; 716 } 717 718 /// 719 struct RGBA 720 { 721 ubyte r, g, b, a; 722 } 723 /// 724 RGBA rgba(int x, int y) { 725 // crash if out of image, todo: exception 726 if (x < 0 || x >= width()) 727 assert(0); 728 if (y < 0 || y >= height()) 729 assert(0); 730 SDL_PixelFormat* fmt = _surface.format; 731 ubyte* pixels = cast(ubyte*)_surface.pixels; 732 int pitch = _surface.pitch; 733 uint* pixel = cast(uint*)(pixels + y * pitch + x * fmt.BytesPerPixel); 734 ubyte r, g, b, a; 735 SDL_GetRGBA(*pixel, fmt, &r, &g, &b, &a); 736 return RGBA(r, g, b, a); 737 } 738 /// 739 void setColorKey(bool enable, uint key) { 740 if (0 != SDL_SetColorKey(this._surface, enable ? SDL_TRUE : SDL_FALSE, key)) 741 _platform.throwPlatformException("SDL_SetColorKey"); 742 } 743 /// 744 void setColorKey(bool enable, ubyte r, ubyte g, ubyte b, ubyte a = 0) { 745 uint key = SDL_MapRGBA(cast(const)this._surface.format, r, g, b, a); 746 this.setColorKey(enable, key); 747 } 748 /// 749 void blit(Surface source, SDL_Rect srcRect, SDL_Rect dstRect) { 750 if (0 != SDL_BlitSurface(source._surface, &srcRect, _surface, &dstRect)) 751 _platform.throwPlatformException("SDL_BlitSurface"); 752 } 753 /// 754 void blitScaled(Surface source, SDL_Rect srcRect, SDL_Rect dstRect) { 755 if (0 != SDL_BlitScaled(source._surface, &srcRect, _surface, &dstRect)) 756 _platform.throwPlatformException("SDL_BlitScaled"); 757 } 758 } 759 /// 760 class Timer { 761 private SDL_TimerID _id; 762 /// Create a new timer. 763 this(Platform platform, uint intervalMs) { 764 _id = SDL_AddTimer(intervalMs, &timerCallbackSDL, cast(void*)this); 765 if (_id == 0) { 766 platform.throwPlatformException("SDL_AddTimer"); 767 } 768 } 769 /// Returns timer id. 770 int id() pure const nothrow { 771 return _id; 772 } 773 ~this() { 774 if (_id != 0) { 775 import liberty.core.memory : ensureNotInGC; 776 debug ensureNotInGC("SDL2Timer"); 777 SDL_RemoveTimer(_id); 778 _id = 0; 779 } 780 } 781 /// 782 protected abstract uint onTimer(uint interval) nothrow; 783 } 784 extern(C) private uint timerCallbackSDL(uint interval, void* param) nothrow { 785 try { 786 Timer timer = cast(Timer)param; 787 return timer.onTimer(interval); 788 } catch (Throwable e) { 789 import core.stdc.stdlib : exit; 790 exit(-1); 791 return 0; 792 } 793 }