1 /**
2  * ae.ui.shell.sdl2.shell
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.ui.shell.sdl2.shell;
15 
16 import std.conv;
17 import std.string;
18 
19 import derelict.sdl2.sdl;
20 import derelict.util.loader;
21 
22 import ae.ui.shell.shell;
23 //import ae.ui.video.video;
24 import ae.ui.video.sdl2common.video;
25 import ae.ui.app.application;
26 public import ae.ui.shell.events;
27 import ae.ui.timer.timer;
28 
29 //!!version(Posix) pragma(lib, "dl"); // for Derelict
30 
31 final class SDL2Shell : Shell
32 {
33 	Application application;
34 	SDL2CommonVideo sdlVideo;
35 
36 	this(Application application)
37 	{
38 		this.application = application;
39 
40 		//!!SharedLibLoader.disableAutoUnload(); // SDL MM timers may crash on exit
41 		DerelictSDL2.load();
42 		auto components = SDL_INIT_VIDEO | SDL_INIT_TIMER;
43 		if (application.needSound())
44 			components |= SDL_INIT_AUDIO;
45 		if (application.needJoystick())
46 			components |= SDL_INIT_JOYSTICK;
47 		sdlEnforce(SDL_Init(components)==0);
48 
49 		//!!SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
50 		//!!SDL_EnableUNICODE(1);
51 
52 		if (application.needJoystick() && SDL_NumJoysticks())
53 		{
54 			SDL_JoystickEventState(SDL_ENABLE);
55 			SDL_JoystickOpen(0);
56 		}
57 	}
58 
59 	/// A version of SDL_WaitEvent that sleeps less than 10ms at a time.
60 	private int waitEvent()
61 	{
62 		while (true)
63 		{
64 			synchronized(this)
65 				if (mainThreadQueue.length)
66 					return 1;
67 
68 			SDL_PumpEvents();
69 			switch (SDL_PeepEvents(null, 1, SDL_GETEVENT, 0, uint.max))
70 			{
71 				case -1: return 0;
72 				case  1: return 1;
73 				case  0: SDL_Delay(1); break;
74 				default: assert(0);
75 			}
76 		}
77 	}
78 
79 	override void run()
80 	{
81 		assert(video, "Video object not set");
82 		sdlVideo = cast(SDL2CommonVideo)video;
83 		assert(sdlVideo, "Video is non-SDL");
84 
85 		video.errorCallback = AppCallback(&quit);
86 		quitting = false;
87 
88 		// video (re-)initialization loop
89 		while (!quitting)
90 		{
91 			reinitPending = false;
92 
93 			// start renderer
94 			video.start(application);
95 
96 			// The main purpose of this call is to allow the application
97 			// to react to window size changes.
98 			application.handleInit();
99 
100 			// pump events
101 			while (!reinitPending && !quitting)
102 			{
103 				sdlEnforce(waitEvent());
104 
105 				synchronized(application)
106 				{
107 					if (mainThreadQueue.length)
108 					{
109 						foreach (fn; mainThreadQueue)
110 							if (fn)
111 								fn();
112 						mainThreadQueue = null;
113 					}
114 
115 					SDL_Event event = void;
116 					while (SDL_PollEvent(&event))
117 						handleEvent(&event);
118 				}
119 			}
120 
121 			// wait for renderer to stop
122 			video.stop();
123 		}
124 	}
125 
126 	~this()
127 	{
128 		SDL_Quit();
129 	}
130 
131 	private void delegate()[] mainThreadQueue;
132 
133 	private void runInMainThread(void delegate() fn)
134 	{
135 		synchronized(this)
136 			mainThreadQueue ~= fn;
137 	}
138 
139 	override void prod()
140 	{
141 		runInMainThread(null);
142 	}
143 
144 	override void setCaption(string caption)
145 	{
146 		runInMainThread({ SDL_SetWindowTitle(sdlVideo.window, toStringz(caption)); });
147 	}
148 
149 	MouseButton translateMouseButton(ubyte sdlButton)
150 	{
151 		switch (sdlButton)
152 		{
153 		case SDL_BUTTON_LEFT:
154 			return MouseButton.Left;
155 		case SDL_BUTTON_MIDDLE:
156 		default:
157 			return MouseButton.Middle;
158 		case SDL_BUTTON_RIGHT:
159 			return MouseButton.Right;
160 		}
161 	}
162 
163 	enum SDL_BUTTON_LAST = SDL_BUTTON_X2;
164 
165 	MouseButtons translateMouseButtons(uint sdlButtons)
166 	{
167 		MouseButtons result;
168 		for (ubyte i=SDL_BUTTON_LEFT; i<=SDL_BUTTON_LAST; i++)
169 			if (sdlButtons & SDL_BUTTON(i))
170 				result |= 1<<translateMouseButton(i);
171 		return result;
172 	}
173 
174 	void handleEvent(SDL_Event* event)
175 	{
176 		switch (event.type)
177 		{
178 		case SDL_KEYDOWN:
179 			/+if ( event.key.keysym.scancode == SDL_SCANCODE_RETURN && (keypressed[SDL_SCANCODE_RALT] || keypressed[SDL_SCANCODE_LALT]))
180 			{
181 				if (application.toggleFullScreen())
182 				{
183 					video.stop();
184 					video.initialize();
185 					video.start();
186 					return false;
187 				}
188 			}+/
189 			return application.handleKeyDown(sdlKeys[event.key.keysym.scancode], event.key.keysym.unicode);
190 		case SDL_KEYUP:
191 			return application.handleKeyUp(sdlKeys[event.key.keysym.scancode]);
192 
193 		case SDL_MOUSEBUTTONDOWN:
194 			return application.handleMouseDown(event.button.x, event.button.y, translateMouseButton(event.button.button));
195 		case SDL_MOUSEBUTTONUP:
196 			return application.handleMouseUp(event.button.x, event.button.y, translateMouseButton(event.button.button));
197 		case SDL_MOUSEMOTION:
198 			return application.handleMouseMove(event.motion.x, event.motion.y, translateMouseButtons(event.motion.state));
199 
200 		case SDL_JOYAXISMOTION:
201 			return application.handleJoyAxisMotion(event.jaxis.axis, cast(short)event.jaxis.value);
202 		case SDL_JOYHATMOTION:
203 			return application.handleJoyHatMotion (event.jhat.hat, cast(JoystickHatState)event.jhat.value);
204 		case SDL_JOYBUTTONDOWN:
205 			return application.handleJoyButtonDown(event.jbutton.button);
206 		case SDL_JOYBUTTONUP:
207 			return application.handleJoyButtonUp  (event.jbutton.button);
208 
209 		case SDL_WINDOWEVENT:
210 			switch (event.window.event)
211 			{
212 				case SDL_WINDOWEVENT_MOVED:
213 					auto settings = application.getShellSettings();
214 					settings.windowPosX = event.window.data1;
215 					settings.windowPosY = event.window.data2;
216 					application.setShellSettings(settings);
217 					break;
218 				case SDL_WINDOWEVENT_SIZE_CHANGED:
219 					auto settings = application.getShellSettings();
220 					settings.windowSizeX = event.window.data1;
221 					settings.windowSizeY = event.window.data2;
222 					application.setShellSettings(settings);
223 					reinitPending = true;
224 					break;
225 				case SDL_WINDOWEVENT_CLOSE:
226 					event.type = SDL_QUIT;
227 					SDL_PushEvent(event);
228 					break;
229 				default:
230 					break;
231 			}
232 			break;
233 		case SDL_QUIT:
234 			application.handleQuit();
235 			break;
236 		default:
237 			break;
238 		}
239 	}
240 
241 	bool reinitPending;
242 }
243 
244 class SdlException : Exception
245 {
246 	this(string message) { super(message); }
247 }
248 
249 T sdlEnforce(T)(T result, string message = null)
250 {
251 	if (!result)
252 		throw new SdlException("SDL error: " ~ (message ? message ~ ": " : "") ~ to!string(SDL_GetError()));
253 	return result;
254 }
255 
256 Key[SDL_NUM_SCANCODES] sdlKeys;
257 
258 shared static this()
259 {
260 	sdlKeys[SDL_SCANCODE_UP    ] = Key.up   ;
261 	sdlKeys[SDL_SCANCODE_DOWN  ] = Key.down ;
262 	sdlKeys[SDL_SCANCODE_LEFT  ] = Key.left ;
263 	sdlKeys[SDL_SCANCODE_RIGHT ] = Key.right;
264 	sdlKeys[SDL_SCANCODE_SPACE ] = Key.space;
265 	sdlKeys[SDL_SCANCODE_ESCAPE] = Key.esc  ;
266 }