1 module nanogui.sdlapp; 2 3 import std.exception: enforce; 4 5 import std.experimental.logger: Logger, FileLogger, globalLogLevel, LogLevel; 6 7 import gfm.opengl: OpenGL; 8 import gfm.sdl2: SDL2, SDL2Window, SDL_Event, SDL_Cursor, SDL_SetCursor, 9 SDL_FreeCursor, SDL_Delay; 10 11 class SdlApp 12 { 13 alias Event = SDL_Event; 14 15 this(int w, int h, string title) 16 { 17 /* Avoid locale-related number parsing issues */ 18 version(Windows) {} 19 else { 20 import core.stdc.locale; 21 setlocale(LC_NUMERIC, "C"); 22 } 23 24 import gfm.opengl : GLSupport, loadOpenGL; 25 import bindbc.sdl : SDLSupport, sdlSupport, loadSDL, SDL_INIT_VIDEO, SDL_INIT_EVENTS, 26 SDL_GL_SetAttribute, SDL_WINDOWPOS_UNDEFINED, SDL_GL_CONTEXT_MAJOR_VERSION, 27 SDL_GL_CONTEXT_MINOR_VERSION,SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE, 28 SDL_GL_STENCIL_SIZE, SDL_WINDOW_OPENGL, SDL_WINDOW_RESIZABLE, SDL_WINDOW_HIDDEN; 29 30 this.width = w; 31 this.height = h; 32 _dirty = true; 33 34 // create a logger 35 import std.stdio : stdout; 36 _log = new FileLogger(stdout); 37 38 // load dynamic libraries 39 SDLSupport ret = loadSDL(); 40 if(ret != sdlSupport) { 41 if(ret == SDLSupport.noLibrary) { 42 /* 43 The system failed to load the library. Usually this means that either the library or one of its dependencies could not be found. 44 */ 45 } 46 else if(SDLSupport.badLibrary) { 47 /* 48 This indicates that the system was able to find and successfully load the library, but one or more symbols the binding expected to find was missing. This usually indicates that the loaded library is of a lower API version than the binding was configured to load, e.g., an SDL 2.0.2 library loaded by an SDL 2.0.10 configuration. 49 50 For many C libraries, including SDL, this is perfectly fine and the application can continue as long as none of the missing functions are called. 51 */ 52 } 53 } 54 _sdl2 = new SDL2(_log); 55 globalLogLevel = LogLevel.error; 56 57 // You have to initialize each SDL subsystem you want by hand 58 _sdl2.subSystemInit(SDL_INIT_VIDEO); 59 _sdl2.subSystemInit(SDL_INIT_EVENTS); 60 61 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); 62 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); 63 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); 64 SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); 65 66 // create an OpenGL-enabled SDL window 67 window = new SDL2Window(_sdl2, 68 SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 69 width, height, 70 SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN ); 71 72 window.setTitle(title); 73 74 GLSupport retVal = loadOpenGL(); 75 if(retVal >= GLSupport.gl33) 76 { 77 // configure renderer for OpenGL 3.3 78 import std.stdio; 79 writefln("Available version of opengl: %s", retVal); 80 } 81 else 82 { 83 import std.stdio; 84 if (retVal == GLSupport.noLibrary) 85 writeln("opengl is not available"); 86 else 87 writefln("Unsupported version of opengl %s", retVal); 88 import std.exception; 89 enforce(0); 90 } 91 92 _gl = new OpenGL(_log); 93 94 // redirect OpenGL output to our Logger 95 _gl.redirectDebugOutput(); 96 } 97 98 ~this() 99 { 100 _gl.destroy(); 101 window.destroy(); 102 _sdl2.destroy(); 103 } 104 105 Logger logger() { return _log; } 106 107 private void delegate () _onBeforeLoopStart; 108 void onBeforeLoopStart(void delegate () dg) 109 { 110 _onBeforeLoopStart = dg; 111 } 112 auto onBeforeLoopStart() { return _onBeforeLoopStart; } 113 114 alias OnDraw = void delegate(); 115 private OnDraw _onDraw; 116 void onDraw(OnDraw handler) 117 { 118 _onDraw = handler; 119 } 120 auto onDraw() { return _onDraw; } 121 122 alias OnResize = void delegate(int w, int h); 123 private OnResize _onResize; 124 void onResize(OnResize handler) 125 { 126 _onResize = handler; 127 } 128 auto onResize() { return _onResize; } 129 130 alias OnKeyboardChar = bool delegate(dchar ch); 131 private OnKeyboardChar _onKeyboardChar; 132 void onKeyboardChar(OnKeyboardChar handler) 133 { 134 _onKeyboardChar = handler; 135 } 136 auto onKeyboardChar() { return _onKeyboardChar; } 137 138 alias OnSdlEvent = bool delegate(ref const(SDL_Event) event); 139 private OnSdlEvent _onKeyDown; 140 void onKeyDown(OnSdlEvent handler) 141 { 142 _onKeyDown = handler; 143 } 144 auto onKeyDown() { return _onKeyDown; } 145 146 private OnSdlEvent _onKeyUp; 147 void onKeyUp(OnSdlEvent handler) 148 { 149 _onKeyUp = handler; 150 } 151 auto onKeyUp() { return _onKeyUp; } 152 153 private OnSdlEvent _onMouseWheel; 154 void onMouseWheel(OnSdlEvent handler) 155 { 156 _onMouseWheel = handler; 157 } 158 auto onMouseWheel() { return _onMouseWheel; } 159 160 private OnSdlEvent _onMouseMotion; 161 void onMouseMotion(OnSdlEvent handler) 162 { 163 _onMouseMotion = handler; 164 } 165 auto onMouseMotion() { return _onMouseMotion; } 166 167 private OnSdlEvent _onMouseUp; 168 void onMouseUp(OnSdlEvent handler) 169 { 170 _onMouseUp = handler; 171 } 172 auto onMouseUp() { return _onMouseUp; } 173 174 private OnSdlEvent _onMouseDown; 175 void onMouseDown(OnSdlEvent handler) 176 { 177 _onMouseDown = handler; 178 } 179 auto onMouseDown() { return _onMouseDown; } 180 181 alias OnClose = bool delegate(); 182 private OnClose _onClose; 183 void onClose(OnClose handler) 184 { 185 _onClose = handler; 186 } 187 auto onClose() { return _onClose; } 188 189 private bool _running = true; 190 void close() { _running = false; } 191 192 void addHandler(alias currentHandler)(OnSdlEvent newHandler) 193 { 194 auto oldHandler = currentHandler; 195 if (oldHandler) 196 { 197 currentHandler = (ref const(SDL_Event) event) 198 { 199 if (oldHandler(event)) 200 return true; 201 return newHandler(event); 202 }; 203 } 204 else 205 { 206 currentHandler = newHandler; 207 } 208 } 209 210 void invalidate() 211 { 212 _dirty = true; 213 } 214 215 void run() 216 { 217 import gfm.sdl2; 218 219 window.hide; 220 SDL_FlushEvents(SDL_WINDOWEVENT, SDL_SYSWMEVENT); 221 222 window.show; 223 224 SDL_Event event; 225 226 while (_running) 227 { 228 if (_onBeforeLoopStart) 229 _onBeforeLoopStart(); 230 231 SDL_PumpEvents(); 232 233 while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_SYSWMEVENT)) 234 { 235 switch (event.type) 236 { 237 case SDL_WINDOWEVENT: 238 { 239 switch (event.window.event) 240 { 241 case SDL_WINDOWEVENT_MOVED: 242 // window has been moved to other position 243 break; 244 245 case SDL_WINDOWEVENT_RESIZED: 246 case SDL_WINDOWEVENT_SIZE_CHANGED: 247 { 248 // window size has been resized 249 with(event.window) 250 { 251 width = data1; 252 height = data2; 253 if (_onResize) 254 _onResize(width, height); 255 } 256 break; 257 } 258 259 case SDL_WINDOWEVENT_SHOWN: 260 case SDL_WINDOWEVENT_FOCUS_GAINED: 261 case SDL_WINDOWEVENT_RESTORED: 262 case SDL_WINDOWEVENT_MAXIMIZED: 263 // window has been activated 264 break; 265 266 case SDL_WINDOWEVENT_HIDDEN: 267 case SDL_WINDOWEVENT_FOCUS_LOST: 268 case SDL_WINDOWEVENT_MINIMIZED: 269 // window has been deactivated 270 break; 271 272 case SDL_WINDOWEVENT_ENTER: 273 // mouse cursor has entered window 274 // for example default cursor can be disable 275 // using SDL_ShowCursor(SDL_FALSE); 276 break; 277 278 case SDL_WINDOWEVENT_LEAVE: 279 // mouse cursor has left window 280 // for example default cursor can be disable 281 // using SDL_ShowCursor(SDL_TRUE); 282 break; 283 284 case SDL_WINDOWEVENT_CLOSE: 285 _running = (_onClose) ? !_onClose() : false; 286 // if we continue running then update the screen 287 // to improve responce time 288 if (_running) 289 invalidate; 290 break; 291 default: 292 } 293 break; 294 } 295 default: 296 } 297 } 298 299 // mouse update 300 while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEWHEEL)) 301 { 302 switch (event.type) 303 { 304 case SDL_MOUSEBUTTONDOWN: 305 if (_onMouseDown) 306 _onMouseDown(event); 307 break; 308 case SDL_MOUSEBUTTONUP: 309 if (_onMouseUp) 310 _onMouseUp(event); 311 break; 312 case SDL_MOUSEMOTION: 313 if (_onMouseMotion) 314 _onMouseMotion(event); 315 break; 316 case SDL_MOUSEWHEEL: 317 if (_onMouseWheel) 318 _onMouseWheel(event); 319 break; 320 default: 321 } 322 } 323 324 // keyboard update 325 while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_KEYDOWN, SDL_KEYUP)) 326 { 327 switch (event.type) 328 { 329 case SDL_KEYDOWN: 330 if (_onKeyDown) 331 _onKeyDown(event); 332 break; 333 case SDL_KEYUP: 334 if (_onKeyUp) 335 _onKeyUp(event); 336 break; 337 default: 338 } 339 } 340 341 // text update 342 while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_TEXTINPUT, SDL_TEXTINPUT)) 343 { 344 switch (event.type) 345 { 346 case SDL_TEXTINPUT: 347 if (_onKeyboardChar is null) 348 break; 349 import core.stdc.string : strlen; 350 auto len = strlen(&event.text.text[0]); 351 if (!len) 352 break; 353 assert(len < event.text.text.sizeof); 354 auto txt = event.text.text[0..len]; 355 import std.utf : byDchar; 356 foreach(ch; txt.byDchar) 357 _onKeyboardChar(ch); 358 break; 359 default: 360 break; 361 } 362 } 363 364 // user event, we use it as timer notification 365 while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_USEREVENT, SDL_USEREVENT)) 366 { 367 switch (event.type) 368 { 369 case SDL_USEREVENT: 370 invalidate(); 371 break; 372 default: 373 break; 374 } 375 } 376 377 // perform drawing if needed 378 if (_dirty) 379 { 380 pauseTimeMs = 0; 381 382 if (_onDraw) 383 _onDraw(); 384 385 window.swapBuffers(); 386 _dirty = false; 387 } 388 else 389 { 390 pauseTimeMs = pauseTimeMs * 2 + 1; // exponential pause 391 if (pauseTimeMs > 100) 392 pauseTimeMs = 100; // max 100ms of pause 393 SDL_Delay(pauseTimeMs); 394 } 395 } 396 } 397 398 protected: 399 SDL2Window window; 400 int width; 401 int height; 402 bool _dirty; 403 int pauseTimeMs; 404 405 Logger _log; 406 OpenGL _gl; 407 SDL2 _sdl2; 408 } 409 410 private auto convertSdlKeyToNanoguiKey(int sdlkey) 411 { 412 import gfm.sdl2; 413 import nanogui.common : KeyAction, Key; 414 415 int nanogui_key; 416 switch(sdlkey) 417 { 418 case SDLK_LEFT: 419 nanogui_key = Key.Left; 420 break; 421 case SDLK_RIGHT: 422 nanogui_key = Key.Right; 423 break; 424 case SDLK_UP: 425 nanogui_key = Key.Up; 426 break; 427 case SDLK_DOWN: 428 nanogui_key = Key.Down; 429 break; 430 case SDLK_BACKSPACE: 431 nanogui_key = Key.Backspace; 432 break; 433 case SDLK_DELETE: 434 nanogui_key = Key.Delete; 435 break; 436 case SDLK_HOME: 437 nanogui_key = Key.Home; 438 break; 439 case SDLK_END: 440 nanogui_key = Key.End; 441 break; 442 case SDLK_RETURN: 443 nanogui_key = Key.Enter; 444 break; 445 case SDLK_a: 446 nanogui_key = Key.A; 447 break; 448 case SDLK_x: 449 nanogui_key = Key.X; 450 break; 451 case SDLK_c: 452 nanogui_key = Key.C; 453 break; 454 case SDLK_v: 455 nanogui_key = Key.V; 456 break; 457 case SDLK_ESCAPE: 458 nanogui_key = Key.Esc; 459 break; 460 default: 461 nanogui_key = sdlkey; 462 } 463 464 return nanogui_key; 465 } 466 467 private auto convertSdlModifierToNanoguiModifier(int mod) 468 { 469 import gfm.sdl2; 470 import nanogui.common : KeyMod; 471 472 int nanogui_mod; 473 474 if (mod & KMOD_LCTRL) 475 nanogui_mod |= KeyMod.Ctrl; 476 if (mod & KMOD_LSHIFT) 477 nanogui_mod |= KeyMod.Shift; 478 if (mod & KMOD_LALT) 479 nanogui_mod |= KeyMod.Alt; 480 if (mod & KMOD_RCTRL) 481 nanogui_mod |= KeyMod.Ctrl; 482 if (mod & KMOD_RSHIFT) 483 nanogui_mod |= KeyMod.Shift; 484 if (mod & KMOD_RALT) 485 nanogui_mod |= KeyMod.Alt; 486 487 return nanogui_mod; 488 }