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 }