1 module nanogui.sdlbackend; 2 3 import std.algorithm: map; 4 import std.array: array; 5 import std.exception: enforce; 6 import std.file: thisExePath; 7 import std.path: dirName, buildPath; 8 import std.range: iota; 9 import std.datetime : Clock; 10 11 import std.experimental.logger: Logger, NullLogger, FileLogger, globalLogLevel, LogLevel; 12 13 import gfm.math: mat4f, vec3f, vec4f; 14 import gfm.opengl: OpenGL; 15 import gfm.sdl2: SDL2, SDL2Window, SDL_Event, SDL_Cursor, SDL_SetCursor, 16 SDL_FreeCursor, SDL_Delay; 17 18 import arsd.nanovega : nvgCreateContext, kill, NVGContextFlag; 19 import nanogui.screen : Screen; 20 import nanogui.theme : Theme; 21 import nanogui.common : NanoContext, Vector2i, MouseButton, MouseAction, Cursor; 22 23 class SdlBackend : Screen 24 { 25 this(int w, int h, string title) 26 { 27 /* Avoid locale-related number parsing issues */ 28 version(Windows) {} 29 else { 30 import core.stdc.locale; 31 setlocale(LC_NUMERIC, "C"); 32 } 33 34 import gfm.sdl2, gfm.opengl; 35 import bindbc.sdl; 36 37 this.width = w; 38 this.height = h; 39 40 // create a logger 41 import std.stdio : stdout; 42 _log = new FileLogger(stdout); 43 44 // load dynamic libraries 45 SDLSupport ret = loadSDL(); 46 if(ret != sdlSupport) { 47 if(ret == SDLSupport.noLibrary) { 48 /* 49 The system failed to load the library. Usually this means that either the library or one of its dependencies could not be found. 50 */ 51 } 52 else if(SDLSupport.badLibrary) { 53 /* 54 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. 55 56 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. 57 */ 58 } 59 } 60 _sdl2 = new SDL2(_log); 61 globalLogLevel = LogLevel.error; 62 63 // You have to initialize each SDL subsystem you want by hand 64 _sdl2.subSystemInit(SDL_INIT_VIDEO); 65 _sdl2.subSystemInit(SDL_INIT_EVENTS); 66 67 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); 68 SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); 69 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); 70 SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); 71 72 // create an OpenGL-enabled SDL window 73 window = new SDL2Window(_sdl2, 74 SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 75 width, height, 76 SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN ); 77 78 window.setTitle(title); 79 80 GLSupport retVal = loadOpenGL(); 81 if(retVal >= GLSupport.gl33) 82 { 83 // configure renderer for OpenGL 3.3 84 import std.stdio; 85 writefln("Available version of opengl: %s", retVal); 86 } 87 else 88 { 89 import std.stdio; 90 if (retVal == GLSupport.noLibrary) 91 writeln("opengl is not available"); 92 else 93 writefln("Unsupported version of opengl %s", retVal); 94 import std.exception; 95 enforce(0); 96 } 97 98 _gl = new OpenGL(_log); 99 100 // redirect OpenGL output to our Logger 101 _gl.redirectDebugOutput(); 102 103 ctx = NanoContext(NVGContextFlag.Debug); 104 enforce(ctx !is null, "cannot initialize NanoGui"); 105 106 mCursorSet[Cursor.Arrow] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); 107 mCursorSet[Cursor.IBeam] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); 108 mCursorSet[Cursor.Crosshair] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR); 109 mCursorSet[Cursor.Hand] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); 110 mCursorSet[Cursor.HResize] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE); 111 mCursorSet[Cursor.VResize] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS); 112 113 super(width, height, Clock.currTime.stdTime); 114 theme = new Theme(ctx); 115 } 116 117 ~this() 118 { 119 SDL_FreeCursor(mCursorSet[Cursor.Arrow]); 120 SDL_FreeCursor(mCursorSet[Cursor.IBeam]); 121 SDL_FreeCursor(mCursorSet[Cursor.Crosshair]); 122 SDL_FreeCursor(mCursorSet[Cursor.Hand]); 123 SDL_FreeCursor(mCursorSet[Cursor.HResize]); 124 SDL_FreeCursor(mCursorSet[Cursor.VResize]); 125 126 ctx.kill(); 127 _gl.destroy(); 128 window.destroy(); 129 _sdl2.destroy(); 130 } 131 132 private void delegate () _onBeforeLoopStart; 133 void onBeforeLoopStart(void delegate () dg) 134 { 135 _onBeforeLoopStart = dg; 136 } 137 138 void run() 139 { 140 import gfm.sdl2; 141 142 window.hide; 143 SDL_FlushEvents(SDL_WINDOWEVENT, SDL_SYSWMEVENT); 144 145 onVisibleForTheFirstTime(); 146 window.show; 147 148 SDL_Event event; 149 150 bool running = true; 151 while (running) 152 { 153 if (_onBeforeLoopStart) 154 _onBeforeLoopStart(); 155 156 SDL_PumpEvents(); 157 158 while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_SYSWMEVENT)) 159 { 160 switch (event.type) 161 { 162 case SDL_WINDOWEVENT: 163 { 164 switch (event.window.event) 165 { 166 case SDL_WINDOWEVENT_MOVED: 167 // window has been moved to other position 168 break; 169 170 case SDL_WINDOWEVENT_RESIZED: 171 case SDL_WINDOWEVENT_SIZE_CHANGED: 172 { 173 // window size has been resized 174 with(event.window) 175 { 176 width = data1; 177 height = data2; 178 resizeEvent(size); 179 } 180 break; 181 } 182 183 case SDL_WINDOWEVENT_SHOWN: 184 case SDL_WINDOWEVENT_FOCUS_GAINED: 185 case SDL_WINDOWEVENT_RESTORED: 186 case SDL_WINDOWEVENT_MAXIMIZED: 187 // window has been activated 188 break; 189 190 case SDL_WINDOWEVENT_HIDDEN: 191 case SDL_WINDOWEVENT_FOCUS_LOST: 192 case SDL_WINDOWEVENT_MINIMIZED: 193 // window has been deactivated 194 break; 195 196 case SDL_WINDOWEVENT_ENTER: 197 // mouse cursor has entered window 198 // for example default cursor can be disable 199 // using SDL_ShowCursor(SDL_FALSE); 200 break; 201 202 case SDL_WINDOWEVENT_LEAVE: 203 // mouse cursor has left window 204 // for example default cursor can be disable 205 // using SDL_ShowCursor(SDL_TRUE); 206 break; 207 208 case SDL_WINDOWEVENT_CLOSE: 209 running = false; 210 break; 211 default: 212 } 213 break; 214 } 215 default: 216 } 217 } 218 219 // mouse update 220 { 221 while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEWHEEL)) 222 { 223 switch (event.type) 224 { 225 case SDL_MOUSEBUTTONDOWN: 226 onMouseDown(event); 227 // force redrawing 228 mNeedToDraw = true; 229 break; 230 case SDL_MOUSEBUTTONUP: 231 onMouseUp(event); 232 // force redrawing 233 mNeedToDraw = true; 234 break; 235 case SDL_MOUSEMOTION: 236 onMouseMotion(event); 237 // force redrawing 238 mNeedToDraw = true; 239 break; 240 case SDL_MOUSEWHEEL: 241 onMouseWheel(event); 242 // force redrawing 243 mNeedToDraw = true; 244 break; 245 default: 246 } 247 } 248 } 249 250 // keyboard update 251 { 252 while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_KEYDOWN, SDL_KEYUP)) 253 { 254 switch (event.type) 255 { 256 case SDL_KEYDOWN: 257 onKeyDown(event); 258 // force redrawing 259 mNeedToDraw = true; 260 break; 261 case SDL_KEYUP: 262 onKeyUp(event); 263 // force redrawing 264 mNeedToDraw = true; 265 break; 266 default: 267 } 268 } 269 } 270 271 // text update 272 { 273 while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_TEXTINPUT, SDL_TEXTINPUT)) 274 { 275 switch (event.type) 276 { 277 case SDL_TEXTINPUT: 278 import core.stdc.string : strlen; 279 auto len = strlen(&event.text.text[0]); 280 if (!len) 281 break; 282 assert(len < event.text.text.sizeof); 283 auto txt = event.text.text[0..len]; 284 import std.utf : byDchar; 285 foreach(ch; txt.byDchar) 286 super.keyboardCharacterEvent(ch); 287 288 // force redrawing 289 mNeedToDraw = true; 290 break; 291 default: 292 break; 293 } 294 } 295 } 296 297 // user event, we use it as timer notification 298 { 299 while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_USEREVENT, SDL_USEREVENT)) 300 { 301 switch (event.type) 302 { 303 case SDL_USEREVENT: 304 // force redrawing 305 mNeedToDraw = true; 306 break; 307 default: 308 break; 309 } 310 } 311 } 312 313 // perform drawing if needed 314 { 315 import std.datetime : dur; 316 317 static auto pauseTimeMs = 0; 318 currTime = Clock.currTime.stdTime; 319 if (currTime - mBlinkingCursorTimestamp > dur!"msecs"(500).total!"hnsecs") 320 { 321 mBlinkingCursorVisible = !mBlinkingCursorVisible; 322 mNeedToDraw = true; 323 mBlinkingCursorTimestamp = currTime; 324 } 325 326 if (needToDraw) 327 { 328 pauseTimeMs = 0; 329 size = Vector2i(width, height); 330 super.draw(ctx); 331 332 window.swapBuffers(); 333 } 334 else 335 { 336 pauseTimeMs = pauseTimeMs * 2 + 1; // exponential pause 337 if (pauseTimeMs > 100) 338 pauseTimeMs = 100; // max 100ms of pause 339 SDL_Delay(pauseTimeMs); 340 } 341 } 342 } 343 } 344 345 abstract void onVisibleForTheFirstTime(); 346 347 override Logger logger() { return _log; } 348 349 protected: 350 SDL2Window window; 351 int width; 352 int height; 353 354 MouseButton btn; 355 MouseAction action; 356 int modifiers; 357 358 Logger _log; 359 OpenGL _gl; 360 SDL2 _sdl2; 361 362 NanoContext ctx; 363 364 SDL_Cursor*[6] mCursorSet; 365 366 public void onKeyDown(ref const(SDL_Event) event) 367 { 368 import nanogui.common : KeyAction; 369 370 auto key = event.key.keysym.sym.convertSdlKeyToNanoguiKey; 371 int modifiers = event.key.keysym.mod.convertSdlModifierToNanoguiModifier; 372 super.keyboardEvent(key, event.key.keysym.scancode, KeyAction.Press, modifiers); 373 } 374 375 public void onKeyUp(ref const(SDL_Event) event) 376 { 377 378 } 379 380 public void onMouseWheel(ref const(SDL_Event) event) 381 { 382 if (event.wheel.y > 0) 383 { 384 btn = MouseButton.WheelUp; 385 super.scrollCallbackEvent(0, +1, Clock.currTime.stdTime); 386 } 387 else if (event.wheel.y < 0) 388 { 389 btn = MouseButton.WheelDown; 390 super.scrollCallbackEvent(0, -1, Clock.currTime.stdTime); 391 } 392 } 393 394 public void onMouseMotion(ref const(SDL_Event) event) 395 { 396 import gfm.sdl2 : SDL_BUTTON_LMASK, SDL_BUTTON_RMASK, SDL_BUTTON_MMASK; 397 398 ctx.mouse.x = event.motion.x; 399 ctx.mouse.y = event.motion.y; 400 401 if (event.motion.state & SDL_BUTTON_LMASK) 402 btn = MouseButton.Left; 403 else if (event.motion.state & SDL_BUTTON_RMASK) 404 btn = MouseButton.Right; 405 else if (event.motion.state & SDL_BUTTON_MMASK) 406 btn = MouseButton.Middle; 407 408 if (event.motion.state & SDL_BUTTON_LMASK) 409 modifiers |= MouseButton.Left; 410 if (event.motion.state & SDL_BUTTON_RMASK) 411 modifiers |= MouseButton.Right; 412 if (event.motion.state & SDL_BUTTON_MMASK) 413 modifiers |= MouseButton.Middle; 414 415 action = MouseAction.Motion; 416 super.cursorPosCallbackEvent(ctx.mouse.x, ctx.mouse.y, Clock.currTime.stdTime); 417 } 418 419 public void onMouseUp(ref const(SDL_Event) event) 420 { 421 import gfm.sdl2 : SDL_BUTTON_LEFT, SDL_BUTTON_RIGHT, SDL_BUTTON_MIDDLE; 422 423 switch(event.button.button) 424 { 425 case SDL_BUTTON_LEFT: 426 btn = MouseButton.Left; 427 break; 428 case SDL_BUTTON_RIGHT: 429 btn = MouseButton.Right; 430 break; 431 case SDL_BUTTON_MIDDLE: 432 btn = MouseButton.Middle; 433 break; 434 default: 435 } 436 action = MouseAction.Release; 437 super.mouseButtonCallbackEvent(btn, action, modifiers, Clock.currTime.stdTime); 438 } 439 440 public void onMouseDown(ref const(SDL_Event) event) 441 { 442 import gfm.sdl2 : SDL_BUTTON_LEFT, SDL_BUTTON_RIGHT, SDL_BUTTON_MIDDLE; 443 444 switch(event.button.button) 445 { 446 case SDL_BUTTON_LEFT: 447 btn = MouseButton.Left; 448 break; 449 case SDL_BUTTON_RIGHT: 450 btn = MouseButton.Right; 451 break; 452 case SDL_BUTTON_MIDDLE: 453 btn = MouseButton.Middle; 454 break; 455 default: 456 } 457 action = MouseAction.Press; 458 super.mouseButtonCallbackEvent(btn, action, modifiers, Clock.currTime.stdTime); 459 } 460 461 override void cursor(Cursor value) 462 { 463 mCursor = value; 464 SDL_SetCursor(mCursorSet[mCursor]); 465 } 466 467 override Cursor cursor() const 468 { 469 return mCursor; 470 } 471 } 472 473 private auto convertSdlKeyToNanoguiKey(int sdlkey) 474 { 475 import gfm.sdl2; 476 import nanogui.common : KeyAction, Key; 477 478 int nanogui_key; 479 switch(sdlkey) 480 { 481 case SDLK_LEFT: 482 nanogui_key = Key.Left; 483 break; 484 case SDLK_RIGHT: 485 nanogui_key = Key.Right; 486 break; 487 case SDLK_UP: 488 nanogui_key = Key.Up; 489 break; 490 case SDLK_DOWN: 491 nanogui_key = Key.Down; 492 break; 493 case SDLK_BACKSPACE: 494 nanogui_key = Key.Backspace; 495 break; 496 case SDLK_DELETE: 497 nanogui_key = Key.Delete; 498 break; 499 case SDLK_HOME: 500 nanogui_key = Key.Home; 501 break; 502 case SDLK_END: 503 nanogui_key = Key.End; 504 break; 505 case SDLK_RETURN: 506 nanogui_key = Key.Enter; 507 break; 508 case SDLK_a: 509 nanogui_key = Key.A; 510 break; 511 case SDLK_x: 512 nanogui_key = Key.X; 513 break; 514 case SDLK_c: 515 nanogui_key = Key.C; 516 break; 517 case SDLK_v: 518 nanogui_key = Key.V; 519 break; 520 case SDLK_ESCAPE: 521 nanogui_key = Key.Esc; 522 break; 523 default: 524 nanogui_key = sdlkey; 525 } 526 527 return nanogui_key; 528 } 529 530 private auto convertSdlModifierToNanoguiModifier(int mod) 531 { 532 import gfm.sdl2; 533 import nanogui.common : KeyMod; 534 535 int nanogui_mod; 536 537 if (mod & KMOD_LCTRL) 538 nanogui_mod |= KeyMod.Ctrl; 539 if (mod & KMOD_LSHIFT) 540 nanogui_mod |= KeyMod.Shift; 541 if (mod & KMOD_LALT) 542 nanogui_mod |= KeyMod.Alt; 543 if (mod & KMOD_RCTRL) 544 nanogui_mod |= KeyMod.Ctrl; 545 if (mod & KMOD_RSHIFT) 546 nanogui_mod |= KeyMod.Shift; 547 if (mod & KMOD_RALT) 548 nanogui_mod |= KeyMod.Alt; 549 550 return nanogui_mod; 551 }