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