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