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