1 module nanogui.sdlbackend;
2 
3 import std.datetime : Clock;
4 import std.exception: enforce;
5 
6 import std.experimental.logger: Logger;
7 
8 import gfm.sdl2: SDL_Event, SDL_Cursor, SDL_SetCursor, SDL_FreeCursor;
9 
10 import arsd.nanovega : kill, NVGContextFlag;
11 
12 import nanogui.screen : Screen;
13 import nanogui.theme : Theme;
14 import nanogui.common : NanoContext, Vector2i, MouseButton, MouseAction, Cursor;
15 import nanogui.sdlapp : SdlApp;
16 
17 class SdlBackend : Screen
18 {
19 	this(int w, int h, string title)
20 	{
21 		_sdlApp = new SdlApp(w, h, title);
22 
23 		_sdlApp.onBeforeLoopStart = ()
24 		{
25 			import std.datetime : dur;
26 
27 			currTime = Clock.currTime.stdTime;
28 			if (currTime - mBlinkingCursorTimestamp > dur!"msecs"(500).total!"hnsecs")
29 			{
30 				mBlinkingCursorVisible = !mBlinkingCursorVisible;
31 				_sdlApp.invalidate();
32 				mBlinkingCursorTimestamp = currTime;
33 			}
34 
35 			if (_onBeforeLoopStart)
36 				_onBeforeLoopStart();
37 		};
38 
39 		_sdlApp.onDraw = ()
40 		{
41 			if (mNeedToDraw)
42 				_sdlApp.invalidate;
43 			size = Vector2i(width, height);
44 			super.draw(ctx);
45 		};
46 
47 		_sdlApp.onKeyDown = (ref const(SDL_Event) event)
48 		{
49 			import nanogui.common : KeyAction;
50 
51 			_sdlApp.invalidate;
52 
53 			auto key = event.key.keysym.sym.convertSdlKeyToNanoguiKey;
54 			int modifiers = event.key.keysym.mod.convertSdlModifierToNanoguiModifier;
55 			return super.keyboardEvent(key, event.key.keysym.scancode, KeyAction.Press, modifiers);
56 		};
57 
58 		_sdlApp.onMouseWheel = (ref const(SDL_Event) event)
59 		{
60 			_sdlApp.invalidate;
61 			if (event.wheel.y > 0)
62 			{
63 				btn = MouseButton.WheelUp;
64 				return super.scrollCallbackEvent(0, +1, Clock.currTime.stdTime);
65 			}
66 			else if (event.wheel.y < 0)
67 			{
68 				btn = MouseButton.WheelDown;
69 				return super.scrollCallbackEvent(0, -1, Clock.currTime.stdTime);
70 			}
71 			return false;
72 		};
73 		
74 		_sdlApp.onMouseMotion = (ref const(SDL_Event) event)
75 		{
76 			import gfm.sdl2 : SDL_BUTTON_LMASK, SDL_BUTTON_RMASK, SDL_BUTTON_MMASK;
77 
78 			_sdlApp.invalidate;
79 
80 			ctx.mouse.x = event.motion.x;
81 			ctx.mouse.y = event.motion.y;
82 
83 			if (event.motion.state & SDL_BUTTON_LMASK)
84 				btn = MouseButton.Left;
85 			else if (event.motion.state & SDL_BUTTON_RMASK)
86 				btn = MouseButton.Right;
87 			else if (event.motion.state & SDL_BUTTON_MMASK)
88 				btn = MouseButton.Middle;
89 
90 			if (event.motion.state & SDL_BUTTON_LMASK)
91 				modifiers |= MouseButton.Left;
92 			if (event.motion.state & SDL_BUTTON_RMASK)
93 				modifiers |= MouseButton.Right;
94 			if (event.motion.state & SDL_BUTTON_MMASK)
95 				modifiers |= MouseButton.Middle;
96 
97 			action = MouseAction.Motion;
98 			return super.cursorPosCallbackEvent(ctx.mouse.x, ctx.mouse.y, Clock.currTime.stdTime);
99 		};
100 
101 		_sdlApp.onMouseUp = (ref const(SDL_Event) event)
102 		{
103 			import gfm.sdl2 : SDL_BUTTON_LEFT, SDL_BUTTON_RIGHT, SDL_BUTTON_MIDDLE;
104 
105 			_sdlApp.invalidate;
106 
107 			switch(event.button.button)
108 			{
109 				case SDL_BUTTON_LEFT:
110 					btn = MouseButton.Left;
111 				break;
112 				case SDL_BUTTON_RIGHT:
113 					btn = MouseButton.Right;
114 				break;
115 				case SDL_BUTTON_MIDDLE:
116 					btn = MouseButton.Middle;
117 				break;
118 				default:
119 			}
120 			action = MouseAction.Release;
121 			return super.mouseButtonCallbackEvent(btn, action, modifiers, Clock.currTime.stdTime);
122 		};
123 
124 		_sdlApp.onMouseDown = (ref const(SDL_Event) event)
125 		{
126 			import gfm.sdl2 : SDL_BUTTON_LEFT, SDL_BUTTON_RIGHT, SDL_BUTTON_MIDDLE;
127 
128 			_sdlApp.invalidate;
129 
130 			switch(event.button.button)
131 			{
132 				case SDL_BUTTON_LEFT:
133 					btn = MouseButton.Left;
134 				break;
135 				case SDL_BUTTON_RIGHT:
136 					btn = MouseButton.Right;
137 				break;
138 				case SDL_BUTTON_MIDDLE:
139 					btn = MouseButton.Middle;
140 				break;
141 				default:
142 			}
143 			action = MouseAction.Press;
144 			return super.mouseButtonCallbackEvent(btn, action, modifiers, Clock.currTime.stdTime);
145 		};
146 
147 		_sdlApp.onKeyboardChar = delegate(dchar codepoint)
148 		{
149 			return keyboardCharacterEvent(codepoint);
150 		};
151 
152 		_sdlApp.onClose = ()
153 		{
154 			if (_onClose)
155 				return _onClose();
156 
157 			return true;
158 		};
159 
160 		ctx = NanoContext(NVGContextFlag.Debug);
161 		enforce(ctx !is null, "cannot initialize NanoGui");
162 
163 		import gfm.sdl2;
164 		mCursorSet[Cursor.Arrow]     = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
165 		mCursorSet[Cursor.IBeam]     = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM);
166 		mCursorSet[Cursor.Crosshair] = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR);
167 		mCursorSet[Cursor.Hand]      = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND);
168 		mCursorSet[Cursor.HResize]   = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE);
169 		mCursorSet[Cursor.VResize]   = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS);
170 
171 		super(w, h, Clock.currTime.stdTime);
172 		theme = new Theme(ctx);
173 	}
174 
175 	~this()
176 	{
177 		SDL_FreeCursor(mCursorSet[Cursor.Arrow]);
178 		SDL_FreeCursor(mCursorSet[Cursor.IBeam]);
179 		SDL_FreeCursor(mCursorSet[Cursor.Crosshair]);
180 		SDL_FreeCursor(mCursorSet[Cursor.Hand]);
181 		SDL_FreeCursor(mCursorSet[Cursor.HResize]);
182 		SDL_FreeCursor(mCursorSet[Cursor.VResize]);
183 
184 		ctx.kill();
185 		destroy(_sdlApp);
186 	}
187 
188 	private void delegate () _onBeforeLoopStart;
189 	void onBeforeLoopStart(void delegate () dg)
190 	{
191 		_onBeforeLoopStart = dg;
192 	}
193 	
194 	private bool delegate() _onClose;
195 	void onClose(bool delegate() dg) @safe
196 	{
197 		_onClose = dg;
198 	}
199 
200 	void run()
201 	{
202 		onVisibleForTheFirstTime();
203 
204 		_sdlApp.run();
205 	}
206 
207 	void close()
208 	{
209 		_sdlApp.close();
210 	}
211 
212 	auto invalidate() { _sdlApp.invalidate; }
213 
214 	abstract void onVisibleForTheFirstTime();
215 
216 	override Logger logger() { return _sdlApp.logger; }
217 
218 protected:
219 	SdlApp _sdlApp;
220 
221 	MouseButton btn;
222 	MouseAction action;
223 	int modifiers;
224 
225 	NanoContext ctx;
226 
227 	SDL_Cursor*[6] mCursorSet;
228 
229 	override void cursor(Cursor value)
230 	{
231 		mCursor = value;
232 		SDL_SetCursor(mCursorSet[mCursor]);
233 	}
234 
235 	override Cursor cursor() const
236 	{
237 		return mCursor;
238 	}
239 }
240 
241 private auto convertSdlKeyToNanoguiKey(int sdlkey)
242 {
243 	import gfm.sdl2;
244 	import nanogui.common : KeyAction, Key;
245 
246 	int nanogui_key;
247 	switch(sdlkey)
248 	{
249 		case SDLK_LEFT:
250 			nanogui_key = Key.Left;
251 		break;
252 		case SDLK_RIGHT:
253 			nanogui_key = Key.Right;
254 		break;
255 		case SDLK_UP:
256 			nanogui_key = Key.Up;
257 		break;
258 		case SDLK_DOWN:
259 			nanogui_key = Key.Down;
260 		break;
261 		case SDLK_BACKSPACE:
262 			nanogui_key = Key.Backspace;
263 		break;
264 		case SDLK_DELETE:
265 			nanogui_key = Key.Delete;
266 		break;
267 		case SDLK_HOME:
268 			nanogui_key = Key.Home;
269 		break;
270 		case SDLK_END:
271 			nanogui_key = Key.End;
272 		break;
273 		case SDLK_RETURN:
274 			nanogui_key = Key.Enter;
275 		break;
276 		case SDLK_a:
277 			nanogui_key = Key.A;
278 		break;
279 		case SDLK_x:
280 			nanogui_key = Key.X;
281 		break;
282 		case SDLK_c:
283 			nanogui_key = Key.C;
284 		break;
285 		case SDLK_v:
286 			nanogui_key = Key.V;
287 		break;
288 		case SDLK_ESCAPE:
289 			nanogui_key = Key.Esc;
290 		break;
291 		default:
292 			nanogui_key = sdlkey;
293 	}
294 
295 	return nanogui_key;
296 }
297 
298 private auto convertSdlModifierToNanoguiModifier(int mod)
299 {
300 	import gfm.sdl2;
301 	import nanogui.common : KeyMod;
302 
303 	int nanogui_mod;
304 
305 	if (mod & KMOD_LCTRL)
306 		nanogui_mod |= KeyMod.Ctrl;
307 	if (mod & KMOD_LSHIFT)
308 		nanogui_mod |= KeyMod.Shift;
309 	if (mod & KMOD_LALT)
310 		nanogui_mod |= KeyMod.Alt;
311 	if (mod & KMOD_RCTRL)
312 		nanogui_mod |= KeyMod.Ctrl;
313 	if (mod & KMOD_RSHIFT)
314 		nanogui_mod |= KeyMod.Shift;
315 	if (mod & KMOD_RALT)
316 		nanogui_mod |= KeyMod.Alt;
317 
318 	return nanogui_mod;
319 }