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