1 /**
2 Copyright: Copyright (c) 2015-2016 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko, Stephan Dilly (imgui_d_test).
5 */
6 module voxelman.imgui_glfw;
7 
8 import derelict.imgui.imgui;
9 import derelict.opengl3.gl3;
10 import derelict.glfw3.glfw3;
11 
12 struct ImguiState
13 {
14 	GLFWwindow* window;
15 	double      time = 0.0f;
16 	bool[3]     mousePressed;
17 	float       mouseWheel = 0.0f;
18 	GLuint      fontTexture = 0;
19 	int         shaderHandle = 0;
20 	int         vertHandle = 0;
21 	int         fragHandle = 0;
22 	int         attribLocationTex = 0;
23 	int         attribLocationProjMtx = 0;
24 	int         attribLocationPosition = 0;
25 	int         attribLocationUV = 0;
26 	int         attribLocationColor = 0;
27 	uint        vboHandle;
28 	uint        vaoHandle;
29 	uint        elementsHandle;
30 	ClipboardHelper clipboardHelper;
31 
32 	void init(GLFWwindow* window)
33 	{
34 		this.window = window;
35 		clipboardHelper.window = window;
36 
37 		ImGuiIO* io = igGetIO();
38 
39 		io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB;
40 		io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT;
41 		io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT;
42 		io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP;
43 		io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN;
44 		io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME;
45 		io.KeyMap[ImGuiKey_End] = GLFW_KEY_END;
46 		io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE;
47 		io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE;
48 		io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER;
49 		io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE;
50 		io.KeyMap[ImGuiKey_A] = GLFW_KEY_A;
51 		io.KeyMap[ImGuiKey_C] = GLFW_KEY_C;
52 		io.KeyMap[ImGuiKey_V] = GLFW_KEY_V;
53 		io.KeyMap[ImGuiKey_X] = GLFW_KEY_X;
54 		io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y;
55 		io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z;
56 
57 		io.SetClipboardTextFn = &clipboardHelper.setClipboardText;
58 		io.GetClipboardTextFn = &clipboardHelper.getClipboardText;
59 	}
60 
61 	void newFrame()
62 	{
63 		if (!fontTexture)
64 			createDeviceObjects();
65 
66 		auto io = igGetIO();
67 
68 		// Setup display size (every frame to accommodate for window resizing)
69 		int w, h;
70 		int display_w, display_h;
71 		glfwGetWindowSize(window, &w, &h);
72 		glfwGetFramebufferSize(window, &display_w, &display_h);
73 		io.DisplaySize = ImVec2(cast(float)display_w, cast(float)display_h);
74 
75 		// Setup time step
76 		double current_time =  glfwGetTime();
77 		io.DeltaTime = time > 0.0 ? cast(float)(current_time - time) : cast(float)(1.0f/60.0f);
78 		time = current_time;
79 
80 		// Setup inputs
81 		// (we already got mouse wheel, keyboard keys & characters from glfw callbacks polled in glfwPollEvents())
82 		if (glfwGetWindowAttrib(window, GLFW_FOCUSED))
83 		{
84 			double mouse_x, mouse_y;
85 			glfwGetCursorPos(window, &mouse_x, &mouse_y);
86 			mouse_x *= cast(float)display_w / w;                        // Convert mouse coordinates to pixels
87 			mouse_y *= cast(float)display_h / h;
88 			io.MousePos = ImVec2(mouse_x, mouse_y);   // Mouse position, in pixels (set to -1,-1 if no mouse / on another screen, etc.)
89 
90 			io.KeyShift = glfwGetKey(window, GLFW_KEY_LSHIFT) || glfwGetKey(window, GLFW_KEY_RSHIFT);
91 			io.KeyCtrl = glfwGetKey(window, GLFW_KEY_LCTRL) || glfwGetKey(window, GLFW_KEY_RCTRL);
92 			io.KeyAlt = glfwGetKey(window, GLFW_KEY_LALT) || glfwGetKey(window, GLFW_KEY_RALT);
93 		}
94 		else
95 		{
96 			io.MousePos = ImVec2(-1,-1);
97 		}
98 
99 		for (int i = 0; i < 3; i++)
100 		{
101 			io.MouseDown[i] = mousePressed[i] || glfwGetMouseButton(window, i) != 0;    // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame.
102 			mousePressed[i] = false;
103 		}
104 
105 		io.MouseWheel = mouseWheel;
106 		mouseWheel = 0.0f;
107 
108 		// Hide/show hardware mouse cursor
109 		glfwSetInputMode(window, GLFW_CURSOR, io.MouseDrawCursor ? GLFW_CURSOR_HIDDEN : GLFW_CURSOR_NORMAL);
110 
111 		igNewFrame();
112 	}
113 
114 	bool mouseCaptured() @property
115 	{
116 		return igGetIO().WantCaptureMouse;
117 	}
118 
119 	bool keyboardCaptured() @property
120 	{
121 		return igGetIO().WantCaptureKeyboard || igGetIO().WantTextInput;
122 	}
123 
124 	void render()
125 	{
126 		igRender();
127 		renderDrawLists(igGetDrawData());
128 	}
129 
130 	void renderDrawLists(ImDrawData* data)
131 	{
132 		// Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled
133 		GLint last_program, last_texture;
134 		glGetIntegerv(GL_CURRENT_PROGRAM, &last_program);
135 		glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
136 		glEnable(GL_BLEND);
137 		glBlendEquation(GL_FUNC_ADD);
138 		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
139 		glDisable(GL_CULL_FACE);
140 		glDisable(GL_DEPTH_TEST);
141 		glEnable(GL_SCISSOR_TEST);
142 		glActiveTexture(GL_TEXTURE0);
143 
144 		auto io = igGetIO();
145 		// Setup orthographic projection matrix
146 		const float width = io.DisplaySize.x;
147 		const float height = io.DisplaySize.y;
148 		const float[4][4] ortho_projection =
149 		[
150 			[ 2.0f/width,	0.0f,			0.0f,		0.0f ],
151 			[ 0.0f,			2.0f/-height,	0.0f,		0.0f ],
152 			[ 0.0f,			0.0f,			-1.0f,		0.0f ],
153 			[ -1.0f,		1.0f,			0.0f,		1.0f ],
154 		];
155 		glUseProgram(shaderHandle);
156 		glUniform1i(attribLocationTex, 0);
157 		glUniformMatrix4fv(attribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]);
158 
159 		glBindVertexArray(vaoHandle);
160 		glBindBuffer(GL_ARRAY_BUFFER, vboHandle);
161 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementsHandle);
162 
163 		foreach (n; 0..data.CmdListsCount)
164 		{
165 			ImDrawList* cmd_list = data.CmdLists[n];
166 			ImDrawIdx* idx_buffer_offset;
167 
168 			auto countVertices = ImDrawList_GetVertexBufferSize(cmd_list);
169 			auto countIndices = ImDrawList_GetIndexBufferSize(cmd_list);
170 
171 			glBufferData(GL_ARRAY_BUFFER, countVertices * ImDrawVert.sizeof, cast(GLvoid*)ImDrawList_GetVertexPtr(cmd_list,0), GL_STREAM_DRAW);
172 			glBufferData(GL_ELEMENT_ARRAY_BUFFER, countIndices * ImDrawIdx.sizeof, cast(GLvoid*)ImDrawList_GetIndexPtr(cmd_list,0), GL_STREAM_DRAW);
173 
174 			auto cmdCnt = ImDrawList_GetCmdSize(cmd_list);
175 
176 			foreach(i; 0..cmdCnt)
177 			{
178 				auto pcmd = ImDrawList_GetCmdPtr(cmd_list, i);
179 
180 				if (pcmd.UserCallback)
181 				{
182 					pcmd.UserCallback(cmd_list, pcmd);
183 				}
184 				else
185 				{
186 					glBindTexture(GL_TEXTURE_2D, cast(GLuint)pcmd.TextureId);
187 					glScissor(cast(int)pcmd.ClipRect.x, cast(int)(height - pcmd.ClipRect.w), cast(int)(pcmd.ClipRect.z - pcmd.ClipRect.x), cast(int)(pcmd.ClipRect.w - pcmd.ClipRect.y));
188 					glDrawElements(GL_TRIANGLES, pcmd.ElemCount, GL_UNSIGNED_SHORT, idx_buffer_offset);
189 				}
190 
191 				idx_buffer_offset += pcmd.ElemCount;
192 			}
193 		}
194 
195 		// Restore modified state
196 		glBindVertexArray(0);
197 		glBindBuffer(GL_ARRAY_BUFFER, 0);
198 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
199 		glUseProgram(last_program);
200 		glDisable(GL_SCISSOR_TEST);
201 		glBindTexture(GL_TEXTURE_2D, last_texture);
202 	}
203 
204 	void createDeviceObjects()
205 	{
206 		const GLchar* vertex_shader =
207 			"#version 330\n"
208 				"uniform mat4 ProjMtx;\n"
209 				"in vec2 Position;\n"
210 				"in vec2 UV;\n"
211 				"in vec4 Color;\n"
212 				"out vec2 Frag_UV;\n"
213 				"out vec4 Frag_Color;\n"
214 				"void main()\n"
215 				"{\n"
216 				"	Frag_UV = UV;\n"
217 				"	Frag_Color = Color;\n"
218 				"	gl_Position = ProjMtx * vec4(Position.xy,0,1);\n"
219 				"}\n";
220 
221 		const GLchar* fragment_shader =
222 			"#version 330\n"
223 				"uniform sampler2D Texture;\n"
224 				"in vec2 Frag_UV;\n"
225 				"in vec4 Frag_Color;\n"
226 				"out vec4 Out_Color;\n"
227 				"void main()\n"
228 				"{\n"
229 				"	Out_Color = Frag_Color * texture( Texture, Frag_UV.st);\n"
230 				"}\n";
231 
232 		shaderHandle = glCreateProgram();
233 		vertHandle = glCreateShader(GL_VERTEX_SHADER);
234 		fragHandle = glCreateShader(GL_FRAGMENT_SHADER);
235 		glShaderSource(vertHandle, 1, &vertex_shader, null);
236 		glShaderSource(fragHandle, 1, &fragment_shader, null);
237 		glCompileShader(vertHandle);
238 		glCompileShader(fragHandle);
239 		glAttachShader(shaderHandle, vertHandle);
240 		glAttachShader(shaderHandle, fragHandle);
241 		glLinkProgram(shaderHandle);
242 
243 		attribLocationTex = glGetUniformLocation(shaderHandle, "Texture");
244 		attribLocationProjMtx = glGetUniformLocation(shaderHandle, "ProjMtx");
245 		attribLocationPosition = glGetAttribLocation(shaderHandle, "Position");
246 		attribLocationUV = glGetAttribLocation(shaderHandle, "UV");
247 		attribLocationColor = glGetAttribLocation(shaderHandle, "Color");
248 
249 		glGenBuffers(1, &vboHandle);
250 		glGenBuffers(1, &elementsHandle);
251 
252 		glGenVertexArrays(1, &vaoHandle);
253 		glBindVertexArray(vaoHandle);
254 		glBindBuffer(GL_ARRAY_BUFFER, vboHandle);
255 		glEnableVertexAttribArray(attribLocationPosition);
256 		glEnableVertexAttribArray(attribLocationUV);
257 		glEnableVertexAttribArray(attribLocationColor);
258 
259 		glVertexAttribPointer(attribLocationPosition, 2, GL_FLOAT, GL_FALSE, ImDrawVert.sizeof, cast(void*)0);
260 		glVertexAttribPointer(attribLocationUV, 2, GL_FLOAT, GL_FALSE, ImDrawVert.sizeof, cast(void*)ImDrawVert.uv.offsetof);
261 		glVertexAttribPointer(attribLocationColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, ImDrawVert.sizeof, cast(void*)ImDrawVert.col.offsetof);
262 
263 		glBindVertexArray(0);
264 		glBindBuffer(GL_ARRAY_BUFFER, 0);
265 
266 		createFontsTexture();
267 	}
268 
269 	void createFontsTexture()
270 	{
271 		ImGuiIO* io = igGetIO();
272 
273 		ubyte* pixels;
274 		int width, height;
275 		ImFontAtlas_GetTexDataAsRGBA32(io.Fonts,&pixels,&width,&height,null);
276 
277 		glGenTextures(1, &fontTexture);
278 		glBindTexture(GL_TEXTURE_2D, fontTexture);
279 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
280 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
281 		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
282 
283 		// Store our identifier
284 		ImFontAtlas_SetTexID(io.Fonts, cast(void*)fontTexture);
285 	}
286 
287 	void onMousePressed(uint button)
288 	{
289 		mousePressed[button] = true;
290 	}
291 
292 	void onMouseReleased(uint button)
293 	{
294 		mousePressed[button] = false;
295 	}
296 
297 	void scrollCallback(float scroll)
298 	{
299 		mouseWheel += scroll;
300 	}
301 
302 	void onKeyPressed(uint key)
303 	{
304 		igGetIO().KeysDown[key] = true;
305 	}
306 
307 	void onKeyReleased(uint key)
308 	{
309 		igGetIO().KeysDown[key] = false;
310 	}
311 
312 	void charCallback(dchar c)
313 	{
314 		if (c > 0 && c < 0x10000)
315 			ImGuiIO_AddInputCharacter(cast(ushort)c);
316 	}
317 
318 	void shutdown()
319 	{
320 		if (vaoHandle) glDeleteVertexArrays(1, &vaoHandle);
321 		if (vboHandle) glDeleteBuffers(1, &vboHandle);
322 		if (elementsHandle) glDeleteBuffers(1, &elementsHandle);
323 		vaoHandle = 0;
324 		vboHandle = 0;
325 		elementsHandle = 0;
326 
327 		glDetachShader(shaderHandle, vertHandle);
328 		glDeleteShader(vertHandle);
329 		vertHandle = 0;
330 
331 		glDetachShader(shaderHandle, fragHandle);
332 		glDeleteShader(fragHandle);
333 		fragHandle = 0;
334 
335 		glDeleteProgram(shaderHandle);
336 		shaderHandle = 0;
337 
338 		if (fontTexture)
339 		{
340 			glDeleteTextures(1, &fontTexture);
341 			ImFontAtlas_SetTexID(igGetIO().Fonts, cast(void*)0);
342 			fontTexture = 0;
343 		}
344 
345 		igShutdown();
346 	}
347 }
348 
349 GLFWwindow* startGlfw(string windowTitle, int w, int h)
350 {
351 	// Setup window
352 	glfwSetErrorCallback(&error_callback);
353 	if (!glfwInit())
354 		return null;
355 	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
356 	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
357 	//glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
358 	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, true);
359 	glfwWindowHint(GLFW_VISIBLE, false);
360 	auto window = glfwCreateWindow(w, h, windowTitle.ptr, null, null);
361 	glfwMakeContextCurrent(window);
362 
363 	DerelictGL3.reload();
364 
365 	return window;
366 }
367 
368 extern(C) nothrow void error_callback(int error, const(char)* description)
369 {
370 	import std.stdio;
371 	import std.conv;
372 	try writefln("glfw err: %s ('%s')", error, to!string(description));
373 	catch{}
374 }
375 
376 struct ClipboardHelper
377 {
378 	static GLFWwindow* window;
379 
380 	static extern(C) nothrow
381 	const(char)* getClipboardText()
382 	{
383 		return glfwGetClipboardString(window);
384 	}
385 
386 	static extern(C) nothrow
387 	void setClipboardText(const(char)* text)
388 	{
389 		glfwSetClipboardString(window, text);
390 	}
391 }