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),¤tItem,&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 }