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 }