1 module unecht.core.components.internal.gui;
2 
3 import std..string:toStringz;
4 
5 import unecht.core.component;
6 import unecht.core.components.sceneNode;
7 import unecht.core.events;
8 import unecht.core.logger;
9 
10 import gl3n.linalg;
11 
12 import derelict.imgui.imgui;
13 
14 import derelict.opengl3.gl3;
15 import derelict.glfw3.glfw3;
16 
17 ///
18 final class UEGui : UEComponent
19 {
20 public:
21 	mixin(UERegisterObject!());
22 
23 	override void onCreate() {
24 		import unecht.ue:ue;
25 		import unecht.core.hideFlags:HideFlags;
26 
27 		sceneNode.hideFlags.set(HideFlags.hideInHirarchie);
28 
29 		registerEvent(UEEventType.text, &OnCharInput);
30 		registerEvent(UEEventType.key, &OnKeyInput);
31 		registerEvent(UEEventType.mouseScroll, &OnScrollInput);
32 
33 		g_window = cast(GLFWwindow*)ue.application.mainWindow.windowPtr;
34 
35 		ImGuiIO* io = igGetIO();
36 
37 		io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB;                 // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array.
38 		io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT;
39 		io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT;
40 		io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP;
41 		io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN;
42 		io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME;
43 		io.KeyMap[ImGuiKey_End] = GLFW_KEY_END;
44 		io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE;
45 		io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE;
46 		io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER;
47 		io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE;
48 		io.KeyMap[ImGuiKey_A] = GLFW_KEY_A;
49 		io.KeyMap[ImGuiKey_C] = GLFW_KEY_C;
50 		io.KeyMap[ImGuiKey_V] = GLFW_KEY_V;
51 		io.KeyMap[ImGuiKey_X] = GLFW_KEY_X;
52 		io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y;
53 		io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z;
54 
55 		io.RenderDrawListsFn = &renderDrawLists;
56 		if(ue.application.mainWindow.isRetina)
57 			io.FontGlobalScale = 2;
58 	}
59 
60 	///
61 	public static @property bool capturesMouse() { return g_capturesMouse; }
62 	///
63 	public static @property float framerate() { return igGetIO().Framerate; }
64 
65 	private void OnKeyInput(UEEvent event)
66 	{
67 		auto io = igGetIO();
68 
69 		if(cast(uint)event.keyEvent.key >= 512)
70 		{
71 
72 			log.errorf("invalid key: %s",event.keyEvent.key);
73 			return;
74 		}
75 
76 		if (UEEvent.KeyEvent.Action.Down == event.keyEvent.action)
77 			io.KeysDown[event.keyEvent.key] = true;
78 		if (UEEvent.KeyEvent.Action.Up == event.keyEvent.action)
79 			io.KeysDown[event.keyEvent.key] = false;
80 
81 		io.KeyCtrl = event.keyEvent.mods.isModCtrl;
82 		io.KeyShift = event.keyEvent.mods.isModShift;
83 		io.KeyAlt = event.keyEvent.mods.isModAlt;
84 	}
85 
86 	private void OnCharInput(UEEvent event)
87 	{
88 		auto utfchar = cast(uint)event.textEvent.character;
89 
90 		if (utfchar > 0 && utfchar < 0x10000)
91 		{
92 			ImGuiIO_AddInputCharacter(cast(ushort)utfchar);
93 		}
94 	}
95 
96 	private void OnScrollInput(UEEvent event)
97 	{
98 		if(event.mouseScrollEvent.yoffset != 0)
99 		{
100 			g_MouseWheel += cast(float)event.mouseScrollEvent.yoffset;
101 		}
102 	}
103 
104 	static bool TreeNode(string txt)
105 	{
106 		return igTreeNode(toStringz(txt));
107 	}
108 
109 	static bool TreeNode(const void* pid, string txt)
110 	{
111 		return igTreeNodePtr(pid, toStringz(txt));
112 	}
113 
114 	static bool checkbox(string label, ref bool v)
115 	{
116 		return igCheckbox(toStringz(label), &v);
117 	}
118 
119 	///
120 	static bool EnumCombo(T)(string label, ref T v)
121 		if(is(T == enum))
122 	{
123 		enum enumMembers = __traits(allMembers, T);
124 		enum enumElemCount = enumMembers.length;
125 
126 		static string[enumElemCount] enumMemberNames = void;
127 		foreach(i,enumMember; enumMembers)
128 			enumMemberNames[i] = enumMember;
129 
130 		static extern(C) bool getItemText(void* data, int idx, const(char)** outText) nothrow
131 		{
132 			*outText = toStringz(enumMemberNames[idx]);
133 			return true;
134 		}
135 
136 		int currentItem = 0;
137 
138 		import std.traits;
139 		foreach(i,enumMember; EnumMembers!T)
140 			if(enumMember == v)
141 				currentItem = i;
142 
143 		auto res = igCombo3(toStringz(label),&currentItem,&getItemText,null,enumElemCount);
144 
145 		v = cast(T)currentItem;
146 		return res;
147 	}
148 
149 	static bool InputVec(string label, ref vec2 v)
150 	{
151 		return igDragFloat2(toStringz(label), v.vector);
152 	}
153 
154 	static bool InputVec(string label, ref vec3 v)
155 	{
156 		return igDragFloat3(toStringz(label), v.vector);
157 	}
158 
159 	static bool DragFloat(string label, ref float v, float min=0.0f, float max=0.0f)
160 	{
161 		return igDragFloat(toStringz(label),&v,1,min,max);
162 	}
163 
164 	static bool DragInt(string label, ref int v, int min=0, int max=0)
165 	{
166 		return igDragInt(toStringz(label),&v,1,min,max);
167 	}
168 
169 	static bool InputText(int MaxLength=64)(string label, ref string v)
170 	{
171 		assert(v.length <= MaxLength);
172 
173 		static char[MaxLength] buf;
174 
175 		buf[] = '\0';
176 		buf[0..v.length] = v[];
177 
178 		auto res = igInputText(toStringz(label), buf.ptr, MaxLength, ImGuiInputTextFlags_EnterReturnsTrue);
179 
180 		import std.c..string:strlen;
181 		auto newLength = strlen(buf.ptr);
182 
183 		//note: try to avoid alloc when nothing changed
184 		if(v.length != newLength || buf[0..v.length] != v)
185 		{
186 			v = cast(string)buf[0..newLength].idup;
187 		}
188 
189 		return res;
190 	}
191 
192 	static void Text(string txt)
193 	{
194 		igText(toStringz(txt));
195 	}
196 
197 	static void BulletText(string txt)
198 	{
199 		igBulletText(toStringz(txt));
200 	}
201 
202 	static bool Button(string txt)
203 	{
204 		return igButton(toStringz(txt));
205 	}
206 
207 	static bool DisabledButton(string txt)
208 	{
209 		igPushStyleColor(ImGuiCol_Button, ImVec4(0.1f, 0.1f, 0.1f, 0.6f));
210 		scope(exit)igPopStyleColor(1);
211 
212 		Button(txt);
213 		return false;
214 	}
215 
216 	static bool ImageAtlasButton(void* texId, ImVec2 buttonSize, ImVec2 tileSize, ImVec2 texSize, ImVec2 tileIdx, bool enabled)
217 	{
218 		if(!enabled)
219 			igPushStyleColor(ImGuiCol_Button, ImVec4(0.1f, 0.1f, 0.1f, 0.6f));
220 
221 		scope(exit){if(!enabled)igPopStyleColor(1);}
222 
223 		ImVec2 uvTileSize = ImVec2(1.0f/(texSize.x/tileSize.x),1.0f/(texSize.y/tileSize.y));
224 		ImVec2 uv0 = ImVec2(uvTileSize.x * tileIdx.x,uvTileSize.y * tileIdx.y);
225 		ImVec2 uv1 = ImVec2(uv0.x + uvTileSize.x,uv0.y + uvTileSize.y);
226 
227 		igPushIdInt(cast(int)texId + cast(int)(tileIdx.x*10.0f));
228 		scope(exit)igPopId();
229 
230 		auto res = igImageButton(texId, buttonSize, uv0, uv1);
231 
232 		return enabled && res;
233 	}
234 
235 	static bool Selectable(string txt, bool selected)
236 	{
237 		return igSelectable(toStringz(txt), selected);
238 	}
239 
240 	static bool SmallButton(string txt)
241 	{
242 		return igSmallButton(toStringz(txt));
243 	}
244 
245 	static void startFrame()
246 	{
247 		if (!g_FontTexture)
248 			createDeviceObjects();
249 
250 		auto io = igGetIO();
251 
252 		// Setup display size (every frame to accommodate for window resizing)
253 		int w, h;
254 		int display_w, display_h;
255 		glfwGetWindowSize(g_window, &w, &h);
256 		glfwGetFramebufferSize(g_window, &display_w, &display_h);
257 		io.DisplaySize = ImVec2(cast(float)display_w, cast(float)display_h);
258 
259 		// Setup time step
260 		double current_time =  glfwGetTime();
261 		io.DeltaTime = g_Time > 0.0 ? cast(float)(current_time - g_Time) : cast(float)(1.0f/60.0f);
262 		g_Time = current_time;
263 
264 		// Setup inputs
265 		// (we already got mouse wheel, keyboard keys & characters from glfw callbacks polled in glfwPollEvents())
266 		if (glfwGetWindowAttrib(g_window, GLFW_FOCUSED))
267 		{
268 			double mouse_x, mouse_y;
269 			glfwGetCursorPos(g_window, &mouse_x, &mouse_y);
270 			mouse_x *= cast(float)display_w / w;                        // Convert mouse coordinates to pixels
271 			mouse_y *= cast(float)display_h / h;
272 			io.MousePos = ImVec2(mouse_x, mouse_y);   // Mouse position, in pixels (set to -1,-1 if no mouse / on another screen, etc.)
273 		}
274 		else
275 		{
276 			io.MousePos = ImVec2(-1,-1);
277 		}
278 
279 		for (int i = 0; i < 3; i++)
280 		{
281 			// If a mouse press event came, always pass it as "mouse held this frame",
282 			// so we don't miss click-release events that are shorter than 1 frame.
283 			io.MouseDown[i] = g_MousePressed[i] || glfwGetMouseButton(g_window, i) != 0;
284 			g_MousePressed[i] = false;
285 		}
286 
287 		io.MouseWheel = g_MouseWheel;
288 		g_MouseWheel = 0.0f;
289 
290 		igNewFrame();
291 
292 		g_capturesMouse = io.WantCaptureMouse;
293 	}
294 
295 	static void renderGUI()
296 	{
297 		igRender();
298 	}
299 
300 private:
301 	static GLFWwindow*  g_window;
302 	static double       g_Time = 0.0f;
303 	static bool[3]      g_MousePressed;
304 	static double       g_MouseWheel = 0.0f;
305 	static GLuint       g_FontTexture = 0;
306 	static int          g_ShaderHandle = 0, g_VertHandle = 0, g_FragHandle = 0;
307 	static int          g_AttribLocationTex = 0, g_AttribLocationProjMtx = 0;
308 	static int          g_AttribLocationPosition = 0, g_AttribLocationUV = 0, g_AttribLocationColor = 0;
309 	static size_t       g_VboMaxSize = 20000;
310 	static uint         g_VboHandle, g_VaoHandle, g_ElementsHandle;
311 	static bool         g_capturesMouse;
312 
313 	static extern(C) nothrow
314 	void renderDrawLists(ImDrawData* data)
315 	{
316 		// Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled
317 		GLint last_program, last_texture;
318 		glGetIntegerv(GL_CURRENT_PROGRAM, &last_program);
319 		glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
320 		glEnable(GL_BLEND);
321 		glBlendEquation(GL_FUNC_ADD);
322 		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
323 		glDisable(GL_CULL_FACE);
324 		glDisable(GL_DEPTH_TEST);
325 		glEnable(GL_SCISSOR_TEST);
326 		glActiveTexture(GL_TEXTURE0);
327 
328 		auto io = igGetIO();
329 		// Setup orthographic projection matrix
330 		const float width = io.DisplaySize.x;
331 		const float height = io.DisplaySize.y;
332 		const float[4][4] ortho_projection =
333 		[
334 			[ 2.0f/width,   0.0f,           0.0f,       0.0f ],
335 			[ 0.0f,         2.0f/-height,   0.0f,       0.0f ],
336 			[ 0.0f,         0.0f,           -1.0f,      0.0f ],
337 			[ -1.0f,        1.0f,           0.0f,       1.0f ],
338 		];
339 		glUseProgram(g_ShaderHandle);
340 		glUniform1i(g_AttribLocationTex, 0);
341 		glUniformMatrix4fv(g_AttribLocationProjMtx, 1, GL_FALSE, &ortho_projection[0][0]);
342 
343 		glBindVertexArray(g_VaoHandle);
344 		glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle);
345 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_ElementsHandle);
346 
347 		foreach (n; 0..data.CmdListsCount)
348 		{
349 			ImDrawList* cmd_list = data.CmdLists[n];
350 			ImDrawIdx* idx_buffer_offset;
351 
352 			auto countVertices = ImDrawList_GetVertexBufferSize(cmd_list);
353 			auto countIndices = ImDrawList_GetIndexBufferSize(cmd_list);
354 
355 			glBufferData(GL_ARRAY_BUFFER, countVertices * ImDrawVert.sizeof, cast(GLvoid*)ImDrawList_GetVertexPtr(cmd_list,0), GL_STREAM_DRAW);
356 			glBufferData(GL_ELEMENT_ARRAY_BUFFER, countIndices * ImDrawIdx.sizeof, cast(GLvoid*)ImDrawList_GetIndexPtr(cmd_list,0), GL_STREAM_DRAW);
357 
358 			auto cmdCnt = ImDrawList_GetCmdSize(cmd_list);
359 
360 			foreach(i; 0..cmdCnt)
361 			{
362 				auto pcmd = ImDrawList_GetCmdPtr(cmd_list, i);
363 
364 				if (pcmd.UserCallback)
365 				{
366 					pcmd.UserCallback(cmd_list, pcmd);
367 				}
368 				else
369 				{
370 					glBindTexture(GL_TEXTURE_2D, cast(GLuint)pcmd.TextureId);
371 					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));
372 					glDrawElements(GL_TRIANGLES, pcmd.ElemCount, GL_UNSIGNED_SHORT, idx_buffer_offset);
373 				}
374 
375 				idx_buffer_offset += pcmd.ElemCount;
376 			}
377 		}
378 
379 		// Restore modified state
380 		glBindVertexArray(0);
381 		glBindBuffer(GL_ARRAY_BUFFER, 0);
382 		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
383 		glUseProgram(last_program);
384 		glDisable(GL_SCISSOR_TEST);
385 		glBindTexture(GL_TEXTURE_2D, last_texture);
386 	}
387 
388 	static void createDeviceObjects()
389 	{
390 		const GLchar *vertex_shader =
391 			"#version 330\n" ~
392 			"uniform mat4 ProjMtx;\n" ~
393 			"in vec2 Position;\n" ~
394 			"in vec2 UV;\n" ~
395 			"in vec4 Color;\n" ~
396 			"out vec2 Frag_UV;\n" ~
397 			"out vec4 Frag_Color;\n" ~
398 			"void main()\n" ~
399 			"{\n" ~
400 			"   Frag_UV = UV;\n" ~
401 			"   Frag_Color = Color;\n" ~
402 			"   gl_Position = ProjMtx * vec4(Position.xy,0,1);\n" ~
403 			"}\n";
404 
405 		const GLchar* fragment_shader =
406 			"#version 330\n" ~
407 			"uniform sampler2D Texture;\n" ~
408 			"in vec2 Frag_UV;\n" ~
409 			"in vec4 Frag_Color;\n" ~
410 			"out vec4 Out_Color;\n" ~
411 			"void main()\n" ~
412 			"{\n" ~
413 			"   Out_Color = Frag_Color * texture( Texture, Frag_UV.st);\n" ~
414 			"}\n";
415 
416 		g_ShaderHandle = glCreateProgram();
417 		g_VertHandle = glCreateShader(GL_VERTEX_SHADER);
418 		g_FragHandle = glCreateShader(GL_FRAGMENT_SHADER);
419 		glShaderSource(g_VertHandle, 1, &vertex_shader, null);
420 		glShaderSource(g_FragHandle, 1, &fragment_shader, null);
421 		glCompileShader(g_VertHandle);
422 		glCompileShader(g_FragHandle);
423 		glAttachShader(g_ShaderHandle, g_VertHandle);
424 		glAttachShader(g_ShaderHandle, g_FragHandle);
425 		glLinkProgram(g_ShaderHandle);
426 
427 		g_AttribLocationTex = glGetUniformLocation(g_ShaderHandle, "Texture");
428 		g_AttribLocationProjMtx = glGetUniformLocation(g_ShaderHandle, "ProjMtx");
429 		g_AttribLocationPosition = glGetAttribLocation(g_ShaderHandle, "Position");
430 		g_AttribLocationUV = glGetAttribLocation(g_ShaderHandle, "UV");
431 		g_AttribLocationColor = glGetAttribLocation(g_ShaderHandle, "Color");
432 
433 		glGenBuffers(1, &g_VboHandle);
434 		glGenBuffers(1, &g_ElementsHandle);
435 
436 		glGenVertexArrays(1, &g_VaoHandle);
437 		glBindVertexArray(g_VaoHandle);
438 		glBindBuffer(GL_ARRAY_BUFFER, g_VboHandle);
439 		glEnableVertexAttribArray(g_AttribLocationPosition);
440 		glEnableVertexAttribArray(g_AttribLocationUV);
441 		glEnableVertexAttribArray(g_AttribLocationColor);
442 
443 		glVertexAttribPointer(g_AttribLocationPosition, 2, GL_FLOAT, GL_FALSE, ImDrawVert.sizeof, cast(void*)0);
444 		glVertexAttribPointer(g_AttribLocationUV, 2, GL_FLOAT, GL_FALSE, ImDrawVert.sizeof, cast(void*)ImDrawVert.uv.offsetof);
445 		glVertexAttribPointer(g_AttribLocationColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, ImDrawVert.sizeof, cast(void*)ImDrawVert.col.offsetof);
446 
447 		glBindVertexArray(0);
448 		glBindBuffer(GL_ARRAY_BUFFER, 0);
449 
450 		createFontsTexture();
451 	}
452 
453 	static void createFontsTexture()
454 	{
455 		ImGuiIO* io = igGetIO();
456 
457 		ubyte* pixels;
458 		int width, height;
459 		ImFontAtlas_GetTexDataAsRGBA32(io.Fonts,&pixels,&width,&height,null);
460 
461 		glGenTextures(1, &g_FontTexture);
462 		glBindTexture(GL_TEXTURE_2D, g_FontTexture);
463 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
464 		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
465 		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
466 
467 		// Store our identifier
468 		ImFontAtlas_SetTexID(io.Fonts, cast(void*)g_FontTexture);
469 	}
470 }