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 		// reload OpenGL now that a context exists
59 		_gl.reload();
60 
61 		// redirect OpenGL output to our Logger
62 		_gl.redirectDebugOutput();
63 
64 		nvg = nvgCreateContext(NVGContextFlag.Debug);
65 		enforce(nvg !is null, "cannot initialize NanoGui");
66 
67 		mCursorSet[Cursor.Arrow]     = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
68 		mCursorSet[Cursor.IBeam]     = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
69 		mCursorSet[Cursor.Crosshair] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR);
70 		mCursorSet[Cursor.Hand]      = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);
71 		mCursorSet[Cursor.HResize]   = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
72 		mCursorSet[Cursor.VResize]   = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);
73 
74 		super(width, height, Clock.currTime.stdTime);
75 		theme = new Theme(nvg);
76 	}
77 
78 	~this()
79 	{
80 		SDL_FreeCursor(mCursorSet[Cursor.Arrow]);
81 		SDL_FreeCursor(mCursorSet[Cursor.IBeam]);
82 		SDL_FreeCursor(mCursorSet[Cursor.Crosshair]);
83 		SDL_FreeCursor(mCursorSet[Cursor.Hand]);
84 		SDL_FreeCursor(mCursorSet[Cursor.HResize]);
85 		SDL_FreeCursor(mCursorSet[Cursor.VResize]);
86 
87 		nvg.kill();
88 		_gl.destroy();
89 		window.destroy();
90 		_sdl2.destroy();
91 	}
92 
93 	auto run()
94 	{
95 		import gfm.sdl2;
96 
97 		window.hide;
98 		SDL_FlushEvents(SDL_WINDOWEVENT, SDL_SYSWMEVENT);
99 		window.show;
100 
101 		onVisibleForTheFirstTime();
102 
103 		SDL_Event event;
104 
105 		SDL_PumpEvents();
106 		SDL_PeepEvents(&event, 1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_SYSWMEVENT);
107 
108 		uint prev_tick = SDL_GetTicks();
109 		while (SDL_QUIT != event.type)
110 		{
111 			const this_tick = SDL_GetTicks();
112 			if ( this_tick - prev_tick < 1)
113 				SDL_Delay(1);
114 			prev_tick = this_tick;
115 
116 			if (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_SYSWMEVENT))
117 			{
118 				switch (event.type)
119 				{
120 					case SDL_WINDOWEVENT:
121 					{
122 						switch (event.window.event)
123 						{
124 							case SDL_WINDOWEVENT_MOVED:
125 								// window has been moved to other position
126 								break;
127 
128 							case SDL_WINDOWEVENT_RESIZED:
129 							case SDL_WINDOWEVENT_SIZE_CHANGED:
130 							{
131 								// window size has been resized
132 								break;
133 							}
134 
135 							case SDL_WINDOWEVENT_SHOWN:
136 							case SDL_WINDOWEVENT_FOCUS_GAINED:
137 							case SDL_WINDOWEVENT_RESTORED:
138 							case SDL_WINDOWEVENT_MAXIMIZED:
139 								// window has been activated
140 								break;
141 
142 							case SDL_WINDOWEVENT_HIDDEN:
143 							case SDL_WINDOWEVENT_FOCUS_LOST:
144 							case SDL_WINDOWEVENT_MINIMIZED:
145 								// window has been deactivated
146 								break;
147 
148 							case SDL_WINDOWEVENT_ENTER:
149 								// mouse cursor has entered window
150 								// for example default cursor can be disable
151 								// using SDL_ShowCursor(SDL_FALSE);
152 								break;
153 
154 							case SDL_WINDOWEVENT_LEAVE:
155 								// mouse cursor has left window
156 								// for example default cursor can be disable
157 								// using SDL_ShowCursor(SDL_TRUE);
158 								break;
159 
160 							case SDL_WINDOWEVENT_CLOSE:
161 								event.type = SDL_QUIT;
162 								break;
163 							default:
164 						}
165 						break;
166 					}
167 					default:
168 				}
169 			}
170 
171 			{
172 				currTime = Clock.currTime.stdTime;
173 
174 				if (needToDraw)
175 				{
176 					size = Vector2i(width, height);
177 					super.draw(nvg);
178 
179 					window.swapBuffers();
180 				}
181 			}
182 			SDL_PumpEvents();
183 
184 			// mouse update
185 			{
186 				SDL_PumpEvents();
187 
188 				while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEWHEEL))
189 				{
190 					switch (event.type)
191 					{
192 					case SDL_MOUSEBUTTONDOWN:
193 						onMouseDown(event);
194 						// force redrawing
195 						mNeedToDraw = true;
196 						break;
197 					case SDL_MOUSEBUTTONUP:
198 						onMouseUp(event);
199 						// force redrawing
200 						mNeedToDraw = true;
201 						break;
202 					case SDL_MOUSEMOTION:
203 						onMouseMotion(event);
204 						// force redrawing
205 						mNeedToDraw = true;
206 						break;
207 					case SDL_MOUSEWHEEL:
208 						onMouseWheel(event);
209 						// force redrawing
210 						mNeedToDraw = true;
211 						break;
212 					default:
213 					}
214 				}
215 			}
216 
217 			// keyboard update
218 			{
219 				SDL_PumpEvents();
220 
221 				while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_KEYDOWN, SDL_KEYUP))
222 				{
223 					switch (event.type)
224 					{
225 						case SDL_KEYDOWN:
226 							onKeyDown(event);
227 							// force redrawing
228 							mNeedToDraw = true;
229 							break;
230 						case SDL_KEYUP:
231 							onKeyUp(event);
232 							// force redrawing
233 							mNeedToDraw = true;
234 							break;
235 						default:
236 					}
237 				}
238 			}
239 
240 			// text update
241 			{
242 				while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_TEXTINPUT, SDL_TEXTINPUT))
243 				{
244 					switch (event.type)
245 					{
246 						case SDL_TEXTINPUT:
247 							import core.stdc..string : strlen;
248 							auto len = strlen(&event.text.text[0]);
249 							if (!len)
250 								break;
251 							assert(len < event.text.text.sizeof);
252 							auto txt = event.text.text[0..len];
253 							import std.utf : byDchar;
254 							foreach(ch; txt.byDchar)
255 								super.keyboardCharacterEvent(ch);
256 
257 							// force redrawing
258 							mNeedToDraw = true;
259 							break;
260 						default:
261 							break;
262 					}
263 				}
264 			}
265 
266 			// user event, we use it as timer notification
267 			{
268 				while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_USEREVENT, SDL_USEREVENT))
269 				{
270 					switch (event.type)
271 					{
272 						case SDL_USEREVENT:
273 							// force redrawing
274 							mNeedToDraw = true;
275 							break;
276 						default:
277 							break;
278 					}
279 				}
280 			}
281 		}
282 	}
283 
284 	abstract void onVisibleForTheFirstTime();
285 
286 	auto gl() { return _gl; }
287 
288 protected:
289 	SDL2Window window;
290 	int width;
291 	int height;
292 
293 	MouseButton btn;
294 	MouseAction action;
295 	int modifiers;
296 
297 	Logger _log;
298 	OpenGL _gl;
299 	SDL2 _sdl2;
300 
301 	NVGContext nvg;
302 
303 	SDL_Cursor*[6] mCursorSet;
304 
305 	public void onKeyDown(ref const(SDL_Event) event)
306 	{
307 		import nanogui.common : KeyAction;
308 		int modifiers;
309 
310 		auto key = event.key.keysym.sym.convertSdlKeyToNanoguiKey;
311 		super.keyboardEvent(key, event.key.keysym.scancode, KeyAction.Press, modifiers);
312 	}
313 
314 	public void onKeyUp(ref const(SDL_Event) event)
315 	{
316 		
317 	}
318 
319 	public void onMouseWheel(ref const(SDL_Event) event)
320 	{
321 		if (event.wheel.y > 0)
322 		{
323 			btn = MouseButton.WheelUp;
324 			super.scrollCallbackEvent(0, +1, Clock.currTime.stdTime);
325 		}
326 		else if (event.wheel.y < 0)
327 		{
328 			btn = MouseButton.WheelDown;
329 			super.scrollCallbackEvent(0, -1, Clock.currTime.stdTime);
330 		}
331 	}
332 	
333 	public void onMouseMotion(ref const(SDL_Event) event)
334 	{
335 		import gfm.sdl2 : SDL_BUTTON_LMASK, SDL_BUTTON_RMASK, SDL_BUTTON_MMASK;
336 
337 		auto mouse_x = event.motion.x;
338 		auto mouse_y = event.motion.y;
339 
340 		if (event.motion.state & SDL_BUTTON_LMASK)
341 			btn = MouseButton.Left;
342 		else if (event.motion.state & SDL_BUTTON_RMASK)
343 			btn = MouseButton.Right;
344 		else if (event.motion.state & SDL_BUTTON_MMASK)
345 			btn = MouseButton.Middle;
346 
347 		if (event.motion.state & SDL_BUTTON_LMASK)
348 			modifiers |= MouseButton.Left;
349 		if (event.motion.state & SDL_BUTTON_RMASK)
350 			modifiers |= MouseButton.Right;
351 		if (event.motion.state & SDL_BUTTON_MMASK)
352 			modifiers |= MouseButton.Middle;
353 
354 		action = MouseAction.Motion;
355 		super.cursorPosCallbackEvent(mouse_x, mouse_y, Clock.currTime.stdTime);
356 	}
357 
358 	public void onMouseUp(ref const(SDL_Event) event)
359 	{
360 		import gfm.sdl2 : SDL_BUTTON_LEFT, SDL_BUTTON_RIGHT, SDL_BUTTON_MIDDLE;
361 
362 		switch(event.button.button)
363 		{
364 			case SDL_BUTTON_LEFT:
365 				btn = MouseButton.Left;
366 			break;
367 			case SDL_BUTTON_RIGHT:
368 				btn = MouseButton.Right;
369 			break;
370 			case SDL_BUTTON_MIDDLE:
371 				btn = MouseButton.Middle;
372 			break;
373 			default:
374 		}
375 		action = MouseAction.Release;
376 		super.mouseButtonCallbackEvent(btn, action, modifiers, Clock.currTime.stdTime);
377 	}
378 
379 	public void onMouseDown(ref const(SDL_Event) event)
380 	{
381 		import gfm.sdl2 : SDL_BUTTON_LEFT, SDL_BUTTON_RIGHT, SDL_BUTTON_MIDDLE;
382 
383 		switch(event.button.button)
384 		{
385 			case SDL_BUTTON_LEFT:
386 				btn = MouseButton.Left;
387 			break;
388 			case SDL_BUTTON_RIGHT:
389 				btn = MouseButton.Right;
390 			break;
391 			case SDL_BUTTON_MIDDLE:
392 				btn = MouseButton.Middle;
393 			break;
394 			default:
395 		}
396 		action = MouseAction.Press;
397 		super.mouseButtonCallbackEvent(btn, action, modifiers, Clock.currTime.stdTime);
398 	}
399 
400 	override void cursor(Cursor value)
401 	{
402 		mCursor = value;
403 		SDL_SetCursor(mCursorSet[mCursor]);
404 	}
405 
406 	override Cursor cursor() const
407 	{
408 		return mCursor;
409 	}
410 }
411 
412 private auto convertSdlKeyToNanoguiKey(int sdlkey)
413 {
414 	import gfm.sdl2;
415 	import nanogui.common : KeyAction, Key;
416 
417 	int nanogui_key;
418 	switch(sdlkey)
419 	{
420 		case SDLK_LEFT:
421 			nanogui_key = Key.Left;
422 		break;
423 		case SDLK_RIGHT:
424 			nanogui_key = Key.Right;
425 		break;
426 		case SDLK_UP:
427 			nanogui_key = Key.Up;
428 		break;
429 		case SDLK_DOWN:
430 			nanogui_key = Key.Down;
431 		break;
432 		case SDLK_BACKSPACE:
433 			nanogui_key = Key.Backspace;
434 		break;
435 		case SDLK_DELETE:
436 			nanogui_key = Key.Delete;
437 		break;
438 		case SDLK_HOME:
439 			nanogui_key = Key.Home;
440 		break;
441 		case SDLK_END:
442 			nanogui_key = Key.End;
443 		break;
444 		default:
445 			nanogui_key = sdlkey;
446 	}
447 
448 	return nanogui_key;
449 }