1 /**
2 * Copyright © Novelate 2020
3 * License: MIT (https://github.com/Novelate/NovelateEngine/blob/master/LICENSE)
4 * Author: Jacob Jensen (bausshf)
5 * Website: https://novelate.com/
6 * ------
7 * Novelate is a free and open-source visual novel engine and framework written in the D programming language.
8 * It can be used freely for both personal and commercial projects.
9 * ------
10 * Module Description:
11 * This module is used for external bindings to SDL. Types and functions should only be exposed through internal types ex. SDL_Texture is only referenced through ex. ExternalImage.
12 * This module must always have public members exposed equally to the module: novelate.external.dsfmlbinding
13 */
14 module novelate.sdlbinding;
15 
16 import novelate.buildstate;
17 
18 static if (useSDL && useDerelict)
19 {
20   public import std.string : toStringz;
21   import std.conv : to;
22   import std.array : replace, split;
23   import std.traits : isSomeString;
24   import std.process : thisThreadID;
25 
26   import novelate.external.core;
27   import novelate.config;
28   import novelate.ui.textwrap;
29 
30   private
31   {
32     import derelict.sdl2.sdl;
33     import derelict.sdl2.image;
34     import derelict.sdl2.mixer;
35     import derelict.sdl2.ttf;
36     import derelict.sdl2.net;
37   }
38 
39   /// Initialization of the external binding.
40   void initExternalBinding()
41   {
42     DerelictSDL2.load();
43     DerelictSDL2Image.load();
44     DerelictSDL2Mixer.load();
45     DerelictSDL2ttf.load();
46     DerelictSDL2Net.load();
47 
48     TTF_Init();
49   }
50 
51   /// Paint -> SDL_Color
52   SDL_Color toNative(Paint paint)
53   {
54     return SDL_Color(paint.r, paint.g, paint.b, paint.a);
55   }
56 
57   /// SDL_Color -> Paint
58   Paint fromNative(SDL_Color color)
59   {
60     return Paint(color.r, color.g, color.b, color.a);
61   }
62 
63   /// KeyboardKey -> SDL_Keycode
64   SDL_Keycode toNative(KeyboardKey key)
65   {
66     switch (key)
67     {
68       case KeyboardKey.num0:
69         return SDL_Keycode.SDLK_0;
70       case KeyboardKey.num1:
71         return SDL_Keycode.SDLK_1;
72       case KeyboardKey.num2:
73         return SDL_Keycode.SDLK_2;
74       case KeyboardKey.num3:
75         return SDL_Keycode.SDLK_3;
76       case KeyboardKey.num4:
77         return SDL_Keycode.SDLK_4;
78       case KeyboardKey.num5:
79         return SDL_Keycode.SDLK_5;
80       case KeyboardKey.num6:
81         return SDL_Keycode.SDLK_6;
82       case KeyboardKey.num7:
83         return SDL_Keycode.SDLK_7;
84       case KeyboardKey.num8:
85         return SDL_Keycode.SDLK_8;
86       case KeyboardKey.num9:
87         return SDL_Keycode.SDLK_9;
88       case KeyboardKey.LControl:
89         return SDL_Keycode.SDLK_LCTRL;
90       case KeyboardKey.LShift:
91         return SDL_Keycode.SDLK_LSHIFT;
92       case KeyboardKey.LAlt:
93         return SDL_Keycode.SDLK_LALT;
94       case KeyboardKey.RControl:
95         return SDL_Keycode.SDLK_RCTRL;
96       case KeyboardKey.RShift:
97         return SDL_Keycode.SDLK_RSHIFT;
98       case KeyboardKey.RAlt:
99         return SDL_Keycode.SDLK_RALT;
100       case KeyboardKey.escape:
101         return SDL_Keycode.SDLK_ESCAPE;
102       case KeyboardKey.returnKey:
103         return SDL_Keycode.SDLK_RETURN;
104       case KeyboardKey.tab:
105         return SDL_Keycode.SDLK_TAB;
106 
107       default: return cast(SDL_Keycode)'\0';
108     }
109   }
110 
111   /// SDL_Keycode -> KeyboardKey
112   KeyboardKey fromNative(SDL_Keycode keyCode)
113   {
114     switch (keyCode)
115     {
116       case SDLK_0:
117         return KeyboardKey.num0;
118       case SDLK_1:
119         return KeyboardKey.num1;
120       case SDLK_2:
121         return KeyboardKey.num2;
122       case SDLK_3:
123         return KeyboardKey.num3;
124       case SDLK_4:
125         return KeyboardKey.num4;
126       case SDLK_5:
127         return KeyboardKey.num5;
128       case SDLK_6:
129         return KeyboardKey.num6;
130       case SDLK_7:
131         return KeyboardKey.num7;
132       case SDLK_8:
133         return KeyboardKey.num8;
134       case SDLK_9:
135         return KeyboardKey.num9;
136       case SDLK_LCTRL:
137         return KeyboardKey.LControl;
138       case SDLK_LSHIFT:
139         return KeyboardKey.LShift;
140       case SDLK_LALT:
141         return KeyboardKey.LAlt;
142       case SDLK_RCTRL:
143         return KeyboardKey.RControl;
144       case SDLK_RSHIFT:
145         return KeyboardKey.RShift;
146       case SDLK_RALT:
147         return KeyboardKey.RAlt;
148       case SDLK_ESCAPE:
149         return KeyboardKey.escape;
150       case SDLK_RETURN:
151         return KeyboardKey.returnKey;
152       case SDLK_TAB:
153         return KeyboardKey.tab;
154 
155       default: return KeyboardKey.unknown;
156     }
157   }
158 
159   /// button -> SDL_D_MouseButton
160   SDL_D_MouseButton toNative(MouseButton button)
161   {
162     switch (button)
163     {
164       case MouseButton.left:
165         return SDL_D_MouseButton.SDL_BUTTON_LEFT;
166 
167       case MouseButton.middle:
168         return SDL_D_MouseButton.SDL_BUTTON_MIDDLE;
169 
170       case MouseButton.right:
171         return SDL_D_MouseButton.SDL_BUTTON_RIGHT;
172 
173       case MouseButton.extraButton1:
174         return SDL_D_MouseButton.SDL_BUTTON_X1;
175 
176       case MouseButton.extraButton2:
177         return SDL_D_MouseButton.SDL_BUTTON_X2;
178 
179       default: return cast(SDL_D_MouseButton)0;
180     }
181   }
182 
183   /// SDL_D_MouseButton -> MouseButton
184   MouseButton fromNative(SDL_D_MouseButton button)
185   {
186     switch (button)
187     {
188       case SDL_D_MouseButton.SDL_BUTTON_LEFT:
189         return MouseButton.left;
190 
191       case SDL_D_MouseButton.SDL_BUTTON_MIDDLE:
192         return MouseButton.middle;
193 
194       case SDL_D_MouseButton.SDL_BUTTON_RIGHT:
195         return MouseButton.right;
196 
197       case SDL_D_MouseButton.SDL_BUTTON_X1:
198         return MouseButton.extraButton1;
199 
200       case SDL_D_MouseButton.SDL_BUTTON_X2:
201         return MouseButton.extraButton2;
202 
203       default: return cast(MouseButton)-1;
204     }
205   }
206 
207   /// ExternalEventType -> SDL_EventType
208   SDL_EventType toNative(ExternalEventType eventType)
209   {
210     switch (eventType)
211     {
212       case ExternalEventType.closed:
213         return SDL_EventType.SDL_QUIT;
214 
215       case ExternalEventType.keyPressed:
216         return SDL_EventType.SDL_KEYDOWN;
217 
218       case ExternalEventType.keyReleased:
219         return SDL_EventType.SDL_KEYUP;
220 
221       case ExternalEventType.mouseButtonPressed:
222         return SDL_EventType.SDL_MOUSEBUTTONDOWN;
223 
224       case ExternalEventType.mouseButtonReleased:
225         return SDL_EventType.SDL_MOUSEBUTTONUP;
226 
227       case ExternalEventType.mouseMoved:
228         return SDL_EventType.SDL_MOUSEMOTION;
229 
230       default: return cast(SDL_EventType)-1;
231     }
232   }
233 
234   /// SDL_EventType -> ExternalEventType
235   ExternalEventType fromNative(SDL_EventType eventType)
236   {
237     switch (eventType)
238     {
239       case SDL_EventType.SDL_QUIT:
240         return ExternalEventType.closed;
241 
242       case SDL_EventType.SDL_KEYDOWN:
243         return ExternalEventType.keyPressed;
244 
245       case SDL_EventType.SDL_KEYUP:
246         return ExternalEventType.keyReleased;
247 
248       case SDL_EventType.SDL_MOUSEBUTTONDOWN:
249         return ExternalEventType.mouseButtonPressed;
250 
251       case SDL_EventType.SDL_MOUSEBUTTONUP:
252         return ExternalEventType.mouseButtonReleased;
253 
254       case SDL_EventType.SDL_MOUSEMOTION:
255         return ExternalEventType.mouseMoved;
256 
257       default: return cast(ExternalEventType)-1;
258     }
259   }
260 
261   /// IExternalDrawableComponent
262   interface IExternalDrawableComponent
263   {
264     /// draw()
265     void draw(ExternalWindow window);
266   }
267 
268   /// The window.
269   private SDL_Window* window;
270   /// The renderer.
271   private SDL_Renderer* renderer;
272   /// The external window.
273   private ExternalWindow externalWindow;
274 
275   /// A SDL Exception.
276   private final class SDLException : Exception
277   {
278     public:
279     /**
280     * Creates a new SDL exception.
281     * Params:
282     *   message =   The message.
283     *   fn =        The file.
284     *   ln =        The line.
285     */
286     this(string message, string fn = __FILE__, size_t ln = __LINE__) @safe
287     {
288       super(message, fn, ln);
289     }
290   }
291 
292   /// reportError()
293   private void reportError(string errorMessageFun = "SDL_GetError", T = string)(T extraData = null)
294   if (isSomeString!T)
295   {
296     mixin("T message = to!T(" ~ errorMessageFun ~ "());");
297 
298     if (extraData && extraData.length)
299     {
300       message = "Data: " ~ extraData ~ "\r\n---\r\n" ~ message;
301     }
302 
303     throw new SDLException(to!string(message));
304   }
305 
306   // ExternalImage
307   final class ExternalImage : IExternalDrawableComponent
308   {
309     private:
310     /// _image
311     SDL_Texture* _image;
312     /// _width
313     int _width;
314     /// _height
315     int _height;
316     /// _rect
317     SDL_Rect _rect;
318     /// _color
319     Paint _color;
320     /// _path
321     string _path;
322     /// _scale
323     FloatVector _scale;
324 
325     /// loadTexture
326     SDL_Texture* loadTexture(string path)
327     {
328         SDL_Texture* texture = null;
329 
330         SDL_Surface* surface = IMG_Load(path.toStringz);
331 
332         if (surface)
333         {
334             texture = SDL_CreateTextureFromSurface(renderer, surface);
335 
336             if (texture)
337             {
338                 SDL_FreeSurface(surface);
339             }
340             else
341             {
342               reportError(path);
343             }
344         }
345         else
346         {
347           reportError(path);
348         }
349 
350         return texture;
351     }
352 
353     public:
354     /// this()
355     this()
356     {
357     }
358 
359     /// loadFromFile()
360     void loadFromFile(string path)
361     {
362       _path = path;
363 
364       _image = loadTexture(path);
365 
366       if (SDL_QueryTexture(_image, null, null, &_width, &_height) != 0)
367       {
368         reportError(_path);
369       }
370 
371       _rect.x = 0;
372       _rect.y = 0;
373       _rect.w = _width;
374       _rect.h = _height;
375     }
376 
377     @property
378     {
379       /// Gets bounds.
380       FloatVector bounds()
381       {
382         return FloatVector(_width, _height);
383       }
384 
385       /// Gets size.
386       UintVector size()
387       {
388         return UintVector(cast(uint)_width, cast(uint)_height);
389       }
390 
391       /// Gets position.
392       FloatVector position()
393       {
394         return FloatVector(_rect.x, _rect.y);
395       }
396 
397       /// Sets position.
398       void position(FloatVector newPosition)
399       {
400         _rect.x = cast(int)newPosition.x;
401         _rect.y = cast(int)newPosition.y;
402       }
403 
404       /// Gets color.
405       Paint color()
406       {
407         return _color;
408       }
409 
410       /// Sets color.
411       void color(Paint newColor)
412       {
413         _color = newColor;
414 
415         if (!_image)
416         {
417           return;
418         }
419 
420         if (SDL_SetTextureBlendMode(_image, SDL_BLENDMODE_BLEND) != 0)
421         {
422           reportError(_path);
423         }
424 
425         if (SDL_SetTextureAlphaMod(_image, _color.a) != 0)
426         {
427           reportError(_path);
428         }
429       }
430 
431       /// Gets scale.
432       FloatVector scale()
433       {
434         return _scale;
435       }
436 
437       /// Sets scale.
438       void scale(FloatVector newScale)
439       {
440         _scale = newScale;
441 
442         _rect.w = cast(int)(((_scale.x * 100) / 100) * cast(float)_width);
443         _rect.h = cast(int)(((_scale.y * 100) / 100) * cast(float)_height);
444       }
445     }
446 
447     /// draw()
448     void draw(ExternalWindow window)
449     {
450       if (_image)
451       {
452         if (SDL_RenderCopy(renderer, _image, null, &_rect) != 0)
453         {
454           reportError(_path);
455         }
456       }
457     }
458 
459     static if (isManualMemory)
460     {
461       /// clean()
462       void clean()
463       {
464         if (_image)
465         {
466           SDL_DestroyTexture(_image);
467 
468           _image = null;
469         }
470       }
471     }
472   }
473 
474   /// ExternalWindow.
475   final class ExternalWindow
476   {
477     private:
478     /// _fps.
479     uint _fps;
480     /// _lastTicks
481     uint _lastTicks;
482 
483     public:
484     /// this()
485     this()
486     {
487       _lastTicks = SDL_GetTicks();
488     }
489 
490     @property
491     {
492       /// Gets isOpen.
493       bool isOpen() { return window && renderer; }
494 
495       /// Gets fps.
496       uint fps() { return _fps; }
497 
498       /// Sets fps.
499       void fps(uint newFps)
500       {
501         _fps = newFps;
502       }
503 
504       /// Gets canUpdate.
505       bool canUpdate()
506       {
507         auto ticks = SDL_GetTicks();
508         size_t delta = ticks - _lastTicks;
509 
510         auto deltaFps = cast(size_t)(1000 / cast(double)_fps);
511 
512         if (delta > deltaFps)
513         {
514           _lastTicks = ticks;
515 
516           return true;
517         }
518 
519         return false;
520       }
521     }
522 
523     /// close()
524     void close()
525     {
526       clean();
527     }
528 
529     /// render()
530     void render()
531     {
532       if (renderer)
533       {
534         SDL_RenderPresent(renderer);
535       }
536     }
537 
538     /// clear()
539     void clear(Paint paint)
540     {
541       if (renderer)
542       {
543         if (SDL_RenderClear(renderer) != 0)
544         {
545           reportError();
546         }
547       }
548     }
549 
550     /// processEvents()
551     bool processEvents(ExternalEventManager eventManager)
552     {
553       SDL_Event e;
554       while (SDL_PollEvent(&e))
555       {
556         switch (e.type)
557         {
558           case SDL_EventType.SDL_QUIT:
559             eventManager.fireEvent(e.type.fromNative());
560             return false;
561 
562           case SDL_EventType.SDL_KEYDOWN:
563           case SDL_EventType.SDL_KEYUP:
564             auto keysym = e.key.keysym.sym.fromNative();
565             ExternalEventState._keyEvent.code = keysym;
566             break;
567 
568           case SDL_EventType.SDL_MOUSEBUTTONDOWN:
569           case SDL_EventType.SDL_MOUSEBUTTONUP:
570             ExternalEventState._mouseButtonEvent.button = fromNative(cast(SDL_D_MouseButton)e.button.button);
571             break;
572 
573           case SDL_EventType.SDL_MOUSEMOTION:
574             ExternalEventState._mouseMoveEvent.x = cast(int)e.motion.x;
575             ExternalEventState._mouseMoveEvent.y = cast(int)e.motion.y;
576             break;
577 
578           default: break;
579         }
580 
581         eventManager.fireEvent(e.type.fromNative());
582       }
583 
584       return true;
585     }
586 
587     static if (isManualMemory)
588     {
589       /// clean()
590       void clean()
591       {
592         if (renderer)
593         {
594           SDL_DestroyRenderer(renderer);
595 
596           renderer = null;
597         }
598 
599         if (window)
600         {
601           SDL_DestroyWindow(window);
602 
603           window = null;
604         }
605       }
606     }
607 
608     static:
609     /// create()
610     ExternalWindow create(string title, size_t width, size_t height, bool fullScreen)
611     {
612       if (externalWindow)
613       {
614         return externalWindow;
615       }
616 
617       if (fullScreen)
618       {
619         window = SDL_CreateWindow(
620           title.toStringz,
621           SDL_WINDOWPOS_UNDEFINED,
622           SDL_WINDOWPOS_UNDEFINED,
623           cast(int)width,
624           cast(int)height,
625           SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN
626         );
627       }
628       else
629       {
630         window = SDL_CreateWindow(
631           title.toStringz,
632           SDL_WINDOWPOS_UNDEFINED,
633           SDL_WINDOWPOS_UNDEFINED,
634           cast(int)width,
635           cast(int)height,
636           SDL_WINDOW_OPENGL
637         );
638       }
639 
640       renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
641 
642       SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
643 
644       auto window = new ExternalWindow;
645       return window;
646     }
647   }
648 
649   /// ExternalRectangleShape
650   final class ExternalRectangleShape : IExternalDrawableComponent
651   {
652     private:
653     /// _rect
654     SDL_Rect _rect;
655     /// _fillColor
656     Paint _fillColor;
657 
658     public:
659     final:
660     /// this()
661     this(FloatVector size)
662     {
663       _rect.w = cast(int)size.x;
664       _rect.h = cast(int)size.y;
665     }
666 
667     @property
668     {
669       /// Gets fillColor
670       Paint fillColor()
671       {
672         return _fillColor;
673       }
674 
675       /// Sets fillColor
676       void fillColor(Paint newColor)
677       {
678         _fillColor = newColor;
679       }
680 
681       /// Gets position
682       FloatVector position()
683       {
684         return FloatVector(_rect.x, _rect.y);
685       }
686 
687       /// Sets position
688       void position(FloatVector newPosition)
689       {
690         _rect.x = cast(int)newPosition.x;
691         _rect.y = cast(int)newPosition.y;
692       }
693     }
694 
695     /// draw()
696     void draw(ExternalWindow window)
697     {
698       if (SDL_SetRenderDrawColor(renderer, _fillColor.r, _fillColor.g, _fillColor.b, _fillColor.a) != 0)
699       {
700         reportError();
701       }
702 
703       if (SDL_RenderFillRect(renderer, &_rect) != 0)
704       {
705         reportError();
706       }
707     }
708 
709     static if (isManualMemory)
710     {
711       /// clean()
712       void clean()
713       {
714       }
715     }
716   }
717 
718   /// ExternalText
719   final class ExternalText : IExternalDrawableComponent
720   {
721     /// SDL_TextEntry
722     private class SDL_TextEntry
723     {
724       /// _texture
725       SDL_Texture* _texture;
726       /// _rect
727       SDL_Rect _rect;
728     }
729 
730     private:
731     /// _text
732     dstring _text;
733     /// _color
734     Paint _color;
735     /// _font
736     ExternalFont _font;
737     /// _fontSize
738     uint _fontSize;
739     /// _position
740     FloatVector _position;
741     /// _textWidth
742     int _textWidth;
743     /// _textHeight
744     int _textHeight;
745     /// _entries
746     SDL_TextEntry[] _entries;
747     /// hasGottenPosition
748     bool hasGottenPosition;
749 
750     /// updateText()
751     void updateText()
752     {
753       clean();
754 
755       if (!_font || !_text || !renderer || !_font._fontPath || !_fontSize)
756       {
757         return;
758       }
759 
760       dstring[] lines = _text.replace("\r", "").split("\n");
761 
762       _textHeight = 0;
763       _textWidth = 0;
764 
765       _entries = [];
766 
767       foreach (l; lines)
768       {
769         auto line = l;
770 
771         if (!line || !line.length)
772         {
773           line = " "; // Weird workaround to TTF not handling empty strings properly.
774         }
775 
776         auto textSurface = TTF_RenderUTF8_Blended(getFont(_font._fontPath, _fontSize), to!string(line).toStringz, _color.toNative());
777 
778         if (!textSurface)
779         {
780           reportError(_text);
781         }
782 
783         auto text = new SDL_TextEntry;
784         text._texture = SDL_CreateTextureFromSurface(renderer, textSurface);
785 
786         if (!text._texture)
787         {
788           reportError(_text);
789         }
790 
791         _textWidth = textSurface.w > _textWidth ? textSurface.w : _textWidth;
792 
793         _textHeight += textSurface.h;
794 
795         text._rect.x = cast(int)_position.x;
796         text._rect.y = cast(int)(_position.y + (_textHeight - textSurface.h));
797         text._rect.w = textSurface.w;
798         text._rect.h = textSurface.h;
799 
800         if (textSurface)
801         {
802           SDL_FreeSurface(textSurface);
803         }
804 
805         _entries ~= text;
806       }
807     }
808 
809     public:
810     final:
811     /// this()
812     this()
813     {
814     }
815 
816     @property
817     {
818       /// Gets bounds.
819       FloatVector bounds()
820       {
821         return FloatVector(_textWidth, _textHeight);
822       }
823 
824       /// Gets position.
825       FloatVector position()
826       {
827         return _position;
828       }
829 
830       /// Sets position.
831       void position(FloatVector newPosition)
832       {
833         _position = newPosition;
834 
835         if (_entries)
836         {
837           foreach (texture; _entries)
838           {
839             texture._rect.x = cast(int)_position.x;
840             texture._rect.y = cast(int)_position.y;
841           }
842         }
843       }
844     }
845 
846     /// setFont()
847     void setFont(ExternalFont font)
848     {
849       _font = font;
850 
851       updateText();
852     }
853 
854     /// setString()
855     void setString(dstring text)
856     {
857       _text = text;
858 
859       updateText();
860     }
861 
862     /// setCharacterSize()
863     void setCharacterSize(size_t characterSize)
864     {
865       _fontSize = cast(uint)characterSize;
866 
867       updateText();
868     }
869 
870     /// setColor()
871     void setColor(Paint newColor)
872     {
873       _color = newColor;
874 
875       updateText();
876     }
877 
878     /// draw()
879     void draw(ExternalWindow window)
880     {
881       if (_entries)
882       {
883         if (!hasGottenPosition)
884         {
885           position = _position;
886 
887           hasGottenPosition = true;
888         }
889 
890         foreach (texture; _entries)
891         {
892           if (SDL_RenderCopy(renderer, texture._texture, null, &texture._rect) != 0)
893           {
894             reportError(_text);
895           }
896         }
897       }
898     }
899 
900     static if (isManualMemory)
901     {
902       /// clean()
903       void clean()
904       {
905         if (_entries)
906         {
907           foreach (texture; _entries)
908           {
909             SDL_DestroyTexture(texture._texture);
910           }
911         }
912       }
913     }
914   }
915 
916   /// TTF_FontPtr
917   private alias TTF_FontPtr = TTF_Font*;
918 
919   /// _fonts
920   private TTF_FontPtr[string] _fonts;
921 
922   /// getFont()
923   private TTF_FontPtr getFont(string path, size_t size)
924   {
925     auto key = path ~ "_" ~ to!string(size);
926 
927     auto font = _fonts.get(key, cast(TTF_FontPtr)null);
928 
929     if (!font)
930     {
931       font = TTF_OpenFont(path.toStringz, cast(uint)size);
932 
933       if (!font)
934       {
935         return null;
936       }
937 
938       _fonts[key] = font;
939     }
940 
941     return font;
942   }
943 
944   /// ExternalFont
945   final class ExternalFont
946   {
947     private:
948     /// _fontPath
949     string _fontPath;
950 
951     public:
952     final:
953     /// this()
954     this()
955     {
956     }
957 
958     /// loadFromFile()
959     void loadFromFile(string path)
960     {
961       if (_fontPath)
962       {
963         return;
964       }
965 
966       _fontPath = path;
967     }
968 
969     static if (isManualMemory)
970     {
971       /// clean()
972       void clean()
973       {
974       }
975     }
976   }
977 
978   /// ExternalMusic
979   final class ExternalMusic
980   {
981     private:
982     /// _music
983     Mix_Music* _music;
984 
985     public:
986     final:
987     /// this()
988     this()
989     {
990     }
991 
992     @property
993     {
994       /// Gets looping
995       bool looping()
996       {
997         return true;
998       }
999 
1000       /// Sets looping
1001       void looping(bool isLooping)
1002       {
1003         // Do nothing ...
1004       }
1005     }
1006 
1007     /// openFromFile()
1008     bool openFromFile(string music)
1009     {
1010       clean();
1011 
1012       if (!music || !music.length)
1013       {
1014         return false;
1015       }
1016 
1017       int flags = MIX_INIT_MP3 | MIX_INIT_OGG | MIX_INIT_FLAC | MIX_INIT_MOD;
1018 
1019       int initted = Mix_Init(flags);
1020 
1021       if ((initted & flags) != flags)
1022       {
1023         throw new Exception("Mix_GetError()");
1024       }
1025 
1026       if (Mix_OpenAudio(22050, AUDIO_S16SYS, 2, 640) != 0)
1027       {
1028         throw new Exception("Mix_GetError()");
1029       }
1030 
1031       _music = Mix_LoadMUS(music.toStringz);
1032 
1033       if (!_music)
1034       {
1035         throw new Exception("Mix_GetError()");
1036       }
1037 
1038       return true;
1039     }
1040 
1041     /// play()
1042     void play()
1043     {
1044       if (Mix_PlayMusic(_music, 1) != 0)
1045       {
1046         throw new Exception("Mix_GetError()");
1047       }
1048     }
1049 
1050     /// pause()
1051     void pause()
1052     {
1053       Mix_PauseMusic();
1054     }
1055 
1056     /// stop()
1057     void stop()
1058     {
1059       Mix_HaltMusic();
1060     }
1061 
1062     static if (isManualMemory)
1063     {
1064       /// clean
1065       void clean()
1066       {
1067         if (_music)
1068         {
1069           Mix_FreeMusic(_music);
1070 
1071           _music = null;
1072         }
1073       }
1074     }
1075   }
1076 
1077   /// quit()
1078   void quit()
1079   {
1080     if (externalWindow)
1081     {
1082       externalWindow.clean();
1083     }
1084 
1085     SDL_Quit();
1086   }
1087 }