From d35ceb793e7a08d070714f15de79ce82377c3f0b Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 25 Sep 2014 11:35:23 +0100 Subject: [PATCH] OpenGL example: added commented code to load custom font from filesystem --- examples/opengl_example/main - Copy.cpp | 312 ++++++++++++++++++++++++ imgui.cpp | 134 +++++----- 2 files changed, 379 insertions(+), 67 deletions(-) create mode 100644 examples/opengl_example/main - Copy.cpp diff --git a/examples/opengl_example/main - Copy.cpp b/examples/opengl_example/main - Copy.cpp new file mode 100644 index 000000000..58ced640f --- /dev/null +++ b/examples/opengl_example/main - Copy.cpp @@ -0,0 +1,312 @@ +#define GLEW_STATIC +#include +#include +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" // for .png loading +#include "../../imgui.h" +#ifdef _MSC_VER +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen +#endif + +static GLFWwindow* window; +static GLuint fontTex; +static ImVec2 mousePosScale(1.0f, 1.0f); + +// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structure) +// If text or lines are blurry when integrating ImGui in your engine: +// - in your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f) +// - try adjusting ImGui::GetIO().PixelCenterOffset to 0.5f or 0.375f +static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) +{ + if (cmd_lists_count == 0) + return; + + // We are using the OpenGL fixed pipeline to make the example code simpler to read! + // A probable faster way to render would be to collate all vertices from all cmd_lists into a single vertex buffer. + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers. + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glEnable(GL_SCISSOR_TEST); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + // Setup texture + glBindTexture(GL_TEXTURE_2D, fontTex); + glEnable(GL_TEXTURE_2D); + + // Setup orthographic projection matrix + const float width = ImGui::GetIO().DisplaySize.x; + const float height = ImGui::GetIO().DisplaySize.y; + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0.0f, width, height, 0.0f, -1.0f, +1.0f); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + // Render command lists + for (int n = 0; n < cmd_lists_count; n++) + { + const ImDrawList* cmd_list = cmd_lists[n]; + const unsigned char* vtx_buffer = (const unsigned char*)cmd_list->vtx_buffer.begin(); + glVertexPointer(2, GL_FLOAT, sizeof(ImDrawVert), (void*)(vtx_buffer)); + glTexCoordPointer(2, GL_FLOAT, sizeof(ImDrawVert), (void*)(vtx_buffer+8)); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(ImDrawVert), (void*)(vtx_buffer+16)); + + int vtx_offset = 0; + const ImDrawCmd* pcmd_end = cmd_list->commands.end(); + for (const ImDrawCmd* pcmd = cmd_list->commands.begin(); pcmd != pcmd_end; pcmd++) + { + glScissor((int)pcmd->clip_rect.x, (int)(height - pcmd->clip_rect.w), (int)(pcmd->clip_rect.z - pcmd->clip_rect.x), (int)(pcmd->clip_rect.w - pcmd->clip_rect.y)); + glDrawArrays(GL_TRIANGLES, vtx_offset, pcmd->vtx_count); + vtx_offset += pcmd->vtx_count; + } + } + glDisable(GL_SCISSOR_TEST); + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); +} + +static const char* ImImpl_GetClipboardTextFn() +{ + return glfwGetClipboardString(window); +} + +static void ImImpl_SetClipboardTextFn(const char* text, const char* text_end) +{ + if (!text_end) + text_end = text + strlen(text); + + if (*text_end == 0) + { + // Already got a zero-terminator at 'text_end', we don't need to add one + glfwSetClipboardString(window, text); + } + else + { + // Add a zero-terminator because glfw function doesn't take a size + char* buf = (char*)malloc(text_end - text + 1); + memcpy(buf, text, text_end-text); + buf[text_end-text] = '\0'; + glfwSetClipboardString(window, buf); + free(buf); + } +} + + +// GLFW callbacks to get events +static void glfw_error_callback(int error, const char* description) +{ + fputs(description, stderr); +} + +static void glfw_scroll_callback(GLFWwindow* window, double xoffset, double yoffset) +{ + ImGuiIO& io = ImGui::GetIO(); + io.MouseWheel = (yoffset != 0.0f) ? yoffset > 0.0f ? 1 : - 1 : 0; // Mouse wheel: -1,0,+1 +} + +static void glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) +{ + ImGuiIO& io = ImGui::GetIO(); + if (action == GLFW_PRESS) + io.KeysDown[key] = true; + if (action == GLFW_RELEASE) + io.KeysDown[key] = false; + io.KeyCtrl = (mods & GLFW_MOD_CONTROL) != 0; + io.KeyShift = (mods & GLFW_MOD_SHIFT) != 0; +} + +static void glfw_char_callback(GLFWwindow* window, unsigned int c) +{ + if (c > 0 && c < 0x10000) + ImGui::GetIO().AddInputCharacter((ImWchar)c); +} + +// OpenGL code based on http://open.gl tutorials +void InitGL() +{ + glfwSetErrorCallback(glfw_error_callback); + + if (!glfwInit()) + exit(1); + + glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); + window = glfwCreateWindow(1280, 720, "ImGui OpenGL example", NULL, NULL); + glfwMakeContextCurrent(window); + glfwSetKeyCallback(window, glfw_key_callback); + glfwSetScrollCallback(window, glfw_scroll_callback); + glfwSetCharCallback(window, glfw_char_callback); + + glewInit(); +} + +void InitImGui() +{ + int w, h; + int fb_w, fb_h; + glfwGetWindowSize(window, &w, &h); + glfwGetFramebufferSize(window, &fb_w, &fb_h); + mousePosScale.x = (float)fb_w / w; // Some screens e.g. Retina display have framebuffer size != from window size, and mouse inputs are given in window/screen coordinates. + mousePosScale.y = (float)fb_h / h; + + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize = ImVec2((float)fb_w, (float)fb_h); // Display size, in pixels. For clamping windows positions. + io.DeltaTime = 1.0f/60.0f; // Time elapsed since last frame, in seconds (in this sample app we'll override this every frame because our timestep is variable) + io.PixelCenterOffset = 0.0f; // Align OpenGL texels + io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array. + io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP; + io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN; + io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME; + io.KeyMap[ImGuiKey_End] = GLFW_KEY_END; + io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE; + io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE; + io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER; + io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE; + io.KeyMap[ImGuiKey_A] = GLFW_KEY_A; + io.KeyMap[ImGuiKey_C] = GLFW_KEY_C; + io.KeyMap[ImGuiKey_V] = GLFW_KEY_V; + io.KeyMap[ImGuiKey_X] = GLFW_KEY_X; + io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y; + io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z; + + io.RenderDrawListsFn = ImImpl_RenderDrawLists; + io.SetClipboardTextFn = ImImpl_SetClipboardTextFn; + io.GetClipboardTextFn = ImImpl_GetClipboardTextFn; + + // Load font texture + glGenTextures(1, &fontTex); + glBindTexture(GL_TEXTURE_2D, fontTex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + +#if 0 + // Default font (embedded in code) + const void* png_data; + unsigned int png_size; + ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); + int tex_x, tex_y, tex_comp; + void* tex_data = stbi_load_from_memory((const unsigned char*)png_data, (int)png_size, &tex_x, &tex_y, &tex_comp, 0); +#else + // Custom font from filesystem + io.Font = new ImBitmapFont(); + io.Font->LoadFromFile("../../extra_fonts/arial_unicode_ms_18_CJK.fnt"); + io.FontTexUvForWhite = ImVec2(3.f/255.f,55.f/512.f); + int tex_x, tex_y, tex_comp; + void* tex_data = stbi_load("../../extra_fonts/arial_unicode_ms_18_CJK.png", &tex_x, &tex_y, &tex_comp, 4); + + // Automatically find pure white pixel from the texture we just loaded + for (int tex_data_off = 0; tex_data_off < tex_x*tex_y; tex_data_off++) + if (((unsigned int*)tex_data)[tex_data_off] == 0xffffffff) + { + io.FontTexUvForWhite = ImVec2((float)(tex_data_off % tex_x)/(tex_x-1.0f), (float)(tex_data_off / tex_x)/(tex_y-1.0f)); + break; + } +#endif + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_x, tex_y, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex_data); + stbi_image_free(tex_data); +} + +void UpdateImGui() +{ + ImGuiIO& io = ImGui::GetIO(); + + // Setup timestep + static double time = 0.0f; + const double current_time = glfwGetTime(); + io.DeltaTime = (float)(current_time - time); + time = current_time; + + // Setup inputs + // (we already got mouse wheel, keyboard keys & characters from glfw callbacks polled in glfwPollEvents()) + double mouse_x, mouse_y; + glfwGetCursorPos(window, &mouse_x, &mouse_y); + io.MousePos = ImVec2((float)mouse_x * mousePosScale.x, (float)mouse_y * mousePosScale.y); // Mouse position, in pixels (set to -1,-1 if no mouse / on another screen, etc.) + io.MouseDown[0] = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) != 0; + io.MouseDown[1] = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT) != 0; + + // Start the frame + ImGui::NewFrame(); +} + +// Application code +int main(int argc, char** argv) +{ + InitGL(); + InitImGui(); + + while (!glfwWindowShouldClose(window)) + { + ImGuiIO& io = ImGui::GetIO(); + io.MouseWheel = 0; + glfwPollEvents(); + UpdateImGui(); + + // Create a simple window + // Tip: if we don't call ImGui::Begin()/ImGui::End() the widgets appears in a window automatically called "Debug" + static bool show_test_window = true; + static bool show_another_window = false; + static float f; + ImGui::Text("Hello, world!"); + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); + show_test_window ^= ImGui::Button("Test Window"); + show_another_window ^= ImGui::Button("Another Window"); + + // Calculate and show framerate + static float ms_per_frame[120] = { 0 }; + static int ms_per_frame_idx = 0; + static float ms_per_frame_accum = 0.0f; + ms_per_frame_accum -= ms_per_frame[ms_per_frame_idx]; + ms_per_frame[ms_per_frame_idx] = ImGui::GetIO().DeltaTime * 1000.0f; + ms_per_frame_accum += ms_per_frame[ms_per_frame_idx]; + ms_per_frame_idx = (ms_per_frame_idx + 1) % 120; + const float ms_per_frame_avg = ms_per_frame_accum / 120; + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", ms_per_frame_avg, 1000.0f / ms_per_frame_avg); + + // Show the ImGui test window + // Most of user example code is in ImGui::ShowTestWindow() + if (show_test_window) + { + ImGui::SetNewWindowDefaultPos(ImVec2(650, 20)); // Normally user code doesn't need/want to call it because positions are saved in .ini file anyway. Here we just want to make the demo initial state a bit more friendly! + ImGui::ShowTestWindow(&show_test_window); + } + + ImGui::Begin("Test UTF-8 with Arial Unicode Ms, Size 18"); + ImGui::Text("Hello, actual world!"); + ImGui::Text("かきくけこ 日本語"); + ImGui::Text("いろはにほへと ちりぬるを わかよたれそ つねならむ うゐのおくやま けふこえて あさきゆめみし ゑひもせす(ん)"); + ImGui::Text("色は匂へど 散りぬるを 我が世誰ぞ 常ならむ 有為の奥山 今日越えて 浅き夢見じ 酔ひもせず(ん)"); + ImGui::Button("無理"); + ImGui::SameLine(); + ImGui::Button("オーケー!"); + static char test[256] = "hello 日本語"; + ImGui::InputText("utf8", test, 256); + ImGui::End(); + + // Show another simple window + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); + ImGui::Text("Hello"); + ImGui::End(); + } + + // Rendering + glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); + glClearColor(0.8f, 0.6f, 0.6f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + ImGui::Render(); + glfwSwapBuffers(window); + } + + ImGui::Shutdown(); + glfwTerminate(); + return 0; +} + diff --git a/imgui.cpp b/imgui.cpp index 2a1f54e0f..c35a30cba 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4119,7 +4119,7 @@ bool InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlag else if (is_ctrl_down && IsKeyPressedMap(ImGuiKey_V)) { if (g.IO.GetClipboardTextFn) - { + { if (const char* clipboard = g.IO.GetClipboardTextFn()) { // Remove new-line from pasted buffer @@ -4137,7 +4137,7 @@ bool InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlag stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len); ImGui::MemFree(clipboard_filtered); } - } + } } else if (g.IO.InputCharacters[0]) { @@ -5394,60 +5394,60 @@ const ImBitmapFont::FntGlyph* ImBitmapFont::FindGlyph(unsigned short c) const // Based on stb_from_utf8() from github.com/nothings/stb/ static int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end) { - if (*in_text != 0) - { - unsigned int c = -1; - const char* str = in_text; - if (!(*str & 0x80)) - { - c = (unsigned int)(*str++); - *out_char = c; - return 1; - } - if ((*str & 0xe0) == 0xc0) - { - if (in_text_end && in_text_end - str < 2) return -1; - if (*str < 0xc2) return -1; - c = (*str++ & 0x1f) << 6; - if ((*str & 0xc0) != 0x80) return -1; - c += (*str++ & 0x3f); - *out_char = c; - return 2; - } - if ((*str & 0xf0) == 0xe0) - { - if (in_text_end && in_text_end - str < 3) return -1; - if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return -1; - if (*str == 0xed && str[1] > 0x9f) return -1; // str[1] < 0x80 is checked below - c = (*str++ & 0x0f) << 12; - if ((*str & 0xc0) != 0x80) return -1; - c += (*str++ & 0x3f) << 6; - if ((*str & 0xc0) != 0x80) return -1; - c += (*str++ & 0x3f); - *out_char = c; - return 3; - } - if ((*str & 0xf8) == 0xf0) - { - if (in_text_end && in_text_end - str < 4) return -1; - if (*str > 0xf4) return -1; - if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return -1; - if (*str == 0xf4 && str[1] > 0x8f) return -1; // str[1] < 0x80 is checked below - c = (*str++ & 0x07) << 18; - if ((*str & 0xc0) != 0x80) return -1; - c += (*str++ & 0x3f) << 12; - if ((*str & 0xc0) != 0x80) return -1; - c += (*str++ & 0x3f) << 6; - if ((*str & 0xc0) != 0x80) return -1; - c += (*str++ & 0x3f); - // utf-8 encodings of values used in surrogate pairs are invalid - if ((c & 0xFFFFF800) == 0xD800) return -1; - *out_char = c; - return 4; - } - } - *out_char = 0; - return 0; + if (*in_text != 0) + { + unsigned int c = -1; + const char* str = in_text; + if (!(*str & 0x80)) + { + c = (unsigned int)(*str++); + *out_char = c; + return 1; + } + if ((*str & 0xe0) == 0xc0) + { + if (in_text_end && in_text_end - str < 2) return -1; + if (*str < 0xc2) return -1; + c = (*str++ & 0x1f) << 6; + if ((*str & 0xc0) != 0x80) return -1; + c += (*str++ & 0x3f); + *out_char = c; + return 2; + } + if ((*str & 0xf0) == 0xe0) + { + if (in_text_end && in_text_end - str < 3) return -1; + if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return -1; + if (*str == 0xed && str[1] > 0x9f) return -1; // str[1] < 0x80 is checked below + c = (*str++ & 0x0f) << 12; + if ((*str & 0xc0) != 0x80) return -1; + c += (*str++ & 0x3f) << 6; + if ((*str & 0xc0) != 0x80) return -1; + c += (*str++ & 0x3f); + *out_char = c; + return 3; + } + if ((*str & 0xf8) == 0xf0) + { + if (in_text_end && in_text_end - str < 4) return -1; + if (*str > 0xf4) return -1; + if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return -1; + if (*str == 0xf4 && str[1] > 0x8f) return -1; // str[1] < 0x80 is checked below + c = (*str++ & 0x07) << 18; + if ((*str & 0xc0) != 0x80) return -1; + c += (*str++ & 0x3f) << 12; + if ((*str & 0xc0) != 0x80) return -1; + c += (*str++ & 0x3f) << 6; + if ((*str & 0xc0) != 0x80) return -1; + c += (*str++ & 0x3f); + // utf-8 encodings of values used in surrogate pairs are invalid + if ((c & 0xFFFFF800) == 0xD800) return -1; + *out_char = c; + return 4; + } + } + *out_char = 0; + return 0; } ImVec2 ImBitmapFont::CalcTextSize(float size, float max_width, const char* text_begin, const char* text_end, const char** remaining) const @@ -5466,11 +5466,11 @@ ImVec2 ImBitmapFont::CalcTextSize(float size, float max_width, const char* text_ const char* s = text_begin; while (s < text_end) { - unsigned int c; - const int bytes_count = ImTextCharFromUtf8(&c, s, text_end); - s += bytes_count > 0 ? bytes_count : 1; // Handle decoding failure by skipping to next byte - if (c > 0x10000) - continue; + unsigned int c; + const int bytes_count = ImTextCharFromUtf8(&c, s, text_end); + s += bytes_count > 0 ? bytes_count : 1; // Handle decoding failure by skipping to next byte + if (c > 0x10000) + continue; if (c == '\n') { @@ -5528,13 +5528,13 @@ void ImBitmapFont::RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& c float y = pos.y; for (const char* s = text_begin; s < text_end; ) { - unsigned int c; - const int bytes_count = ImTextCharFromUtf8(&c, s, text_end); - s += bytes_count > 0 ? bytes_count : 1; // Handle decoding failure by skipping to next byte - if (c >= 0x10000) - continue; + unsigned int c; + const int bytes_count = ImTextCharFromUtf8(&c, s, text_end); + s += bytes_count > 0 ? bytes_count : 1; // Handle decoding failure by skipping to next byte + if (c >= 0x10000) + continue; - if (c == '\n') + if (c == '\n') { x = pos.x; y += line_height * scale;