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