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 if (audio) 85 audio.start(application); 86 87 video.errorCallback = AppCallback(&quit); 88 quitting = false; 89 90 // video (re-)initialization loop 91 while (!quitting) 92 { 93 reinitPending = false; 94 95 // start renderer 96 video.start(application); 97 98 // The main purpose of this call is to allow the application 99 // to react to window size changes. 100 application.handleInit(); 101 102 // pump events 103 while (!reinitPending && !quitting) 104 { 105 sdlEnforce(waitEvent()); 106 107 synchronized(application) 108 { 109 if (mainThreadQueue.length) 110 { 111 foreach (fn; mainThreadQueue) 112 if (fn) 113 fn(); 114 mainThreadQueue = null; 115 } 116 117 SDL_Event event = void; 118 while (SDL_PollEvent(&event)) 119 handleEvent(&event); 120 } 121 } 122 123 // wait for renderer to stop 124 video.stop(); 125 } 126 127 if (audio) 128 audio.stop(); 129 } 130 131 ~this() 132 { 133 SDL_Quit(); 134 } 135 136 private void delegate()[] mainThreadQueue; 137 138 private void runInMainThread(void delegate() fn) 139 { 140 synchronized(this) 141 mainThreadQueue ~= fn; 142 } 143 144 override void prod() 145 { 146 runInMainThread(null); 147 } 148 149 override void setCaption(string caption) 150 { 151 runInMainThread({ SDL_SetWindowTitle(sdlVideo.window, toStringz(caption)); }); 152 } 153 154 MouseButton translateMouseButton(ubyte sdlButton) 155 { 156 switch (sdlButton) 157 { 158 case SDL_BUTTON_LEFT: 159 return MouseButton.Left; 160 case SDL_BUTTON_MIDDLE: 161 default: 162 return MouseButton.Middle; 163 case SDL_BUTTON_RIGHT: 164 return MouseButton.Right; 165 } 166 } 167 168 enum SDL_BUTTON_LAST = SDL_BUTTON_X2; 169 170 MouseButtons translateMouseButtons(uint sdlButtons) 171 { 172 MouseButtons result; 173 for (ubyte i=SDL_BUTTON_LEFT; i<=SDL_BUTTON_LAST; i++) 174 if (sdlButtons & SDL_BUTTON(i)) 175 result |= 1<<translateMouseButton(i); 176 return result; 177 } 178 179 void handleEvent(SDL_Event* event) 180 { 181 switch (event.type) 182 { 183 case SDL_KEYDOWN: 184 /+if ( event.key.keysym.scancode == SDL_SCANCODE_RETURN && (keypressed[SDL_SCANCODE_RALT] || keypressed[SDL_SCANCODE_LALT])) 185 { 186 if (application.toggleFullScreen()) 187 { 188 video.stop(); 189 video.initialize(); 190 video.start(); 191 return false; 192 } 193 }+/ 194 return application.handleKeyDown(sdlKeys[event.key.keysym.scancode], /*event.key.keysym.unicode*/event.key.keysym.sym); // TODO: Use SDL_TextInputEvent 195 case SDL_KEYUP: 196 return application.handleKeyUp(sdlKeys[event.key.keysym.scancode]); 197 198 case SDL_MOUSEBUTTONDOWN: 199 return application.handleMouseDown(event.button.x, event.button.y, translateMouseButton(event.button.button)); 200 case SDL_MOUSEBUTTONUP: 201 return application.handleMouseUp(event.button.x, event.button.y, translateMouseButton(event.button.button)); 202 case SDL_MOUSEMOTION: 203 return application.handleMouseMove(event.motion.x, event.motion.y, translateMouseButtons(event.motion.state)); 204 205 case SDL_JOYAXISMOTION: 206 return application.handleJoyAxisMotion(event.jaxis.axis, cast(short)event.jaxis.value); 207 case SDL_JOYHATMOTION: 208 return application.handleJoyHatMotion (event.jhat.hat, cast(JoystickHatState)event.jhat.value); 209 case SDL_JOYBUTTONDOWN: 210 return application.handleJoyButtonDown(event.jbutton.button); 211 case SDL_JOYBUTTONUP: 212 return application.handleJoyButtonUp (event.jbutton.button); 213 214 case SDL_WINDOWEVENT: 215 switch (event.window.event) 216 { 217 case SDL_WINDOWEVENT_MOVED: 218 auto settings = application.getShellSettings(); 219 settings.windowPosX = event.window.data1; 220 settings.windowPosY = event.window.data2; 221 application.setShellSettings(settings); 222 break; 223 case SDL_WINDOWEVENT_SIZE_CHANGED: 224 auto settings = application.getShellSettings(); 225 settings.windowSizeX = event.window.data1; 226 settings.windowSizeY = event.window.data2; 227 application.setShellSettings(settings); 228 reinitPending = true; 229 break; 230 case SDL_WINDOWEVENT_CLOSE: 231 event.type = SDL_QUIT; 232 SDL_PushEvent(event); 233 break; 234 default: 235 break; 236 } 237 break; 238 case SDL_QUIT: 239 application.handleQuit(); 240 break; 241 default: 242 break; 243 } 244 } 245 246 bool reinitPending; 247 } 248 249 class SdlException : Exception 250 { 251 this(string message) { super(message); } 252 } 253 254 T sdlEnforce(T)(T result, string message = null) 255 { 256 if (!result) 257 throw new SdlException("SDL error: " ~ (message ? message ~ ": " : "") ~ to!string(SDL_GetError())); 258 return result; 259 } 260 261 Key[SDL_NUM_SCANCODES] sdlKeys; 262 263 shared static this() 264 { 265 sdlKeys[SDL_SCANCODE_UP ] = Key.up ; 266 sdlKeys[SDL_SCANCODE_DOWN ] = Key.down ; 267 sdlKeys[SDL_SCANCODE_LEFT ] = Key.left ; 268 sdlKeys[SDL_SCANCODE_RIGHT ] = Key.right; 269 sdlKeys[SDL_SCANCODE_SPACE ] = Key.space; 270 sdlKeys[SDL_SCANCODE_ESCAPE] = Key.esc ; 271 }