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({ 152 static string oldCaption; 153 if (caption != oldCaption) 154 { 155 oldCaption = caption; 156 SDL_SetWindowTitle(sdlVideo.window, toStringz(caption)); 157 } 158 }); 159 } 160 161 MouseButton translateMouseButton(ubyte sdlButton) 162 { 163 switch (sdlButton) 164 { 165 case SDL_BUTTON_LEFT: 166 return MouseButton.Left; 167 case SDL_BUTTON_MIDDLE: 168 default: 169 return MouseButton.Middle; 170 case SDL_BUTTON_RIGHT: 171 return MouseButton.Right; 172 } 173 } 174 175 enum SDL_BUTTON_LAST = SDL_BUTTON_X2; 176 177 MouseButtons translateMouseButtons(uint sdlButtons) 178 { 179 MouseButtons result; 180 for (ubyte i=SDL_BUTTON_LEFT; i<=SDL_BUTTON_LAST; i++) 181 if (sdlButtons & SDL_BUTTON(i)) 182 result |= 1<<translateMouseButton(i); 183 return result; 184 } 185 186 void handleEvent(SDL_Event* event) 187 { 188 switch (event.type) 189 { 190 case SDL_KEYDOWN: 191 /+if ( event.key.keysym.scancode == SDL_SCANCODE_RETURN && (keypressed[SDL_SCANCODE_RALT] || keypressed[SDL_SCANCODE_LALT])) 192 { 193 if (application.toggleFullScreen()) 194 { 195 video.stop(); 196 video.initialize(); 197 video.start(); 198 return false; 199 } 200 }+/ 201 return application.handleKeyDown(sdlKeys[event.key.keysym.scancode], /*event.key.keysym.unicode*/event.key.keysym.sym); // TODO: Use SDL_TextInputEvent 202 case SDL_KEYUP: 203 return application.handleKeyUp(sdlKeys[event.key.keysym.scancode]); 204 205 case SDL_MOUSEBUTTONDOWN: 206 return application.handleMouseDown(event.button.x, event.button.y, translateMouseButton(event.button.button)); 207 case SDL_MOUSEBUTTONUP: 208 return application.handleMouseUp(event.button.x, event.button.y, translateMouseButton(event.button.button)); 209 case SDL_MOUSEMOTION: 210 return application.handleMouseMove(event.motion.x, event.motion.y, translateMouseButtons(event.motion.state)); 211 212 case SDL_JOYAXISMOTION: 213 return application.handleJoyAxisMotion(event.jaxis.axis, cast(short)event.jaxis.value); 214 case SDL_JOYHATMOTION: 215 return application.handleJoyHatMotion (event.jhat.hat, cast(JoystickHatState)event.jhat.value); 216 case SDL_JOYBUTTONDOWN: 217 return application.handleJoyButtonDown(event.jbutton.button); 218 case SDL_JOYBUTTONUP: 219 return application.handleJoyButtonUp (event.jbutton.button); 220 221 case SDL_WINDOWEVENT: 222 switch (event.window.event) 223 { 224 case SDL_WINDOWEVENT_MOVED: 225 auto settings = application.getShellSettings(); 226 settings.windowPosX = event.window.data1; 227 settings.windowPosY = event.window.data2; 228 application.setShellSettings(settings); 229 break; 230 case SDL_WINDOWEVENT_SIZE_CHANGED: 231 auto settings = application.getShellSettings(); 232 settings.windowSizeX = event.window.data1; 233 settings.windowSizeY = event.window.data2; 234 application.setShellSettings(settings); 235 reinitPending = true; 236 break; 237 case SDL_WINDOWEVENT_CLOSE: 238 event.type = SDL_QUIT; 239 SDL_PushEvent(event); 240 break; 241 default: 242 break; 243 } 244 break; 245 case SDL_QUIT: 246 application.handleQuit(); 247 break; 248 default: 249 break; 250 } 251 } 252 253 bool reinitPending; 254 } 255 256 class SdlException : Exception 257 { 258 this(string message) { super(message); } 259 } 260 261 T sdlEnforce(T)(T result, string message = null) 262 { 263 if (!result) 264 throw new SdlException("SDL error: " ~ (message ? message ~ ": " : "") ~ to!string(SDL_GetError())); 265 return result; 266 } 267 268 Key[SDL_NUM_SCANCODES] sdlKeys; 269 270 shared static this() 271 { 272 sdlKeys[SDL_SCANCODE_UP ] = Key.up ; 273 sdlKeys[SDL_SCANCODE_DOWN ] = Key.down ; 274 sdlKeys[SDL_SCANCODE_LEFT ] = Key.left ; 275 sdlKeys[SDL_SCANCODE_RIGHT ] = Key.right ; 276 sdlKeys[SDL_SCANCODE_PAGEUP ] = Key.pageUp ; 277 sdlKeys[SDL_SCANCODE_PAGEDOWN] = Key.pageDown; 278 sdlKeys[SDL_SCANCODE_HOME ] = Key.home ; 279 sdlKeys[SDL_SCANCODE_END ] = Key.end ; 280 sdlKeys[SDL_SCANCODE_SPACE ] = Key.space ; 281 sdlKeys[SDL_SCANCODE_ESCAPE ] = Key.esc ; 282 }