1 /**
2 Copyright: Copyright (c) 2013-2018 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 
7 module voxelman.platform.glfwwindow;
8 
9 import std.conv : to;
10 import std..string : toStringz, fromStringz, format;
11 
12 import derelict.glfw3.glfw3;
13 import voxelman.graphics.gl;
14 import voxelman.log;
15 import voxelman.math;
16 import voxelman.platform.iwindow : IWindow, CursorIcon, WindowParams;
17 import voxelman.platform.isharedcontext;
18 import voxelman.platform.input : KeyCode, PointerButton;
19 
20 class GlfwSharedContext : ISharedContext
21 {
22 	GLFWwindow* sharedContext;
23 
24 	this(GLFWwindow* _sharedContext)
25 	{
26 		this.sharedContext = _sharedContext;
27 	}
28 
29 	void makeCurrent()
30 	{
31 		glfwMakeContextCurrent(sharedContext);
32 	}
33 }
34 
35 class GlfwWindow : IWindow
36 {
37 private:
38 	GLFWwindow*	glfwWindowPtr;
39 	static bool	glfwInited = false;
40 	bool isProcessingEvents = false;
41 	GLFWcursor*[6] cursors;
42 
43 public:
44 	override void init(WindowParams params)
45 	{
46 		if (!glfwInited)
47 			initGlfw();
48 
49 		scope(failure) glfwTerminate();
50 
51 		glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, params.openglDebugContext);
52 		glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, params.openglVersion.openglMajorVersion);
53 		glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, params.openglVersion.openglMinorVersion);
54 
55 		if (params.openglVersion >= GLVersion.gl30)
56 		{
57 			if (params.openglForwardCompat) glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
58 		}
59 
60 		if (params.openglVersion >= GLVersion.gl32)
61 		{
62 			if (params.openglCoreProfile) glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
63 		}
64 
65 		glfwWindowHint(GLFW_VISIBLE, false);
66 
67 		//BUG: sometimes fails in Windows 8. Maybe because of old drivers.
68 		glfwWindowPtr = glfwCreateWindow(params.size.x, params.size.y, toStringz(params.title), null,  null);
69 
70 		if (glfwWindowPtr is null)
71 		{
72 			throw new Error("Error creating GLFW3 window");
73 		}
74 
75 		if (params.center) moveToCenter();
76 
77 		glfwShowWindow(glfwWindowPtr);
78 
79 		glfwMakeContextCurrent(glfwWindowPtr);
80 
81 		glClearColor(1.0, 1.0, 1.0, 1.0);
82 		glViewport(0, 0, params.size.x, params.size.y);
83 
84 		glfwSetWindowUserPointer(glfwWindowPtr, cast(void*)this);
85 		glfwSetWindowPosCallback(glfwWindowPtr, &windowposfun);
86 		glfwSetFramebufferSizeCallback(glfwWindowPtr, &windowsizefun);
87 		glfwSetWindowCloseCallback(glfwWindowPtr, &windowclosefun);
88 		//glfwSetWindowRefreshCallback(glfwWindowPtr, &windowrefreshfun);
89 		glfwSetWindowFocusCallback(glfwWindowPtr, &windowfocusfun);
90 		glfwSetWindowIconifyCallback(glfwWindowPtr, &windowiconifyfun);
91 		glfwSetMouseButtonCallback(glfwWindowPtr, &mousebuttonfun);
92 		glfwSetCursorPosCallback(glfwWindowPtr, &cursorposfun);
93 		glfwSetScrollCallback(glfwWindowPtr, &scrollfun);
94 		glfwSetKeyCallback(glfwWindowPtr, &keyfun);
95 		glfwSetCharCallback(glfwWindowPtr, &charfun);
96 		//glfwSetCursorEnterCallback(window, GLFWcursorenterfun cbfun);
97 		glfwSetWindowRefreshCallback(glfwWindowPtr, &refreshfun);
98 
99 		// create standard cursors
100 		cursors[0] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
101 		cursors[1] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR);
102 		cursors[2] = glfwCreateStandardCursor(GLFW_CROSSHAIR_CURSOR);
103 		cursors[3] = glfwCreateStandardCursor(GLFW_HAND_CURSOR);
104 		cursors[4] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR);
105 		cursors[5] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR);
106 	}
107 
108 	override ISharedContext createSharedContext()
109 	{
110 		assert(glfwInited);
111 		assert(glfwWindowPtr);
112 		glfwWindowHint(GLFW_VISIBLE, false);
113 		GLFWwindow* sharedContext = glfwCreateWindow(100, 100, "", null, glfwWindowPtr);
114 		return new GlfwSharedContext(sharedContext);
115 	}
116 
117 	override void moveToCenter()
118 	{
119 		GLFWmonitor* primaryMonitor = glfwGetPrimaryMonitor();
120 		ivec2 currentSize = size();
121 		ivec2 primPos;
122 		glfwGetMonitorPos(primaryMonitor, &primPos.x, &primPos.y);
123 		const GLFWvidmode* primMode = glfwGetVideoMode(primaryMonitor);
124 		ivec2 primSize = ivec2(primMode.width, primMode.height);
125 		ivec2 newPos = primPos + primSize/2 - currentSize/2;
126 		glfwSetWindowPos(glfwWindowPtr, newPos.x, newPos.y);
127 	}
128 
129 	override void processEvents()
130 	{
131 		// prevent calling glfwPollEvents when inside window_refresh callback
132 		if (!isProcessingEvents)
133 		{
134 			isProcessingEvents = true;
135 			glfwPollEvents();
136 			isProcessingEvents = false;
137 		}
138 	}
139 
140 	override double elapsedTime() @property //in seconds
141 	{
142 		return glfwGetTime();
143 	}
144 
145 	override void reshape(ivec2 viewportSize)
146 	{
147 		glViewport(0, 0, viewportSize.x, viewportSize.y);
148 	}
149 
150 	override void releaseWindow()
151 	{
152 		glfwDestroyWindow(glfwWindowPtr);
153 		glfwTerminate();
154 	}
155 
156 	override void mousePosition(ivec2 newPosition) @property
157 	{
158 		glfwSetCursorPos(glfwWindowPtr, newPosition.x, newPosition.y);
159 	}
160 
161 	override ivec2 mousePosition() @property
162 	{
163 		double x, y;
164 		glfwGetCursorPos(glfwWindowPtr, &x, &y);
165 		return ivec2(x, y) * pixelSize;
166 	}
167 
168 	override void setVsync(bool value)
169 	{
170 		glfwSwapInterval(value);
171 	}
172 
173 	override void swapBuffers()
174 	{
175 		glfwSwapBuffers(glfwWindowPtr);
176 	}
177 
178 	override void size(ivec2 newSize) @property
179 	{
180 		glfwSetWindowSize(glfwWindowPtr, newSize.x, newSize.y);
181 	}
182 
183 	override ivec2 size() @property
184 	{
185 		int width, height;
186 		glfwGetWindowSize(glfwWindowPtr, &width, &height);
187 		return ivec2(width, height);
188 	}
189 
190 	override ivec2 framebufferSize() @property
191 	{
192 		int width, height;
193 		glfwGetFramebufferSize(glfwWindowPtr, &width, &height);
194 		return ivec2(width, height);
195 	}
196 
197 	ivec2 pixelSize()
198 	{
199 		return framebufferSize / size;
200 	}
201 
202 	override string clipboardString() @property
203 	{
204 		const(char*) data = glfwGetClipboardString(glfwWindowPtr);
205 		if (data is null) return "";
206 		return cast(string)fromStringz(data);
207 	}
208 
209 	override void clipboardString(string newClipboardString) @property
210 	{
211 		glfwSetClipboardString(glfwWindowPtr, toStringz(newClipboardString));
212 	}
213 
214 	override bool isKeyPressed(uint key)
215 	{
216 		return glfwGetKey(glfwWindowPtr, key) == GLFW_PRESS;
217 	}
218 
219 	override void isCursorLocked(bool locked)
220 	{
221 		glfwSetInputMode(glfwWindowPtr, GLFW_CURSOR, locked ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL);
222 	}
223 
224 	override bool isCursorLocked()
225 	{
226 		return glfwGetInputMode(glfwWindowPtr, GLFW_CURSOR) == GLFW_CURSOR_DISABLED;
227 	}
228 
229 	override void setCursorIcon(CursorIcon icon)
230 	{
231 		glfwSetCursor(glfwWindowPtr, cursors[icon]);
232 	}
233 
234 	GLFWwindow* handle() @property
235 	{
236 		return glfwWindowPtr;
237 	}
238 
239 private:
240 	static void initGlfw()
241 	{
242 		glfwSetErrorCallback(&errorfun);
243 
244 		if (glfwInit() == 0)
245 		{
246 			throw new Error("Error initializing GLFW3"); //TODO: add proper error handling
247 		}
248 		glfwInited = true;
249 	}
250 }
251 
252 GlfwWindow getWinFromUP(GLFWwindow* w) nothrow
253 {
254 	GlfwWindow win;
255 	win = cast(GlfwWindow) glfwGetWindowUserPointer(w);
256 	return win;
257 }
258 
259 extern(C) nothrow
260 {
261 	void errorfun(int errorCode, const(char)* msg)
262 	{
263 		throw new Error(format("GLFW error [%s] : %s", errorCode, fromStringz(msg)));
264 	}
265 	void windowposfun(GLFWwindow* w, int nx, int ny)
266 	{
267 		try getWinFromUP(w).windowMoved.emit(ivec2(nx, ny));
268 		catch(Exception e) throw new Error(to!string(e));
269 	}
270 	void windowsizefun(GLFWwindow* w, int newWidth, int newHeight)
271 	{
272 		try getWinFromUP(w).windowResized.emit(ivec2(newWidth, newHeight));
273 		catch(Exception e) throw new Error(to!string(e));
274 	}
275 	void windowclosefun(GLFWwindow* w)
276 	{
277 		try getWinFromUP(w).closePressed.emit();
278 		catch(Exception e) throw new Error(to!string(e));
279 	}
280 	void windowrefreshfun(GLFWwindow* w)
281 	{
282 
283 	}
284 	void windowfocusfun(GLFWwindow* w, int focus)
285 	{
286 		try getWinFromUP(w).focusChanged.emit(cast(bool)focus);
287 		catch(Exception e) throw new Error(to!string(e));
288 	}
289 	void windowiconifyfun(GLFWwindow* w, int iconified)
290 	{
291 		try getWinFromUP(w).windowIconified.emit(cast(bool)iconified);
292 		catch(Exception e) throw new Error(to!string(e));
293 	}
294 	void mousebuttonfun(GLFWwindow* w, int mouseButton, int action, int mods)
295 	{
296 		try
297 		{
298 			if(action == GLFW_RELEASE)
299 			{
300 				getWinFromUP(w).mouseReleased.emit(cast(PointerButton)mouseButton, mods);
301 			}
302 			else
303 			{
304 				getWinFromUP(w).mousePressed.emit(cast(PointerButton)mouseButton, mods);
305 			}
306 		}
307 		catch(Exception e) throw new Error(to!string(e));
308 	}
309 	void cursorposfun(GLFWwindow* w, double nx, double ny)
310 	{
311 		try getWinFromUP(w).mouseMoved.emit(ivec2(cast(int)nx, cast(int)ny));
312 		catch(Exception e) throw new Error(to!string(e));
313 	}
314 	void scrollfun(GLFWwindow* w, double x, double y)
315 	{
316 		try getWinFromUP(w).wheelScrolled.emit(dvec2(x, y));
317 		catch(Exception e) throw new Error(to!string(e));
318 	}
319 	void cursorenterfun(GLFWwindow* w, int)
320 	{
321 
322 	}
323 	void keyfun(GLFWwindow* w, int key, int, int action, int mods)
324 	{
325 		if (key < 0) return;
326 		try
327 		{
328 			if (action == GLFW_RELEASE)
329 			{
330 				getWinFromUP(w).keyReleased.emit(cast(KeyCode)key, mods);
331 			}
332 			else
333 			{
334 				getWinFromUP(w).keyPressed.emit(cast(KeyCode)key, mods);
335 			}
336 		}
337 		catch(Exception e) throw new Error(to!string(e));
338 	}
339 	void charfun(GLFWwindow* w, uint unicode)
340 	{
341 	    if (unicode > 0 && unicode < 0x10000) {
342 			try getWinFromUP(w).charEntered.emit(cast(dchar)unicode);
343 			catch(Exception e) throw new Error(to!string(e));
344 		}
345 	}
346 	void refreshfun(GLFWwindow* w)
347 	{
348 		try getWinFromUP(w).refresh.emit();
349 		catch(Exception e) throw new Error(to!string(e));
350 	}
351 }