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 }