1 module nanogui.sdlapp;
2 
3 import std.exception: enforce;
4 
5 import std.experimental.logger: Logger, FileLogger, globalLogLevel, LogLevel;
6 
7 import gfm.opengl: OpenGL;
8 import gfm.sdl2: SDL2, SDL2Window, SDL_Event, SDL_Cursor, SDL_SetCursor, 
9 	SDL_FreeCursor, SDL_Delay;
10 
11 class SdlApp
12 {
13 	alias Event = SDL_Event;
14 
15 	this(int w, int h, string title)
16 	{
17 		/* Avoid locale-related number parsing issues */
18 		version(Windows) {}
19 		else {
20 			import core.stdc.locale;
21 			setlocale(LC_NUMERIC, "C");
22 		}
23 
24 		import gfm.opengl : GLSupport, loadOpenGL;
25 		import bindbc.sdl : SDLSupport, sdlSupport, loadSDL, SDL_INIT_VIDEO, SDL_INIT_EVENTS,
26 			SDL_GL_SetAttribute, SDL_WINDOWPOS_UNDEFINED, SDL_GL_CONTEXT_MAJOR_VERSION,
27 			SDL_GL_CONTEXT_MINOR_VERSION,SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE,
28 			SDL_GL_STENCIL_SIZE, SDL_WINDOW_OPENGL, SDL_WINDOW_RESIZABLE, SDL_WINDOW_HIDDEN;
29 
30 		this.width = w;
31 		this.height = h;
32 		_dirty = true;
33 
34 		// create a logger
35 		import std.stdio : stdout;
36 		_log = new FileLogger(stdout);
37 
38 		// load dynamic libraries
39 		SDLSupport ret = loadSDL();
40 		if(ret != sdlSupport) {
41 			if(ret == SDLSupport.noLibrary) {
42 				/*
43 				The system failed to load the library. Usually this means that either the library or one of its dependencies could not be found.
44 				*/
45 			}
46 			else if(SDLSupport.badLibrary) {
47 				/*
48 				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.
49 
50 				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.
51 				*/
52 			}
53 		}
54 		_sdl2 = new SDL2(_log);
55 		globalLogLevel = LogLevel.error;
56 
57 		// You have to initialize each SDL subsystem you want by hand
58 		_sdl2.subSystemInit(SDL_INIT_VIDEO);
59 		_sdl2.subSystemInit(SDL_INIT_EVENTS);
60 
61 		SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
62 		SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
63 		SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
64 		SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
65 
66 		// create an OpenGL-enabled SDL window
67 		window = new SDL2Window(_sdl2,
68 								SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
69 								width, height,
70 								SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN );
71 
72 		window.setTitle(title);
73 
74 		GLSupport retVal = loadOpenGL();
75 		if(retVal >= GLSupport.gl33)
76 		{
77 			// configure renderer for OpenGL 3.3
78 			import std.stdio;
79 			writefln("Available version of opengl: %s", retVal);
80 		}
81 		else
82 		{
83 			import std.stdio;
84 			if (retVal == GLSupport.noLibrary)
85 				writeln("opengl is not available");
86 			else
87 				writefln("Unsupported version of opengl %s", retVal);
88 			import std.exception;
89 			enforce(0);
90 		}
91 
92 		_gl = new OpenGL(_log);
93 
94 		// redirect OpenGL output to our Logger
95 		_gl.redirectDebugOutput();
96 	}
97 
98 	~this()
99 	{
100 		_gl.destroy();
101 		window.destroy();
102 		_sdl2.destroy();
103 	}
104 
105 	Logger logger() { return _log; }
106 
107 	private void delegate () _onBeforeLoopStart;
108 	void onBeforeLoopStart(void delegate () dg)
109 	{
110 		_onBeforeLoopStart = dg;
111 	}
112 	auto onBeforeLoopStart() { return _onBeforeLoopStart; }
113 
114 	alias OnDraw = void delegate();
115 	private OnDraw _onDraw;
116 	void onDraw(OnDraw handler)
117 	{
118 		_onDraw = handler;
119 	}
120 	auto onDraw() { return _onDraw; }
121 
122 	alias OnResize = void delegate(int w, int h);
123 	private OnResize _onResize;
124 	void onResize(OnResize handler)
125 	{
126 		_onResize = handler;
127 	}
128 	auto onResize() { return _onResize; }
129 
130 	alias OnKeyboardChar = bool delegate(dchar ch);
131 	private OnKeyboardChar _onKeyboardChar;
132 	void onKeyboardChar(OnKeyboardChar handler)
133 	{
134 		_onKeyboardChar = handler;
135 	}
136 	auto onKeyboardChar() { return _onKeyboardChar; }
137 
138 	alias OnSdlEvent = bool delegate(ref const(SDL_Event) event);
139 	private OnSdlEvent _onKeyDown;
140 	void onKeyDown(OnSdlEvent handler)
141 	{
142 		_onKeyDown = handler;
143 	}
144 	auto onKeyDown() { return _onKeyDown; }
145 
146 	private OnSdlEvent _onKeyUp;
147 	void onKeyUp(OnSdlEvent handler)
148 	{
149 		_onKeyUp = handler;
150 	}
151 	auto onKeyUp() { return _onKeyUp; }
152 
153 	private OnSdlEvent _onMouseWheel;
154 	void onMouseWheel(OnSdlEvent handler)
155 	{
156 		_onMouseWheel = handler;
157 	}
158 	auto onMouseWheel() { return _onMouseWheel; }
159 
160 	private OnSdlEvent _onMouseMotion;
161 	void onMouseMotion(OnSdlEvent handler)
162 	{
163 		_onMouseMotion = handler;
164 	}
165 	auto onMouseMotion() { return _onMouseMotion; }
166 
167 	private OnSdlEvent _onMouseUp;
168 	void onMouseUp(OnSdlEvent handler)
169 	{
170 		_onMouseUp = handler;
171 	}
172 	auto onMouseUp() { return _onMouseUp; }
173 
174 	private OnSdlEvent _onMouseDown;
175 	void onMouseDown(OnSdlEvent handler)
176 	{
177 		_onMouseDown = handler;
178 	}
179 	auto onMouseDown() { return _onMouseDown; }
180 
181 	alias OnClose = bool delegate();
182 	private OnClose _onClose;
183 	void onClose(OnClose handler)
184 	{
185 		_onClose = handler;
186 	}
187 	auto onClose() { return _onClose; }
188 
189 	private bool _running = true;
190 	void close() { _running = false; }
191 
192 	void addHandler(alias currentHandler)(OnSdlEvent newHandler)
193 	{
194 		auto oldHandler = currentHandler;
195 		if (oldHandler)
196 		{
197 			currentHandler = (ref const(SDL_Event) event)
198 			{
199 				if (oldHandler(event))
200 					return true;
201 				return newHandler(event);
202 			};
203 		}
204 		else
205 		{
206 			currentHandler = newHandler;
207 		}
208 	}
209 
210 	void invalidate()
211 	{
212 		_dirty = true;
213 	}
214 
215 	void run()
216 	{
217 		import gfm.sdl2;
218 
219 		window.hide;
220 		SDL_FlushEvents(SDL_WINDOWEVENT, SDL_SYSWMEVENT);
221 
222 		window.show;
223 
224 		SDL_Event event;
225 
226 		while (_running)
227 		{
228 			if (_onBeforeLoopStart)
229 				_onBeforeLoopStart();
230 
231 			SDL_PumpEvents();
232 
233 			while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_SYSWMEVENT))
234 			{
235 				switch (event.type)
236 				{
237 					case SDL_WINDOWEVENT:
238 					{
239 						switch (event.window.event)
240 						{
241 							case SDL_WINDOWEVENT_MOVED:
242 								// window has been moved to other position
243 								break;
244 
245 							case SDL_WINDOWEVENT_RESIZED:
246 							case SDL_WINDOWEVENT_SIZE_CHANGED:
247 							{
248 								// window size has been resized
249 								with(event.window)
250 								{
251 									width = data1;
252 									height = data2;
253 									if (_onResize)
254 										_onResize(width, height);
255 								}
256 								break;
257 							}
258 
259 							case SDL_WINDOWEVENT_SHOWN:
260 							case SDL_WINDOWEVENT_FOCUS_GAINED:
261 							case SDL_WINDOWEVENT_RESTORED:
262 							case SDL_WINDOWEVENT_MAXIMIZED:
263 								// window has been activated
264 								break;
265 
266 							case SDL_WINDOWEVENT_HIDDEN:
267 							case SDL_WINDOWEVENT_FOCUS_LOST:
268 							case SDL_WINDOWEVENT_MINIMIZED:
269 								// window has been deactivated
270 								break;
271 
272 							case SDL_WINDOWEVENT_ENTER:
273 								// mouse cursor has entered window
274 								// for example default cursor can be disable
275 								// using SDL_ShowCursor(SDL_FALSE);
276 								break;
277 
278 							case SDL_WINDOWEVENT_LEAVE:
279 								// mouse cursor has left window
280 								// for example default cursor can be disable
281 								// using SDL_ShowCursor(SDL_TRUE);
282 								break;
283 
284 							case SDL_WINDOWEVENT_CLOSE:
285 								_running = (_onClose) ? !_onClose() : false;
286 								// if we continue running then update the screen
287 								// to improve responce time
288 								if (_running)
289 									invalidate;
290 								break;
291 							default:
292 						}
293 						break;
294 					}
295 					default:
296 				}
297 			}
298 
299 			// mouse update
300 			while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEWHEEL))
301 			{
302 				switch (event.type)
303 				{
304 				case SDL_MOUSEBUTTONDOWN:
305 					if (_onMouseDown)
306 						_onMouseDown(event);
307 					break;
308 				case SDL_MOUSEBUTTONUP:
309 					if (_onMouseUp)
310 						_onMouseUp(event);
311 					break;
312 				case SDL_MOUSEMOTION:
313 					if (_onMouseMotion)
314 						_onMouseMotion(event);
315 					break;
316 				case SDL_MOUSEWHEEL:
317 					if (_onMouseWheel)
318 						_onMouseWheel(event);
319 					break;
320 				default:
321 				}
322 			}
323 
324 			// keyboard update
325 			while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_KEYDOWN, SDL_KEYUP))
326 			{
327 				switch (event.type)
328 				{
329 					case SDL_KEYDOWN:
330 						if (_onKeyDown)
331 							_onKeyDown(event);
332 						break;
333 					case SDL_KEYUP:
334 						if (_onKeyUp)
335 							_onKeyUp(event);
336 						break;
337 					default:
338 				}
339 			}
340 
341 			// text update
342 			while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_TEXTINPUT, SDL_TEXTINPUT))
343 			{
344 				switch (event.type)
345 				{
346 					case SDL_TEXTINPUT:
347 						if (_onKeyboardChar is null)
348 							break;
349 						import core.stdc.string : strlen;
350 						auto len = strlen(&event.text.text[0]);
351 						if (!len)
352 							break;
353 						assert(len < event.text.text.sizeof);
354 						auto txt = event.text.text[0..len];
355 						import std.utf : byDchar;
356 						foreach(ch; txt.byDchar)
357 							_onKeyboardChar(ch);
358 						break;
359 					default:
360 						break;
361 				}
362 			}
363 
364 			// user event, we use it as timer notification
365 			while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_USEREVENT, SDL_USEREVENT))
366 			{
367 				switch (event.type)
368 				{
369 					case SDL_USEREVENT:
370 						invalidate();
371 						break;
372 					default:
373 						break;
374 				}
375 			}
376 
377 			// perform drawing if needed
378 			if (_dirty)
379 			{
380 				pauseTimeMs = 0;
381 
382 				if (_onDraw)
383 					_onDraw();
384 
385 				window.swapBuffers();
386 				_dirty = false;
387 			}
388 			else
389 			{
390 				pauseTimeMs = pauseTimeMs * 2 + 1; // exponential pause
391 				if (pauseTimeMs > 100)
392 					pauseTimeMs = 100; // max 100ms of pause
393 				SDL_Delay(pauseTimeMs);
394 			}
395 		}
396 	}
397 
398 protected:
399 	SDL2Window window;
400 	int width;
401 	int height;
402 	bool _dirty;
403 	int pauseTimeMs;
404 
405 	Logger _log;
406 	OpenGL _gl;
407 	SDL2 _sdl2;
408 }
409 
410 private auto convertSdlKeyToNanoguiKey(int sdlkey)
411 {
412 	import gfm.sdl2;
413 	import nanogui.common : KeyAction, Key;
414 
415 	int nanogui_key;
416 	switch(sdlkey)
417 	{
418 		case SDLK_LEFT:
419 			nanogui_key = Key.Left;
420 		break;
421 		case SDLK_RIGHT:
422 			nanogui_key = Key.Right;
423 		break;
424 		case SDLK_UP:
425 			nanogui_key = Key.Up;
426 		break;
427 		case SDLK_DOWN:
428 			nanogui_key = Key.Down;
429 		break;
430 		case SDLK_BACKSPACE:
431 			nanogui_key = Key.Backspace;
432 		break;
433 		case SDLK_DELETE:
434 			nanogui_key = Key.Delete;
435 		break;
436 		case SDLK_HOME:
437 			nanogui_key = Key.Home;
438 		break;
439 		case SDLK_END:
440 			nanogui_key = Key.End;
441 		break;
442 		case SDLK_RETURN:
443 			nanogui_key = Key.Enter;
444 		break;
445 		case SDLK_a:
446 			nanogui_key = Key.A;
447 		break;
448 		case SDLK_x:
449 			nanogui_key = Key.X;
450 		break;
451 		case SDLK_c:
452 			nanogui_key = Key.C;
453 		break;
454 		case SDLK_v:
455 			nanogui_key = Key.V;
456 		break;
457 		case SDLK_ESCAPE:
458 			nanogui_key = Key.Esc;
459 		break;
460 		default:
461 			nanogui_key = sdlkey;
462 	}
463 
464 	return nanogui_key;
465 }
466 
467 private auto convertSdlModifierToNanoguiModifier(int mod)
468 {
469 	import gfm.sdl2;
470 	import nanogui.common : KeyMod;
471 
472 	int nanogui_mod;
473 
474 	if (mod & KMOD_LCTRL)
475 		nanogui_mod |= KeyMod.Ctrl;
476 	if (mod & KMOD_LSHIFT)
477 		nanogui_mod |= KeyMod.Shift;
478 	if (mod & KMOD_LALT)
479 		nanogui_mod |= KeyMod.Alt;
480 	if (mod & KMOD_RCTRL)
481 		nanogui_mod |= KeyMod.Ctrl;
482 	if (mod & KMOD_RSHIFT)
483 		nanogui_mod |= KeyMod.Shift;
484 	if (mod & KMOD_RALT)
485 		nanogui_mod |= KeyMod.Alt;
486 
487 	return nanogui_mod;
488 }