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 }