1 /** 2 * ae.ui.shell.sdl2.shell 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <vladimir@thecybershadow.net> 12 */ 13 14 module ae.ui.shell.sdl2.shell; 15 16 import std.conv; 17 import std.string; 18 19 import derelict.sdl2.sdl; 20 import derelict.util.loader; 21 22 import ae.ui.shell.shell; 23 //import ae.ui.video.video; 24 import ae.ui.video.sdl2common.video; 25 import ae.ui.app.application; 26 public import ae.ui.shell.events; 27 import ae.ui.timer.timer; 28 29 //!!version(Posix) pragma(lib, "dl"); // for Derelict 30 31 final class SDL2Shell : Shell 32 { 33 Application application; 34 SDL2CommonVideo sdlVideo; 35 36 this(Application application) 37 { 38 this.application = application; 39 40 //!!SharedLibLoader.disableAutoUnload(); // SDL MM timers may crash on exit 41 DerelictSDL2.load(); 42 auto components = SDL_INIT_VIDEO | SDL_INIT_TIMER; 43 if (application.needSound()) 44 components |= SDL_INIT_AUDIO; 45 if (application.needJoystick()) 46 components |= SDL_INIT_JOYSTICK; 47 sdlEnforce(SDL_Init(components)==0); 48 49 //!!SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); 50 //!!SDL_EnableUNICODE(1); 51 52 if (application.needJoystick() && SDL_NumJoysticks()) 53 { 54 SDL_JoystickEventState(SDL_ENABLE); 55 SDL_JoystickOpen(0); 56 } 57 } 58 59 /// A version of SDL_WaitEvent that sleeps less than 10ms at a time. 60 private int waitEvent() 61 { 62 while (true) 63 { 64 synchronized(this) 65 if (mainThreadQueue.length) 66 return 1; 67 68 SDL_PumpEvents(); 69 switch (SDL_PeepEvents(null, 1, SDL_GETEVENT, 0, uint.max)) 70 { 71 case -1: return 0; 72 case 0: SDL_Delay(1); break; 73 default: return 1; 74 } 75 } 76 } 77 78 override void run() 79 { 80 assert(video, "Video object not set"); 81 sdlVideo = cast(SDL2CommonVideo)video; 82 assert(sdlVideo, "Video is non-SDL"); 83 84 video.errorCallback = AppCallback(&quit); 85 quitting = false; 86 87 // video (re-)initialization loop 88 while (!quitting) 89 { 90 reinitPending = false; 91 92 // start renderer 93 video.start(application); 94 95 // The main purpose of this call is to allow the application 96 // to react to window size changes. 97 application.handleInit(); 98 99 // pump events 100 while (!reinitPending && !quitting) 101 { 102 sdlEnforce(waitEvent()); 103 104 synchronized(application) 105 { 106 if (mainThreadQueue.length) 107 { 108 foreach (fn; mainThreadQueue) 109 if (fn) 110 fn(); 111 mainThreadQueue = null; 112 } 113 114 SDL_Event event = void; 115 while (SDL_PollEvent(&event)) 116 handleEvent(&event); 117 } 118 } 119 120 // wait for renderer to stop 121 video.stop(); 122 } 123 } 124 125 ~this() 126 { 127 SDL_Quit(); 128 } 129 130 private void delegate()[] mainThreadQueue; 131 132 private void runInMainThread(void delegate() fn) 133 { 134 synchronized(this) 135 mainThreadQueue ~= fn; 136 } 137 138 override void prod() 139 { 140 runInMainThread(null); 141 } 142 143 override void setCaption(string caption) 144 { 145 runInMainThread({ SDL_SetWindowTitle(sdlVideo.window, toStringz(caption)); }); 146 } 147 148 MouseButton translateMouseButton(ubyte sdlButton) 149 { 150 switch (sdlButton) 151 { 152 case SDL_BUTTON_LEFT: 153 return MouseButton.Left; 154 case SDL_BUTTON_MIDDLE: 155 default: 156 return MouseButton.Middle; 157 case SDL_BUTTON_RIGHT: 158 return MouseButton.Right; 159 } 160 } 161 162 enum SDL_BUTTON_LAST = SDL_BUTTON_X2; 163 164 MouseButtons translateMouseButtons(uint sdlButtons) 165 { 166 MouseButtons result; 167 for (ubyte i=SDL_BUTTON_LEFT; i<=SDL_BUTTON_LAST; i++) 168 if (sdlButtons & SDL_BUTTON(i)) 169 result |= 1<<translateMouseButton(i); 170 return result; 171 } 172 173 void handleEvent(SDL_Event* event) 174 { 175 switch (event.type) 176 { 177 case SDL_KEYDOWN: 178 /+if ( event.key.keysym.scancode == SDL_SCANCODE_RETURN && (keypressed[SDL_SCANCODE_RALT] || keypressed[SDL_SCANCODE_LALT])) 179 { 180 if (application.toggleFullScreen()) 181 { 182 video.stop(); 183 video.initialize(); 184 video.start(); 185 return false; 186 } 187 }+/ 188 return application.handleKeyDown(sdlKeys[event.key.keysym.scancode], event.key.keysym.unicode); 189 case SDL_KEYUP: 190 return application.handleKeyUp(sdlKeys[event.key.keysym.scancode]); 191 192 case SDL_MOUSEBUTTONDOWN: 193 return application.handleMouseDown(event.button.x, event.button.y, translateMouseButton(event.button.button)); 194 case SDL_MOUSEBUTTONUP: 195 return application.handleMouseUp(event.button.x, event.button.y, translateMouseButton(event.button.button)); 196 case SDL_MOUSEMOTION: 197 return application.handleMouseMove(event.motion.x, event.motion.y, translateMouseButtons(event.motion.state)); 198 199 case SDL_JOYAXISMOTION: 200 return application.handleJoyAxisMotion(event.jaxis.axis, cast(short)event.jaxis.value); 201 case SDL_JOYHATMOTION: 202 return application.handleJoyHatMotion (event.jhat.hat, cast(JoystickHatState)event.jhat.value); 203 case SDL_JOYBUTTONDOWN: 204 return application.handleJoyButtonDown(event.jbutton.button); 205 case SDL_JOYBUTTONUP: 206 return application.handleJoyButtonUp (event.jbutton.button); 207 208 case SDL_WINDOWEVENT: 209 switch (event.window.event) 210 { 211 case SDL_WINDOWEVENT_MOVED: 212 auto settings = application.getShellSettings(); 213 settings.windowPosX = event.window.data1; 214 settings.windowPosY = event.window.data2; 215 application.setShellSettings(settings); 216 break; 217 case SDL_WINDOWEVENT_SIZE_CHANGED: 218 auto settings = application.getShellSettings(); 219 settings.windowSizeX = event.window.data1; 220 settings.windowSizeY = event.window.data2; 221 application.setShellSettings(settings); 222 reinitPending = true; 223 break; 224 case SDL_WINDOWEVENT_CLOSE: 225 event.type = SDL_QUIT; 226 SDL_PushEvent(event); 227 break; 228 default: 229 break; 230 } 231 break; 232 case SDL_QUIT: 233 application.handleQuit(); 234 break; 235 default: 236 break; 237 } 238 } 239 240 bool reinitPending; 241 } 242 243 class SdlException : Exception 244 { 245 this(string message) { super(message); } 246 } 247 248 T sdlEnforce(T)(T result, string message = null) 249 { 250 if (!result) 251 throw new SdlException("SDL error: " ~ (message ? message ~ ": " : "") ~ to!string(SDL_GetError())); 252 return result; 253 } 254 255 Key[SDL_NUM_SCANCODES] sdlKeys; 256 257 shared static this() 258 { 259 sdlKeys[SDL_SCANCODE_UP ] = Key.up ; 260 sdlKeys[SDL_SCANCODE_DOWN ] = Key.down ; 261 sdlKeys[SDL_SCANCODE_LEFT ] = Key.left ; 262 sdlKeys[SDL_SCANCODE_RIGHT ] = Key.right; 263 sdlKeys[SDL_SCANCODE_SPACE ] = Key.space; 264 sdlKeys[SDL_SCANCODE_ESCAPE] = Key.esc ; 265 }