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 }