diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..94f4ef0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +## Visual Studio files +examples/directx9_example/Debug/* +examples/directx9_example/Release/* +examples/directx9_example/ipch/* +examples/opengl_example/Debug/* +examples/opengl_example/Release/* +examples/opengl_example/ipch/* +*.opensdf +*.sdf +*.suo +*.vcxproj.user + +## Ini files +imgui.ini diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..94f4ef0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +## Visual Studio files +examples/directx9_example/Debug/* +examples/directx9_example/Release/* +examples/directx9_example/ipch/* +examples/opengl_example/Debug/* +examples/opengl_example/Release/* +examples/opengl_example/ipch/* +*.opensdf +*.sdf +*.suo +*.vcxproj.user + +## Ini files +imgui.ini diff --git a/README.md b/README.md index 65a9ad7..c3f7767 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@ ImGui ===== -ImGui is a bloat-free graphical user interface library for C/C++. It is portable, renderer agnostic and carries minimal amount of dependencies (only 3 files are needed). It is based on an "immediate" graphical user interface paradigm which allows you to build simple user interfaces with ease. +ImGui is a bloat-free graphical user interface library for C++. It outputs vertex buffers that you can render in your 3D-pipeline enabled application. It is portable, renderer agnostic and carries minimal amount of dependencies (only 3 files are needed). It is based on an "immediate" graphical user interface paradigm which allows you to build simple user interfaces with ease. -ImGui is designed to allow programmers to create "content creation" or "debug" tools (as opposed to tools for the average end-user). It favors simplicity and thus lacks certain features normally found in more high-level libraries, such as string localisation. +ImGui is designed to enable fast iteration and allow programmers to create "content creation" or "debug" tools (as opposed to tools for the average end-user). It favors simplicity and thus lacks certain features normally found in more high-level libraries, such as string localisation. -Usage example: +ImGui is particularly suited to integration in 3D applications, fullscreen applications, embedded applications, games, or any applications on consoles platforms where operating system features are non-standard. + +After ImGui is setup in your engine, you can use it like in this example: ![screenshot of sample code alongside its output with ImGui](/web/code_sample_01.png?raw=true) -ImGui output vertex buffer and simple command-list that you can render in application. Refer to the sample applications in the examples/ folder for instructions on how to integrate ImGui within your existing codebase. - +ImGui outputs vertex buffers and simple command-lists that you can render in your application. Because it doesn't know or touch graphics state directly, you can call ImGui commands anywhere in your code (e.g. in the middle of a running algorithm, or in the middle of your own rendering process). Refer to the sample applications in the examples/ folder for instructions on how to integrate ImGui with your existing codebase. Gallery ------- @@ -20,12 +21,22 @@ ![screenshot 3](/web/test_window_03.png?raw=true) ![screenshot 4](/web/test_window_04.png?raw=true) +References +---------- + +The Immediate Mode GUI paradigm may at first appear unusual to some users. This is mainly because "Retained Mode" GUIs have been so widespread and predominant. The following links can give you a better understanding about how Immediate Mode GUIs works. +- [Johannes 'johno' Norneby's article](http://www.johno.se/book/imgui.html). +- [A presentation by Rickard Gustafsson and Johannes Algelind](http://www.cse.chalmers.se/edu/year/2011/course/TDA361/Advanced%20Computer%20Graphics/IMGUI.pdf). +- [Jari Komppa's tutorial on building an ImGui library](http://iki.fi/sol/imgui/). +- [Casey Muratori's original video that popularized the concept](https://mollyrocket.com/861). + + Credits ------- Developed by [Omar Cornut](http://www.miracleworld.net). The library was developed with the support of [Media Molecule](http://www.mediamolecule.com) and first used internally on the game [Tearaway](http://tearaway.mediamolecule.com). -Embeds [proggy_clean font](http://www.proggyfonts.net/) by Tristan Grimmer (also MIT license). +Embeds [proggy_clean](http://www.proggyfonts.net/) font by Tristan Grimmer (also MIT license). Inspiration, feedback, and testing: Casey Muratori, Atman Binstock, Mikko Mononen, Emmanuel Briney, Stefan Kamoda, Matt Willis. Thanks! diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..94f4ef0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +## Visual Studio files +examples/directx9_example/Debug/* +examples/directx9_example/Release/* +examples/directx9_example/ipch/* +examples/opengl_example/Debug/* +examples/opengl_example/Release/* +examples/opengl_example/ipch/* +*.opensdf +*.sdf +*.suo +*.vcxproj.user + +## Ini files +imgui.ini diff --git a/README.md b/README.md index 65a9ad7..c3f7767 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@ ImGui ===== -ImGui is a bloat-free graphical user interface library for C/C++. It is portable, renderer agnostic and carries minimal amount of dependencies (only 3 files are needed). It is based on an "immediate" graphical user interface paradigm which allows you to build simple user interfaces with ease. +ImGui is a bloat-free graphical user interface library for C++. It outputs vertex buffers that you can render in your 3D-pipeline enabled application. It is portable, renderer agnostic and carries minimal amount of dependencies (only 3 files are needed). It is based on an "immediate" graphical user interface paradigm which allows you to build simple user interfaces with ease. -ImGui is designed to allow programmers to create "content creation" or "debug" tools (as opposed to tools for the average end-user). It favors simplicity and thus lacks certain features normally found in more high-level libraries, such as string localisation. +ImGui is designed to enable fast iteration and allow programmers to create "content creation" or "debug" tools (as opposed to tools for the average end-user). It favors simplicity and thus lacks certain features normally found in more high-level libraries, such as string localisation. -Usage example: +ImGui is particularly suited to integration in 3D applications, fullscreen applications, embedded applications, games, or any applications on consoles platforms where operating system features are non-standard. + +After ImGui is setup in your engine, you can use it like in this example: ![screenshot of sample code alongside its output with ImGui](/web/code_sample_01.png?raw=true) -ImGui output vertex buffer and simple command-list that you can render in application. Refer to the sample applications in the examples/ folder for instructions on how to integrate ImGui within your existing codebase. - +ImGui outputs vertex buffers and simple command-lists that you can render in your application. Because it doesn't know or touch graphics state directly, you can call ImGui commands anywhere in your code (e.g. in the middle of a running algorithm, or in the middle of your own rendering process). Refer to the sample applications in the examples/ folder for instructions on how to integrate ImGui with your existing codebase. Gallery ------- @@ -20,12 +21,22 @@ ![screenshot 3](/web/test_window_03.png?raw=true) ![screenshot 4](/web/test_window_04.png?raw=true) +References +---------- + +The Immediate Mode GUI paradigm may at first appear unusual to some users. This is mainly because "Retained Mode" GUIs have been so widespread and predominant. The following links can give you a better understanding about how Immediate Mode GUIs works. +- [Johannes 'johno' Norneby's article](http://www.johno.se/book/imgui.html). +- [A presentation by Rickard Gustafsson and Johannes Algelind](http://www.cse.chalmers.se/edu/year/2011/course/TDA361/Advanced%20Computer%20Graphics/IMGUI.pdf). +- [Jari Komppa's tutorial on building an ImGui library](http://iki.fi/sol/imgui/). +- [Casey Muratori's original video that popularized the concept](https://mollyrocket.com/861). + + Credits ------- Developed by [Omar Cornut](http://www.miracleworld.net). The library was developed with the support of [Media Molecule](http://www.mediamolecule.com) and first used internally on the game [Tearaway](http://tearaway.mediamolecule.com). -Embeds [proggy_clean font](http://www.proggyfonts.net/) by Tristan Grimmer (also MIT license). +Embeds [proggy_clean](http://www.proggyfonts.net/) font by Tristan Grimmer (also MIT license). Inspiration, feedback, and testing: Casey Muratori, Atman Binstock, Mikko Mononen, Emmanuel Briney, Stefan Kamoda, Matt Willis. Thanks! diff --git a/examples/directx9_example/main.cpp b/examples/directx9_example/main.cpp index fd25f01..3036eb7 100644 --- a/examples/directx9_example/main.cpp +++ b/examples/directx9_example/main.cpp @@ -12,16 +12,15 @@ static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; // Our rendering device static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL; // Buffer to hold vertices static LPDIRECT3DTEXTURE9 g_pTexture = NULL; // Our texture - struct CUSTOMVERTEX { D3DXVECTOR3 position; D3DCOLOR color; float tu, tv; }; - #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1) +// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structuer) static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) { size_t total_vtx_count = 0; @@ -30,31 +29,13 @@ if (total_vtx_count == 0) return; - ImVector clip_rect_stack; - clip_rect_stack.push_back(ImVec4(-9999,-9999,+9999,+9999)); - - // Setup orthographic projection - // Set up world matrix - D3DXMATRIXA16 mat; - D3DXMatrixIdentity(&mat); - g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat); - g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat); - D3DXMatrixOrthoOffCenterLH(&mat, 0.0f, ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y, 0.0f, -1.0f, +1.0f); - g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat); - - D3DSURFACE_DESC texture_desc; - g_pTexture->GetLevelDesc(0, &texture_desc); - - // Fill the vertex buffer + // Copy and convert all vertices into a single contiguous buffer CUSTOMVERTEX* vtx_dst; if (g_pVB->Lock(0, total_vtx_count, (void**)&vtx_dst, D3DLOCK_DISCARD) < 0) return; - for (int n = 0; n < cmd_lists_count; n++) { const ImDrawList* cmd_list = cmd_lists[n]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; const ImDrawVert* vtx_src = &cmd_list->vtx_buffer[0]; for (size_t i = 0; i < cmd_list->vtx_buffer.size(); i++) { @@ -73,11 +54,10 @@ g_pd3dDevice->SetStreamSource( 0, g_pVB, 0, sizeof( CUSTOMVERTEX ) ); g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX ); - // Setup render state: alpha-blending enabled, no face culling, no depth testing + // Setup render state: alpha-blending, no face culling, no depth testing g_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ); g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, false ); g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, false ); - g_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, true ); g_pd3dDevice->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_ADD ); g_pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, false ); @@ -94,97 +74,31 @@ g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); - int vtx_consumed = 0; // offset in vertex buffer. each command consume ImDrawCmd::vtx_count of those - bool clip_rect_dirty = true; + // Setup orthographic projection matrix + D3DXMATRIXA16 mat; + D3DXMatrixIdentity(&mat); + g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat); + g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat); + D3DXMatrixOrthoOffCenterLH(&mat, 0.0f, ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y, 0.0f, -1.0f, +1.0f); + g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat); + // Render command lists + int vtx_offset = 0; for (int n = 0; n < cmd_lists_count; n++) { + // Render command list const ImDrawList* cmd_list = cmd_lists[n]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; - const ImDrawCmd* pcmd = &cmd_list->commands.front(); - const ImDrawCmd* pcmd_end = &cmd_list->commands.back(); - int clip_rect_buf_consumed = 0; // offset in cmd_list->clip_rect_buffer. each PushClipRect command consume 1 of those. - while (pcmd <= pcmd_end) + const ImDrawCmd* pcmd_end = cmd_list->commands.end(); + for (const ImDrawCmd* pcmd = cmd_list->commands.begin(); pcmd != pcmd_end; pcmd++) { - const ImDrawCmd& cmd = *pcmd++; - switch (cmd.cmd_type) - { - case ImDrawCmdType_DrawTriangleList: - if (clip_rect_dirty) - { - const ImVec4& clip_rect = clip_rect_stack.back(); - const RECT r = { (LONG)clip_rect.x, (LONG)clip_rect.y, (LONG)clip_rect.z, (LONG)clip_rect.w }; - g_pd3dDevice->SetScissorRect(&r); - clip_rect_dirty = false; - } - g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, vtx_consumed, cmd.vtx_count/3); - vtx_consumed += cmd.vtx_count; - break; - - case ImDrawCmdType_PushClipRect: - clip_rect_stack.push_back(cmd_list->clip_rect_buffer[clip_rect_buf_consumed++]); - clip_rect_dirty = true; - break; - - case ImDrawCmdType_PopClipRect: - clip_rect_stack.pop_back(); - clip_rect_dirty = true; - break; - } + const RECT r = { (LONG)pcmd->clip_rect.x, (LONG)pcmd->clip_rect.y, (LONG)pcmd->clip_rect.z, (LONG)pcmd->clip_rect.w }; + g_pd3dDevice->SetScissorRect(&r); + g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, vtx_offset, pcmd->vtx_count/3); + vtx_offset += pcmd->vtx_count; } } } -// Get text data in Win32 clipboard -static const char* ImImpl_GetClipboardTextFn() -{ - static char* buf_local = NULL; - if (buf_local) - { - free(buf_local); - buf_local = NULL; - } - - if (!OpenClipboard(NULL)) - return NULL; - - HANDLE buf_handle = GetClipboardData(CF_TEXT); - if (buf_handle == NULL) - return NULL; - - if (char* buf_global = (char*)GlobalLock(buf_handle)) - buf_local = strdup(buf_global); - GlobalUnlock(buf_handle); - CloseClipboard(); - - return buf_local; -} - -// Set text data in Win32 clipboard -static void ImImpl_SetClipboardTextFn(const char* text, const char* text_end) -{ - if (!OpenClipboard(NULL)) - return; - - if (!text_end) - text_end = text + strlen(text); - - const int buf_length = (text_end - text) + 1; - HGLOBAL buf_handle = GlobalAlloc(GMEM_MOVEABLE, buf_length * sizeof(char)); - if (buf_handle == NULL) - return; - - char* buf_global = (char *)GlobalLock(buf_handle); - memcpy(buf_global, text, text_end - text); - buf_global[text_end - text] = 0; - GlobalUnlock(buf_handle); - - EmptyClipboard(); - SetClipboardData(CF_TEXT, buf_handle); - CloseClipboard(); -} - HRESULT InitD3D(HWND hWnd) { if (NULL == (g_pD3D = Direct3DCreate9(D3D_SDK_VERSION))) @@ -203,17 +117,6 @@ if (g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &g_pd3dDevice) < 0) return E_FAIL; - // Create the vertex buffer. - if (g_pd3dDevice->CreateVertexBuffer(10000 * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL) < 0) - return E_FAIL; - - // Load font texture - const void* png_data; - unsigned int png_size; - ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); - if (D3DXCreateTextureFromFileInMemory(g_pd3dDevice, png_data, png_size, &g_pTexture) < 0) - return E_FAIL; - return S_OK; } @@ -247,9 +150,11 @@ io.MouseDown[1] = false; return true; case WM_MOUSEWHEEL: + // Mouse wheel: -1,0,+1 io.MouseWheel = GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? +1 : -1; return true; case WM_MOUSEMOVE: + // Mouse position, in pixels (set to -1,-1 if no mouse / on another screen, etc.) io.MousePos.x = (signed short)(lParam); io.MousePos.y = (signed short)(lParam >> 16); return true; @@ -274,9 +179,10 @@ GetClientRect(hWnd, &rect); ImGuiIO& io = ImGui::GetIO(); - io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); - io.DeltaTime = 1.0f/60.0f; - io.KeyMap[ImGuiKey_Tab] = VK_TAB; + io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); // 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 Direct3D Texels + io.KeyMap[ImGuiKey_Tab] = VK_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array that we will update during the application lifetime. io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT; io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT; io.KeyMap[ImGuiKey_UpArrow] = VK_UP; @@ -295,8 +201,52 @@ io.KeyMap[ImGuiKey_Z] = 'Z'; io.RenderDrawListsFn = ImImpl_RenderDrawLists; - io.SetClipboardTextFn = ImImpl_SetClipboardTextFn; - io.GetClipboardTextFn = ImImpl_GetClipboardTextFn; + + // Create the vertex buffer + if (g_pd3dDevice->CreateVertexBuffer(10000 * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL) < 0) + { + IM_ASSERT(0); + return; + } + + // Load font texture + const void* png_data; + unsigned int png_size; + ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); + if (D3DXCreateTextureFromFileInMemory(g_pd3dDevice, png_data, png_size, &g_pTexture) < 0) + { + IM_ASSERT(0); + return; + } +} + +INT64 ticks_per_second = 0; +INT64 time = 0; + +void UpdateImGui() +{ + ImGuiIO& io = ImGui::GetIO(); + + // Setup timestep + INT64 current_time; + QueryPerformanceCounter((LARGE_INTEGER *)¤t_time); + io.DeltaTime = (float)(current_time - time) / ticks_per_second; + time = current_time; + + // Setup inputs + // (we already got mouse position, buttons, wheel from the window message callback) + BYTE keystate[256]; + GetKeyboardState(keystate); + for (int i = 0; i < 256; i++) + io.KeysDown[i] = (keystate[i] & 0x80) != 0; + io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0; + io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0; + // io.MousePos : filled by WM_MOUSEMOVE event + // io.MouseDown : filled by WM_*BUTTON* events + // io.MouseWheel : filled by WM_MOUSEWHEEL events + + // Start the frame + ImGui::NewFrame(); } int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR, int) @@ -308,99 +258,91 @@ // Create the application's window hWnd = CreateWindow(L"ImGui Example", L"ImGui DirectX9 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, NULL, NULL, wc.hInstance, NULL); - INT64 ticks_per_second, time; if (!QueryPerformanceFrequency((LARGE_INTEGER *)&ticks_per_second)) return 1; if (!QueryPerformanceCounter((LARGE_INTEGER *)&time)) return 1; // Initialize Direct3D - if (InitD3D(hWnd) >= 0) + if (InitD3D(hWnd) < 0) + { + if (g_pVB) + g_pVB->Release(); + UnregisterClass(L"ImGui Example", wc.hInstance); + return 1; + } + + // Show the window + ShowWindow(hWnd, SW_SHOWDEFAULT); + UpdateWindow(hWnd); + + InitImGui(); + + // Enter the message loop + MSG msg; + ZeroMemory(&msg, sizeof(msg)); + while (msg.message != WM_QUIT) { - // Show the window - ShowWindow(hWnd, SW_SHOWDEFAULT); - UpdateWindow(hWnd); - - InitImGui(); - - // Enter the message loop - MSG msg; - ZeroMemory(&msg, sizeof(msg)); - while (msg.message != WM_QUIT) + if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) { - if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - continue; - } + TranslateMessage(&msg); + DispatchMessage(&msg); + continue; + } - // 1) ImGui start frame, setup time delta & inputs - ImGuiIO& io = ImGui::GetIO(); - INT64 current_time; - QueryPerformanceCounter((LARGE_INTEGER *)¤t_time); - io.DeltaTime = (float)(current_time - time) / ticks_per_second; - time = current_time; - BYTE keystate[256]; - GetKeyboardState(keystate); - for (int i = 0; i < 256; i++) - io.KeysDown[i] = (keystate[i] & 0x80) != 0; - io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0; - io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0; - // io.MousePos : filled by WM_MOUSEMOVE event - // io.MouseDown : filled by WM_*BUTTON* events - // io.MouseWheel : filled by WM_MOUSEWHEEL events - ImGui::NewFrame(); + UpdateImGui(); - // 2) ImGui usage - 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"); + // 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] = io.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); + // 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); - if (show_test_window) - { - // More example code in ShowTestWindow() - 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); - } - - if (show_another_window) - { - ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); - ImGui::Text("Hello"); - ImGui::End(); - } - - // 3) Render - g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); - g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false); - g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, false); - g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(204, 153, 153), 1.0f, 0); // Clear the backbuffer and the zbuffer - if (g_pd3dDevice->BeginScene() >= 0) - { - ImGui::Render(); - g_pd3dDevice->EndScene(); - } - g_pd3dDevice->Present(NULL, NULL, NULL, NULL); + // 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::Shutdown(); - } + // Show another simple window + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); + ImGui::Text("Hello"); + ImGui::End(); + } + + // Rendering + g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); + g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false); + g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, false); + g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(204, 153, 153), 1.0f, 0); + if (g_pd3dDevice->BeginScene() >= 0) + { + ImGui::Render(); + g_pd3dDevice->EndScene(); + } + g_pd3dDevice->Present(NULL, NULL, NULL, NULL); + } + + ImGui::Shutdown(); if (g_pVB) g_pVB->Release(); diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..94f4ef0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +## Visual Studio files +examples/directx9_example/Debug/* +examples/directx9_example/Release/* +examples/directx9_example/ipch/* +examples/opengl_example/Debug/* +examples/opengl_example/Release/* +examples/opengl_example/ipch/* +*.opensdf +*.sdf +*.suo +*.vcxproj.user + +## Ini files +imgui.ini diff --git a/README.md b/README.md index 65a9ad7..c3f7767 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@ ImGui ===== -ImGui is a bloat-free graphical user interface library for C/C++. It is portable, renderer agnostic and carries minimal amount of dependencies (only 3 files are needed). It is based on an "immediate" graphical user interface paradigm which allows you to build simple user interfaces with ease. +ImGui is a bloat-free graphical user interface library for C++. It outputs vertex buffers that you can render in your 3D-pipeline enabled application. It is portable, renderer agnostic and carries minimal amount of dependencies (only 3 files are needed). It is based on an "immediate" graphical user interface paradigm which allows you to build simple user interfaces with ease. -ImGui is designed to allow programmers to create "content creation" or "debug" tools (as opposed to tools for the average end-user). It favors simplicity and thus lacks certain features normally found in more high-level libraries, such as string localisation. +ImGui is designed to enable fast iteration and allow programmers to create "content creation" or "debug" tools (as opposed to tools for the average end-user). It favors simplicity and thus lacks certain features normally found in more high-level libraries, such as string localisation. -Usage example: +ImGui is particularly suited to integration in 3D applications, fullscreen applications, embedded applications, games, or any applications on consoles platforms where operating system features are non-standard. + +After ImGui is setup in your engine, you can use it like in this example: ![screenshot of sample code alongside its output with ImGui](/web/code_sample_01.png?raw=true) -ImGui output vertex buffer and simple command-list that you can render in application. Refer to the sample applications in the examples/ folder for instructions on how to integrate ImGui within your existing codebase. - +ImGui outputs vertex buffers and simple command-lists that you can render in your application. Because it doesn't know or touch graphics state directly, you can call ImGui commands anywhere in your code (e.g. in the middle of a running algorithm, or in the middle of your own rendering process). Refer to the sample applications in the examples/ folder for instructions on how to integrate ImGui with your existing codebase. Gallery ------- @@ -20,12 +21,22 @@ ![screenshot 3](/web/test_window_03.png?raw=true) ![screenshot 4](/web/test_window_04.png?raw=true) +References +---------- + +The Immediate Mode GUI paradigm may at first appear unusual to some users. This is mainly because "Retained Mode" GUIs have been so widespread and predominant. The following links can give you a better understanding about how Immediate Mode GUIs works. +- [Johannes 'johno' Norneby's article](http://www.johno.se/book/imgui.html). +- [A presentation by Rickard Gustafsson and Johannes Algelind](http://www.cse.chalmers.se/edu/year/2011/course/TDA361/Advanced%20Computer%20Graphics/IMGUI.pdf). +- [Jari Komppa's tutorial on building an ImGui library](http://iki.fi/sol/imgui/). +- [Casey Muratori's original video that popularized the concept](https://mollyrocket.com/861). + + Credits ------- Developed by [Omar Cornut](http://www.miracleworld.net). The library was developed with the support of [Media Molecule](http://www.mediamolecule.com) and first used internally on the game [Tearaway](http://tearaway.mediamolecule.com). -Embeds [proggy_clean font](http://www.proggyfonts.net/) by Tristan Grimmer (also MIT license). +Embeds [proggy_clean](http://www.proggyfonts.net/) font by Tristan Grimmer (also MIT license). Inspiration, feedback, and testing: Casey Muratori, Atman Binstock, Mikko Mononen, Emmanuel Briney, Stefan Kamoda, Matt Willis. Thanks! diff --git a/examples/directx9_example/main.cpp b/examples/directx9_example/main.cpp index fd25f01..3036eb7 100644 --- a/examples/directx9_example/main.cpp +++ b/examples/directx9_example/main.cpp @@ -12,16 +12,15 @@ static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; // Our rendering device static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL; // Buffer to hold vertices static LPDIRECT3DTEXTURE9 g_pTexture = NULL; // Our texture - struct CUSTOMVERTEX { D3DXVECTOR3 position; D3DCOLOR color; float tu, tv; }; - #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1) +// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structuer) static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) { size_t total_vtx_count = 0; @@ -30,31 +29,13 @@ if (total_vtx_count == 0) return; - ImVector clip_rect_stack; - clip_rect_stack.push_back(ImVec4(-9999,-9999,+9999,+9999)); - - // Setup orthographic projection - // Set up world matrix - D3DXMATRIXA16 mat; - D3DXMatrixIdentity(&mat); - g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat); - g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat); - D3DXMatrixOrthoOffCenterLH(&mat, 0.0f, ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y, 0.0f, -1.0f, +1.0f); - g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat); - - D3DSURFACE_DESC texture_desc; - g_pTexture->GetLevelDesc(0, &texture_desc); - - // Fill the vertex buffer + // Copy and convert all vertices into a single contiguous buffer CUSTOMVERTEX* vtx_dst; if (g_pVB->Lock(0, total_vtx_count, (void**)&vtx_dst, D3DLOCK_DISCARD) < 0) return; - for (int n = 0; n < cmd_lists_count; n++) { const ImDrawList* cmd_list = cmd_lists[n]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; const ImDrawVert* vtx_src = &cmd_list->vtx_buffer[0]; for (size_t i = 0; i < cmd_list->vtx_buffer.size(); i++) { @@ -73,11 +54,10 @@ g_pd3dDevice->SetStreamSource( 0, g_pVB, 0, sizeof( CUSTOMVERTEX ) ); g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX ); - // Setup render state: alpha-blending enabled, no face culling, no depth testing + // Setup render state: alpha-blending, no face culling, no depth testing g_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ); g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, false ); g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, false ); - g_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, true ); g_pd3dDevice->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_ADD ); g_pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, false ); @@ -94,97 +74,31 @@ g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); - int vtx_consumed = 0; // offset in vertex buffer. each command consume ImDrawCmd::vtx_count of those - bool clip_rect_dirty = true; + // Setup orthographic projection matrix + D3DXMATRIXA16 mat; + D3DXMatrixIdentity(&mat); + g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat); + g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat); + D3DXMatrixOrthoOffCenterLH(&mat, 0.0f, ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y, 0.0f, -1.0f, +1.0f); + g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat); + // Render command lists + int vtx_offset = 0; for (int n = 0; n < cmd_lists_count; n++) { + // Render command list const ImDrawList* cmd_list = cmd_lists[n]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; - const ImDrawCmd* pcmd = &cmd_list->commands.front(); - const ImDrawCmd* pcmd_end = &cmd_list->commands.back(); - int clip_rect_buf_consumed = 0; // offset in cmd_list->clip_rect_buffer. each PushClipRect command consume 1 of those. - while (pcmd <= pcmd_end) + const ImDrawCmd* pcmd_end = cmd_list->commands.end(); + for (const ImDrawCmd* pcmd = cmd_list->commands.begin(); pcmd != pcmd_end; pcmd++) { - const ImDrawCmd& cmd = *pcmd++; - switch (cmd.cmd_type) - { - case ImDrawCmdType_DrawTriangleList: - if (clip_rect_dirty) - { - const ImVec4& clip_rect = clip_rect_stack.back(); - const RECT r = { (LONG)clip_rect.x, (LONG)clip_rect.y, (LONG)clip_rect.z, (LONG)clip_rect.w }; - g_pd3dDevice->SetScissorRect(&r); - clip_rect_dirty = false; - } - g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, vtx_consumed, cmd.vtx_count/3); - vtx_consumed += cmd.vtx_count; - break; - - case ImDrawCmdType_PushClipRect: - clip_rect_stack.push_back(cmd_list->clip_rect_buffer[clip_rect_buf_consumed++]); - clip_rect_dirty = true; - break; - - case ImDrawCmdType_PopClipRect: - clip_rect_stack.pop_back(); - clip_rect_dirty = true; - break; - } + const RECT r = { (LONG)pcmd->clip_rect.x, (LONG)pcmd->clip_rect.y, (LONG)pcmd->clip_rect.z, (LONG)pcmd->clip_rect.w }; + g_pd3dDevice->SetScissorRect(&r); + g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, vtx_offset, pcmd->vtx_count/3); + vtx_offset += pcmd->vtx_count; } } } -// Get text data in Win32 clipboard -static const char* ImImpl_GetClipboardTextFn() -{ - static char* buf_local = NULL; - if (buf_local) - { - free(buf_local); - buf_local = NULL; - } - - if (!OpenClipboard(NULL)) - return NULL; - - HANDLE buf_handle = GetClipboardData(CF_TEXT); - if (buf_handle == NULL) - return NULL; - - if (char* buf_global = (char*)GlobalLock(buf_handle)) - buf_local = strdup(buf_global); - GlobalUnlock(buf_handle); - CloseClipboard(); - - return buf_local; -} - -// Set text data in Win32 clipboard -static void ImImpl_SetClipboardTextFn(const char* text, const char* text_end) -{ - if (!OpenClipboard(NULL)) - return; - - if (!text_end) - text_end = text + strlen(text); - - const int buf_length = (text_end - text) + 1; - HGLOBAL buf_handle = GlobalAlloc(GMEM_MOVEABLE, buf_length * sizeof(char)); - if (buf_handle == NULL) - return; - - char* buf_global = (char *)GlobalLock(buf_handle); - memcpy(buf_global, text, text_end - text); - buf_global[text_end - text] = 0; - GlobalUnlock(buf_handle); - - EmptyClipboard(); - SetClipboardData(CF_TEXT, buf_handle); - CloseClipboard(); -} - HRESULT InitD3D(HWND hWnd) { if (NULL == (g_pD3D = Direct3DCreate9(D3D_SDK_VERSION))) @@ -203,17 +117,6 @@ if (g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &g_pd3dDevice) < 0) return E_FAIL; - // Create the vertex buffer. - if (g_pd3dDevice->CreateVertexBuffer(10000 * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL) < 0) - return E_FAIL; - - // Load font texture - const void* png_data; - unsigned int png_size; - ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); - if (D3DXCreateTextureFromFileInMemory(g_pd3dDevice, png_data, png_size, &g_pTexture) < 0) - return E_FAIL; - return S_OK; } @@ -247,9 +150,11 @@ io.MouseDown[1] = false; return true; case WM_MOUSEWHEEL: + // Mouse wheel: -1,0,+1 io.MouseWheel = GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? +1 : -1; return true; case WM_MOUSEMOVE: + // Mouse position, in pixels (set to -1,-1 if no mouse / on another screen, etc.) io.MousePos.x = (signed short)(lParam); io.MousePos.y = (signed short)(lParam >> 16); return true; @@ -274,9 +179,10 @@ GetClientRect(hWnd, &rect); ImGuiIO& io = ImGui::GetIO(); - io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); - io.DeltaTime = 1.0f/60.0f; - io.KeyMap[ImGuiKey_Tab] = VK_TAB; + io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); // 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 Direct3D Texels + io.KeyMap[ImGuiKey_Tab] = VK_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array that we will update during the application lifetime. io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT; io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT; io.KeyMap[ImGuiKey_UpArrow] = VK_UP; @@ -295,8 +201,52 @@ io.KeyMap[ImGuiKey_Z] = 'Z'; io.RenderDrawListsFn = ImImpl_RenderDrawLists; - io.SetClipboardTextFn = ImImpl_SetClipboardTextFn; - io.GetClipboardTextFn = ImImpl_GetClipboardTextFn; + + // Create the vertex buffer + if (g_pd3dDevice->CreateVertexBuffer(10000 * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL) < 0) + { + IM_ASSERT(0); + return; + } + + // Load font texture + const void* png_data; + unsigned int png_size; + ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); + if (D3DXCreateTextureFromFileInMemory(g_pd3dDevice, png_data, png_size, &g_pTexture) < 0) + { + IM_ASSERT(0); + return; + } +} + +INT64 ticks_per_second = 0; +INT64 time = 0; + +void UpdateImGui() +{ + ImGuiIO& io = ImGui::GetIO(); + + // Setup timestep + INT64 current_time; + QueryPerformanceCounter((LARGE_INTEGER *)¤t_time); + io.DeltaTime = (float)(current_time - time) / ticks_per_second; + time = current_time; + + // Setup inputs + // (we already got mouse position, buttons, wheel from the window message callback) + BYTE keystate[256]; + GetKeyboardState(keystate); + for (int i = 0; i < 256; i++) + io.KeysDown[i] = (keystate[i] & 0x80) != 0; + io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0; + io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0; + // io.MousePos : filled by WM_MOUSEMOVE event + // io.MouseDown : filled by WM_*BUTTON* events + // io.MouseWheel : filled by WM_MOUSEWHEEL events + + // Start the frame + ImGui::NewFrame(); } int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR, int) @@ -308,99 +258,91 @@ // Create the application's window hWnd = CreateWindow(L"ImGui Example", L"ImGui DirectX9 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, NULL, NULL, wc.hInstance, NULL); - INT64 ticks_per_second, time; if (!QueryPerformanceFrequency((LARGE_INTEGER *)&ticks_per_second)) return 1; if (!QueryPerformanceCounter((LARGE_INTEGER *)&time)) return 1; // Initialize Direct3D - if (InitD3D(hWnd) >= 0) + if (InitD3D(hWnd) < 0) + { + if (g_pVB) + g_pVB->Release(); + UnregisterClass(L"ImGui Example", wc.hInstance); + return 1; + } + + // Show the window + ShowWindow(hWnd, SW_SHOWDEFAULT); + UpdateWindow(hWnd); + + InitImGui(); + + // Enter the message loop + MSG msg; + ZeroMemory(&msg, sizeof(msg)); + while (msg.message != WM_QUIT) { - // Show the window - ShowWindow(hWnd, SW_SHOWDEFAULT); - UpdateWindow(hWnd); - - InitImGui(); - - // Enter the message loop - MSG msg; - ZeroMemory(&msg, sizeof(msg)); - while (msg.message != WM_QUIT) + if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) { - if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - continue; - } + TranslateMessage(&msg); + DispatchMessage(&msg); + continue; + } - // 1) ImGui start frame, setup time delta & inputs - ImGuiIO& io = ImGui::GetIO(); - INT64 current_time; - QueryPerformanceCounter((LARGE_INTEGER *)¤t_time); - io.DeltaTime = (float)(current_time - time) / ticks_per_second; - time = current_time; - BYTE keystate[256]; - GetKeyboardState(keystate); - for (int i = 0; i < 256; i++) - io.KeysDown[i] = (keystate[i] & 0x80) != 0; - io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0; - io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0; - // io.MousePos : filled by WM_MOUSEMOVE event - // io.MouseDown : filled by WM_*BUTTON* events - // io.MouseWheel : filled by WM_MOUSEWHEEL events - ImGui::NewFrame(); + UpdateImGui(); - // 2) ImGui usage - 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"); + // 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] = io.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); + // 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); - if (show_test_window) - { - // More example code in ShowTestWindow() - 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); - } - - if (show_another_window) - { - ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); - ImGui::Text("Hello"); - ImGui::End(); - } - - // 3) Render - g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); - g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false); - g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, false); - g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(204, 153, 153), 1.0f, 0); // Clear the backbuffer and the zbuffer - if (g_pd3dDevice->BeginScene() >= 0) - { - ImGui::Render(); - g_pd3dDevice->EndScene(); - } - g_pd3dDevice->Present(NULL, NULL, NULL, NULL); + // 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::Shutdown(); - } + // Show another simple window + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); + ImGui::Text("Hello"); + ImGui::End(); + } + + // Rendering + g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); + g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false); + g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, false); + g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(204, 153, 153), 1.0f, 0); + if (g_pd3dDevice->BeginScene() >= 0) + { + ImGui::Render(); + g_pd3dDevice->EndScene(); + } + g_pd3dDevice->Present(NULL, NULL, NULL, NULL); + } + + ImGui::Shutdown(); if (g_pVB) g_pVB->Release(); diff --git a/examples/opengl_example/Makefile.Linux b/examples/opengl_example/Makefile.Linux new file mode 100644 index 0000000..6fd0b97 --- /dev/null +++ b/examples/opengl_example/Makefile.Linux @@ -0,0 +1,18 @@ +# +# Quick and dirty makefile to build on Linux +# tested on Ubuntu 14.04.1 32bit +# + +SRC = main.cpp ../../imgui.cpp + +OBJ = $(SRC:.cpp=.o) + +CXXFLAGS = -I../../ `pkg-config --cflags glfw3` + +LIBS = `pkg-config --static --libs glfw3` -lGLEW + +all: $(OBJ) + $(CXX) $(OBJ) $(LIBS) + +clean: + $(RM) -f $(OBJ) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..94f4ef0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +## Visual Studio files +examples/directx9_example/Debug/* +examples/directx9_example/Release/* +examples/directx9_example/ipch/* +examples/opengl_example/Debug/* +examples/opengl_example/Release/* +examples/opengl_example/ipch/* +*.opensdf +*.sdf +*.suo +*.vcxproj.user + +## Ini files +imgui.ini diff --git a/README.md b/README.md index 65a9ad7..c3f7767 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@ ImGui ===== -ImGui is a bloat-free graphical user interface library for C/C++. It is portable, renderer agnostic and carries minimal amount of dependencies (only 3 files are needed). It is based on an "immediate" graphical user interface paradigm which allows you to build simple user interfaces with ease. +ImGui is a bloat-free graphical user interface library for C++. It outputs vertex buffers that you can render in your 3D-pipeline enabled application. It is portable, renderer agnostic and carries minimal amount of dependencies (only 3 files are needed). It is based on an "immediate" graphical user interface paradigm which allows you to build simple user interfaces with ease. -ImGui is designed to allow programmers to create "content creation" or "debug" tools (as opposed to tools for the average end-user). It favors simplicity and thus lacks certain features normally found in more high-level libraries, such as string localisation. +ImGui is designed to enable fast iteration and allow programmers to create "content creation" or "debug" tools (as opposed to tools for the average end-user). It favors simplicity and thus lacks certain features normally found in more high-level libraries, such as string localisation. -Usage example: +ImGui is particularly suited to integration in 3D applications, fullscreen applications, embedded applications, games, or any applications on consoles platforms where operating system features are non-standard. + +After ImGui is setup in your engine, you can use it like in this example: ![screenshot of sample code alongside its output with ImGui](/web/code_sample_01.png?raw=true) -ImGui output vertex buffer and simple command-list that you can render in application. Refer to the sample applications in the examples/ folder for instructions on how to integrate ImGui within your existing codebase. - +ImGui outputs vertex buffers and simple command-lists that you can render in your application. Because it doesn't know or touch graphics state directly, you can call ImGui commands anywhere in your code (e.g. in the middle of a running algorithm, or in the middle of your own rendering process). Refer to the sample applications in the examples/ folder for instructions on how to integrate ImGui with your existing codebase. Gallery ------- @@ -20,12 +21,22 @@ ![screenshot 3](/web/test_window_03.png?raw=true) ![screenshot 4](/web/test_window_04.png?raw=true) +References +---------- + +The Immediate Mode GUI paradigm may at first appear unusual to some users. This is mainly because "Retained Mode" GUIs have been so widespread and predominant. The following links can give you a better understanding about how Immediate Mode GUIs works. +- [Johannes 'johno' Norneby's article](http://www.johno.se/book/imgui.html). +- [A presentation by Rickard Gustafsson and Johannes Algelind](http://www.cse.chalmers.se/edu/year/2011/course/TDA361/Advanced%20Computer%20Graphics/IMGUI.pdf). +- [Jari Komppa's tutorial on building an ImGui library](http://iki.fi/sol/imgui/). +- [Casey Muratori's original video that popularized the concept](https://mollyrocket.com/861). + + Credits ------- Developed by [Omar Cornut](http://www.miracleworld.net). The library was developed with the support of [Media Molecule](http://www.mediamolecule.com) and first used internally on the game [Tearaway](http://tearaway.mediamolecule.com). -Embeds [proggy_clean font](http://www.proggyfonts.net/) by Tristan Grimmer (also MIT license). +Embeds [proggy_clean](http://www.proggyfonts.net/) font by Tristan Grimmer (also MIT license). Inspiration, feedback, and testing: Casey Muratori, Atman Binstock, Mikko Mononen, Emmanuel Briney, Stefan Kamoda, Matt Willis. Thanks! diff --git a/examples/directx9_example/main.cpp b/examples/directx9_example/main.cpp index fd25f01..3036eb7 100644 --- a/examples/directx9_example/main.cpp +++ b/examples/directx9_example/main.cpp @@ -12,16 +12,15 @@ static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; // Our rendering device static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL; // Buffer to hold vertices static LPDIRECT3DTEXTURE9 g_pTexture = NULL; // Our texture - struct CUSTOMVERTEX { D3DXVECTOR3 position; D3DCOLOR color; float tu, tv; }; - #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1) +// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structuer) static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) { size_t total_vtx_count = 0; @@ -30,31 +29,13 @@ if (total_vtx_count == 0) return; - ImVector clip_rect_stack; - clip_rect_stack.push_back(ImVec4(-9999,-9999,+9999,+9999)); - - // Setup orthographic projection - // Set up world matrix - D3DXMATRIXA16 mat; - D3DXMatrixIdentity(&mat); - g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat); - g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat); - D3DXMatrixOrthoOffCenterLH(&mat, 0.0f, ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y, 0.0f, -1.0f, +1.0f); - g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat); - - D3DSURFACE_DESC texture_desc; - g_pTexture->GetLevelDesc(0, &texture_desc); - - // Fill the vertex buffer + // Copy and convert all vertices into a single contiguous buffer CUSTOMVERTEX* vtx_dst; if (g_pVB->Lock(0, total_vtx_count, (void**)&vtx_dst, D3DLOCK_DISCARD) < 0) return; - for (int n = 0; n < cmd_lists_count; n++) { const ImDrawList* cmd_list = cmd_lists[n]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; const ImDrawVert* vtx_src = &cmd_list->vtx_buffer[0]; for (size_t i = 0; i < cmd_list->vtx_buffer.size(); i++) { @@ -73,11 +54,10 @@ g_pd3dDevice->SetStreamSource( 0, g_pVB, 0, sizeof( CUSTOMVERTEX ) ); g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX ); - // Setup render state: alpha-blending enabled, no face culling, no depth testing + // Setup render state: alpha-blending, no face culling, no depth testing g_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ); g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, false ); g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, false ); - g_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, true ); g_pd3dDevice->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_ADD ); g_pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, false ); @@ -94,97 +74,31 @@ g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); - int vtx_consumed = 0; // offset in vertex buffer. each command consume ImDrawCmd::vtx_count of those - bool clip_rect_dirty = true; + // Setup orthographic projection matrix + D3DXMATRIXA16 mat; + D3DXMatrixIdentity(&mat); + g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat); + g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat); + D3DXMatrixOrthoOffCenterLH(&mat, 0.0f, ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y, 0.0f, -1.0f, +1.0f); + g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat); + // Render command lists + int vtx_offset = 0; for (int n = 0; n < cmd_lists_count; n++) { + // Render command list const ImDrawList* cmd_list = cmd_lists[n]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; - const ImDrawCmd* pcmd = &cmd_list->commands.front(); - const ImDrawCmd* pcmd_end = &cmd_list->commands.back(); - int clip_rect_buf_consumed = 0; // offset in cmd_list->clip_rect_buffer. each PushClipRect command consume 1 of those. - while (pcmd <= pcmd_end) + const ImDrawCmd* pcmd_end = cmd_list->commands.end(); + for (const ImDrawCmd* pcmd = cmd_list->commands.begin(); pcmd != pcmd_end; pcmd++) { - const ImDrawCmd& cmd = *pcmd++; - switch (cmd.cmd_type) - { - case ImDrawCmdType_DrawTriangleList: - if (clip_rect_dirty) - { - const ImVec4& clip_rect = clip_rect_stack.back(); - const RECT r = { (LONG)clip_rect.x, (LONG)clip_rect.y, (LONG)clip_rect.z, (LONG)clip_rect.w }; - g_pd3dDevice->SetScissorRect(&r); - clip_rect_dirty = false; - } - g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, vtx_consumed, cmd.vtx_count/3); - vtx_consumed += cmd.vtx_count; - break; - - case ImDrawCmdType_PushClipRect: - clip_rect_stack.push_back(cmd_list->clip_rect_buffer[clip_rect_buf_consumed++]); - clip_rect_dirty = true; - break; - - case ImDrawCmdType_PopClipRect: - clip_rect_stack.pop_back(); - clip_rect_dirty = true; - break; - } + const RECT r = { (LONG)pcmd->clip_rect.x, (LONG)pcmd->clip_rect.y, (LONG)pcmd->clip_rect.z, (LONG)pcmd->clip_rect.w }; + g_pd3dDevice->SetScissorRect(&r); + g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, vtx_offset, pcmd->vtx_count/3); + vtx_offset += pcmd->vtx_count; } } } -// Get text data in Win32 clipboard -static const char* ImImpl_GetClipboardTextFn() -{ - static char* buf_local = NULL; - if (buf_local) - { - free(buf_local); - buf_local = NULL; - } - - if (!OpenClipboard(NULL)) - return NULL; - - HANDLE buf_handle = GetClipboardData(CF_TEXT); - if (buf_handle == NULL) - return NULL; - - if (char* buf_global = (char*)GlobalLock(buf_handle)) - buf_local = strdup(buf_global); - GlobalUnlock(buf_handle); - CloseClipboard(); - - return buf_local; -} - -// Set text data in Win32 clipboard -static void ImImpl_SetClipboardTextFn(const char* text, const char* text_end) -{ - if (!OpenClipboard(NULL)) - return; - - if (!text_end) - text_end = text + strlen(text); - - const int buf_length = (text_end - text) + 1; - HGLOBAL buf_handle = GlobalAlloc(GMEM_MOVEABLE, buf_length * sizeof(char)); - if (buf_handle == NULL) - return; - - char* buf_global = (char *)GlobalLock(buf_handle); - memcpy(buf_global, text, text_end - text); - buf_global[text_end - text] = 0; - GlobalUnlock(buf_handle); - - EmptyClipboard(); - SetClipboardData(CF_TEXT, buf_handle); - CloseClipboard(); -} - HRESULT InitD3D(HWND hWnd) { if (NULL == (g_pD3D = Direct3DCreate9(D3D_SDK_VERSION))) @@ -203,17 +117,6 @@ if (g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &g_pd3dDevice) < 0) return E_FAIL; - // Create the vertex buffer. - if (g_pd3dDevice->CreateVertexBuffer(10000 * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL) < 0) - return E_FAIL; - - // Load font texture - const void* png_data; - unsigned int png_size; - ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); - if (D3DXCreateTextureFromFileInMemory(g_pd3dDevice, png_data, png_size, &g_pTexture) < 0) - return E_FAIL; - return S_OK; } @@ -247,9 +150,11 @@ io.MouseDown[1] = false; return true; case WM_MOUSEWHEEL: + // Mouse wheel: -1,0,+1 io.MouseWheel = GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? +1 : -1; return true; case WM_MOUSEMOVE: + // Mouse position, in pixels (set to -1,-1 if no mouse / on another screen, etc.) io.MousePos.x = (signed short)(lParam); io.MousePos.y = (signed short)(lParam >> 16); return true; @@ -274,9 +179,10 @@ GetClientRect(hWnd, &rect); ImGuiIO& io = ImGui::GetIO(); - io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); - io.DeltaTime = 1.0f/60.0f; - io.KeyMap[ImGuiKey_Tab] = VK_TAB; + io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); // 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 Direct3D Texels + io.KeyMap[ImGuiKey_Tab] = VK_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array that we will update during the application lifetime. io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT; io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT; io.KeyMap[ImGuiKey_UpArrow] = VK_UP; @@ -295,8 +201,52 @@ io.KeyMap[ImGuiKey_Z] = 'Z'; io.RenderDrawListsFn = ImImpl_RenderDrawLists; - io.SetClipboardTextFn = ImImpl_SetClipboardTextFn; - io.GetClipboardTextFn = ImImpl_GetClipboardTextFn; + + // Create the vertex buffer + if (g_pd3dDevice->CreateVertexBuffer(10000 * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL) < 0) + { + IM_ASSERT(0); + return; + } + + // Load font texture + const void* png_data; + unsigned int png_size; + ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); + if (D3DXCreateTextureFromFileInMemory(g_pd3dDevice, png_data, png_size, &g_pTexture) < 0) + { + IM_ASSERT(0); + return; + } +} + +INT64 ticks_per_second = 0; +INT64 time = 0; + +void UpdateImGui() +{ + ImGuiIO& io = ImGui::GetIO(); + + // Setup timestep + INT64 current_time; + QueryPerformanceCounter((LARGE_INTEGER *)¤t_time); + io.DeltaTime = (float)(current_time - time) / ticks_per_second; + time = current_time; + + // Setup inputs + // (we already got mouse position, buttons, wheel from the window message callback) + BYTE keystate[256]; + GetKeyboardState(keystate); + for (int i = 0; i < 256; i++) + io.KeysDown[i] = (keystate[i] & 0x80) != 0; + io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0; + io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0; + // io.MousePos : filled by WM_MOUSEMOVE event + // io.MouseDown : filled by WM_*BUTTON* events + // io.MouseWheel : filled by WM_MOUSEWHEEL events + + // Start the frame + ImGui::NewFrame(); } int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR, int) @@ -308,99 +258,91 @@ // Create the application's window hWnd = CreateWindow(L"ImGui Example", L"ImGui DirectX9 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, NULL, NULL, wc.hInstance, NULL); - INT64 ticks_per_second, time; if (!QueryPerformanceFrequency((LARGE_INTEGER *)&ticks_per_second)) return 1; if (!QueryPerformanceCounter((LARGE_INTEGER *)&time)) return 1; // Initialize Direct3D - if (InitD3D(hWnd) >= 0) + if (InitD3D(hWnd) < 0) + { + if (g_pVB) + g_pVB->Release(); + UnregisterClass(L"ImGui Example", wc.hInstance); + return 1; + } + + // Show the window + ShowWindow(hWnd, SW_SHOWDEFAULT); + UpdateWindow(hWnd); + + InitImGui(); + + // Enter the message loop + MSG msg; + ZeroMemory(&msg, sizeof(msg)); + while (msg.message != WM_QUIT) { - // Show the window - ShowWindow(hWnd, SW_SHOWDEFAULT); - UpdateWindow(hWnd); - - InitImGui(); - - // Enter the message loop - MSG msg; - ZeroMemory(&msg, sizeof(msg)); - while (msg.message != WM_QUIT) + if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) { - if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - continue; - } + TranslateMessage(&msg); + DispatchMessage(&msg); + continue; + } - // 1) ImGui start frame, setup time delta & inputs - ImGuiIO& io = ImGui::GetIO(); - INT64 current_time; - QueryPerformanceCounter((LARGE_INTEGER *)¤t_time); - io.DeltaTime = (float)(current_time - time) / ticks_per_second; - time = current_time; - BYTE keystate[256]; - GetKeyboardState(keystate); - for (int i = 0; i < 256; i++) - io.KeysDown[i] = (keystate[i] & 0x80) != 0; - io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0; - io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0; - // io.MousePos : filled by WM_MOUSEMOVE event - // io.MouseDown : filled by WM_*BUTTON* events - // io.MouseWheel : filled by WM_MOUSEWHEEL events - ImGui::NewFrame(); + UpdateImGui(); - // 2) ImGui usage - 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"); + // 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] = io.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); + // 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); - if (show_test_window) - { - // More example code in ShowTestWindow() - 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); - } - - if (show_another_window) - { - ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); - ImGui::Text("Hello"); - ImGui::End(); - } - - // 3) Render - g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); - g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false); - g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, false); - g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(204, 153, 153), 1.0f, 0); // Clear the backbuffer and the zbuffer - if (g_pd3dDevice->BeginScene() >= 0) - { - ImGui::Render(); - g_pd3dDevice->EndScene(); - } - g_pd3dDevice->Present(NULL, NULL, NULL, NULL); + // 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::Shutdown(); - } + // Show another simple window + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); + ImGui::Text("Hello"); + ImGui::End(); + } + + // Rendering + g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); + g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false); + g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, false); + g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(204, 153, 153), 1.0f, 0); + if (g_pd3dDevice->BeginScene() >= 0) + { + ImGui::Render(); + g_pd3dDevice->EndScene(); + } + g_pd3dDevice->Present(NULL, NULL, NULL, NULL); + } + + ImGui::Shutdown(); if (g_pVB) g_pVB->Release(); diff --git a/examples/opengl_example/Makefile.Linux b/examples/opengl_example/Makefile.Linux new file mode 100644 index 0000000..6fd0b97 --- /dev/null +++ b/examples/opengl_example/Makefile.Linux @@ -0,0 +1,18 @@ +# +# Quick and dirty makefile to build on Linux +# tested on Ubuntu 14.04.1 32bit +# + +SRC = main.cpp ../../imgui.cpp + +OBJ = $(SRC:.cpp=.o) + +CXXFLAGS = -I../../ `pkg-config --cflags glfw3` + +LIBS = `pkg-config --static --libs glfw3` -lGLEW + +all: $(OBJ) + $(CXX) $(OBJ) $(LIBS) + +clean: + $(RM) -f $(OBJ) diff --git a/examples/opengl_example/Makefile.Macosx b/examples/opengl_example/Makefile.Macosx new file mode 100644 index 0000000..44ffe15 --- /dev/null +++ b/examples/opengl_example/Makefile.Macosx @@ -0,0 +1,18 @@ +# This makefile currently only works for mac os +# You should install via homebrew: +# brew install glew +# brew install glfw3 +# + +CXXFLAGS=-framework OpenGL -framework Cocoa -framework IOKit +CXXFLAGS+=-I/usr/local/Cellar/glew/1.10.0/include -I/usr/local/Cellar/glfw3/3.0.4/include +CXXFLAGS+=-L/usr/local/Cellar/glew/1.10.0/lib -L/usr/local/Cellar/glfw3/3.0.4/lib +CXXFLAGS+=-lglew -lglfw3 +CXXFLAGS+=-I../../ +CXXFLAGS+= -D__APPLE__ + +main: main.cpp ../../imgui.cpp + $(CXX) $(CXXFLAGS) -o $@ $^ + +clean: + rm main diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..94f4ef0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +## Visual Studio files +examples/directx9_example/Debug/* +examples/directx9_example/Release/* +examples/directx9_example/ipch/* +examples/opengl_example/Debug/* +examples/opengl_example/Release/* +examples/opengl_example/ipch/* +*.opensdf +*.sdf +*.suo +*.vcxproj.user + +## Ini files +imgui.ini diff --git a/README.md b/README.md index 65a9ad7..c3f7767 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@ ImGui ===== -ImGui is a bloat-free graphical user interface library for C/C++. It is portable, renderer agnostic and carries minimal amount of dependencies (only 3 files are needed). It is based on an "immediate" graphical user interface paradigm which allows you to build simple user interfaces with ease. +ImGui is a bloat-free graphical user interface library for C++. It outputs vertex buffers that you can render in your 3D-pipeline enabled application. It is portable, renderer agnostic and carries minimal amount of dependencies (only 3 files are needed). It is based on an "immediate" graphical user interface paradigm which allows you to build simple user interfaces with ease. -ImGui is designed to allow programmers to create "content creation" or "debug" tools (as opposed to tools for the average end-user). It favors simplicity and thus lacks certain features normally found in more high-level libraries, such as string localisation. +ImGui is designed to enable fast iteration and allow programmers to create "content creation" or "debug" tools (as opposed to tools for the average end-user). It favors simplicity and thus lacks certain features normally found in more high-level libraries, such as string localisation. -Usage example: +ImGui is particularly suited to integration in 3D applications, fullscreen applications, embedded applications, games, or any applications on consoles platforms where operating system features are non-standard. + +After ImGui is setup in your engine, you can use it like in this example: ![screenshot of sample code alongside its output with ImGui](/web/code_sample_01.png?raw=true) -ImGui output vertex buffer and simple command-list that you can render in application. Refer to the sample applications in the examples/ folder for instructions on how to integrate ImGui within your existing codebase. - +ImGui outputs vertex buffers and simple command-lists that you can render in your application. Because it doesn't know or touch graphics state directly, you can call ImGui commands anywhere in your code (e.g. in the middle of a running algorithm, or in the middle of your own rendering process). Refer to the sample applications in the examples/ folder for instructions on how to integrate ImGui with your existing codebase. Gallery ------- @@ -20,12 +21,22 @@ ![screenshot 3](/web/test_window_03.png?raw=true) ![screenshot 4](/web/test_window_04.png?raw=true) +References +---------- + +The Immediate Mode GUI paradigm may at first appear unusual to some users. This is mainly because "Retained Mode" GUIs have been so widespread and predominant. The following links can give you a better understanding about how Immediate Mode GUIs works. +- [Johannes 'johno' Norneby's article](http://www.johno.se/book/imgui.html). +- [A presentation by Rickard Gustafsson and Johannes Algelind](http://www.cse.chalmers.se/edu/year/2011/course/TDA361/Advanced%20Computer%20Graphics/IMGUI.pdf). +- [Jari Komppa's tutorial on building an ImGui library](http://iki.fi/sol/imgui/). +- [Casey Muratori's original video that popularized the concept](https://mollyrocket.com/861). + + Credits ------- Developed by [Omar Cornut](http://www.miracleworld.net). The library was developed with the support of [Media Molecule](http://www.mediamolecule.com) and first used internally on the game [Tearaway](http://tearaway.mediamolecule.com). -Embeds [proggy_clean font](http://www.proggyfonts.net/) by Tristan Grimmer (also MIT license). +Embeds [proggy_clean](http://www.proggyfonts.net/) font by Tristan Grimmer (also MIT license). Inspiration, feedback, and testing: Casey Muratori, Atman Binstock, Mikko Mononen, Emmanuel Briney, Stefan Kamoda, Matt Willis. Thanks! diff --git a/examples/directx9_example/main.cpp b/examples/directx9_example/main.cpp index fd25f01..3036eb7 100644 --- a/examples/directx9_example/main.cpp +++ b/examples/directx9_example/main.cpp @@ -12,16 +12,15 @@ static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; // Our rendering device static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL; // Buffer to hold vertices static LPDIRECT3DTEXTURE9 g_pTexture = NULL; // Our texture - struct CUSTOMVERTEX { D3DXVECTOR3 position; D3DCOLOR color; float tu, tv; }; - #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1) +// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structuer) static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) { size_t total_vtx_count = 0; @@ -30,31 +29,13 @@ if (total_vtx_count == 0) return; - ImVector clip_rect_stack; - clip_rect_stack.push_back(ImVec4(-9999,-9999,+9999,+9999)); - - // Setup orthographic projection - // Set up world matrix - D3DXMATRIXA16 mat; - D3DXMatrixIdentity(&mat); - g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat); - g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat); - D3DXMatrixOrthoOffCenterLH(&mat, 0.0f, ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y, 0.0f, -1.0f, +1.0f); - g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat); - - D3DSURFACE_DESC texture_desc; - g_pTexture->GetLevelDesc(0, &texture_desc); - - // Fill the vertex buffer + // Copy and convert all vertices into a single contiguous buffer CUSTOMVERTEX* vtx_dst; if (g_pVB->Lock(0, total_vtx_count, (void**)&vtx_dst, D3DLOCK_DISCARD) < 0) return; - for (int n = 0; n < cmd_lists_count; n++) { const ImDrawList* cmd_list = cmd_lists[n]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; const ImDrawVert* vtx_src = &cmd_list->vtx_buffer[0]; for (size_t i = 0; i < cmd_list->vtx_buffer.size(); i++) { @@ -73,11 +54,10 @@ g_pd3dDevice->SetStreamSource( 0, g_pVB, 0, sizeof( CUSTOMVERTEX ) ); g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX ); - // Setup render state: alpha-blending enabled, no face culling, no depth testing + // Setup render state: alpha-blending, no face culling, no depth testing g_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ); g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, false ); g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, false ); - g_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, true ); g_pd3dDevice->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_ADD ); g_pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, false ); @@ -94,97 +74,31 @@ g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); - int vtx_consumed = 0; // offset in vertex buffer. each command consume ImDrawCmd::vtx_count of those - bool clip_rect_dirty = true; + // Setup orthographic projection matrix + D3DXMATRIXA16 mat; + D3DXMatrixIdentity(&mat); + g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat); + g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat); + D3DXMatrixOrthoOffCenterLH(&mat, 0.0f, ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y, 0.0f, -1.0f, +1.0f); + g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat); + // Render command lists + int vtx_offset = 0; for (int n = 0; n < cmd_lists_count; n++) { + // Render command list const ImDrawList* cmd_list = cmd_lists[n]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; - const ImDrawCmd* pcmd = &cmd_list->commands.front(); - const ImDrawCmd* pcmd_end = &cmd_list->commands.back(); - int clip_rect_buf_consumed = 0; // offset in cmd_list->clip_rect_buffer. each PushClipRect command consume 1 of those. - while (pcmd <= pcmd_end) + const ImDrawCmd* pcmd_end = cmd_list->commands.end(); + for (const ImDrawCmd* pcmd = cmd_list->commands.begin(); pcmd != pcmd_end; pcmd++) { - const ImDrawCmd& cmd = *pcmd++; - switch (cmd.cmd_type) - { - case ImDrawCmdType_DrawTriangleList: - if (clip_rect_dirty) - { - const ImVec4& clip_rect = clip_rect_stack.back(); - const RECT r = { (LONG)clip_rect.x, (LONG)clip_rect.y, (LONG)clip_rect.z, (LONG)clip_rect.w }; - g_pd3dDevice->SetScissorRect(&r); - clip_rect_dirty = false; - } - g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, vtx_consumed, cmd.vtx_count/3); - vtx_consumed += cmd.vtx_count; - break; - - case ImDrawCmdType_PushClipRect: - clip_rect_stack.push_back(cmd_list->clip_rect_buffer[clip_rect_buf_consumed++]); - clip_rect_dirty = true; - break; - - case ImDrawCmdType_PopClipRect: - clip_rect_stack.pop_back(); - clip_rect_dirty = true; - break; - } + const RECT r = { (LONG)pcmd->clip_rect.x, (LONG)pcmd->clip_rect.y, (LONG)pcmd->clip_rect.z, (LONG)pcmd->clip_rect.w }; + g_pd3dDevice->SetScissorRect(&r); + g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, vtx_offset, pcmd->vtx_count/3); + vtx_offset += pcmd->vtx_count; } } } -// Get text data in Win32 clipboard -static const char* ImImpl_GetClipboardTextFn() -{ - static char* buf_local = NULL; - if (buf_local) - { - free(buf_local); - buf_local = NULL; - } - - if (!OpenClipboard(NULL)) - return NULL; - - HANDLE buf_handle = GetClipboardData(CF_TEXT); - if (buf_handle == NULL) - return NULL; - - if (char* buf_global = (char*)GlobalLock(buf_handle)) - buf_local = strdup(buf_global); - GlobalUnlock(buf_handle); - CloseClipboard(); - - return buf_local; -} - -// Set text data in Win32 clipboard -static void ImImpl_SetClipboardTextFn(const char* text, const char* text_end) -{ - if (!OpenClipboard(NULL)) - return; - - if (!text_end) - text_end = text + strlen(text); - - const int buf_length = (text_end - text) + 1; - HGLOBAL buf_handle = GlobalAlloc(GMEM_MOVEABLE, buf_length * sizeof(char)); - if (buf_handle == NULL) - return; - - char* buf_global = (char *)GlobalLock(buf_handle); - memcpy(buf_global, text, text_end - text); - buf_global[text_end - text] = 0; - GlobalUnlock(buf_handle); - - EmptyClipboard(); - SetClipboardData(CF_TEXT, buf_handle); - CloseClipboard(); -} - HRESULT InitD3D(HWND hWnd) { if (NULL == (g_pD3D = Direct3DCreate9(D3D_SDK_VERSION))) @@ -203,17 +117,6 @@ if (g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &g_pd3dDevice) < 0) return E_FAIL; - // Create the vertex buffer. - if (g_pd3dDevice->CreateVertexBuffer(10000 * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL) < 0) - return E_FAIL; - - // Load font texture - const void* png_data; - unsigned int png_size; - ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); - if (D3DXCreateTextureFromFileInMemory(g_pd3dDevice, png_data, png_size, &g_pTexture) < 0) - return E_FAIL; - return S_OK; } @@ -247,9 +150,11 @@ io.MouseDown[1] = false; return true; case WM_MOUSEWHEEL: + // Mouse wheel: -1,0,+1 io.MouseWheel = GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? +1 : -1; return true; case WM_MOUSEMOVE: + // Mouse position, in pixels (set to -1,-1 if no mouse / on another screen, etc.) io.MousePos.x = (signed short)(lParam); io.MousePos.y = (signed short)(lParam >> 16); return true; @@ -274,9 +179,10 @@ GetClientRect(hWnd, &rect); ImGuiIO& io = ImGui::GetIO(); - io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); - io.DeltaTime = 1.0f/60.0f; - io.KeyMap[ImGuiKey_Tab] = VK_TAB; + io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); // 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 Direct3D Texels + io.KeyMap[ImGuiKey_Tab] = VK_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array that we will update during the application lifetime. io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT; io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT; io.KeyMap[ImGuiKey_UpArrow] = VK_UP; @@ -295,8 +201,52 @@ io.KeyMap[ImGuiKey_Z] = 'Z'; io.RenderDrawListsFn = ImImpl_RenderDrawLists; - io.SetClipboardTextFn = ImImpl_SetClipboardTextFn; - io.GetClipboardTextFn = ImImpl_GetClipboardTextFn; + + // Create the vertex buffer + if (g_pd3dDevice->CreateVertexBuffer(10000 * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL) < 0) + { + IM_ASSERT(0); + return; + } + + // Load font texture + const void* png_data; + unsigned int png_size; + ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); + if (D3DXCreateTextureFromFileInMemory(g_pd3dDevice, png_data, png_size, &g_pTexture) < 0) + { + IM_ASSERT(0); + return; + } +} + +INT64 ticks_per_second = 0; +INT64 time = 0; + +void UpdateImGui() +{ + ImGuiIO& io = ImGui::GetIO(); + + // Setup timestep + INT64 current_time; + QueryPerformanceCounter((LARGE_INTEGER *)¤t_time); + io.DeltaTime = (float)(current_time - time) / ticks_per_second; + time = current_time; + + // Setup inputs + // (we already got mouse position, buttons, wheel from the window message callback) + BYTE keystate[256]; + GetKeyboardState(keystate); + for (int i = 0; i < 256; i++) + io.KeysDown[i] = (keystate[i] & 0x80) != 0; + io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0; + io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0; + // io.MousePos : filled by WM_MOUSEMOVE event + // io.MouseDown : filled by WM_*BUTTON* events + // io.MouseWheel : filled by WM_MOUSEWHEEL events + + // Start the frame + ImGui::NewFrame(); } int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR, int) @@ -308,99 +258,91 @@ // Create the application's window hWnd = CreateWindow(L"ImGui Example", L"ImGui DirectX9 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, NULL, NULL, wc.hInstance, NULL); - INT64 ticks_per_second, time; if (!QueryPerformanceFrequency((LARGE_INTEGER *)&ticks_per_second)) return 1; if (!QueryPerformanceCounter((LARGE_INTEGER *)&time)) return 1; // Initialize Direct3D - if (InitD3D(hWnd) >= 0) + if (InitD3D(hWnd) < 0) + { + if (g_pVB) + g_pVB->Release(); + UnregisterClass(L"ImGui Example", wc.hInstance); + return 1; + } + + // Show the window + ShowWindow(hWnd, SW_SHOWDEFAULT); + UpdateWindow(hWnd); + + InitImGui(); + + // Enter the message loop + MSG msg; + ZeroMemory(&msg, sizeof(msg)); + while (msg.message != WM_QUIT) { - // Show the window - ShowWindow(hWnd, SW_SHOWDEFAULT); - UpdateWindow(hWnd); - - InitImGui(); - - // Enter the message loop - MSG msg; - ZeroMemory(&msg, sizeof(msg)); - while (msg.message != WM_QUIT) + if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) { - if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - continue; - } + TranslateMessage(&msg); + DispatchMessage(&msg); + continue; + } - // 1) ImGui start frame, setup time delta & inputs - ImGuiIO& io = ImGui::GetIO(); - INT64 current_time; - QueryPerformanceCounter((LARGE_INTEGER *)¤t_time); - io.DeltaTime = (float)(current_time - time) / ticks_per_second; - time = current_time; - BYTE keystate[256]; - GetKeyboardState(keystate); - for (int i = 0; i < 256; i++) - io.KeysDown[i] = (keystate[i] & 0x80) != 0; - io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0; - io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0; - // io.MousePos : filled by WM_MOUSEMOVE event - // io.MouseDown : filled by WM_*BUTTON* events - // io.MouseWheel : filled by WM_MOUSEWHEEL events - ImGui::NewFrame(); + UpdateImGui(); - // 2) ImGui usage - 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"); + // 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] = io.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); + // 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); - if (show_test_window) - { - // More example code in ShowTestWindow() - 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); - } - - if (show_another_window) - { - ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); - ImGui::Text("Hello"); - ImGui::End(); - } - - // 3) Render - g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); - g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false); - g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, false); - g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(204, 153, 153), 1.0f, 0); // Clear the backbuffer and the zbuffer - if (g_pd3dDevice->BeginScene() >= 0) - { - ImGui::Render(); - g_pd3dDevice->EndScene(); - } - g_pd3dDevice->Present(NULL, NULL, NULL, NULL); + // 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::Shutdown(); - } + // Show another simple window + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); + ImGui::Text("Hello"); + ImGui::End(); + } + + // Rendering + g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); + g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false); + g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, false); + g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(204, 153, 153), 1.0f, 0); + if (g_pd3dDevice->BeginScene() >= 0) + { + ImGui::Render(); + g_pd3dDevice->EndScene(); + } + g_pd3dDevice->Present(NULL, NULL, NULL, NULL); + } + + ImGui::Shutdown(); if (g_pVB) g_pVB->Release(); diff --git a/examples/opengl_example/Makefile.Linux b/examples/opengl_example/Makefile.Linux new file mode 100644 index 0000000..6fd0b97 --- /dev/null +++ b/examples/opengl_example/Makefile.Linux @@ -0,0 +1,18 @@ +# +# Quick and dirty makefile to build on Linux +# tested on Ubuntu 14.04.1 32bit +# + +SRC = main.cpp ../../imgui.cpp + +OBJ = $(SRC:.cpp=.o) + +CXXFLAGS = -I../../ `pkg-config --cflags glfw3` + +LIBS = `pkg-config --static --libs glfw3` -lGLEW + +all: $(OBJ) + $(CXX) $(OBJ) $(LIBS) + +clean: + $(RM) -f $(OBJ) diff --git a/examples/opengl_example/Makefile.Macosx b/examples/opengl_example/Makefile.Macosx new file mode 100644 index 0000000..44ffe15 --- /dev/null +++ b/examples/opengl_example/Makefile.Macosx @@ -0,0 +1,18 @@ +# This makefile currently only works for mac os +# You should install via homebrew: +# brew install glew +# brew install glfw3 +# + +CXXFLAGS=-framework OpenGL -framework Cocoa -framework IOKit +CXXFLAGS+=-I/usr/local/Cellar/glew/1.10.0/include -I/usr/local/Cellar/glfw3/3.0.4/include +CXXFLAGS+=-L/usr/local/Cellar/glew/1.10.0/lib -L/usr/local/Cellar/glfw3/3.0.4/lib +CXXFLAGS+=-lglew -lglfw3 +CXXFLAGS+=-I../../ +CXXFLAGS+= -D__APPLE__ + +main: main.cpp ../../imgui.cpp + $(CXX) $(CXXFLAGS) -o $@ $^ + +clean: + rm main diff --git a/examples/opengl_example/main.cpp b/examples/opengl_example/main.cpp index 909ffa9..609021e 100644 --- a/examples/opengl_example/main.cpp +++ b/examples/opengl_example/main.cpp @@ -2,116 +2,65 @@ #include #include #define STB_IMAGE_IMPLEMENTATION -#include "stb_image.h" // for .png loading +#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 +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen #endif static GLFWwindow* window; -static GLuint vbo; -static GLuint vao; -static GLuint vertexShader; -static GLuint fragmentShader; -static GLuint shaderProgram; static GLuint fontTex; -static GLint uniMVP; -static GLint uniClipRect; +// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structuer) +// We are using the fixed pipeline. +// A faster way would be to collate all vertices from all cmd_lists into a single vertex buffer static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) { - size_t total_vtx_count = 0; - for (int n = 0; n < cmd_lists_count; n++) - total_vtx_count += cmd_lists[n]->vtx_buffer.size(); - if (total_vtx_count == 0) + if (cmd_lists_count == 0) return; - int read_pos_clip_rect_buf = 0; // offset in 'clip_rect_buffer'. each PushClipRect command consume 1 of those. - - ImVector clip_rect_stack; - clip_rect_stack.push_back(ImVec4(-9999,-9999,+9999,+9999)); - - // Setup orthographic projection - const float L = 0.0f; - const float R = ImGui::GetIO().DisplaySize.x; - const float B = ImGui::GetIO().DisplaySize.y; - const float T = 0.0f; - const float mvp[4][4] = - { - { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, - { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, - { 0.0f, 0.0f, -1.0f, 0.0f }, - { -(R+L)/(R-L), -(T+B)/(T-B), 0.0f, 1.0f }, - }; - - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBindVertexArray(vao); - glBufferData(GL_ARRAY_BUFFER, total_vtx_count * sizeof(ImDrawVert), NULL, GL_STREAM_DRAW); - unsigned char* buffer_data = (unsigned char*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); - if (!buffer_data) - return; - int vtx_consumed = 0; - for (int n = 0; n < cmd_lists_count; n++) - { - const ImDrawList* cmd_list = cmd_lists[n]; - if (!cmd_list->vtx_buffer.empty()) - { - memcpy(buffer_data, &cmd_list->vtx_buffer[0], cmd_list->vtx_buffer.size() * sizeof(ImDrawVert)); - buffer_data += cmd_list->vtx_buffer.size() * sizeof(ImDrawVert); - vtx_consumed += cmd_list->vtx_buffer.size(); - } - } - glUnmapBuffer(GL_ARRAY_BUFFER); - - glUseProgram(shaderProgram); - glUniformMatrix4fv(uniMVP, 1, GL_FALSE, &mvp[0][0]); - - // Setup render state: alpha-blending enabled, no face culling, no depth testing + // 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); - vtx_consumed = 0; // offset in vertex buffer. each command consume ImDrawCmd::vtx_count of those - bool clip_rect_dirty = true; + // 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]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; - const ImDrawCmd* pcmd = &cmd_list->commands.front(); - const ImDrawCmd* pcmd_end = &cmd_list->commands.back(); - int clip_rect_buf_consumed = 0; // offset in cmd_list->clip_rect_buffer. each PushClipRect command consume 1 of those. - while (pcmd <= pcmd_end) + 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++) { - const ImDrawCmd& cmd = *pcmd++; - switch (cmd.cmd_type) - { - case ImDrawCmdType_DrawTriangleList: - if (clip_rect_dirty) - { - glUniform4fv(uniClipRect, 1, (float*)&clip_rect_stack.back()); - clip_rect_dirty = false; - } - glDrawArrays(GL_TRIANGLES, vtx_consumed, cmd.vtx_count); - vtx_consumed += cmd.vtx_count; - break; - - case ImDrawCmdType_PushClipRect: - clip_rect_stack.push_back(cmd_list->clip_rect_buffer[clip_rect_buf_consumed++]); - clip_rect_dirty = true; - break; - - case ImDrawCmdType_PopClipRect: - clip_rect_stack.pop_back(); - clip_rect_dirty = true; - break; - } + 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); } static const char* ImImpl_GetClipboardTextFn() @@ -124,55 +73,33 @@ if (!text_end) text_end = text + strlen(text); - char* buf = (char*)malloc(text_end - text + 1); - memcpy(buf, text, text_end-text); - buf[text_end-text] = '\0'; - glfwSetClipboardString(window, buf); - free(buf); + 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); + } } -// Shader sources -// FIXME-OPT: clip at vertex level -const GLchar* vertexSource = - "#version 150 core\n" - "uniform mat4 MVP;" - "in vec2 i_pos;" - "in vec2 i_uv;" - "in vec4 i_col;" - "out vec4 col;" - "out vec2 pixel_pos;" - "out vec2 uv;" - "void main() {" - " col = i_col;" - " pixel_pos = i_pos;" - " uv = i_uv;" - " gl_Position = MVP * vec4(i_pos.x, i_pos.y, 0.0f, 1.0f);" - "}"; -const GLchar* fragmentSource = - "#version 150 core\n" - "uniform sampler2D Tex;" - "uniform vec4 ClipRect;" - "in vec4 col;" - "in vec2 pixel_pos;" - "in vec2 uv;" - "out vec4 o_col;" - "void main() {" - " o_col = texture(Tex, uv) * col;" - //" if (pixel_pos.x < ClipRect.x || pixel_pos.y < ClipRect.y || pixel_pos.x > ClipRect.z || pixel_pos.y > ClipRect.w) discard;" // Clipping: using discard - //" if (step(ClipRect.x,pixel_pos.x) * step(ClipRect.y,pixel_pos.y) * step(pixel_pos.x,ClipRect.z) * step(pixel_pos.y,ClipRect.w) < 1.0f) discard;" // Clipping: using discard and step - " o_col.w *= (step(ClipRect.x,pixel_pos.x) * step(ClipRect.y,pixel_pos.y) * step(pixel_pos.x,ClipRect.z) * step(pixel_pos.y,ClipRect.w));" // Clipping: branch-less, set alpha 0.0f - "}"; - +// GLFW callbacks to get events static void glfw_error_callback(int error, const char* description) { - fputs(description, stderr); + fputs(description, stderr); } -static float mouse_wheel = 0.0f; static void glfw_scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { - mouse_wheel = (float)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) @@ -197,79 +124,17 @@ { glfwSetErrorCallback(glfw_error_callback); - if (!glfwInit()) - exit(1); + if (!glfwInit()) + exit(1); - //glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - //glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); - //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); - glfwWindowHint(GLFW_REFRESH_RATE, 60); glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); - window = glfwCreateWindow(1280, 720, "ImGui OpenGL example", nullptr, nullptr); + 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); - glewExperimental = GL_TRUE; glewInit(); - - GLenum err = GL_NO_ERROR; - GLint status = GL_TRUE; - err = glGetError(); IM_ASSERT(err == GL_NO_ERROR); - - // Create and compile the vertex shader - vertexShader = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(vertexShader, 1, &vertexSource, NULL); - glCompileShader(vertexShader); - glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status); - if (status != GL_TRUE) - { - char buffer[512]; - glGetShaderInfoLog(vertexShader, 1024, NULL, buffer); - printf("%s", buffer); - IM_ASSERT(status == GL_TRUE); - } - - // Create and compile the fragment shader - fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(fragmentShader, 1, &fragmentSource, NULL); - glCompileShader(fragmentShader); - glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status); - IM_ASSERT(status == GL_TRUE); - - // Link the vertex and fragment shader into a shader program - shaderProgram = glCreateProgram(); - glAttachShader(shaderProgram, vertexShader); - glAttachShader(shaderProgram, fragmentShader); - glBindFragDataLocation(shaderProgram, 0, "o_col"); - glLinkProgram(shaderProgram); - glGetProgramiv(shaderProgram, GL_LINK_STATUS, &status); - IM_ASSERT(status == GL_TRUE); - - uniMVP = glGetUniformLocation(shaderProgram, "MVP"); - uniClipRect = glGetUniformLocation(shaderProgram, "ClipRect"); - - // Create Vertex Buffer Objects & Vertex Array Objects - glGenBuffers(1, &vbo); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - GLint posAttrib = glGetAttribLocation(shaderProgram, "i_pos"); - glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), 0); - glEnableVertexAttribArray(posAttrib); - - GLint uvAttrib = glGetAttribLocation(shaderProgram, "i_uv"); - glEnableVertexAttribArray(uvAttrib); - glVertexAttribPointer(uvAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (void*)(2*sizeof(float))); - - GLint colAttrib = glGetAttribLocation(shaderProgram, "i_col"); - glVertexAttribPointer(colAttrib, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (void*)(4*sizeof(float))); - glEnableVertexAttribArray(colAttrib); - err = glGetError(); IM_ASSERT(err == GL_NO_ERROR); } void InitImGui() @@ -278,9 +143,10 @@ glfwGetWindowSize(window, &w, &h); ImGuiIO& io = ImGui::GetIO(); - io.DisplaySize = ImVec2((float)w, (float)h); - io.DeltaTime = 1.0f/60.0f; - io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; + io.DisplaySize = ImVec2((float)w, (float)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.5f; // 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; @@ -307,7 +173,6 @@ 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); - const void* png_data; unsigned int png_size; ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); @@ -317,44 +182,43 @@ stbi_image_free(tex_data); } -void Shutdown() +void UpdateImGui() { - ImGui::Shutdown(); + ImGuiIO& io = ImGui::GetIO(); - glDeleteProgram(shaderProgram); - glDeleteShader(fragmentShader); - glDeleteShader(vertexShader); - glDeleteBuffers(1, &vbo); - glDeleteVertexArrays(1, &vao); + // Setup timestep + static double time = 0.0f; + const double current_time = glfwGetTime(); + io.DeltaTime = (float)(current_time - time); + time = current_time; - glfwTerminate(); + // 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, (float)mouse_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(); - double time = glfwGetTime(); while (!glfwWindowShouldClose(window)) { ImGuiIO& io = ImGui::GetIO(); + io.MouseWheel = 0; glfwPollEvents(); + UpdateImGui(); - // 1) ImGui start frame, setup time delta & inputs - const double current_time = glfwGetTime(); - io.DeltaTime = (float)(current_time - time); - time = current_time; - double mouse_x, mouse_y; - glfwGetCursorPos(window, &mouse_x, &mouse_y); - io.MousePos = ImVec2((float)mouse_x, (float)mouse_y); - io.MouseDown[0] = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) != 0; - io.MouseDown[1] = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT) != 0; - io.MouseWheel = (mouse_wheel != 0) ? mouse_wheel > 0.0f ? 1 : - 1 : 0; - mouse_wheel = 0.0f; - ImGui::NewFrame(); - - // 2) ImGui usage + // 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; @@ -368,19 +232,21 @@ 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] = io.DeltaTime * 1000.0f; + 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) { - // More example code in ShowTestWindow() - 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::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); } + // Show another simple window if (show_another_window) { ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); @@ -388,15 +254,15 @@ ImGui::End(); } - // 3) Render - glClearColor(0.8f, 0.6f, 0.6f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); + // 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); } - Shutdown(); - + ImGui::Shutdown(); + glfwTerminate(); return 0; } diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..94f4ef0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +## Visual Studio files +examples/directx9_example/Debug/* +examples/directx9_example/Release/* +examples/directx9_example/ipch/* +examples/opengl_example/Debug/* +examples/opengl_example/Release/* +examples/opengl_example/ipch/* +*.opensdf +*.sdf +*.suo +*.vcxproj.user + +## Ini files +imgui.ini diff --git a/README.md b/README.md index 65a9ad7..c3f7767 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@ ImGui ===== -ImGui is a bloat-free graphical user interface library for C/C++. It is portable, renderer agnostic and carries minimal amount of dependencies (only 3 files are needed). It is based on an "immediate" graphical user interface paradigm which allows you to build simple user interfaces with ease. +ImGui is a bloat-free graphical user interface library for C++. It outputs vertex buffers that you can render in your 3D-pipeline enabled application. It is portable, renderer agnostic and carries minimal amount of dependencies (only 3 files are needed). It is based on an "immediate" graphical user interface paradigm which allows you to build simple user interfaces with ease. -ImGui is designed to allow programmers to create "content creation" or "debug" tools (as opposed to tools for the average end-user). It favors simplicity and thus lacks certain features normally found in more high-level libraries, such as string localisation. +ImGui is designed to enable fast iteration and allow programmers to create "content creation" or "debug" tools (as opposed to tools for the average end-user). It favors simplicity and thus lacks certain features normally found in more high-level libraries, such as string localisation. -Usage example: +ImGui is particularly suited to integration in 3D applications, fullscreen applications, embedded applications, games, or any applications on consoles platforms where operating system features are non-standard. + +After ImGui is setup in your engine, you can use it like in this example: ![screenshot of sample code alongside its output with ImGui](/web/code_sample_01.png?raw=true) -ImGui output vertex buffer and simple command-list that you can render in application. Refer to the sample applications in the examples/ folder for instructions on how to integrate ImGui within your existing codebase. - +ImGui outputs vertex buffers and simple command-lists that you can render in your application. Because it doesn't know or touch graphics state directly, you can call ImGui commands anywhere in your code (e.g. in the middle of a running algorithm, or in the middle of your own rendering process). Refer to the sample applications in the examples/ folder for instructions on how to integrate ImGui with your existing codebase. Gallery ------- @@ -20,12 +21,22 @@ ![screenshot 3](/web/test_window_03.png?raw=true) ![screenshot 4](/web/test_window_04.png?raw=true) +References +---------- + +The Immediate Mode GUI paradigm may at first appear unusual to some users. This is mainly because "Retained Mode" GUIs have been so widespread and predominant. The following links can give you a better understanding about how Immediate Mode GUIs works. +- [Johannes 'johno' Norneby's article](http://www.johno.se/book/imgui.html). +- [A presentation by Rickard Gustafsson and Johannes Algelind](http://www.cse.chalmers.se/edu/year/2011/course/TDA361/Advanced%20Computer%20Graphics/IMGUI.pdf). +- [Jari Komppa's tutorial on building an ImGui library](http://iki.fi/sol/imgui/). +- [Casey Muratori's original video that popularized the concept](https://mollyrocket.com/861). + + Credits ------- Developed by [Omar Cornut](http://www.miracleworld.net). The library was developed with the support of [Media Molecule](http://www.mediamolecule.com) and first used internally on the game [Tearaway](http://tearaway.mediamolecule.com). -Embeds [proggy_clean font](http://www.proggyfonts.net/) by Tristan Grimmer (also MIT license). +Embeds [proggy_clean](http://www.proggyfonts.net/) font by Tristan Grimmer (also MIT license). Inspiration, feedback, and testing: Casey Muratori, Atman Binstock, Mikko Mononen, Emmanuel Briney, Stefan Kamoda, Matt Willis. Thanks! diff --git a/examples/directx9_example/main.cpp b/examples/directx9_example/main.cpp index fd25f01..3036eb7 100644 --- a/examples/directx9_example/main.cpp +++ b/examples/directx9_example/main.cpp @@ -12,16 +12,15 @@ static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; // Our rendering device static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL; // Buffer to hold vertices static LPDIRECT3DTEXTURE9 g_pTexture = NULL; // Our texture - struct CUSTOMVERTEX { D3DXVECTOR3 position; D3DCOLOR color; float tu, tv; }; - #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1) +// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structuer) static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) { size_t total_vtx_count = 0; @@ -30,31 +29,13 @@ if (total_vtx_count == 0) return; - ImVector clip_rect_stack; - clip_rect_stack.push_back(ImVec4(-9999,-9999,+9999,+9999)); - - // Setup orthographic projection - // Set up world matrix - D3DXMATRIXA16 mat; - D3DXMatrixIdentity(&mat); - g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat); - g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat); - D3DXMatrixOrthoOffCenterLH(&mat, 0.0f, ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y, 0.0f, -1.0f, +1.0f); - g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat); - - D3DSURFACE_DESC texture_desc; - g_pTexture->GetLevelDesc(0, &texture_desc); - - // Fill the vertex buffer + // Copy and convert all vertices into a single contiguous buffer CUSTOMVERTEX* vtx_dst; if (g_pVB->Lock(0, total_vtx_count, (void**)&vtx_dst, D3DLOCK_DISCARD) < 0) return; - for (int n = 0; n < cmd_lists_count; n++) { const ImDrawList* cmd_list = cmd_lists[n]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; const ImDrawVert* vtx_src = &cmd_list->vtx_buffer[0]; for (size_t i = 0; i < cmd_list->vtx_buffer.size(); i++) { @@ -73,11 +54,10 @@ g_pd3dDevice->SetStreamSource( 0, g_pVB, 0, sizeof( CUSTOMVERTEX ) ); g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX ); - // Setup render state: alpha-blending enabled, no face culling, no depth testing + // Setup render state: alpha-blending, no face culling, no depth testing g_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ); g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, false ); g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, false ); - g_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, true ); g_pd3dDevice->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_ADD ); g_pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, false ); @@ -94,97 +74,31 @@ g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); - int vtx_consumed = 0; // offset in vertex buffer. each command consume ImDrawCmd::vtx_count of those - bool clip_rect_dirty = true; + // Setup orthographic projection matrix + D3DXMATRIXA16 mat; + D3DXMatrixIdentity(&mat); + g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat); + g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat); + D3DXMatrixOrthoOffCenterLH(&mat, 0.0f, ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y, 0.0f, -1.0f, +1.0f); + g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat); + // Render command lists + int vtx_offset = 0; for (int n = 0; n < cmd_lists_count; n++) { + // Render command list const ImDrawList* cmd_list = cmd_lists[n]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; - const ImDrawCmd* pcmd = &cmd_list->commands.front(); - const ImDrawCmd* pcmd_end = &cmd_list->commands.back(); - int clip_rect_buf_consumed = 0; // offset in cmd_list->clip_rect_buffer. each PushClipRect command consume 1 of those. - while (pcmd <= pcmd_end) + const ImDrawCmd* pcmd_end = cmd_list->commands.end(); + for (const ImDrawCmd* pcmd = cmd_list->commands.begin(); pcmd != pcmd_end; pcmd++) { - const ImDrawCmd& cmd = *pcmd++; - switch (cmd.cmd_type) - { - case ImDrawCmdType_DrawTriangleList: - if (clip_rect_dirty) - { - const ImVec4& clip_rect = clip_rect_stack.back(); - const RECT r = { (LONG)clip_rect.x, (LONG)clip_rect.y, (LONG)clip_rect.z, (LONG)clip_rect.w }; - g_pd3dDevice->SetScissorRect(&r); - clip_rect_dirty = false; - } - g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, vtx_consumed, cmd.vtx_count/3); - vtx_consumed += cmd.vtx_count; - break; - - case ImDrawCmdType_PushClipRect: - clip_rect_stack.push_back(cmd_list->clip_rect_buffer[clip_rect_buf_consumed++]); - clip_rect_dirty = true; - break; - - case ImDrawCmdType_PopClipRect: - clip_rect_stack.pop_back(); - clip_rect_dirty = true; - break; - } + const RECT r = { (LONG)pcmd->clip_rect.x, (LONG)pcmd->clip_rect.y, (LONG)pcmd->clip_rect.z, (LONG)pcmd->clip_rect.w }; + g_pd3dDevice->SetScissorRect(&r); + g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, vtx_offset, pcmd->vtx_count/3); + vtx_offset += pcmd->vtx_count; } } } -// Get text data in Win32 clipboard -static const char* ImImpl_GetClipboardTextFn() -{ - static char* buf_local = NULL; - if (buf_local) - { - free(buf_local); - buf_local = NULL; - } - - if (!OpenClipboard(NULL)) - return NULL; - - HANDLE buf_handle = GetClipboardData(CF_TEXT); - if (buf_handle == NULL) - return NULL; - - if (char* buf_global = (char*)GlobalLock(buf_handle)) - buf_local = strdup(buf_global); - GlobalUnlock(buf_handle); - CloseClipboard(); - - return buf_local; -} - -// Set text data in Win32 clipboard -static void ImImpl_SetClipboardTextFn(const char* text, const char* text_end) -{ - if (!OpenClipboard(NULL)) - return; - - if (!text_end) - text_end = text + strlen(text); - - const int buf_length = (text_end - text) + 1; - HGLOBAL buf_handle = GlobalAlloc(GMEM_MOVEABLE, buf_length * sizeof(char)); - if (buf_handle == NULL) - return; - - char* buf_global = (char *)GlobalLock(buf_handle); - memcpy(buf_global, text, text_end - text); - buf_global[text_end - text] = 0; - GlobalUnlock(buf_handle); - - EmptyClipboard(); - SetClipboardData(CF_TEXT, buf_handle); - CloseClipboard(); -} - HRESULT InitD3D(HWND hWnd) { if (NULL == (g_pD3D = Direct3DCreate9(D3D_SDK_VERSION))) @@ -203,17 +117,6 @@ if (g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &g_pd3dDevice) < 0) return E_FAIL; - // Create the vertex buffer. - if (g_pd3dDevice->CreateVertexBuffer(10000 * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL) < 0) - return E_FAIL; - - // Load font texture - const void* png_data; - unsigned int png_size; - ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); - if (D3DXCreateTextureFromFileInMemory(g_pd3dDevice, png_data, png_size, &g_pTexture) < 0) - return E_FAIL; - return S_OK; } @@ -247,9 +150,11 @@ io.MouseDown[1] = false; return true; case WM_MOUSEWHEEL: + // Mouse wheel: -1,0,+1 io.MouseWheel = GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? +1 : -1; return true; case WM_MOUSEMOVE: + // Mouse position, in pixels (set to -1,-1 if no mouse / on another screen, etc.) io.MousePos.x = (signed short)(lParam); io.MousePos.y = (signed short)(lParam >> 16); return true; @@ -274,9 +179,10 @@ GetClientRect(hWnd, &rect); ImGuiIO& io = ImGui::GetIO(); - io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); - io.DeltaTime = 1.0f/60.0f; - io.KeyMap[ImGuiKey_Tab] = VK_TAB; + io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); // 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 Direct3D Texels + io.KeyMap[ImGuiKey_Tab] = VK_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array that we will update during the application lifetime. io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT; io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT; io.KeyMap[ImGuiKey_UpArrow] = VK_UP; @@ -295,8 +201,52 @@ io.KeyMap[ImGuiKey_Z] = 'Z'; io.RenderDrawListsFn = ImImpl_RenderDrawLists; - io.SetClipboardTextFn = ImImpl_SetClipboardTextFn; - io.GetClipboardTextFn = ImImpl_GetClipboardTextFn; + + // Create the vertex buffer + if (g_pd3dDevice->CreateVertexBuffer(10000 * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL) < 0) + { + IM_ASSERT(0); + return; + } + + // Load font texture + const void* png_data; + unsigned int png_size; + ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); + if (D3DXCreateTextureFromFileInMemory(g_pd3dDevice, png_data, png_size, &g_pTexture) < 0) + { + IM_ASSERT(0); + return; + } +} + +INT64 ticks_per_second = 0; +INT64 time = 0; + +void UpdateImGui() +{ + ImGuiIO& io = ImGui::GetIO(); + + // Setup timestep + INT64 current_time; + QueryPerformanceCounter((LARGE_INTEGER *)¤t_time); + io.DeltaTime = (float)(current_time - time) / ticks_per_second; + time = current_time; + + // Setup inputs + // (we already got mouse position, buttons, wheel from the window message callback) + BYTE keystate[256]; + GetKeyboardState(keystate); + for (int i = 0; i < 256; i++) + io.KeysDown[i] = (keystate[i] & 0x80) != 0; + io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0; + io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0; + // io.MousePos : filled by WM_MOUSEMOVE event + // io.MouseDown : filled by WM_*BUTTON* events + // io.MouseWheel : filled by WM_MOUSEWHEEL events + + // Start the frame + ImGui::NewFrame(); } int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR, int) @@ -308,99 +258,91 @@ // Create the application's window hWnd = CreateWindow(L"ImGui Example", L"ImGui DirectX9 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, NULL, NULL, wc.hInstance, NULL); - INT64 ticks_per_second, time; if (!QueryPerformanceFrequency((LARGE_INTEGER *)&ticks_per_second)) return 1; if (!QueryPerformanceCounter((LARGE_INTEGER *)&time)) return 1; // Initialize Direct3D - if (InitD3D(hWnd) >= 0) + if (InitD3D(hWnd) < 0) + { + if (g_pVB) + g_pVB->Release(); + UnregisterClass(L"ImGui Example", wc.hInstance); + return 1; + } + + // Show the window + ShowWindow(hWnd, SW_SHOWDEFAULT); + UpdateWindow(hWnd); + + InitImGui(); + + // Enter the message loop + MSG msg; + ZeroMemory(&msg, sizeof(msg)); + while (msg.message != WM_QUIT) { - // Show the window - ShowWindow(hWnd, SW_SHOWDEFAULT); - UpdateWindow(hWnd); - - InitImGui(); - - // Enter the message loop - MSG msg; - ZeroMemory(&msg, sizeof(msg)); - while (msg.message != WM_QUIT) + if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) { - if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - continue; - } + TranslateMessage(&msg); + DispatchMessage(&msg); + continue; + } - // 1) ImGui start frame, setup time delta & inputs - ImGuiIO& io = ImGui::GetIO(); - INT64 current_time; - QueryPerformanceCounter((LARGE_INTEGER *)¤t_time); - io.DeltaTime = (float)(current_time - time) / ticks_per_second; - time = current_time; - BYTE keystate[256]; - GetKeyboardState(keystate); - for (int i = 0; i < 256; i++) - io.KeysDown[i] = (keystate[i] & 0x80) != 0; - io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0; - io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0; - // io.MousePos : filled by WM_MOUSEMOVE event - // io.MouseDown : filled by WM_*BUTTON* events - // io.MouseWheel : filled by WM_MOUSEWHEEL events - ImGui::NewFrame(); + UpdateImGui(); - // 2) ImGui usage - 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"); + // 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] = io.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); + // 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); - if (show_test_window) - { - // More example code in ShowTestWindow() - 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); - } - - if (show_another_window) - { - ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); - ImGui::Text("Hello"); - ImGui::End(); - } - - // 3) Render - g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); - g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false); - g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, false); - g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(204, 153, 153), 1.0f, 0); // Clear the backbuffer and the zbuffer - if (g_pd3dDevice->BeginScene() >= 0) - { - ImGui::Render(); - g_pd3dDevice->EndScene(); - } - g_pd3dDevice->Present(NULL, NULL, NULL, NULL); + // 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::Shutdown(); - } + // Show another simple window + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); + ImGui::Text("Hello"); + ImGui::End(); + } + + // Rendering + g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); + g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false); + g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, false); + g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(204, 153, 153), 1.0f, 0); + if (g_pd3dDevice->BeginScene() >= 0) + { + ImGui::Render(); + g_pd3dDevice->EndScene(); + } + g_pd3dDevice->Present(NULL, NULL, NULL, NULL); + } + + ImGui::Shutdown(); if (g_pVB) g_pVB->Release(); diff --git a/examples/opengl_example/Makefile.Linux b/examples/opengl_example/Makefile.Linux new file mode 100644 index 0000000..6fd0b97 --- /dev/null +++ b/examples/opengl_example/Makefile.Linux @@ -0,0 +1,18 @@ +# +# Quick and dirty makefile to build on Linux +# tested on Ubuntu 14.04.1 32bit +# + +SRC = main.cpp ../../imgui.cpp + +OBJ = $(SRC:.cpp=.o) + +CXXFLAGS = -I../../ `pkg-config --cflags glfw3` + +LIBS = `pkg-config --static --libs glfw3` -lGLEW + +all: $(OBJ) + $(CXX) $(OBJ) $(LIBS) + +clean: + $(RM) -f $(OBJ) diff --git a/examples/opengl_example/Makefile.Macosx b/examples/opengl_example/Makefile.Macosx new file mode 100644 index 0000000..44ffe15 --- /dev/null +++ b/examples/opengl_example/Makefile.Macosx @@ -0,0 +1,18 @@ +# This makefile currently only works for mac os +# You should install via homebrew: +# brew install glew +# brew install glfw3 +# + +CXXFLAGS=-framework OpenGL -framework Cocoa -framework IOKit +CXXFLAGS+=-I/usr/local/Cellar/glew/1.10.0/include -I/usr/local/Cellar/glfw3/3.0.4/include +CXXFLAGS+=-L/usr/local/Cellar/glew/1.10.0/lib -L/usr/local/Cellar/glfw3/3.0.4/lib +CXXFLAGS+=-lglew -lglfw3 +CXXFLAGS+=-I../../ +CXXFLAGS+= -D__APPLE__ + +main: main.cpp ../../imgui.cpp + $(CXX) $(CXXFLAGS) -o $@ $^ + +clean: + rm main diff --git a/examples/opengl_example/main.cpp b/examples/opengl_example/main.cpp index 909ffa9..609021e 100644 --- a/examples/opengl_example/main.cpp +++ b/examples/opengl_example/main.cpp @@ -2,116 +2,65 @@ #include #include #define STB_IMAGE_IMPLEMENTATION -#include "stb_image.h" // for .png loading +#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 +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen #endif static GLFWwindow* window; -static GLuint vbo; -static GLuint vao; -static GLuint vertexShader; -static GLuint fragmentShader; -static GLuint shaderProgram; static GLuint fontTex; -static GLint uniMVP; -static GLint uniClipRect; +// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structuer) +// We are using the fixed pipeline. +// A faster way would be to collate all vertices from all cmd_lists into a single vertex buffer static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) { - size_t total_vtx_count = 0; - for (int n = 0; n < cmd_lists_count; n++) - total_vtx_count += cmd_lists[n]->vtx_buffer.size(); - if (total_vtx_count == 0) + if (cmd_lists_count == 0) return; - int read_pos_clip_rect_buf = 0; // offset in 'clip_rect_buffer'. each PushClipRect command consume 1 of those. - - ImVector clip_rect_stack; - clip_rect_stack.push_back(ImVec4(-9999,-9999,+9999,+9999)); - - // Setup orthographic projection - const float L = 0.0f; - const float R = ImGui::GetIO().DisplaySize.x; - const float B = ImGui::GetIO().DisplaySize.y; - const float T = 0.0f; - const float mvp[4][4] = - { - { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, - { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, - { 0.0f, 0.0f, -1.0f, 0.0f }, - { -(R+L)/(R-L), -(T+B)/(T-B), 0.0f, 1.0f }, - }; - - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBindVertexArray(vao); - glBufferData(GL_ARRAY_BUFFER, total_vtx_count * sizeof(ImDrawVert), NULL, GL_STREAM_DRAW); - unsigned char* buffer_data = (unsigned char*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); - if (!buffer_data) - return; - int vtx_consumed = 0; - for (int n = 0; n < cmd_lists_count; n++) - { - const ImDrawList* cmd_list = cmd_lists[n]; - if (!cmd_list->vtx_buffer.empty()) - { - memcpy(buffer_data, &cmd_list->vtx_buffer[0], cmd_list->vtx_buffer.size() * sizeof(ImDrawVert)); - buffer_data += cmd_list->vtx_buffer.size() * sizeof(ImDrawVert); - vtx_consumed += cmd_list->vtx_buffer.size(); - } - } - glUnmapBuffer(GL_ARRAY_BUFFER); - - glUseProgram(shaderProgram); - glUniformMatrix4fv(uniMVP, 1, GL_FALSE, &mvp[0][0]); - - // Setup render state: alpha-blending enabled, no face culling, no depth testing + // 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); - vtx_consumed = 0; // offset in vertex buffer. each command consume ImDrawCmd::vtx_count of those - bool clip_rect_dirty = true; + // 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]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; - const ImDrawCmd* pcmd = &cmd_list->commands.front(); - const ImDrawCmd* pcmd_end = &cmd_list->commands.back(); - int clip_rect_buf_consumed = 0; // offset in cmd_list->clip_rect_buffer. each PushClipRect command consume 1 of those. - while (pcmd <= pcmd_end) + 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++) { - const ImDrawCmd& cmd = *pcmd++; - switch (cmd.cmd_type) - { - case ImDrawCmdType_DrawTriangleList: - if (clip_rect_dirty) - { - glUniform4fv(uniClipRect, 1, (float*)&clip_rect_stack.back()); - clip_rect_dirty = false; - } - glDrawArrays(GL_TRIANGLES, vtx_consumed, cmd.vtx_count); - vtx_consumed += cmd.vtx_count; - break; - - case ImDrawCmdType_PushClipRect: - clip_rect_stack.push_back(cmd_list->clip_rect_buffer[clip_rect_buf_consumed++]); - clip_rect_dirty = true; - break; - - case ImDrawCmdType_PopClipRect: - clip_rect_stack.pop_back(); - clip_rect_dirty = true; - break; - } + 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); } static const char* ImImpl_GetClipboardTextFn() @@ -124,55 +73,33 @@ if (!text_end) text_end = text + strlen(text); - char* buf = (char*)malloc(text_end - text + 1); - memcpy(buf, text, text_end-text); - buf[text_end-text] = '\0'; - glfwSetClipboardString(window, buf); - free(buf); + 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); + } } -// Shader sources -// FIXME-OPT: clip at vertex level -const GLchar* vertexSource = - "#version 150 core\n" - "uniform mat4 MVP;" - "in vec2 i_pos;" - "in vec2 i_uv;" - "in vec4 i_col;" - "out vec4 col;" - "out vec2 pixel_pos;" - "out vec2 uv;" - "void main() {" - " col = i_col;" - " pixel_pos = i_pos;" - " uv = i_uv;" - " gl_Position = MVP * vec4(i_pos.x, i_pos.y, 0.0f, 1.0f);" - "}"; -const GLchar* fragmentSource = - "#version 150 core\n" - "uniform sampler2D Tex;" - "uniform vec4 ClipRect;" - "in vec4 col;" - "in vec2 pixel_pos;" - "in vec2 uv;" - "out vec4 o_col;" - "void main() {" - " o_col = texture(Tex, uv) * col;" - //" if (pixel_pos.x < ClipRect.x || pixel_pos.y < ClipRect.y || pixel_pos.x > ClipRect.z || pixel_pos.y > ClipRect.w) discard;" // Clipping: using discard - //" if (step(ClipRect.x,pixel_pos.x) * step(ClipRect.y,pixel_pos.y) * step(pixel_pos.x,ClipRect.z) * step(pixel_pos.y,ClipRect.w) < 1.0f) discard;" // Clipping: using discard and step - " o_col.w *= (step(ClipRect.x,pixel_pos.x) * step(ClipRect.y,pixel_pos.y) * step(pixel_pos.x,ClipRect.z) * step(pixel_pos.y,ClipRect.w));" // Clipping: branch-less, set alpha 0.0f - "}"; - +// GLFW callbacks to get events static void glfw_error_callback(int error, const char* description) { - fputs(description, stderr); + fputs(description, stderr); } -static float mouse_wheel = 0.0f; static void glfw_scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { - mouse_wheel = (float)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) @@ -197,79 +124,17 @@ { glfwSetErrorCallback(glfw_error_callback); - if (!glfwInit()) - exit(1); + if (!glfwInit()) + exit(1); - //glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - //glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); - //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); - glfwWindowHint(GLFW_REFRESH_RATE, 60); glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); - window = glfwCreateWindow(1280, 720, "ImGui OpenGL example", nullptr, nullptr); + 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); - glewExperimental = GL_TRUE; glewInit(); - - GLenum err = GL_NO_ERROR; - GLint status = GL_TRUE; - err = glGetError(); IM_ASSERT(err == GL_NO_ERROR); - - // Create and compile the vertex shader - vertexShader = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(vertexShader, 1, &vertexSource, NULL); - glCompileShader(vertexShader); - glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status); - if (status != GL_TRUE) - { - char buffer[512]; - glGetShaderInfoLog(vertexShader, 1024, NULL, buffer); - printf("%s", buffer); - IM_ASSERT(status == GL_TRUE); - } - - // Create and compile the fragment shader - fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(fragmentShader, 1, &fragmentSource, NULL); - glCompileShader(fragmentShader); - glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status); - IM_ASSERT(status == GL_TRUE); - - // Link the vertex and fragment shader into a shader program - shaderProgram = glCreateProgram(); - glAttachShader(shaderProgram, vertexShader); - glAttachShader(shaderProgram, fragmentShader); - glBindFragDataLocation(shaderProgram, 0, "o_col"); - glLinkProgram(shaderProgram); - glGetProgramiv(shaderProgram, GL_LINK_STATUS, &status); - IM_ASSERT(status == GL_TRUE); - - uniMVP = glGetUniformLocation(shaderProgram, "MVP"); - uniClipRect = glGetUniformLocation(shaderProgram, "ClipRect"); - - // Create Vertex Buffer Objects & Vertex Array Objects - glGenBuffers(1, &vbo); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - GLint posAttrib = glGetAttribLocation(shaderProgram, "i_pos"); - glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), 0); - glEnableVertexAttribArray(posAttrib); - - GLint uvAttrib = glGetAttribLocation(shaderProgram, "i_uv"); - glEnableVertexAttribArray(uvAttrib); - glVertexAttribPointer(uvAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (void*)(2*sizeof(float))); - - GLint colAttrib = glGetAttribLocation(shaderProgram, "i_col"); - glVertexAttribPointer(colAttrib, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (void*)(4*sizeof(float))); - glEnableVertexAttribArray(colAttrib); - err = glGetError(); IM_ASSERT(err == GL_NO_ERROR); } void InitImGui() @@ -278,9 +143,10 @@ glfwGetWindowSize(window, &w, &h); ImGuiIO& io = ImGui::GetIO(); - io.DisplaySize = ImVec2((float)w, (float)h); - io.DeltaTime = 1.0f/60.0f; - io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; + io.DisplaySize = ImVec2((float)w, (float)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.5f; // 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; @@ -307,7 +173,6 @@ 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); - const void* png_data; unsigned int png_size; ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); @@ -317,44 +182,43 @@ stbi_image_free(tex_data); } -void Shutdown() +void UpdateImGui() { - ImGui::Shutdown(); + ImGuiIO& io = ImGui::GetIO(); - glDeleteProgram(shaderProgram); - glDeleteShader(fragmentShader); - glDeleteShader(vertexShader); - glDeleteBuffers(1, &vbo); - glDeleteVertexArrays(1, &vao); + // Setup timestep + static double time = 0.0f; + const double current_time = glfwGetTime(); + io.DeltaTime = (float)(current_time - time); + time = current_time; - glfwTerminate(); + // 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, (float)mouse_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(); - double time = glfwGetTime(); while (!glfwWindowShouldClose(window)) { ImGuiIO& io = ImGui::GetIO(); + io.MouseWheel = 0; glfwPollEvents(); + UpdateImGui(); - // 1) ImGui start frame, setup time delta & inputs - const double current_time = glfwGetTime(); - io.DeltaTime = (float)(current_time - time); - time = current_time; - double mouse_x, mouse_y; - glfwGetCursorPos(window, &mouse_x, &mouse_y); - io.MousePos = ImVec2((float)mouse_x, (float)mouse_y); - io.MouseDown[0] = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) != 0; - io.MouseDown[1] = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT) != 0; - io.MouseWheel = (mouse_wheel != 0) ? mouse_wheel > 0.0f ? 1 : - 1 : 0; - mouse_wheel = 0.0f; - ImGui::NewFrame(); - - // 2) ImGui usage + // 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; @@ -368,19 +232,21 @@ 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] = io.DeltaTime * 1000.0f; + 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) { - // More example code in ShowTestWindow() - 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::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); } + // Show another simple window if (show_another_window) { ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); @@ -388,15 +254,15 @@ ImGui::End(); } - // 3) Render - glClearColor(0.8f, 0.6f, 0.6f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); + // 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); } - Shutdown(); - + ImGui::Shutdown(); + glfwTerminate(); return 0; } diff --git a/imconfig.h b/imconfig.h index 0a340ee..98b16f1 100644 --- a/imconfig.h +++ b/imconfig.h @@ -4,15 +4,22 @@ #pragma once -//----- Define your own ImVector<> type if you don't want to use the provided implementation defined in imgui.h +//---- Define your own ImVector<> type if you don't want to use the provided implementation defined in imgui.h //#include //#define ImVector std::vector //#define ImVector MyVector -//----- Define assertion handler. Default to calling assert(). -// #define IM_ASSERT(_EXPR) MyAssert(_EXPR) +//---- Define assertion handler. Defaults to calling assert(). +//#define IM_ASSERT(_EXPR) MyAssert(_EXPR) -//----- Define implicit cast operators to convert back<>forth from your math types and ImVec2/ImVec4. +//---- Don't implement default clipboard handlers for Windows (so as not to link with OpenClipboard(), etc.) +//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS + +//---- If you are loading a custom font, ImGui expect to find a pure white pixel at (0,0) +// Change it's UV coordinate here if you can't have a white pixel at (0,0) +//#define IMGUI_FONT_TEX_UV_FOR_WHITE ImVec2(0.f/256.f,0.f/256.f) + +//---- Define implicit cast operators to convert back<>forth from your math types and ImVec2/ImVec4. /* #define IM_VEC2_CLASS_EXTRA \ ImVec2(const MyVec2& f) { x = f.x; y = f.y; } \ @@ -23,12 +30,12 @@ operator MyVec4() const { return MyVec4(x,y,z,w); } */ -//----- Freely implement extra functions within the ImGui:: namespace. -//----- e.g. you can create variants of the ImGui::Value() helper for your low-level math types. +//---- Freely implement extra functions within the ImGui:: namespace. +//---- e.g. you can create variants of the ImGui::Value() helper for your low-level math types. /* namespace ImGui { - void Value(const char* prefix, cosnt MyVec2& v, const char* float_format = NULL); - void Value(const char* prefix, cosnt MyVec4& v, const char* float_format = NULL); + void Value(const char* prefix, const MyVec2& v, const char* float_format = NULL); + void Value(const char* prefix, const MyVec4& v, const char* float_format = NULL); }; */ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..94f4ef0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +## Visual Studio files +examples/directx9_example/Debug/* +examples/directx9_example/Release/* +examples/directx9_example/ipch/* +examples/opengl_example/Debug/* +examples/opengl_example/Release/* +examples/opengl_example/ipch/* +*.opensdf +*.sdf +*.suo +*.vcxproj.user + +## Ini files +imgui.ini diff --git a/README.md b/README.md index 65a9ad7..c3f7767 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@ ImGui ===== -ImGui is a bloat-free graphical user interface library for C/C++. It is portable, renderer agnostic and carries minimal amount of dependencies (only 3 files are needed). It is based on an "immediate" graphical user interface paradigm which allows you to build simple user interfaces with ease. +ImGui is a bloat-free graphical user interface library for C++. It outputs vertex buffers that you can render in your 3D-pipeline enabled application. It is portable, renderer agnostic and carries minimal amount of dependencies (only 3 files are needed). It is based on an "immediate" graphical user interface paradigm which allows you to build simple user interfaces with ease. -ImGui is designed to allow programmers to create "content creation" or "debug" tools (as opposed to tools for the average end-user). It favors simplicity and thus lacks certain features normally found in more high-level libraries, such as string localisation. +ImGui is designed to enable fast iteration and allow programmers to create "content creation" or "debug" tools (as opposed to tools for the average end-user). It favors simplicity and thus lacks certain features normally found in more high-level libraries, such as string localisation. -Usage example: +ImGui is particularly suited to integration in 3D applications, fullscreen applications, embedded applications, games, or any applications on consoles platforms where operating system features are non-standard. + +After ImGui is setup in your engine, you can use it like in this example: ![screenshot of sample code alongside its output with ImGui](/web/code_sample_01.png?raw=true) -ImGui output vertex buffer and simple command-list that you can render in application. Refer to the sample applications in the examples/ folder for instructions on how to integrate ImGui within your existing codebase. - +ImGui outputs vertex buffers and simple command-lists that you can render in your application. Because it doesn't know or touch graphics state directly, you can call ImGui commands anywhere in your code (e.g. in the middle of a running algorithm, or in the middle of your own rendering process). Refer to the sample applications in the examples/ folder for instructions on how to integrate ImGui with your existing codebase. Gallery ------- @@ -20,12 +21,22 @@ ![screenshot 3](/web/test_window_03.png?raw=true) ![screenshot 4](/web/test_window_04.png?raw=true) +References +---------- + +The Immediate Mode GUI paradigm may at first appear unusual to some users. This is mainly because "Retained Mode" GUIs have been so widespread and predominant. The following links can give you a better understanding about how Immediate Mode GUIs works. +- [Johannes 'johno' Norneby's article](http://www.johno.se/book/imgui.html). +- [A presentation by Rickard Gustafsson and Johannes Algelind](http://www.cse.chalmers.se/edu/year/2011/course/TDA361/Advanced%20Computer%20Graphics/IMGUI.pdf). +- [Jari Komppa's tutorial on building an ImGui library](http://iki.fi/sol/imgui/). +- [Casey Muratori's original video that popularized the concept](https://mollyrocket.com/861). + + Credits ------- Developed by [Omar Cornut](http://www.miracleworld.net). The library was developed with the support of [Media Molecule](http://www.mediamolecule.com) and first used internally on the game [Tearaway](http://tearaway.mediamolecule.com). -Embeds [proggy_clean font](http://www.proggyfonts.net/) by Tristan Grimmer (also MIT license). +Embeds [proggy_clean](http://www.proggyfonts.net/) font by Tristan Grimmer (also MIT license). Inspiration, feedback, and testing: Casey Muratori, Atman Binstock, Mikko Mononen, Emmanuel Briney, Stefan Kamoda, Matt Willis. Thanks! diff --git a/examples/directx9_example/main.cpp b/examples/directx9_example/main.cpp index fd25f01..3036eb7 100644 --- a/examples/directx9_example/main.cpp +++ b/examples/directx9_example/main.cpp @@ -12,16 +12,15 @@ static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; // Our rendering device static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL; // Buffer to hold vertices static LPDIRECT3DTEXTURE9 g_pTexture = NULL; // Our texture - struct CUSTOMVERTEX { D3DXVECTOR3 position; D3DCOLOR color; float tu, tv; }; - #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1) +// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structuer) static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) { size_t total_vtx_count = 0; @@ -30,31 +29,13 @@ if (total_vtx_count == 0) return; - ImVector clip_rect_stack; - clip_rect_stack.push_back(ImVec4(-9999,-9999,+9999,+9999)); - - // Setup orthographic projection - // Set up world matrix - D3DXMATRIXA16 mat; - D3DXMatrixIdentity(&mat); - g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat); - g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat); - D3DXMatrixOrthoOffCenterLH(&mat, 0.0f, ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y, 0.0f, -1.0f, +1.0f); - g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat); - - D3DSURFACE_DESC texture_desc; - g_pTexture->GetLevelDesc(0, &texture_desc); - - // Fill the vertex buffer + // Copy and convert all vertices into a single contiguous buffer CUSTOMVERTEX* vtx_dst; if (g_pVB->Lock(0, total_vtx_count, (void**)&vtx_dst, D3DLOCK_DISCARD) < 0) return; - for (int n = 0; n < cmd_lists_count; n++) { const ImDrawList* cmd_list = cmd_lists[n]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; const ImDrawVert* vtx_src = &cmd_list->vtx_buffer[0]; for (size_t i = 0; i < cmd_list->vtx_buffer.size(); i++) { @@ -73,11 +54,10 @@ g_pd3dDevice->SetStreamSource( 0, g_pVB, 0, sizeof( CUSTOMVERTEX ) ); g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX ); - // Setup render state: alpha-blending enabled, no face culling, no depth testing + // Setup render state: alpha-blending, no face culling, no depth testing g_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ); g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, false ); g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, false ); - g_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, true ); g_pd3dDevice->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_ADD ); g_pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, false ); @@ -94,97 +74,31 @@ g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); - int vtx_consumed = 0; // offset in vertex buffer. each command consume ImDrawCmd::vtx_count of those - bool clip_rect_dirty = true; + // Setup orthographic projection matrix + D3DXMATRIXA16 mat; + D3DXMatrixIdentity(&mat); + g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat); + g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat); + D3DXMatrixOrthoOffCenterLH(&mat, 0.0f, ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y, 0.0f, -1.0f, +1.0f); + g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat); + // Render command lists + int vtx_offset = 0; for (int n = 0; n < cmd_lists_count; n++) { + // Render command list const ImDrawList* cmd_list = cmd_lists[n]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; - const ImDrawCmd* pcmd = &cmd_list->commands.front(); - const ImDrawCmd* pcmd_end = &cmd_list->commands.back(); - int clip_rect_buf_consumed = 0; // offset in cmd_list->clip_rect_buffer. each PushClipRect command consume 1 of those. - while (pcmd <= pcmd_end) + const ImDrawCmd* pcmd_end = cmd_list->commands.end(); + for (const ImDrawCmd* pcmd = cmd_list->commands.begin(); pcmd != pcmd_end; pcmd++) { - const ImDrawCmd& cmd = *pcmd++; - switch (cmd.cmd_type) - { - case ImDrawCmdType_DrawTriangleList: - if (clip_rect_dirty) - { - const ImVec4& clip_rect = clip_rect_stack.back(); - const RECT r = { (LONG)clip_rect.x, (LONG)clip_rect.y, (LONG)clip_rect.z, (LONG)clip_rect.w }; - g_pd3dDevice->SetScissorRect(&r); - clip_rect_dirty = false; - } - g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, vtx_consumed, cmd.vtx_count/3); - vtx_consumed += cmd.vtx_count; - break; - - case ImDrawCmdType_PushClipRect: - clip_rect_stack.push_back(cmd_list->clip_rect_buffer[clip_rect_buf_consumed++]); - clip_rect_dirty = true; - break; - - case ImDrawCmdType_PopClipRect: - clip_rect_stack.pop_back(); - clip_rect_dirty = true; - break; - } + const RECT r = { (LONG)pcmd->clip_rect.x, (LONG)pcmd->clip_rect.y, (LONG)pcmd->clip_rect.z, (LONG)pcmd->clip_rect.w }; + g_pd3dDevice->SetScissorRect(&r); + g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, vtx_offset, pcmd->vtx_count/3); + vtx_offset += pcmd->vtx_count; } } } -// Get text data in Win32 clipboard -static const char* ImImpl_GetClipboardTextFn() -{ - static char* buf_local = NULL; - if (buf_local) - { - free(buf_local); - buf_local = NULL; - } - - if (!OpenClipboard(NULL)) - return NULL; - - HANDLE buf_handle = GetClipboardData(CF_TEXT); - if (buf_handle == NULL) - return NULL; - - if (char* buf_global = (char*)GlobalLock(buf_handle)) - buf_local = strdup(buf_global); - GlobalUnlock(buf_handle); - CloseClipboard(); - - return buf_local; -} - -// Set text data in Win32 clipboard -static void ImImpl_SetClipboardTextFn(const char* text, const char* text_end) -{ - if (!OpenClipboard(NULL)) - return; - - if (!text_end) - text_end = text + strlen(text); - - const int buf_length = (text_end - text) + 1; - HGLOBAL buf_handle = GlobalAlloc(GMEM_MOVEABLE, buf_length * sizeof(char)); - if (buf_handle == NULL) - return; - - char* buf_global = (char *)GlobalLock(buf_handle); - memcpy(buf_global, text, text_end - text); - buf_global[text_end - text] = 0; - GlobalUnlock(buf_handle); - - EmptyClipboard(); - SetClipboardData(CF_TEXT, buf_handle); - CloseClipboard(); -} - HRESULT InitD3D(HWND hWnd) { if (NULL == (g_pD3D = Direct3DCreate9(D3D_SDK_VERSION))) @@ -203,17 +117,6 @@ if (g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &g_pd3dDevice) < 0) return E_FAIL; - // Create the vertex buffer. - if (g_pd3dDevice->CreateVertexBuffer(10000 * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL) < 0) - return E_FAIL; - - // Load font texture - const void* png_data; - unsigned int png_size; - ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); - if (D3DXCreateTextureFromFileInMemory(g_pd3dDevice, png_data, png_size, &g_pTexture) < 0) - return E_FAIL; - return S_OK; } @@ -247,9 +150,11 @@ io.MouseDown[1] = false; return true; case WM_MOUSEWHEEL: + // Mouse wheel: -1,0,+1 io.MouseWheel = GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? +1 : -1; return true; case WM_MOUSEMOVE: + // Mouse position, in pixels (set to -1,-1 if no mouse / on another screen, etc.) io.MousePos.x = (signed short)(lParam); io.MousePos.y = (signed short)(lParam >> 16); return true; @@ -274,9 +179,10 @@ GetClientRect(hWnd, &rect); ImGuiIO& io = ImGui::GetIO(); - io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); - io.DeltaTime = 1.0f/60.0f; - io.KeyMap[ImGuiKey_Tab] = VK_TAB; + io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); // 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 Direct3D Texels + io.KeyMap[ImGuiKey_Tab] = VK_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array that we will update during the application lifetime. io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT; io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT; io.KeyMap[ImGuiKey_UpArrow] = VK_UP; @@ -295,8 +201,52 @@ io.KeyMap[ImGuiKey_Z] = 'Z'; io.RenderDrawListsFn = ImImpl_RenderDrawLists; - io.SetClipboardTextFn = ImImpl_SetClipboardTextFn; - io.GetClipboardTextFn = ImImpl_GetClipboardTextFn; + + // Create the vertex buffer + if (g_pd3dDevice->CreateVertexBuffer(10000 * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL) < 0) + { + IM_ASSERT(0); + return; + } + + // Load font texture + const void* png_data; + unsigned int png_size; + ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); + if (D3DXCreateTextureFromFileInMemory(g_pd3dDevice, png_data, png_size, &g_pTexture) < 0) + { + IM_ASSERT(0); + return; + } +} + +INT64 ticks_per_second = 0; +INT64 time = 0; + +void UpdateImGui() +{ + ImGuiIO& io = ImGui::GetIO(); + + // Setup timestep + INT64 current_time; + QueryPerformanceCounter((LARGE_INTEGER *)¤t_time); + io.DeltaTime = (float)(current_time - time) / ticks_per_second; + time = current_time; + + // Setup inputs + // (we already got mouse position, buttons, wheel from the window message callback) + BYTE keystate[256]; + GetKeyboardState(keystate); + for (int i = 0; i < 256; i++) + io.KeysDown[i] = (keystate[i] & 0x80) != 0; + io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0; + io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0; + // io.MousePos : filled by WM_MOUSEMOVE event + // io.MouseDown : filled by WM_*BUTTON* events + // io.MouseWheel : filled by WM_MOUSEWHEEL events + + // Start the frame + ImGui::NewFrame(); } int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR, int) @@ -308,99 +258,91 @@ // Create the application's window hWnd = CreateWindow(L"ImGui Example", L"ImGui DirectX9 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, NULL, NULL, wc.hInstance, NULL); - INT64 ticks_per_second, time; if (!QueryPerformanceFrequency((LARGE_INTEGER *)&ticks_per_second)) return 1; if (!QueryPerformanceCounter((LARGE_INTEGER *)&time)) return 1; // Initialize Direct3D - if (InitD3D(hWnd) >= 0) + if (InitD3D(hWnd) < 0) + { + if (g_pVB) + g_pVB->Release(); + UnregisterClass(L"ImGui Example", wc.hInstance); + return 1; + } + + // Show the window + ShowWindow(hWnd, SW_SHOWDEFAULT); + UpdateWindow(hWnd); + + InitImGui(); + + // Enter the message loop + MSG msg; + ZeroMemory(&msg, sizeof(msg)); + while (msg.message != WM_QUIT) { - // Show the window - ShowWindow(hWnd, SW_SHOWDEFAULT); - UpdateWindow(hWnd); - - InitImGui(); - - // Enter the message loop - MSG msg; - ZeroMemory(&msg, sizeof(msg)); - while (msg.message != WM_QUIT) + if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) { - if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - continue; - } + TranslateMessage(&msg); + DispatchMessage(&msg); + continue; + } - // 1) ImGui start frame, setup time delta & inputs - ImGuiIO& io = ImGui::GetIO(); - INT64 current_time; - QueryPerformanceCounter((LARGE_INTEGER *)¤t_time); - io.DeltaTime = (float)(current_time - time) / ticks_per_second; - time = current_time; - BYTE keystate[256]; - GetKeyboardState(keystate); - for (int i = 0; i < 256; i++) - io.KeysDown[i] = (keystate[i] & 0x80) != 0; - io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0; - io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0; - // io.MousePos : filled by WM_MOUSEMOVE event - // io.MouseDown : filled by WM_*BUTTON* events - // io.MouseWheel : filled by WM_MOUSEWHEEL events - ImGui::NewFrame(); + UpdateImGui(); - // 2) ImGui usage - 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"); + // 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] = io.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); + // 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); - if (show_test_window) - { - // More example code in ShowTestWindow() - 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); - } - - if (show_another_window) - { - ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); - ImGui::Text("Hello"); - ImGui::End(); - } - - // 3) Render - g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); - g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false); - g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, false); - g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(204, 153, 153), 1.0f, 0); // Clear the backbuffer and the zbuffer - if (g_pd3dDevice->BeginScene() >= 0) - { - ImGui::Render(); - g_pd3dDevice->EndScene(); - } - g_pd3dDevice->Present(NULL, NULL, NULL, NULL); + // 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::Shutdown(); - } + // Show another simple window + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); + ImGui::Text("Hello"); + ImGui::End(); + } + + // Rendering + g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); + g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false); + g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, false); + g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(204, 153, 153), 1.0f, 0); + if (g_pd3dDevice->BeginScene() >= 0) + { + ImGui::Render(); + g_pd3dDevice->EndScene(); + } + g_pd3dDevice->Present(NULL, NULL, NULL, NULL); + } + + ImGui::Shutdown(); if (g_pVB) g_pVB->Release(); diff --git a/examples/opengl_example/Makefile.Linux b/examples/opengl_example/Makefile.Linux new file mode 100644 index 0000000..6fd0b97 --- /dev/null +++ b/examples/opengl_example/Makefile.Linux @@ -0,0 +1,18 @@ +# +# Quick and dirty makefile to build on Linux +# tested on Ubuntu 14.04.1 32bit +# + +SRC = main.cpp ../../imgui.cpp + +OBJ = $(SRC:.cpp=.o) + +CXXFLAGS = -I../../ `pkg-config --cflags glfw3` + +LIBS = `pkg-config --static --libs glfw3` -lGLEW + +all: $(OBJ) + $(CXX) $(OBJ) $(LIBS) + +clean: + $(RM) -f $(OBJ) diff --git a/examples/opengl_example/Makefile.Macosx b/examples/opengl_example/Makefile.Macosx new file mode 100644 index 0000000..44ffe15 --- /dev/null +++ b/examples/opengl_example/Makefile.Macosx @@ -0,0 +1,18 @@ +# This makefile currently only works for mac os +# You should install via homebrew: +# brew install glew +# brew install glfw3 +# + +CXXFLAGS=-framework OpenGL -framework Cocoa -framework IOKit +CXXFLAGS+=-I/usr/local/Cellar/glew/1.10.0/include -I/usr/local/Cellar/glfw3/3.0.4/include +CXXFLAGS+=-L/usr/local/Cellar/glew/1.10.0/lib -L/usr/local/Cellar/glfw3/3.0.4/lib +CXXFLAGS+=-lglew -lglfw3 +CXXFLAGS+=-I../../ +CXXFLAGS+= -D__APPLE__ + +main: main.cpp ../../imgui.cpp + $(CXX) $(CXXFLAGS) -o $@ $^ + +clean: + rm main diff --git a/examples/opengl_example/main.cpp b/examples/opengl_example/main.cpp index 909ffa9..609021e 100644 --- a/examples/opengl_example/main.cpp +++ b/examples/opengl_example/main.cpp @@ -2,116 +2,65 @@ #include #include #define STB_IMAGE_IMPLEMENTATION -#include "stb_image.h" // for .png loading +#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 +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen #endif static GLFWwindow* window; -static GLuint vbo; -static GLuint vao; -static GLuint vertexShader; -static GLuint fragmentShader; -static GLuint shaderProgram; static GLuint fontTex; -static GLint uniMVP; -static GLint uniClipRect; +// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structuer) +// We are using the fixed pipeline. +// A faster way would be to collate all vertices from all cmd_lists into a single vertex buffer static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) { - size_t total_vtx_count = 0; - for (int n = 0; n < cmd_lists_count; n++) - total_vtx_count += cmd_lists[n]->vtx_buffer.size(); - if (total_vtx_count == 0) + if (cmd_lists_count == 0) return; - int read_pos_clip_rect_buf = 0; // offset in 'clip_rect_buffer'. each PushClipRect command consume 1 of those. - - ImVector clip_rect_stack; - clip_rect_stack.push_back(ImVec4(-9999,-9999,+9999,+9999)); - - // Setup orthographic projection - const float L = 0.0f; - const float R = ImGui::GetIO().DisplaySize.x; - const float B = ImGui::GetIO().DisplaySize.y; - const float T = 0.0f; - const float mvp[4][4] = - { - { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, - { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, - { 0.0f, 0.0f, -1.0f, 0.0f }, - { -(R+L)/(R-L), -(T+B)/(T-B), 0.0f, 1.0f }, - }; - - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBindVertexArray(vao); - glBufferData(GL_ARRAY_BUFFER, total_vtx_count * sizeof(ImDrawVert), NULL, GL_STREAM_DRAW); - unsigned char* buffer_data = (unsigned char*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); - if (!buffer_data) - return; - int vtx_consumed = 0; - for (int n = 0; n < cmd_lists_count; n++) - { - const ImDrawList* cmd_list = cmd_lists[n]; - if (!cmd_list->vtx_buffer.empty()) - { - memcpy(buffer_data, &cmd_list->vtx_buffer[0], cmd_list->vtx_buffer.size() * sizeof(ImDrawVert)); - buffer_data += cmd_list->vtx_buffer.size() * sizeof(ImDrawVert); - vtx_consumed += cmd_list->vtx_buffer.size(); - } - } - glUnmapBuffer(GL_ARRAY_BUFFER); - - glUseProgram(shaderProgram); - glUniformMatrix4fv(uniMVP, 1, GL_FALSE, &mvp[0][0]); - - // Setup render state: alpha-blending enabled, no face culling, no depth testing + // 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); - vtx_consumed = 0; // offset in vertex buffer. each command consume ImDrawCmd::vtx_count of those - bool clip_rect_dirty = true; + // 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]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; - const ImDrawCmd* pcmd = &cmd_list->commands.front(); - const ImDrawCmd* pcmd_end = &cmd_list->commands.back(); - int clip_rect_buf_consumed = 0; // offset in cmd_list->clip_rect_buffer. each PushClipRect command consume 1 of those. - while (pcmd <= pcmd_end) + 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++) { - const ImDrawCmd& cmd = *pcmd++; - switch (cmd.cmd_type) - { - case ImDrawCmdType_DrawTriangleList: - if (clip_rect_dirty) - { - glUniform4fv(uniClipRect, 1, (float*)&clip_rect_stack.back()); - clip_rect_dirty = false; - } - glDrawArrays(GL_TRIANGLES, vtx_consumed, cmd.vtx_count); - vtx_consumed += cmd.vtx_count; - break; - - case ImDrawCmdType_PushClipRect: - clip_rect_stack.push_back(cmd_list->clip_rect_buffer[clip_rect_buf_consumed++]); - clip_rect_dirty = true; - break; - - case ImDrawCmdType_PopClipRect: - clip_rect_stack.pop_back(); - clip_rect_dirty = true; - break; - } + 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); } static const char* ImImpl_GetClipboardTextFn() @@ -124,55 +73,33 @@ if (!text_end) text_end = text + strlen(text); - char* buf = (char*)malloc(text_end - text + 1); - memcpy(buf, text, text_end-text); - buf[text_end-text] = '\0'; - glfwSetClipboardString(window, buf); - free(buf); + 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); + } } -// Shader sources -// FIXME-OPT: clip at vertex level -const GLchar* vertexSource = - "#version 150 core\n" - "uniform mat4 MVP;" - "in vec2 i_pos;" - "in vec2 i_uv;" - "in vec4 i_col;" - "out vec4 col;" - "out vec2 pixel_pos;" - "out vec2 uv;" - "void main() {" - " col = i_col;" - " pixel_pos = i_pos;" - " uv = i_uv;" - " gl_Position = MVP * vec4(i_pos.x, i_pos.y, 0.0f, 1.0f);" - "}"; -const GLchar* fragmentSource = - "#version 150 core\n" - "uniform sampler2D Tex;" - "uniform vec4 ClipRect;" - "in vec4 col;" - "in vec2 pixel_pos;" - "in vec2 uv;" - "out vec4 o_col;" - "void main() {" - " o_col = texture(Tex, uv) * col;" - //" if (pixel_pos.x < ClipRect.x || pixel_pos.y < ClipRect.y || pixel_pos.x > ClipRect.z || pixel_pos.y > ClipRect.w) discard;" // Clipping: using discard - //" if (step(ClipRect.x,pixel_pos.x) * step(ClipRect.y,pixel_pos.y) * step(pixel_pos.x,ClipRect.z) * step(pixel_pos.y,ClipRect.w) < 1.0f) discard;" // Clipping: using discard and step - " o_col.w *= (step(ClipRect.x,pixel_pos.x) * step(ClipRect.y,pixel_pos.y) * step(pixel_pos.x,ClipRect.z) * step(pixel_pos.y,ClipRect.w));" // Clipping: branch-less, set alpha 0.0f - "}"; - +// GLFW callbacks to get events static void glfw_error_callback(int error, const char* description) { - fputs(description, stderr); + fputs(description, stderr); } -static float mouse_wheel = 0.0f; static void glfw_scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { - mouse_wheel = (float)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) @@ -197,79 +124,17 @@ { glfwSetErrorCallback(glfw_error_callback); - if (!glfwInit()) - exit(1); + if (!glfwInit()) + exit(1); - //glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - //glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); - //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); - glfwWindowHint(GLFW_REFRESH_RATE, 60); glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); - window = glfwCreateWindow(1280, 720, "ImGui OpenGL example", nullptr, nullptr); + 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); - glewExperimental = GL_TRUE; glewInit(); - - GLenum err = GL_NO_ERROR; - GLint status = GL_TRUE; - err = glGetError(); IM_ASSERT(err == GL_NO_ERROR); - - // Create and compile the vertex shader - vertexShader = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(vertexShader, 1, &vertexSource, NULL); - glCompileShader(vertexShader); - glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status); - if (status != GL_TRUE) - { - char buffer[512]; - glGetShaderInfoLog(vertexShader, 1024, NULL, buffer); - printf("%s", buffer); - IM_ASSERT(status == GL_TRUE); - } - - // Create and compile the fragment shader - fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(fragmentShader, 1, &fragmentSource, NULL); - glCompileShader(fragmentShader); - glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status); - IM_ASSERT(status == GL_TRUE); - - // Link the vertex and fragment shader into a shader program - shaderProgram = glCreateProgram(); - glAttachShader(shaderProgram, vertexShader); - glAttachShader(shaderProgram, fragmentShader); - glBindFragDataLocation(shaderProgram, 0, "o_col"); - glLinkProgram(shaderProgram); - glGetProgramiv(shaderProgram, GL_LINK_STATUS, &status); - IM_ASSERT(status == GL_TRUE); - - uniMVP = glGetUniformLocation(shaderProgram, "MVP"); - uniClipRect = glGetUniformLocation(shaderProgram, "ClipRect"); - - // Create Vertex Buffer Objects & Vertex Array Objects - glGenBuffers(1, &vbo); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - GLint posAttrib = glGetAttribLocation(shaderProgram, "i_pos"); - glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), 0); - glEnableVertexAttribArray(posAttrib); - - GLint uvAttrib = glGetAttribLocation(shaderProgram, "i_uv"); - glEnableVertexAttribArray(uvAttrib); - glVertexAttribPointer(uvAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (void*)(2*sizeof(float))); - - GLint colAttrib = glGetAttribLocation(shaderProgram, "i_col"); - glVertexAttribPointer(colAttrib, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (void*)(4*sizeof(float))); - glEnableVertexAttribArray(colAttrib); - err = glGetError(); IM_ASSERT(err == GL_NO_ERROR); } void InitImGui() @@ -278,9 +143,10 @@ glfwGetWindowSize(window, &w, &h); ImGuiIO& io = ImGui::GetIO(); - io.DisplaySize = ImVec2((float)w, (float)h); - io.DeltaTime = 1.0f/60.0f; - io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; + io.DisplaySize = ImVec2((float)w, (float)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.5f; // 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; @@ -307,7 +173,6 @@ 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); - const void* png_data; unsigned int png_size; ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); @@ -317,44 +182,43 @@ stbi_image_free(tex_data); } -void Shutdown() +void UpdateImGui() { - ImGui::Shutdown(); + ImGuiIO& io = ImGui::GetIO(); - glDeleteProgram(shaderProgram); - glDeleteShader(fragmentShader); - glDeleteShader(vertexShader); - glDeleteBuffers(1, &vbo); - glDeleteVertexArrays(1, &vao); + // Setup timestep + static double time = 0.0f; + const double current_time = glfwGetTime(); + io.DeltaTime = (float)(current_time - time); + time = current_time; - glfwTerminate(); + // 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, (float)mouse_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(); - double time = glfwGetTime(); while (!glfwWindowShouldClose(window)) { ImGuiIO& io = ImGui::GetIO(); + io.MouseWheel = 0; glfwPollEvents(); + UpdateImGui(); - // 1) ImGui start frame, setup time delta & inputs - const double current_time = glfwGetTime(); - io.DeltaTime = (float)(current_time - time); - time = current_time; - double mouse_x, mouse_y; - glfwGetCursorPos(window, &mouse_x, &mouse_y); - io.MousePos = ImVec2((float)mouse_x, (float)mouse_y); - io.MouseDown[0] = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) != 0; - io.MouseDown[1] = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT) != 0; - io.MouseWheel = (mouse_wheel != 0) ? mouse_wheel > 0.0f ? 1 : - 1 : 0; - mouse_wheel = 0.0f; - ImGui::NewFrame(); - - // 2) ImGui usage + // 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; @@ -368,19 +232,21 @@ 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] = io.DeltaTime * 1000.0f; + 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) { - // More example code in ShowTestWindow() - 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::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); } + // Show another simple window if (show_another_window) { ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); @@ -388,15 +254,15 @@ ImGui::End(); } - // 3) Render - glClearColor(0.8f, 0.6f, 0.6f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); + // 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); } - Shutdown(); - + ImGui::Shutdown(); + glfwTerminate(); return 0; } diff --git a/imconfig.h b/imconfig.h index 0a340ee..98b16f1 100644 --- a/imconfig.h +++ b/imconfig.h @@ -4,15 +4,22 @@ #pragma once -//----- Define your own ImVector<> type if you don't want to use the provided implementation defined in imgui.h +//---- Define your own ImVector<> type if you don't want to use the provided implementation defined in imgui.h //#include //#define ImVector std::vector //#define ImVector MyVector -//----- Define assertion handler. Default to calling assert(). -// #define IM_ASSERT(_EXPR) MyAssert(_EXPR) +//---- Define assertion handler. Defaults to calling assert(). +//#define IM_ASSERT(_EXPR) MyAssert(_EXPR) -//----- Define implicit cast operators to convert back<>forth from your math types and ImVec2/ImVec4. +//---- Don't implement default clipboard handlers for Windows (so as not to link with OpenClipboard(), etc.) +//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS + +//---- If you are loading a custom font, ImGui expect to find a pure white pixel at (0,0) +// Change it's UV coordinate here if you can't have a white pixel at (0,0) +//#define IMGUI_FONT_TEX_UV_FOR_WHITE ImVec2(0.f/256.f,0.f/256.f) + +//---- Define implicit cast operators to convert back<>forth from your math types and ImVec2/ImVec4. /* #define IM_VEC2_CLASS_EXTRA \ ImVec2(const MyVec2& f) { x = f.x; y = f.y; } \ @@ -23,12 +30,12 @@ operator MyVec4() const { return MyVec4(x,y,z,w); } */ -//----- Freely implement extra functions within the ImGui:: namespace. -//----- e.g. you can create variants of the ImGui::Value() helper for your low-level math types. +//---- Freely implement extra functions within the ImGui:: namespace. +//---- e.g. you can create variants of the ImGui::Value() helper for your low-level math types. /* namespace ImGui { - void Value(const char* prefix, cosnt MyVec2& v, const char* float_format = NULL); - void Value(const char* prefix, cosnt MyVec4& v, const char* float_format = NULL); + void Value(const char* prefix, const MyVec2& v, const char* float_format = NULL); + void Value(const char* prefix, const MyVec4& v, const char* float_format = NULL); }; */ diff --git a/imgui.cpp b/imgui.cpp index 1242b02..888a43b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -101,7 +101,7 @@ - if you want to use a different font than the default - create bitmap font data using BMFont. allocate ImGui::GetIO().Font and use ->LoadFromFile()/LoadFromMemory(), set ImGui::GetIO().FontHeight - - load your texture yourself. texture *MUST* have white pixel at UV coordinate 'IMDRAW_TEX_UV_FOR_WHITE' (you can #define it in imconfig.h), this is used by solid objects. + - load your texture yourself. texture *MUST* have white pixel at UV coordinate 'IMGUI_FONT_TEX_UV_FOR_WHITE' (you can #define it in imconfig.h), this is used by solid objects. - tip: the construct 'if (IMGUI_ONCE_UPON_A_FRAME)' will evaluate to true only once a frame, you can use it to add custom UI in the middle of a deep nested inner loop in your code. - tip: you can call Render() multiple times (e.g for VR renders), up to you to communicate the extra state to your RenderDrawListFn function. @@ -110,18 +110,20 @@ ISSUES AND TODO-LIST - misc: merge ImVec4 / ImGuiAabb, they are essentially duplicate containers - - main: make IsHovered() more consistent for various type of widgets, widgets with multiple components, etc. also effectively IsHovered() region sometimes differs from hot region, e.g tree nodes - window: autofit is losing its purpose when user relies on any dynamic layout (window width multiplier, column). maybe just discard autofit? - window: support horizontal scroll - window: fix resize grip scaling along with Rounding style setting + - window/style: add global alpha modifier (not just "fill_alpha") - widgets: switching from "widget-label" to "label-widget" would make it more convenient to integrate widgets in trees - widgets: clip text? hover clipped text shows it in a tooltip or in-place overlay + - main: make IsHovered() more consistent for various type of widgets, widgets with multiple components, etc. also effectively IsHovered() region sometimes differs from hot region, e.g tree nodes + - main: make IsHovered() info stored in a stack? so that 'if TreeNode() { Text; TreePop; } if IsHovered' return the hover state of the TreeNode? - scrollbar: use relative mouse movement when first-clicking inside of scroll grab box. - input number: optional range min/max - input number: holding [-]/[+] buttons should increase the step non-linearly - input number: rename Input*() to Input(), Slider*() to Slider() ? - layout: clean up the InputFloat3/SliderFloat3/ColorEdit4 horrible layout code. item width should include frame padding, then we can have a generic horizontal layout helper. - - add input2/4 helper (once above layout helpers are in they'll be smaller) + - add input4 helper (once above layout helpers are in they'll be smaller) - columns: declare column set (each column: fixed size, %, fill, distribute default size among fills) - columns: columns header to act as button (~sort op) and allow resize/reorder - columns: user specify columns size @@ -151,13 +153,12 @@ - optimisation: turn some the various stack vectors into statically-sized arrays - optimisation: better clipping for multi-component widgets - optimisation: specialize for height based clipping first (assume widgets never go up + height tests before width tests?) - - optimisation/portability: provide ImVector style implementation - - optimisation/portability: remove dependency on */ #include "imgui.h" -#include +#include // toupper #include // sqrt +#include // intptr_t #include // vsnprintf #include // memset @@ -188,7 +189,7 @@ static void ItemSize(const ImGuiAabb& aabb, ImVec2* adjust_start_offset = NULL); static void PushColumnClipRect(int column_index = -1); static bool IsClipped(const ImGuiAabb& aabb); -static bool ClipAdvance(const ImGuiAabb& aabb, bool skip_columns = false); +static bool ClipAdvance(const ImGuiAabb& aabb); static bool IsMouseHoveringBox(const ImGuiAabb& box); static bool IsKeyPressedMap(ImGuiKey key, bool repeat = true); @@ -200,6 +201,13 @@ }; // namespace ImGui //----------------------------------------------------------------------------- +// Platform dependant default implementations +//----------------------------------------------------------------------------- + +static const char* GetClipboardTextFn_DefaultImpl(); +static void SetClipboardTextFn_DefaultImpl(const char* text, const char* text_end); + +//----------------------------------------------------------------------------- // User facing structures //----------------------------------------------------------------------------- @@ -230,6 +238,7 @@ Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.80f, 0.40f); Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.80f, 0.50f, 0.50f, 0.40f); Colors[ImGuiCol_ComboBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.99f); + Colors[ImGuiCol_CheckHovered] = ImVec4(0.60f, 0.40f, 0.40f, 0.45f); Colors[ImGuiCol_CheckActive] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f); Colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.80f, 0.50f, 0.50f, 1.00f); @@ -265,10 +274,15 @@ LogFilename = "imgui_log.txt"; Font = NULL; FontAllowScaling = false; + PixelCenterOffset = 0.5f; MousePos = ImVec2(-1,-1); MousePosPrev = ImVec2(-1,-1); MouseDoubleClickTime = 0.30f; MouseDoubleClickMaxDist = 6.0f; + + // Platform dependant default implementations + GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; + SetClipboardTextFn = SetClipboardTextFn_DefaultImpl; } // Pass in translated ASCII characters for text input. @@ -276,7 +290,7 @@ // - on Windows you can get those using ToAscii+keyboard state, or via the VM_CHAR message void ImGuiIO::AddInputCharacter(char c) { - const int n = strlen(InputCharacters); + const size_t n = strlen(InputCharacters); if (n < sizeof(InputCharacters) / sizeof(InputCharacters[0])) { InputCharacters[n] = c; @@ -288,12 +302,17 @@ // Helpers //----------------------------------------------------------------------------- -#undef ARRAYSIZE -#define ARRAYSIZE(_ARR) (sizeof(_ARR)/sizeof(*_ARR)) +#define IM_ARRAYSIZE(_ARR) ((int)(sizeof(_ARR)/sizeof(*_ARR))) #undef PI const float PI = 3.14159265358979323846f; +#ifdef INT_MAX +#define IM_INT_MAX INT_MAX +#else +#define IM_INT_MAX 2147483647 +#endif + // Math bits // We are keeping those static in the .cpp file so as not to leak them outside, in the case the user has implicit cast operators between ImVec2 and its own types. static inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x*rhs, lhs.y*rhs); } @@ -333,7 +352,7 @@ if (!needle_end) needle_end = needle + strlen(needle); - const char un0 = toupper(*needle); + const char un0 = (char)toupper(*needle); while (*haystack) { if (toupper(*haystack) == un0) @@ -378,7 +397,7 @@ int w = vsnprintf(buf, buf_size, fmt, args); va_end(args); buf[buf_size-1] = 0; - if (w == -1) w = buf_size; + if (w == -1) w = (int)buf_size; return w; } @@ -386,7 +405,7 @@ { int w = vsnprintf(buf, buf_size, fmt, args); buf[buf_size-1] = 0; - if (w == -1) w = buf_size; + if (w == -1) w = (int)buf_size; return w; } @@ -416,7 +435,7 @@ } const float chroma = r - (g < b ? g : b); - out_h = abs(K + (g - b) / (6.f * chroma + 1e-20f)); + out_h = fabsf(K + (g - b) / (6.f * chroma + 1e-20f)); out_s = chroma / (r + 1e-20f); out_v = r; } @@ -493,6 +512,7 @@ float PrevLineHeight; float LogLineHeight; int TreeDepth; + ImGuiAabb LastItemAabb; bool LastItemHovered; ImVector ChildWindows; ImVector AllowKeyboardFocus; @@ -515,6 +535,7 @@ CurrentLineHeight = PrevLineHeight = 0.0f; LogLineHeight = -1.0f; TreeDepth = 0; + LastItemAabb = ImGuiAabb(0.0f,0.0f,0.0f,0.0f); LastItemHovered = false; StateStorage = NULL; OpenNextNode = -1; @@ -537,7 +558,7 @@ { char Text[1024]; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so own buffer. char InitialText[1024]; // backup of end-user buffer at focusing time, to ESC key can do a revert. Also used for arithmetic operations (but could use a pre-parsed float there). - int MaxLength; // end-user buffer size <= 1024 (or increase above) + size_t BufSize; // end-user buffer size, <= 1024 (or increase above) float Width; // widget width float ScrollX; STB_TexteditState StbState; @@ -551,7 +572,7 @@ void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking bool CursorIsVisible() const { return CursorAnim <= 0.0f || fmodf(CursorAnim, 1.20f) <= 0.80f; } // Blinking bool HasSelection() const { return StbState.select_start != StbState.select_end; } - void SelectAll() { StbState.select_start = 0; StbState.select_end = strlen(Text); StbState.cursor = StbState.select_end; StbState.has_preferred_x = false; } + void SelectAll() { StbState.select_start = 0; StbState.select_end = (int)strlen(Text); StbState.cursor = StbState.select_end; StbState.has_preferred_x = false; } void OnKeyboardPressed(int key); void UpdateScrollOffset(); @@ -604,6 +625,7 @@ ImGuiStorage ColorEditModeStorage; // for user selection ImGuiID ActiveComboID; char Tooltip[1024]; + char* PrivateClipboard; // if no custom clipboard handler is defined // Logging bool LogEnabled; @@ -627,6 +649,7 @@ SliderAsInputTextId = 0; ActiveComboID = 0; memset(Tooltip, 0, sizeof(Tooltip)); + PrivateClipboard = NULL; LogEnabled = false; LogFile = NULL; LogAutoExpandMaxDepth = 2; @@ -690,6 +713,7 @@ static ImGuiWindow* GetCurrentWindow() { + IM_ASSERT(GImGui.CurrentWindow != NULL); // ImGui::NewFrame() hasn't been called yet? GImGui.CurrentWindow->Accessed = true; return GImGui.CurrentWindow; } @@ -712,7 +736,7 @@ { ImVector::iterator first = data.begin(); ImVector::iterator last = data.end(); - int count = last - first; + int count = (int)(last - first); while (count > 0) { int count2 = count / 2; @@ -789,7 +813,7 @@ width = ImMax(window->Pos.x + ImGui::GetWindowContentRegionMax().x - window->DC.CursorPos.x - (label_size.x + GImGui.Style.ItemSpacing.x*4), 10.0f); } ImGui::PushItemWidth(width); - ImGui::InputText(label, InputBuf, ARRAYSIZE(InputBuf)); + ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf)); ImGui::PopItemWidth(); Build(); } @@ -865,18 +889,20 @@ //----------------------------------------------------------------------------- -void ImGuiTextBuffer::Append(const char* fmt, ...) +void ImGuiTextBuffer::append(const char* fmt, ...) { va_list args; va_start(args, fmt); int len = vsnprintf(NULL, 0, fmt, args); va_end(args); + if (len <= 0) + return; const size_t write_off = Buf.size(); if (write_off + len >= Buf.capacity()) Buf.reserve(Buf.capacity() * 2); - Buf.resize(write_off + len); + Buf.resize(write_off + (size_t)len); va_start(args, fmt); ImFormatStringV(&Buf[write_off] - 1, len+1, fmt, args); @@ -909,8 +935,8 @@ AutoFitFrames = 3; FocusIdxCounter = -1; - FocusIdxRequestCurrent = INT_MAX; - FocusIdxRequestNext = INT_MAX; + FocusIdxRequestCurrent = IM_INT_MAX; + FocusIdxRequestNext = IM_INT_MAX; DrawList = new ImDrawList(); } @@ -951,7 +977,7 @@ return false; // Process input at this point: TAB, Shift-TAB switch focus - if (FocusIdxRequestNext == INT_MAX && is_active && ImGui::IsKeyPressedMap(ImGuiKey_Tab)) + if (FocusIdxRequestNext == IM_INT_MAX && is_active && ImGui::IsKeyPressedMap(ImGuiKey_Tab)) { // Modulo on index will be applied at the end of frame once we've got the total counter of items. FocusIdxRequestNext = FocusIdxCounter + (g.IO.KeyShift ? -1 : +1); @@ -971,7 +997,11 @@ ImGuiState& g = GImGui; if (!DrawList->commands.empty() && !DrawList->vtx_buffer.empty()) + { + if (DrawList->commands.back().vtx_count == 0) + DrawList->commands.pop_back(); g.RenderDrawLists.push_back(DrawList); + } for (size_t i = 0; i < DC.ChildWindows.size(); i++) { ImGuiWindow* child = DC.ChildWindows[i]; @@ -1025,7 +1055,7 @@ if (fseek(f, 0, SEEK_SET)) return; char* f_data = new char[f_size+1]; - f_size = fread(f_data, 1, f_size, f); // Text conversion alter read size so let's not be fussy about return value + f_size = (long)fread(f_data, 1, f_size, f); // Text conversion alter read size so let's not be fussy about return value fclose(f); if (f_size == 0) { @@ -1045,7 +1075,7 @@ if (line_start[0] == '[' && line_end > line_start && line_end[-1] == ']') { char name[64]; - ImFormatString(name, ARRAYSIZE(name), "%.*s", line_end-line_start-2, line_start+1); + ImFormatString(name, IM_ARRAYSIZE(name), "%.*s", line_end-line_start-2, line_start+1); settings = FindWindowSettings(name); } else if (settings) @@ -1160,7 +1190,7 @@ else g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev; g.IO.MousePosPrev = g.IO.MousePos; - for (int i = 0; i < ARRAYSIZE(g.IO.MouseDown); i++) + for (size_t i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) { g.IO.MouseDownTime[i] = g.IO.MouseDown[i] ? (g.IO.MouseDownTime[i] < 0.0f ? 0.0f : g.IO.MouseDownTime[i] + g.IO.DeltaTime) : -1.0f; g.IO.MouseClicked[i] = (g.IO.MouseDownTime[i] == 0.0f); @@ -1180,7 +1210,7 @@ } } } - for (int i = 0; i < ARRAYSIZE(g.IO.KeysDown); i++) + for (size_t i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++) g.IO.KeysDownTime[i] = g.IO.KeysDown[i] ? (g.IO.KeysDownTime[i] < 0.0f ? 0.0f : g.IO.KeysDownTime[i] + g.IO.DeltaTime) : -1.0f; // Clear reference to active widget if the widget isn't alive anymore @@ -1280,6 +1310,12 @@ g.IO.Font = NULL; } + if (g.PrivateClipboard) + { + free(g.PrivateClipboard); + g.PrivateClipboard = NULL; + } + g.Initialized = false; } @@ -1298,7 +1334,6 @@ static void PushClipRect(const ImVec4& clip_rect, bool clipped = true) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); ImVec4 cr = clip_rect; @@ -1315,7 +1350,6 @@ static void PopClipRect() { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); window->ClipRectStack.pop_back(); window->DrawList->PopClipRect(); @@ -1446,9 +1480,9 @@ else { if (log_new_line || !is_first_line) - g.LogClipboard.Append("\n%*s%.*s", tree_depth*4, "", char_count, text_remaining); + g.LogClipboard.append("\n%*s%.*s", tree_depth*4, "", char_count, text_remaining); else - g.LogClipboard.Append(" %.*s", char_count, text_remaining); + g.LogClipboard.append(" %.*s", char_count, text_remaining); } } @@ -1491,7 +1525,6 @@ static void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding); @@ -1504,7 +1537,6 @@ static void RenderCollapseTriangle(ImVec2 p_min, bool open, float scale = 1.0f, bool shadow = false) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); const float h = window->FontSize() * 1.00f; @@ -1533,7 +1565,6 @@ static ImVec2 CalcTextSize(const char* text, const char* text_end, const bool hide_text_after_hash) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); const char* text_display_end; @@ -1551,7 +1582,7 @@ ImGuiState& g = GImGui; for (int i = (int)g.Windows.size()-1; i >= 0; i--) { - ImGuiWindow* window = g.Windows[i]; + ImGuiWindow* window = g.Windows[(size_t)i]; if (!window->Visible) continue; if (excluding_childs && (window->Flags & ImGuiWindowFlags_ChildWindow) != 0) @@ -1583,6 +1614,11 @@ return box_for_touch.Contains(g.IO.MousePos); } +bool IsMouseHoveringBox(const ImVec2& box_min, const ImVec2& box_max) +{ + return IsMouseHoveringBox(ImGuiAabb(box_min, box_max)); +} + static bool IsKeyPressedMap(ImGuiKey key, bool repeat) { ImGuiState& g = GImGui; @@ -1593,7 +1629,7 @@ bool IsKeyPressed(int key_index, bool repeat) { ImGuiState& g = GImGui; - IM_ASSERT(key_index >= 0 && key_index < ARRAYSIZE(g.IO.KeysDown)); + IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysDown)); const float t = g.IO.KeysDownTime[key_index]; if (t == 0.0f) return true; @@ -1611,7 +1647,7 @@ bool IsMouseClicked(int button, bool repeat) { ImGuiState& g = GImGui; - IM_ASSERT(button >= 0 && button < ARRAYSIZE(g.IO.MouseDown)); + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); const float t = g.IO.MouseDownTime[button]; if (t == 0.0f) return true; @@ -1626,6 +1662,13 @@ return false; } +bool IsMouseDoubleClicked(int button) +{ + ImGuiState& g = GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseDoubleClicked[button]; +} + ImVec2 GetMousePos() { return GImGui.IO.MousePos; @@ -1637,12 +1680,24 @@ return window->DC.LastItemHovered; } +ImVec2 GetItemBoxMin() +{ + ImGuiWindow* window = GetCurrentWindow(); + return window->DC.LastItemAabb.Min; +} + +ImVec2 GetItemBoxMax() +{ + ImGuiWindow* window = GetCurrentWindow(); + return window->DC.LastItemAabb.Max; +} + void SetTooltip(const char* fmt, ...) { ImGuiState& g = GImGui; va_list args; va_start(args, fmt); - ImFormatStringV(g.Tooltip, ARRAYSIZE(g.Tooltip), fmt, args); + ImFormatStringV(g.Tooltip, IM_ARRAYSIZE(g.Tooltip), fmt, args); va_end(args); } @@ -1676,7 +1731,7 @@ ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); - ImU32 flags = ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_ChildWindow; + ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_ChildWindow; const ImVec2 content_max = window->Pos + ImGui::GetWindowContentRegionMax(); const ImVec2 cursor_pos = window->Pos + ImGui::GetCursorPos(); @@ -1695,7 +1750,7 @@ flags |= extra_flags; char title[256]; - ImFormatString(title, ARRAYSIZE(title), "%s.%s", window->Name, str_id); + ImFormatString(title, IM_ARRAYSIZE(title), "%s.%s", window->Name, str_id); const float alpha = (flags & ImGuiWindowFlags_ComboBox) ? 1.0f : 0.0f; ImGui::Begin(title, NULL, size, alpha, flags); @@ -1784,10 +1839,14 @@ parent_window->DC.ChildWindows.push_back(window); window->Pos = window->PosFloat = parent_window->DC.CursorPos; window->SizeFull = size; - if (!(flags & ImGuiWindowFlags_ComboBox)) - ImGui::PushClipRect(parent_window->ClipRectStack.back()); } + // Outer clipping rectangle + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_ComboBox)) + ImGui::PushClipRect(g.CurrentWindowStack[g.CurrentWindowStack.size()-2]->ClipRectStack.back()); + else + ImGui::PushClipRect(ImVec4(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y)); + // ID stack window->IDStack.resize(0); ImGui::PushID(window); @@ -1828,9 +1887,9 @@ window->ItemWidthDefault = (float)(int)(window->Size.x > 0.0f ? window->Size.x * 0.65f : 250.0f); // Prepare for focus requests - if (window->FocusIdxRequestNext == INT_MAX || window->FocusIdxCounter == -1) + if (window->FocusIdxRequestNext == IM_INT_MAX || window->FocusIdxCounter == -1) { - window->FocusIdxRequestCurrent = INT_MAX; + window->FocusIdxRequestCurrent = IM_INT_MAX; } else { @@ -1838,7 +1897,7 @@ window->FocusIdxRequestCurrent = (window->FocusIdxRequestNext + mod) % mod; } window->FocusIdxCounter = -1; - window->FocusIdxRequestNext = INT_MAX; + window->FocusIdxRequestNext = IM_INT_MAX; ImGuiAabb title_bar_aabb = window->TitleBarAabb(); @@ -2021,16 +2080,22 @@ // Title bar if (!(window->Flags & ImGuiWindowFlags_NoTitleBar)) { - ImGui::PushClipRect(ImVec4(window->Pos.x-0.5f, window->Pos.y-0.5f, window->Pos.x+window->Size.x-1.5f, window->Pos.y+window->Size.y-1.5f), false); RenderCollapseTriangle(window->Pos + style.FramePadding, !window->Collapsed, 1.0f, true); RenderText(window->Pos + style.FramePadding + ImVec2(window->FontSize() + style.ItemInnerSpacing.x, 0), name); if (open) ImGui::CloseWindowButton(open); - ImGui::PopClipRect(); } } + else + { + // Outer clipping rectangle + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_ComboBox)) + ImGui::PushClipRect(g.CurrentWindowStack[g.CurrentWindowStack.size()-2]->ClipRectStack.back()); + else + ImGui::PushClipRect(ImVec4(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y)); + } - // Clip rectangle + // Innter clipping rectangle // We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame const ImGuiAabb title_bar_aabb = window->TitleBarAabb(); ImVec4 clip_rect(title_bar_aabb.Min.x+0.5f, title_bar_aabb.Max.y+0.5f, window->Aabb().Max.x-1.5f, window->Aabb().Max.y-1.5f); @@ -2054,10 +2119,8 @@ ImGuiWindow* window = g.CurrentWindow; ImGui::Columns(1, "#CloseColumns"); - ImGui::PopClipRect(); - if (window->Flags & ImGuiWindowFlags_ChildWindow) - if (!(window->Flags & ImGuiWindowFlags_ComboBox)) - ImGui::PopClipRect(); + ImGui::PopClipRect(); // inner window clip rectangle + ImGui::PopClipRect(); // outer window clip rectangle // Select window for move/focus when we're done with all our widgets ImGuiAabb bb(window->Pos, window->Pos+window->Size); @@ -2079,7 +2142,7 @@ } if (g.LogClipboard.size() > 1) { - g.LogClipboard.Append("\n"); + g.LogClipboard.append("\n"); if (g.IO.SetClipboardTextFn) g.IO.SetClipboardTextFn(g.LogClipboard.begin(), g.LogClipboard.end()); g.LogClipboard.clear(); @@ -2119,6 +2182,12 @@ window->DC.ItemWidth.pop_back(); } +float GetItemWidth() +{ + ImGuiWindow* window = GetCurrentWindow(); + return window->DC.ItemWidth.back(); +} + void PushAllowKeyboardFocus(bool allow_keyboard_focus) { ImGuiWindow* window = GetCurrentWindow(); @@ -2170,6 +2239,7 @@ case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered"; case ImGuiCol_ScrollbarGrabActive: return "ScrollbarGrabActive"; case ImGuiCol_ComboBg: return "ComboBg"; + case ImGuiCol_CheckHovered: return "CheckHovered"; case ImGuiCol_CheckActive: return "CheckActive"; case ImGuiCol_SliderGrab: return "SliderGrab"; case ImGuiCol_SliderGrabActive: return "SliderGrabActive"; @@ -2284,27 +2354,30 @@ void SetScrollPosHere() { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); window->NextScrollY = (window->DC.CursorPos.y + window->ScrollY) - (window->Pos.y + window->SizeFull.y * 0.5f) - (window->TitleBarHeight() + window->WindowPadding().y); } void SetTreeStateStorage(ImGuiStorage* tree) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); window->DC.StateStorage = tree ? tree : &window->StateStorage; } +ImGuiStorage* GetTreeStateStorage() +{ + ImGuiWindow* window = GetCurrentWindow(); + return window->DC.StateStorage; +} + void TextV(const char* fmt, va_list args) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->Collapsed) return; static char buf[1024]; - const char* text_end = buf + ImFormatStringV(buf, ARRAYSIZE(buf), fmt, args); + const char* text_end = buf + ImFormatStringV(buf, IM_ARRAYSIZE(buf), fmt, args); TextUnformatted(buf, text_end); } @@ -2358,10 +2431,6 @@ pos.y += lines_skipped * line_height; } } - else - { - printf(""); - } // lines to render? if (line < text_end) @@ -2443,7 +2512,7 @@ va_list args; va_start(args, fmt); const char* text_begin = &buf[0]; - const char* text_end = text_begin + ImFormatStringV(buf, ARRAYSIZE(buf), fmt, args); + const char* text_end = text_begin + ImFormatStringV(buf, IM_ARRAYSIZE(buf), fmt, args); va_end(args); const ImVec2 text_size = CalcTextSize(label); @@ -2569,7 +2638,6 @@ static bool CloseWindowButton(bool* open) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); const ImGuiID id = window->GetID("##CLOSE"); @@ -2605,7 +2673,8 @@ return; g.LogEnabled = true; g.LogFile = stdout; - g.LogAutoExpandMaxDepth = max_depth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; } void LogToFile(int max_depth, const char* filename) @@ -2613,10 +2682,12 @@ ImGuiState& g = GImGui; if (g.LogEnabled) return; - IM_ASSERT(filename); + if (!filename) + filename = g.IO.LogFilename; g.LogEnabled = true; g.LogFile = fopen(filename, "at"); - g.LogAutoExpandMaxDepth = max_depth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; } void LogToClipboard(int max_depth) @@ -2626,7 +2697,8 @@ return; g.LogEnabled = true; g.LogFile = NULL; - g.LogAutoExpandMaxDepth = max_depth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; } void LogButtons() @@ -2748,12 +2820,12 @@ va_list args; va_start(args, fmt); const char* text_begin = buf; - const char* text_end = text_begin + ImFormatStringV(buf, ARRAYSIZE(buf), fmt, args); + const char* text_end = text_begin + ImFormatStringV(buf, IM_ARRAYSIZE(buf), fmt, args); va_end(args); const float line_height = window->FontSize(); const ImVec2 text_size = CalcTextSize(text_begin, text_end); - const ImGuiAabb bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(line_height + (text_size.x ? (g.Style.FramePadding.x*2) : 0.0f),0) + text_size); // Empty text doesn't add padding + const ImGuiAabb bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(line_height + (text_size.x > 0.0f ? (g.Style.FramePadding.x*2) : 0.0f),0) + text_size); // Empty text doesn't add padding ItemSize(bb); if (ClipAdvance(bb)) @@ -2767,14 +2839,10 @@ bool TreeNode(const char* str_id, const char* fmt, ...) { - ImGuiState& g = GImGui; - ImGuiWindow* window = GetCurrentWindow(); - ImGuiStorage* tree = window->DC.StateStorage; - static char buf[1024]; va_list args; va_start(args, fmt); - ImFormatStringV(buf, ARRAYSIZE(buf), fmt, args); + ImFormatStringV(buf, IM_ARRAYSIZE(buf), fmt, args); va_end(args); if (!str_id || !str_id[0]) @@ -2792,14 +2860,10 @@ bool TreeNode(const void* ptr_id, const char* fmt, ...) { - ImGuiState& g = GImGui; - ImGuiWindow* window = GetCurrentWindow(); - ImGuiStorage* tree = window->DC.StateStorage; - static char buf[1024]; va_list args; va_start(args, fmt); - ImFormatStringV(buf, ARRAYSIZE(buf), fmt, args); + ImFormatStringV(buf, IM_ARRAYSIZE(buf), fmt, args); va_end(args); if (!ptr_id) @@ -2959,8 +3023,8 @@ if (v_min * v_max < 0.0f) { // Different sign - const float linear_dist_min_to_0 = powf(abs(0.0f - v_min), 1.0f/power); - const float linear_dist_max_to_0 = powf(abs(v_max - 0.0f), 1.0f/power); + const float linear_dist_min_to_0 = powf(fabsf(0.0f - v_min), 1.0f/power); + const float linear_dist_max_to_0 = powf(fabsf(v_max - 0.0f), 1.0f/power); linear_zero_pos = linear_dist_min_to_0 / (linear_dist_min_to_0+linear_dist_max_to_0); } else @@ -2992,12 +3056,12 @@ if (start_text_input || (g.ActiveId == id && id == g.SliderAsInputTextId)) { char text_buf[64]; - ImFormatString(text_buf, ARRAYSIZE(text_buf), "%.*f", decimal_precision, *v); + ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "%.*f", decimal_precision, *v); g.ActiveId = g.SliderAsInputTextId; g.HoveredId = 0; window->FocusItemUnregister(); // Our replacement slider will override the focus ID (that we needed to declare previously to allow for a TAB focus to happen before we got selected) - value_changed = ImGui::InputText(label, text_buf, ARRAYSIZE(text_buf), ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_AlignCenter); + value_changed = ImGui::InputText(label, text_buf, IM_ARRAYSIZE(text_buf), ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_AlignCenter); if (g.SliderAsInputTextId == 0) { // First frame @@ -3030,7 +3094,7 @@ if (!is_unbound) { const float normalized_pos = ImClamp((g.IO.MousePos.x - slider_effective_x1) / slider_effective_w, 0.0f, 1.0f); - + // Linear slider //float new_value = ImLerp(v_min, v_max, normalized_pos); @@ -3046,9 +3110,11 @@ else { // Positive: rescale to the positive range before powering - float a = normalized_pos; - if (abs(linear_zero_pos - 1.0f) > 1.e-6) - a = (a - linear_zero_pos) / (1.0f - linear_zero_pos); + float a; + if (fabsf(linear_zero_pos - 1.0f) > 1.e-6) + a = (normalized_pos - linear_zero_pos) / (1.0f - linear_zero_pos); + else + a = normalized_pos; a = powf(a, power); new_value = ImLerp(ImMax(v_min,0.0f), v_max, a); } @@ -3105,7 +3171,7 @@ } char value_buf[64]; - ImFormatString(value_buf, ARRAYSIZE(value_buf), display_format, *v); + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); RenderText(ImVec2(slider_bb.GetCenter().x-CalcTextSize(value_buf).x*0.5f, frame_bb.Min.y + style.FramePadding.y), value_buf); RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, slider_bb.Min.y), label); @@ -3131,6 +3197,38 @@ return changed; } +bool SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* display_format, float power) +{ + ImGuiState& g = GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->Collapsed) + return false; + + const ImGuiStyle& style = g.Style; + + bool value_changed = false; + ImGui::PushID(label); + + const int components = 2; + const float w_full = window->DC.ItemWidth.back(); + const float w_item_one = ImMax(1.0f, (float)(int)((w_full - (style.FramePadding.x*2.0f+style.ItemInnerSpacing.x)*(components-1)) / (float)components)); + const float w_item_last = ImMax(1.0f, (float)(int)(w_full - (w_item_one+style.FramePadding.x*2.0f+style.ItemInnerSpacing.x)*(components-1))); + + ImGui::PushItemWidth(w_item_one); + value_changed |= ImGui::SliderFloat("##X", &v[0], v_min, v_max, display_format, power); + ImGui::SameLine(0, 0); + ImGui::PopItemWidth(); + ImGui::PushItemWidth(w_item_last); + value_changed |= ImGui::SliderFloat("##Y", &v[1], v_min, v_max, display_format, power); + ImGui::SameLine(0, 0); + ImGui::PopItemWidth(); + + ImGui::TextUnformatted(label, FindTextDisplayEnd(label)); + + ImGui::PopID(); + return value_changed; +} + bool SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* display_format, float power) { ImGuiState& g = GImGui; @@ -3139,10 +3237,8 @@ return false; const ImGuiStyle& style = g.Style; - const ImVec2 text_size = CalcTextSize(label); bool value_changed = false; - ImGui::PushID(label); const int components = 3; @@ -3165,7 +3261,6 @@ ImGui::TextUnformatted(label, FindTextDisplayEnd(label)); ImGui::PopID(); - return value_changed; } @@ -3184,7 +3279,6 @@ return; const ImGuiStyle& style = g.Style; - const ImGuiID id = window->GetID(label); const ImVec2 text_size = CalcTextSize(label); if (graph_size.x == 0) @@ -3302,13 +3396,12 @@ const ImGuiAabb text_bb(window->DC.CursorPos + ImVec2(0,style.FramePadding.y), window->DC.CursorPos + ImVec2(0,style.FramePadding.y) + text_size); ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight())); + const ImGuiAabb total_bb(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max)); - if (ClipAdvance(check_bb)) + if (ClipAdvance(total_bb)) return; - RenderFrame(check_bb.Min, check_bb.Max, window->Color(ImGuiCol_FrameBg)); - - const bool hovered = (g.HoveredWindow == window) && (g.HoveredId == 0) && IsMouseHoveringBox(check_bb); + const bool hovered = (g.HoveredWindow == window) && (g.HoveredId == 0) && IsMouseHoveringBox(total_bb); const bool pressed = hovered && g.IO.MouseClicked[0]; if (hovered) g.HoveredId = id; @@ -3318,6 +3411,7 @@ g.ActiveId = 0; // Clear focus } + RenderFrame(check_bb.Min, check_bb.Max, window->Color(hovered ? ImGuiCol_CheckHovered : ImGuiCol_FrameBg)); if (*v) { window->DrawList->AddRectFilled(check_bb.Min+ImVec2(4,4), check_bb.Max-ImVec2(4,4), window->Color(ImGuiCol_CheckActive)); @@ -3356,8 +3450,9 @@ const ImGuiAabb text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + text_size); ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight())); + const ImGuiAabb total_bb(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max)); - if (ClipAdvance(check_bb)) + if (ClipAdvance(total_bb)) return false; ImVec2 center = check_bb.GetCenter(); @@ -3365,12 +3460,12 @@ center.y = (float)(int)center.y + 0.5f; const float radius = check_bb.GetHeight() * 0.5f; - const bool hovered = (g.HoveredWindow == window) && (g.HoveredId == 0) && IsMouseHoveringBox(check_bb); + const bool hovered = (g.HoveredWindow == window) && (g.HoveredId == 0) && IsMouseHoveringBox(total_bb); const bool pressed = hovered && g.IO.MouseClicked[0]; if (hovered) g.HoveredId = id; - window->DrawList->AddCircleFilled(center, radius, window->Color(ImGuiCol_FrameBg), 16); + window->DrawList->AddCircleFilled(center, radius, window->Color(hovered ? ImGuiCol_CheckHovered : ImGuiCol_FrameBg), 16); if (active) window->DrawList->AddCircleFilled(center, radius-2, window->Color(ImGuiCol_CheckActive), 16); @@ -3400,7 +3495,7 @@ // Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, ASCII, fixed-width font) int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return (int)strlen(obj->Text); } char STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return (char)obj->Text[idx]; } -float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { return obj->Font->CalcTextSize(obj->FontSize, 0, &obj->Text[char_idx], &obj->Text[char_idx]+1, NULL).x; } +float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { (void)line_start_idx; return obj->Font->CalcTextSize(obj->FontSize, 0, &obj->Text[char_idx], &obj->Text[char_idx]+1, NULL).x; } char STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : (char)key; } char STB_TEXTEDIT_NEWLINE = '\n'; void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx) @@ -3421,17 +3516,17 @@ #define STB_TEXTEDIT_IS_SPACE(c) (is_white(c) || is_separator(c)) void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int idx, int n) { char* dst = obj->Text+idx; const char* src = obj->Text+idx+n; while (char c = *src++) *dst++ = c; *dst = '\0'; } -bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int idx, const char* new_text, int new_text_size) +bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int idx, const char* new_text, int new_text_len) { - char* buf_end = obj->Text + obj->MaxLength; - int text_size = strlen(obj->Text); + char* buf_end = obj->Text + obj->BufSize; + const int text_len = (int)strlen(obj->Text); - if (new_text_size > buf_end - (obj->Text + text_size + 1)) + if (new_text_len > buf_end - (obj->Text + text_len + 1)) return false; - memmove(obj->Text + idx + new_text_size, obj->Text + idx, text_size - idx); - memcpy(obj->Text + idx, new_text, new_text_size); - obj->Text[text_size + new_text_size] = 0; + memmove(obj->Text + idx + new_text_len, obj->Text + idx, text_len - idx); + memcpy(obj->Text + idx, new_text, new_text_len); + obj->Text[text_len + new_text_len] = 0; return true; } @@ -3513,7 +3608,7 @@ const float clip_end = (text_end[0] != '\0' && text_end > text_start) ? symbol_w : 0.0f; // Draw text - ImGui::RenderText(pos+ImVec2(clip_begin,0), text_start+(clip_begin?1:0), text_end-(clip_end?1:0), false);//, &text_params_with_clipping); + ImGui::RenderText(pos+ImVec2(clip_begin,0), text_start+(clip_begin>0.0f?1:0), text_end-(clip_end>0.0f?1:0), false);//, &text_params_with_clipping); // Draw the clip symbol const char s[2] = {symbol_c,'\0'}; @@ -3540,35 +3635,34 @@ ImGui::PushID(label); const float button_sz = window->FontSize(); - if (step) + if (step > 0.0f) ImGui::PushItemWidth(ImMax(1.0f, window->DC.ItemWidth.back() - (button_sz+g.Style.FramePadding.x*2.0f+g.Style.ItemInnerSpacing.x)*2)); char buf[64]; if (decimal_precision < 0) - ImFormatString(buf, ARRAYSIZE(buf), "%f", *v); // Ideally we'd have a minimum decimal precision of 1 to visually denote that it is a float, while hiding non-significant digits? + ImFormatString(buf, IM_ARRAYSIZE(buf), "%f", *v); // Ideally we'd have a minimum decimal precision of 1 to visually denote that it is a float, while hiding non-significant digits? else - ImFormatString(buf, ARRAYSIZE(buf), "%.*f", decimal_precision, *v); + ImFormatString(buf, IM_ARRAYSIZE(buf), "%.*f", decimal_precision, *v); bool value_changed = false; - if (ImGui::InputText("", buf, ARRAYSIZE(buf), ImGuiInputTextFlags_CharsDecimal|ImGuiInputTextFlags_AlignCenter|ImGuiInputTextFlags_AutoSelectAll)) + if (ImGui::InputText("", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsDecimal|ImGuiInputTextFlags_AlignCenter|ImGuiInputTextFlags_AutoSelectAll)) { ApplyNumericalTextInput(buf, v); value_changed = true; } - if (step) - ImGui::PopItemWidth(); - if (step) + if (step > 0.0f) { + ImGui::PopItemWidth(); ImGui::SameLine(0, 0); if (ImGui::Button("-", ImVec2(button_sz,button_sz), true)) { - *v -= g.IO.KeyCtrl && step_fast ? step_fast : step; + *v -= g.IO.KeyCtrl && step_fast > 0.0f ? step_fast : step; value_changed = true; } ImGui::SameLine(0, (int)g.Style.ItemInnerSpacing.x); if (ImGui::Button("+", ImVec2(button_sz,button_sz), true)) { - *v += g.IO.KeyCtrl && step_fast ? step_fast : step; + *v += g.IO.KeyCtrl && step_fast > 0.0f ? step_fast : step; value_changed = true; } } @@ -3655,7 +3749,7 @@ bool cancel_edit = false; if (g.ActiveId == id) { - edit_state.MaxLength = buf_size < ARRAYSIZE(edit_state.Text) ? buf_size : ARRAYSIZE(edit_state.Text); + edit_state.BufSize = buf_size < IM_ARRAYSIZE(edit_state.Text) ? buf_size : IM_ARRAYSIZE(edit_state.Text); edit_state.Font = window->Font(); edit_state.FontSize = window->FontSize(); @@ -3720,7 +3814,7 @@ if (const char* clipboard = g.IO.GetClipboardTextFn()) { // Remove new-line from pasted buffer - int clipboard_len = strlen(clipboard); + size_t clipboard_len = strlen(clipboard); char* clipboard_filtered = (char*)malloc(clipboard_len+1); int clipboard_filtered_len = 0; for (int i = 0; clipboard[i]; i++) @@ -3738,7 +3832,7 @@ else if (g.IO.InputCharacters[0]) { // Text input - for (int n = 0; n < ARRAYSIZE(g.IO.InputCharacters) && g.IO.InputCharacters[n]; n++) + for (int n = 0; n < IM_ARRAYSIZE(g.IO.InputCharacters) && g.IO.InputCharacters[n]; n++) { const char c = g.IO.InputCharacters[n]; if (c) @@ -3815,6 +3909,38 @@ return value_changed; } +bool InputFloat2(const char* label, float v[2], int decimal_precision) +{ + ImGuiState& g = GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->Collapsed) + return false; + + const ImGuiStyle& style = g.Style; + + bool value_changed = false; + ImGui::PushID(label); + + const int components = 2; + const float w_full = window->DC.ItemWidth.back(); + const float w_item_one = ImMax(1.0f, (float)(int)((w_full - (style.FramePadding.x*2.0f+style.ItemInnerSpacing.x) * (components-1)) / (float)components)); + const float w_item_last = ImMax(1.0f, (float)(int)(w_full - (w_item_one+style.FramePadding.x*2.0f+style.ItemInnerSpacing.x) * (components-1))); + + ImGui::PushItemWidth(w_item_one); + value_changed |= ImGui::InputFloat("##X", &v[0], 0, 0, decimal_precision); + ImGui::SameLine(0, 0); + ImGui::PopItemWidth(); + ImGui::PushItemWidth(w_item_last); + value_changed |= ImGui::InputFloat("##Y", &v[1], 0, 0, decimal_precision); + ImGui::SameLine(0, 0); + ImGui::PopItemWidth(); + + ImGui::TextUnformatted(label, FindTextDisplayEnd(label)); + + ImGui::PopID(); + return value_changed; +} + bool InputFloat3(const char* label, float v[3], int decimal_precision) { ImGuiState& g = GImGui; @@ -3823,10 +3949,8 @@ return false; const ImGuiStyle& style = g.Style; - const ImVec2 text_size = CalcTextSize(label); bool value_changed = false; - ImGui::PushID(label); const int components = 3; @@ -3849,7 +3973,6 @@ ImGui::TextUnformatted(label, FindTextDisplayEnd(label)); ImGui::PopID(); - return value_changed; } @@ -3958,6 +4081,7 @@ ImGuiWindowFlags flags = ImGuiWindowFlags_ComboBox | ((window->Flags & ImGuiWindowFlags_ShowBorders) ? ImGuiWindowFlags_ShowBorders : 0); ImGui::BeginChild("#ComboBox", popup_aabb.GetSize(), false, flags); ImGuiWindow* child_window = GetCurrentWindow(); + ImGui::Spacing(); bool combo_item_active = false; combo_item_active |= (g.ActiveId == child_window->GetID("#SCROLLY")); @@ -4074,8 +4198,6 @@ const float w_full = window->DC.ItemWidth.back(); const float square_sz = (window->FontSize() + style.FramePadding.x * 2.0f); - const ImVec2 text_size = CalcTextSize(label); - ImGuiColorEditMode edit_mode = window->DC.ColorEditMode; if (edit_mode == ImGuiColorEditMode_UserSelect) edit_mode = g.ColorEditModeStorage.GetInt(id, 0) % 3; @@ -4142,7 +4264,7 @@ else sprintf(buf, "#%02X%02X%02X", ix, iy, iz); ImGui::PushItemWidth(w_slider_all - g.Style.ItemInnerSpacing.x); - value_changed |= ImGui::InputText("##Text", buf, ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal); + value_changed |= ImGui::InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal); ImGui::PopItemWidth(); char* p = buf; while (*p == '#' || *p == ' ' || *p == '\t') @@ -4197,15 +4319,12 @@ void ColorEditMode(ImGuiColorEditMode mode) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); - window->DC.ColorEditMode = mode; } void Separator() { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->Collapsed) return; @@ -4216,7 +4335,7 @@ const ImGuiAabb bb(ImVec2(window->Pos.x, window->DC.CursorPos.y), ImVec2(window->Pos.x + window->Size.x, window->DC.CursorPos.y)); ItemSize(ImVec2(0.0f, bb.GetSize().y)); // NB: we don't provide our width so that it doesn't get feed back into AutoFit - if (ClipAdvance(bb, true)) + if (ClipAdvance(bb)) { if (window->DC.ColumnsCount > 1) ImGui::PushColumnClipRect(); @@ -4231,7 +4350,6 @@ void Spacing() { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->Collapsed) return; @@ -4301,9 +4419,10 @@ return IsClipped(ImGuiAabb(window->DC.CursorPos, window->DC.CursorPos + item_size)); } -static bool ClipAdvance(const ImGuiAabb& bb, bool skip_columns) +static bool ClipAdvance(const ImGuiAabb& bb) { ImGuiWindow* window = GetCurrentWindow(); + window->DC.LastItemAabb = bb; if (ImGui::IsClipped(bb)) { window->DC.LastItemHovered = false; @@ -4424,7 +4543,8 @@ // Draw before resize so our items positioning are in sync with the line const ImU32 col = window->Color(held ? ImGuiCol_ColumnActive : hovered ? ImGuiCol_ColumnHovered : ImGuiCol_Column); - window->DrawList->AddLine(ImVec2(x, y1), ImVec2(x, y2), col); + const float xi = (float)(int)x; + window->DrawList->AddLine(ImVec2(xi, y1), ImVec2(xi, y2), col); if (held) { @@ -4538,58 +4658,62 @@ { commands.resize(0); vtx_buffer.resize(0); - clip_rect_buffer.resize(0); - vtx_write_ = NULL; - clip_rect_stack_.resize(0); + vtx_write = NULL; + clip_rect_stack.resize(0); } void ImDrawList::PushClipRect(const ImVec4& clip_rect) { - commands.push_back(ImDrawCmd(ImDrawCmdType_PushClipRect)); - clip_rect_buffer.push_back(clip_rect); - clip_rect_stack_.push_back(clip_rect); + if (!commands.empty() && commands.back().vtx_count == 0) + { + // Reuse empty command because high-level clipping may have discarded the other vertices already + commands.back().clip_rect = clip_rect; + } + else + { + ImDrawCmd draw_cmd; + draw_cmd.vtx_count = 0; + draw_cmd.clip_rect = clip_rect; + commands.push_back(draw_cmd); + } + clip_rect_stack.push_back(clip_rect); } void ImDrawList::PopClipRect() { - if (!commands.empty() && commands.back().cmd_type == ImDrawCmdType_PushClipRect) + clip_rect_stack.pop_back(); + const ImVec4 clip_rect = clip_rect_stack.empty() ? ImVec4(-9999.0f,-9999.0f, +9999.0f, +9999.0f) : clip_rect_stack.back(); + if (!commands.empty() && commands.back().vtx_count == 0) { - // Discard push/pop combo because high-level clipping may have discarded the other draw commands already - commands.pop_back(); - clip_rect_buffer.pop_back(); + // Reuse empty command because high-level clipping may have discarded the other vertices already + commands.back().clip_rect = clip_rect; } else { - commands.push_back(ImDrawCmd(ImDrawCmdType_PopClipRect)); + ImDrawCmd draw_cmd; + draw_cmd.vtx_count = 0; + draw_cmd.clip_rect = clip_rect; + commands.push_back(draw_cmd); } - clip_rect_stack_.pop_back(); } -void ImDrawList::AddCommand(ImDrawCmdType cmd_type, int vtx_count) +void ImDrawList::ReserveVertices(unsigned int vtx_count) { - // Maximum value that can fit in our u16 vtx_count member - const int VTX_COUNT_MAX = (1<<16); - - // Merge commands if we can, turning them into less draw calls - ImDrawCmd* prev = commands.empty() ? NULL : &commands.back(); - if (vtx_count > 0 && prev && prev->cmd_type == (ImU32)cmd_type && prev->vtx_count + vtx_count < VTX_COUNT_MAX) - prev->vtx_count += vtx_count; - else - commands.push_back(ImDrawCmd(cmd_type, vtx_count)); - if (vtx_count > 0) { + ImDrawCmd& draw_cmd = commands.back(); + draw_cmd.vtx_count += vtx_count; vtx_buffer.resize(vtx_buffer.size() + vtx_count); - vtx_write_ = &vtx_buffer[vtx_buffer.size() - vtx_count]; + vtx_write = &vtx_buffer[vtx_buffer.size() - vtx_count]; } } void ImDrawList::AddVtx(const ImVec2& pos, ImU32 col) { - vtx_write_->pos = pos; - vtx_write_->col = col; - vtx_write_->uv = IMDRAW_TEX_UV_FOR_WHITE; - vtx_write_++; + vtx_write->pos = pos; + vtx_write->col = col; + vtx_write->uv = IMGUI_FONT_TEX_UV_FOR_WHITE; + vtx_write++; } void ImDrawList::AddVtxLine(const ImVec2& a, const ImVec2& b, ImU32 col) @@ -4611,7 +4735,7 @@ if ((col >> 24) == 0) return; - AddCommand(ImDrawCmdType_DrawTriangleList, 6); + ReserveVertices(6); AddVtxLine(a, b, col); } @@ -4621,9 +4745,9 @@ static bool circle_vtx_builds = false; if (!circle_vtx_builds) { - for (int i = 0; i < ARRAYSIZE(circle_vtx); i++) + for (int i = 0; i < IM_ARRAYSIZE(circle_vtx); i++) { - const float a = ((float)i / (float)ARRAYSIZE(circle_vtx)) * 2*PI; + const float a = ((float)i / (float)IM_ARRAYSIZE(circle_vtx)) * 2*PI; circle_vtx[i].x = cos(a + PI); circle_vtx[i].y = sin(a + PI); } @@ -4632,19 +4756,19 @@ if (tris) { - AddCommand(ImDrawCmdType_DrawTriangleList, (a_max-a_min) * 3); + ReserveVertices((a_max-a_min) * 3); for (int a = a_min; a < a_max; a++) { - AddVtx(center + circle_vtx[a % ARRAYSIZE(circle_vtx)] * rad, col); - AddVtx(center + circle_vtx[(a+1) % ARRAYSIZE(circle_vtx)] * rad, col); + AddVtx(center + circle_vtx[a % IM_ARRAYSIZE(circle_vtx)] * rad, col); + AddVtx(center + circle_vtx[(a+1) % IM_ARRAYSIZE(circle_vtx)] * rad, col); AddVtx(center + third_point_offset, col); } } else { - AddCommand(ImDrawCmdType_DrawTriangleList, (a_max-a_min) * 6); + ReserveVertices((a_max-a_min) * 6); for (int a = a_min; a < a_max; a++) - AddVtxLine(center + circle_vtx[a % ARRAYSIZE(circle_vtx)] * rad, center + circle_vtx[(a+1) % ARRAYSIZE(circle_vtx)] * rad, col); + AddVtxLine(center + circle_vtx[a % IM_ARRAYSIZE(circle_vtx)] * rad, center + circle_vtx[(a+1) % IM_ARRAYSIZE(circle_vtx)] * rad, col); } } @@ -4653,14 +4777,14 @@ if ((col >> 24) == 0) return; - //const float r = ImMin(rounding, ImMin(abs(b.x-a.x), abs(b.y-a.y))*0.5f); + //const float r = ImMin(rounding, ImMin(fabsf(b.x-a.x), fabsf(b.y-a.y))*0.5f); float r = rounding; - r = ImMin(r, abs(b.x-a.x) * ( ((rounding_corners&(1|2))==(1|2)) || ((rounding_corners&(4|8))==(4|8)) ? 0.5f : 1.0f )); - r = ImMin(r, abs(b.y-a.y) * ( ((rounding_corners&(1|8))==(1|8)) || ((rounding_corners&(2|4))==(2|4)) ? 0.5f : 1.0f )); + r = ImMin(r, fabsf(b.x-a.x) * ( ((rounding_corners&(1|2))==(1|2)) || ((rounding_corners&(4|8))==(4|8)) ? 0.5f : 1.0f )); + r = ImMin(r, fabsf(b.y-a.y) * ( ((rounding_corners&(1|8))==(1|8)) || ((rounding_corners&(2|4))==(2|4)) ? 0.5f : 1.0f )); if (r == 0.0f || rounding_corners == 0) { - AddCommand(ImDrawCmdType_DrawTriangleList, 4*6); + ReserveVertices(4*6); AddVtxLine(ImVec2(a.x,a.y), ImVec2(b.x,a.y), col); AddVtxLine(ImVec2(b.x,a.y), ImVec2(b.x,b.y), col); AddVtxLine(ImVec2(b.x,b.y), ImVec2(a.x,b.y), col); @@ -4668,7 +4792,7 @@ } else { - AddCommand(ImDrawCmdType_DrawTriangleList, 4*6); + ReserveVertices(4*6); AddVtxLine(ImVec2(a.x + ((rounding_corners & 1)?r:0), a.y), ImVec2(b.x - ((rounding_corners & 2)?r:0), a.y), col); AddVtxLine(ImVec2(b.x, a.y + ((rounding_corners & 2)?r:0)), ImVec2(b.x, b.y - ((rounding_corners & 4)?r:0)), col); AddVtxLine(ImVec2(b.x - ((rounding_corners & 4)?r:0), b.y), ImVec2(a.x + ((rounding_corners & 8)?r:0), b.y), col); @@ -4686,15 +4810,15 @@ if ((col >> 24) == 0) return; - //const float r = ImMin(rounding, ImMin(abs(b.x-a.x), abs(b.y-a.y))*0.5f); + //const float r = ImMin(rounding, ImMin(fabsf(b.x-a.x), fabsf(b.y-a.y))*0.5f); float r = rounding; - r = ImMin(r, abs(b.x-a.x) * ( ((rounding_corners&(1|2))==(1|2)) || ((rounding_corners&(4|8))==(4|8)) ? 0.5f : 1.0f )); - r = ImMin(r, abs(b.y-a.y) * ( ((rounding_corners&(1|8))==(1|8)) || ((rounding_corners&(2|4))==(2|4)) ? 0.5f : 1.0f )); + r = ImMin(r, fabsf(b.x-a.x) * ( ((rounding_corners&(1|2))==(1|2)) || ((rounding_corners&(4|8))==(4|8)) ? 0.5f : 1.0f )); + r = ImMin(r, fabsf(b.y-a.y) * ( ((rounding_corners&(1|8))==(1|8)) || ((rounding_corners&(2|4))==(2|4)) ? 0.5f : 1.0f )); if (r == 0.0f || rounding_corners == 0) { // Use triangle so we can merge more draw calls together (at the cost of extra vertices) - AddCommand(ImDrawCmdType_DrawTriangleList, 6); + ReserveVertices(6); AddVtx(ImVec2(a.x,a.y), col); AddVtx(ImVec2(b.x,a.y), col); AddVtx(ImVec2(b.x,b.y), col); @@ -4704,7 +4828,7 @@ } else { - AddCommand(ImDrawCmdType_DrawTriangleList, 6+6*2); + ReserveVertices(6+6*2); AddVtx(ImVec2(a.x+r,a.y), col); AddVtx(ImVec2(b.x-r,a.y), col); AddVtx(ImVec2(b.x-r,b.y), col); @@ -4742,7 +4866,7 @@ if ((col >> 24) == 0) return; - AddCommand(ImDrawCmdType_DrawTriangleList, 3); + ReserveVertices(3); AddVtx(a, col); AddVtx(b, col); AddVtx(c, col); @@ -4753,7 +4877,7 @@ if ((col >> 24) == 0) return; - AddCommand(ImDrawCmdType_DrawTriangleList, num_segments*6); + ReserveVertices(num_segments*6); const float a_step = 2*PI/(float)num_segments; float a0 = 0.0f; for (int i = 0; i < num_segments; i++) @@ -4769,7 +4893,7 @@ if ((col >> 24) == 0) return; - AddCommand(ImDrawCmdType_DrawTriangleList, num_segments*3); + ReserveVertices(num_segments*3); const float a_step = 2*PI/(float)num_segments; float a0 = 0.0f; for (int i = 0; i < num_segments; i++) @@ -4790,17 +4914,19 @@ if (text_end == NULL) text_end = text_begin + strlen(text_begin); - int char_count = text_end - text_begin; - int vtx_count_max = char_count * 6; - int vtx_begin = vtx_buffer.size(); - AddCommand(ImDrawCmdType_DrawTriangleList, vtx_count_max); + // reserve vertices for worse case + const int char_count = (int)(text_end - text_begin); + const int vtx_count_max = char_count * 6; + const size_t vtx_begin = vtx_buffer.size(); + ReserveVertices(vtx_count_max); - font->RenderText(font_size, pos, col, clip_rect_stack_.back(), text_begin, text_end, vtx_write_); - vtx_buffer.resize(vtx_write_ - &vtx_buffer.front()); - int vtx_count = vtx_buffer.size() - vtx_begin; + font->RenderText(font_size, pos, col, clip_rect_stack.back(), text_begin, text_end, vtx_write); + // give unused vertices + vtx_buffer.resize(vtx_write - &vtx_buffer.front()); + const int vtx_count = (int)(vtx_buffer.size() - vtx_begin); commands.back().vtx_count -= (vtx_count_max - vtx_count); - vtx_write_ -= (vtx_count_max - vtx_count); + vtx_write -= (vtx_count_max - vtx_count); } //----------------------------------------------------------------------------- @@ -4849,7 +4975,7 @@ fclose(f); return false; } - if (fread(Data, 1, DataSize, f) != DataSize) + if ((int)fread(Data, 1, DataSize, f) != DataSize) { fclose(f); free(Data); @@ -4910,7 +5036,7 @@ void ImBitmapFont::BuildLookupTable() { ImU32 max_c = 0; - for (int i = 0; i != GlyphsCount; i++) + for (size_t i = 0; i != GlyphsCount; i++) if (max_c < Glyphs[i].Id) max_c = Glyphs[i].Id; @@ -4960,7 +5086,8 @@ if (const FntGlyph* glyph = FindGlyph((unsigned short)c)) { const float char_width = (glyph->XAdvance + Info->SpacingHoriz) * scale; - const float char_extend = (glyph->XOffset + glyph->Width * scale); + //const float char_extend = (glyph->XOffset + glyph->Width * scale); + if (line_width + char_width >= max_width) break; line_width += char_width; @@ -5002,10 +5129,10 @@ pos.x = (float)(int)pos.x + 0.5f; pos.y = (float)(int)pos.y + 0.5f; - ImVec2 text_size = ImVec2(0,0); - float line_width = 0.0f; const ImVec4 clip_rect = clip_rect_ref; + const float uv_offset = GImGui.IO.PixelCenterOffset; + float x = pos.x; float y = pos.y; for (const char* s = text_begin; s < text_end; s++) @@ -5021,7 +5148,7 @@ if (const FntGlyph* glyph = FindGlyph((unsigned short)c)) { const float char_width = (glyph->XAdvance + Info->SpacingHoriz) * scale; - const float char_extend = (glyph->XOffset + glyph->Width * scale); + //const float char_extend = (glyph->XOffset + glyph->Width * scale); if (c != ' ' && c != '\n') { @@ -5042,10 +5169,10 @@ continue; } - const float s1 = (0.0f + glyph->X) * tex_scale_x; - const float t1 = (0.0f + glyph->Y) * tex_scale_y; - const float s2 = (0.0f + glyph->X + glyph->Width) * tex_scale_x; - const float t2 = (0.0f + glyph->Y + glyph->Height) * tex_scale_y; + const float s1 = (uv_offset + glyph->X) * tex_scale_x; + const float t1 = (uv_offset + glyph->Y) * tex_scale_y; + const float s2 = (uv_offset + glyph->X + glyph->Width) * tex_scale_x; + const float t2 = (uv_offset + glyph->Y + glyph->Height) * tex_scale_y; out_vertices[0].pos = ImVec2(x1, y1); out_vertices[0].uv = ImVec2(s1, t1); @@ -5080,6 +5207,81 @@ } //----------------------------------------------------------------------------- +// PLATFORM DEPENDANT HELPERS +//----------------------------------------------------------------------------- + +#if defined(_MSC_VER) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS) + +#define WIN32_LEAN_AND_MEAN +#include + +// Win32 API clipboard implementation +static const char* GetClipboardTextFn_DefaultImpl() +{ + static char* buf_local = NULL; + if (buf_local) + { + free(buf_local); + buf_local = NULL; + } + if (!OpenClipboard(NULL)) + return NULL; + HANDLE buf_handle = GetClipboardData(CF_TEXT); + if (buf_handle == NULL) + return NULL; + if (char* buf_global = (char*)GlobalLock(buf_handle)) + buf_local = strdup(buf_global); + GlobalUnlock(buf_handle); + CloseClipboard(); + return buf_local; +} + +// Win32 API clipboard implementation +static void SetClipboardTextFn_DefaultImpl(const char* text, const char* text_end) +{ + if (!OpenClipboard(NULL)) + return; + if (!text_end) + text_end = text + strlen(text); + const int buf_length = (text_end - text) + 1; + HGLOBAL buf_handle = GlobalAlloc(GMEM_MOVEABLE, buf_length * sizeof(char)); + if (buf_handle == NULL) + return; + char* buf_global = (char *)GlobalLock(buf_handle); + memcpy(buf_global, text, text_end - text); + buf_global[text_end - text] = 0; + GlobalUnlock(buf_handle); + EmptyClipboard(); + SetClipboardData(CF_TEXT, buf_handle); + CloseClipboard(); +} + +#else + +// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers +static const char* GetClipboardTextFn_DefaultImpl() +{ + return GImGui.PrivateClipboard; +} + +// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers +static void SetClipboardTextFn_DefaultImpl(const char* text, const char* text_end) +{ + if (GImGui.PrivateClipboard) + { + free(GImGui.PrivateClipboard); + GImGui.PrivateClipboard = NULL; + } + if (!text_end) + text_end = text + strlen(text); + GImGui.PrivateClipboard = (char*)malloc(text_end - text + 1); + memcpy(GImGui.PrivateClipboard, text, text_end - text); + GImGui.PrivateClipboard[text_end - text] = 0; +} + +#endif + +//----------------------------------------------------------------------------- // HELP //----------------------------------------------------------------------------- @@ -5135,11 +5337,17 @@ ImGui::SameLine(); ImGui::RadioButton("HEX", &edit_mode, ImGuiColorEditMode_HEX); + static ImGuiTextFilter filter; + filter.Draw("Filter colors", 200); + ImGui::ColorEditMode(edit_mode); - for (size_t i = 0; i < ImGuiCol_COUNT; i++) + for (int i = 0; i < ImGuiCol_COUNT; i++) { + const char* name = GetStyleColorName(i); + if (!filter.PassFilter(name)) + continue; ImGui::PushID(i); - ImGui::ColorEdit4(GetStyleColorName(i), (float*)&style.Colors[i], true); + ImGui::ColorEdit4(name, (float*)&style.Colors[i], true); if (memcmp(&style.Colors[i], (ref ? &ref->Colors[i] : &def.Colors[i]), sizeof(ImVec4)) != 0) { ImGui::SameLine(); if (ImGui::Button("Revert")) style.Colors[i] = ref ? ref->Colors[i] : def.Colors[i]; @@ -5251,15 +5459,18 @@ const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK" }; static int item2 = -1; - ImGui::Combo("combo scroll", &item2, items, ARRAYSIZE(items)); + ImGui::Combo("combo scroll", &item2, items, IM_ARRAYSIZE(items)); static char str0[128] = "Hello, world!"; static int i0=123; static float f0=0.001f; - ImGui::InputText("string", str0, ARRAYSIZE(str0)); + ImGui::InputText("string", str0, IM_ARRAYSIZE(str0)); ImGui::InputInt("input int", &i0); ImGui::InputFloat("input float", &f0, 0.01f, 1.0f); + //static float vec2b[3] = { 0.10f, 0.20f }; + //ImGui::InputFloat2("input float2", vec2b); + static float vec3b[3] = { 0.10f, 0.20f, 0.30f }; ImGui::InputFloat3("input float3", vec3b); @@ -5279,6 +5490,9 @@ static float angle = 0.0f; ImGui::SliderAngle("angle", &angle); + //static float vec2a[3] = { 0.10f, 0.20f }; + //ImGui::SliderFloat2("slider float2", vec2a, 0.0f, 1.0f); + static float vec3a[3] = { 0.10f, 0.20f, 0.30f }; ImGui::SliderFloat3("slider float3", vec3a, 0.0f, 1.0f); @@ -5293,7 +5507,7 @@ if (ImGui::CollapsingHeader("Graphs widgets")) { static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; - ImGui::PlotLines("Frame Times", arr, ARRAYSIZE(arr)); + ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr)); static bool pause; static ImVector values; if (values.empty()) { values.resize(100); memset(&values.front(), 0, values.size()*sizeof(float)); } @@ -5311,10 +5525,10 @@ phase += 0.10f*values_offset; } } - ImGui::PlotLines("Frame Times", &values.front(), values.size(), values_offset, "avg 0.0", -1.0f, 1.0f, ImVec2(0,70)); + ImGui::PlotLines("Frame Times", &values.front(), (int)values.size(), values_offset, "avg 0.0", -1.0f, 1.0f, ImVec2(0,70)); ImGui::SameLine(); ImGui::Checkbox("pause", &pause); - ImGui::PlotHistogram("Histogram", arr, ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0,70)); + ImGui::PlotHistogram("Histogram", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0,70)); } if (ImGui::CollapsingHeader("Widgets on same line")) @@ -5404,7 +5618,7 @@ for (int i = 0; i < 100; i++) { char buf[32]; - ImFormatString(buf, ARRAYSIZE(buf), "%08x", i*5731); + ImFormatString(buf, IM_ARRAYSIZE(buf), "%08x", i*5731); ImGui::Button(buf); ImGui::NextColumn(); } @@ -5459,7 +5673,7 @@ static float foo = 1.0f; ImGui::InputFloat("red", &foo, 0.05f, 0, 3); ImGui::NextColumn(); static float bar = 1.0f; - ImGui::InputFloat("blue", &foo, 0.05f, 0, 3); ImGui::NextColumn(); + ImGui::InputFloat("blue", &bar, 0.05f, 0, 3); ImGui::NextColumn(); ImGui::Columns(1); ImGui::Separator(); @@ -5503,7 +5717,7 @@ static ImGuiTextFilter filter; filter.Draw(); const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" }; - for (size_t i = 0; i < ARRAYSIZE(lines); i++) + for (size_t i = 0; i < IM_ARRAYSIZE(lines); i++) if (filter.PassFilter(lines[i])) ImGui::BulletText("%s", lines[i]); } @@ -5519,7 +5733,7 @@ if (ImGui::Button("Add 1000 lines")) { for (size_t i = 0; i < 1000; i++) - log.Append("%i The quick brown fox jumps over the lazy dog\n", lines+i); + log.append("%i The quick brown fox jumps over the lazy dog\n", lines+i); lines += 1000; } ImGui::BeginChild("Log"); diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..94f4ef0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +## Visual Studio files +examples/directx9_example/Debug/* +examples/directx9_example/Release/* +examples/directx9_example/ipch/* +examples/opengl_example/Debug/* +examples/opengl_example/Release/* +examples/opengl_example/ipch/* +*.opensdf +*.sdf +*.suo +*.vcxproj.user + +## Ini files +imgui.ini diff --git a/README.md b/README.md index 65a9ad7..c3f7767 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@ ImGui ===== -ImGui is a bloat-free graphical user interface library for C/C++. It is portable, renderer agnostic and carries minimal amount of dependencies (only 3 files are needed). It is based on an "immediate" graphical user interface paradigm which allows you to build simple user interfaces with ease. +ImGui is a bloat-free graphical user interface library for C++. It outputs vertex buffers that you can render in your 3D-pipeline enabled application. It is portable, renderer agnostic and carries minimal amount of dependencies (only 3 files are needed). It is based on an "immediate" graphical user interface paradigm which allows you to build simple user interfaces with ease. -ImGui is designed to allow programmers to create "content creation" or "debug" tools (as opposed to tools for the average end-user). It favors simplicity and thus lacks certain features normally found in more high-level libraries, such as string localisation. +ImGui is designed to enable fast iteration and allow programmers to create "content creation" or "debug" tools (as opposed to tools for the average end-user). It favors simplicity and thus lacks certain features normally found in more high-level libraries, such as string localisation. -Usage example: +ImGui is particularly suited to integration in 3D applications, fullscreen applications, embedded applications, games, or any applications on consoles platforms where operating system features are non-standard. + +After ImGui is setup in your engine, you can use it like in this example: ![screenshot of sample code alongside its output with ImGui](/web/code_sample_01.png?raw=true) -ImGui output vertex buffer and simple command-list that you can render in application. Refer to the sample applications in the examples/ folder for instructions on how to integrate ImGui within your existing codebase. - +ImGui outputs vertex buffers and simple command-lists that you can render in your application. Because it doesn't know or touch graphics state directly, you can call ImGui commands anywhere in your code (e.g. in the middle of a running algorithm, or in the middle of your own rendering process). Refer to the sample applications in the examples/ folder for instructions on how to integrate ImGui with your existing codebase. Gallery ------- @@ -20,12 +21,22 @@ ![screenshot 3](/web/test_window_03.png?raw=true) ![screenshot 4](/web/test_window_04.png?raw=true) +References +---------- + +The Immediate Mode GUI paradigm may at first appear unusual to some users. This is mainly because "Retained Mode" GUIs have been so widespread and predominant. The following links can give you a better understanding about how Immediate Mode GUIs works. +- [Johannes 'johno' Norneby's article](http://www.johno.se/book/imgui.html). +- [A presentation by Rickard Gustafsson and Johannes Algelind](http://www.cse.chalmers.se/edu/year/2011/course/TDA361/Advanced%20Computer%20Graphics/IMGUI.pdf). +- [Jari Komppa's tutorial on building an ImGui library](http://iki.fi/sol/imgui/). +- [Casey Muratori's original video that popularized the concept](https://mollyrocket.com/861). + + Credits ------- Developed by [Omar Cornut](http://www.miracleworld.net). The library was developed with the support of [Media Molecule](http://www.mediamolecule.com) and first used internally on the game [Tearaway](http://tearaway.mediamolecule.com). -Embeds [proggy_clean font](http://www.proggyfonts.net/) by Tristan Grimmer (also MIT license). +Embeds [proggy_clean](http://www.proggyfonts.net/) font by Tristan Grimmer (also MIT license). Inspiration, feedback, and testing: Casey Muratori, Atman Binstock, Mikko Mononen, Emmanuel Briney, Stefan Kamoda, Matt Willis. Thanks! diff --git a/examples/directx9_example/main.cpp b/examples/directx9_example/main.cpp index fd25f01..3036eb7 100644 --- a/examples/directx9_example/main.cpp +++ b/examples/directx9_example/main.cpp @@ -12,16 +12,15 @@ static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; // Our rendering device static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL; // Buffer to hold vertices static LPDIRECT3DTEXTURE9 g_pTexture = NULL; // Our texture - struct CUSTOMVERTEX { D3DXVECTOR3 position; D3DCOLOR color; float tu, tv; }; - #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1) +// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structuer) static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) { size_t total_vtx_count = 0; @@ -30,31 +29,13 @@ if (total_vtx_count == 0) return; - ImVector clip_rect_stack; - clip_rect_stack.push_back(ImVec4(-9999,-9999,+9999,+9999)); - - // Setup orthographic projection - // Set up world matrix - D3DXMATRIXA16 mat; - D3DXMatrixIdentity(&mat); - g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat); - g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat); - D3DXMatrixOrthoOffCenterLH(&mat, 0.0f, ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y, 0.0f, -1.0f, +1.0f); - g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat); - - D3DSURFACE_DESC texture_desc; - g_pTexture->GetLevelDesc(0, &texture_desc); - - // Fill the vertex buffer + // Copy and convert all vertices into a single contiguous buffer CUSTOMVERTEX* vtx_dst; if (g_pVB->Lock(0, total_vtx_count, (void**)&vtx_dst, D3DLOCK_DISCARD) < 0) return; - for (int n = 0; n < cmd_lists_count; n++) { const ImDrawList* cmd_list = cmd_lists[n]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; const ImDrawVert* vtx_src = &cmd_list->vtx_buffer[0]; for (size_t i = 0; i < cmd_list->vtx_buffer.size(); i++) { @@ -73,11 +54,10 @@ g_pd3dDevice->SetStreamSource( 0, g_pVB, 0, sizeof( CUSTOMVERTEX ) ); g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX ); - // Setup render state: alpha-blending enabled, no face culling, no depth testing + // Setup render state: alpha-blending, no face culling, no depth testing g_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ); g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, false ); g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, false ); - g_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, true ); g_pd3dDevice->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_ADD ); g_pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, false ); @@ -94,97 +74,31 @@ g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); - int vtx_consumed = 0; // offset in vertex buffer. each command consume ImDrawCmd::vtx_count of those - bool clip_rect_dirty = true; + // Setup orthographic projection matrix + D3DXMATRIXA16 mat; + D3DXMatrixIdentity(&mat); + g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat); + g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat); + D3DXMatrixOrthoOffCenterLH(&mat, 0.0f, ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y, 0.0f, -1.0f, +1.0f); + g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat); + // Render command lists + int vtx_offset = 0; for (int n = 0; n < cmd_lists_count; n++) { + // Render command list const ImDrawList* cmd_list = cmd_lists[n]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; - const ImDrawCmd* pcmd = &cmd_list->commands.front(); - const ImDrawCmd* pcmd_end = &cmd_list->commands.back(); - int clip_rect_buf_consumed = 0; // offset in cmd_list->clip_rect_buffer. each PushClipRect command consume 1 of those. - while (pcmd <= pcmd_end) + const ImDrawCmd* pcmd_end = cmd_list->commands.end(); + for (const ImDrawCmd* pcmd = cmd_list->commands.begin(); pcmd != pcmd_end; pcmd++) { - const ImDrawCmd& cmd = *pcmd++; - switch (cmd.cmd_type) - { - case ImDrawCmdType_DrawTriangleList: - if (clip_rect_dirty) - { - const ImVec4& clip_rect = clip_rect_stack.back(); - const RECT r = { (LONG)clip_rect.x, (LONG)clip_rect.y, (LONG)clip_rect.z, (LONG)clip_rect.w }; - g_pd3dDevice->SetScissorRect(&r); - clip_rect_dirty = false; - } - g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, vtx_consumed, cmd.vtx_count/3); - vtx_consumed += cmd.vtx_count; - break; - - case ImDrawCmdType_PushClipRect: - clip_rect_stack.push_back(cmd_list->clip_rect_buffer[clip_rect_buf_consumed++]); - clip_rect_dirty = true; - break; - - case ImDrawCmdType_PopClipRect: - clip_rect_stack.pop_back(); - clip_rect_dirty = true; - break; - } + const RECT r = { (LONG)pcmd->clip_rect.x, (LONG)pcmd->clip_rect.y, (LONG)pcmd->clip_rect.z, (LONG)pcmd->clip_rect.w }; + g_pd3dDevice->SetScissorRect(&r); + g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, vtx_offset, pcmd->vtx_count/3); + vtx_offset += pcmd->vtx_count; } } } -// Get text data in Win32 clipboard -static const char* ImImpl_GetClipboardTextFn() -{ - static char* buf_local = NULL; - if (buf_local) - { - free(buf_local); - buf_local = NULL; - } - - if (!OpenClipboard(NULL)) - return NULL; - - HANDLE buf_handle = GetClipboardData(CF_TEXT); - if (buf_handle == NULL) - return NULL; - - if (char* buf_global = (char*)GlobalLock(buf_handle)) - buf_local = strdup(buf_global); - GlobalUnlock(buf_handle); - CloseClipboard(); - - return buf_local; -} - -// Set text data in Win32 clipboard -static void ImImpl_SetClipboardTextFn(const char* text, const char* text_end) -{ - if (!OpenClipboard(NULL)) - return; - - if (!text_end) - text_end = text + strlen(text); - - const int buf_length = (text_end - text) + 1; - HGLOBAL buf_handle = GlobalAlloc(GMEM_MOVEABLE, buf_length * sizeof(char)); - if (buf_handle == NULL) - return; - - char* buf_global = (char *)GlobalLock(buf_handle); - memcpy(buf_global, text, text_end - text); - buf_global[text_end - text] = 0; - GlobalUnlock(buf_handle); - - EmptyClipboard(); - SetClipboardData(CF_TEXT, buf_handle); - CloseClipboard(); -} - HRESULT InitD3D(HWND hWnd) { if (NULL == (g_pD3D = Direct3DCreate9(D3D_SDK_VERSION))) @@ -203,17 +117,6 @@ if (g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &g_pd3dDevice) < 0) return E_FAIL; - // Create the vertex buffer. - if (g_pd3dDevice->CreateVertexBuffer(10000 * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL) < 0) - return E_FAIL; - - // Load font texture - const void* png_data; - unsigned int png_size; - ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); - if (D3DXCreateTextureFromFileInMemory(g_pd3dDevice, png_data, png_size, &g_pTexture) < 0) - return E_FAIL; - return S_OK; } @@ -247,9 +150,11 @@ io.MouseDown[1] = false; return true; case WM_MOUSEWHEEL: + // Mouse wheel: -1,0,+1 io.MouseWheel = GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? +1 : -1; return true; case WM_MOUSEMOVE: + // Mouse position, in pixels (set to -1,-1 if no mouse / on another screen, etc.) io.MousePos.x = (signed short)(lParam); io.MousePos.y = (signed short)(lParam >> 16); return true; @@ -274,9 +179,10 @@ GetClientRect(hWnd, &rect); ImGuiIO& io = ImGui::GetIO(); - io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); - io.DeltaTime = 1.0f/60.0f; - io.KeyMap[ImGuiKey_Tab] = VK_TAB; + io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); // 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 Direct3D Texels + io.KeyMap[ImGuiKey_Tab] = VK_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array that we will update during the application lifetime. io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT; io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT; io.KeyMap[ImGuiKey_UpArrow] = VK_UP; @@ -295,8 +201,52 @@ io.KeyMap[ImGuiKey_Z] = 'Z'; io.RenderDrawListsFn = ImImpl_RenderDrawLists; - io.SetClipboardTextFn = ImImpl_SetClipboardTextFn; - io.GetClipboardTextFn = ImImpl_GetClipboardTextFn; + + // Create the vertex buffer + if (g_pd3dDevice->CreateVertexBuffer(10000 * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL) < 0) + { + IM_ASSERT(0); + return; + } + + // Load font texture + const void* png_data; + unsigned int png_size; + ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); + if (D3DXCreateTextureFromFileInMemory(g_pd3dDevice, png_data, png_size, &g_pTexture) < 0) + { + IM_ASSERT(0); + return; + } +} + +INT64 ticks_per_second = 0; +INT64 time = 0; + +void UpdateImGui() +{ + ImGuiIO& io = ImGui::GetIO(); + + // Setup timestep + INT64 current_time; + QueryPerformanceCounter((LARGE_INTEGER *)¤t_time); + io.DeltaTime = (float)(current_time - time) / ticks_per_second; + time = current_time; + + // Setup inputs + // (we already got mouse position, buttons, wheel from the window message callback) + BYTE keystate[256]; + GetKeyboardState(keystate); + for (int i = 0; i < 256; i++) + io.KeysDown[i] = (keystate[i] & 0x80) != 0; + io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0; + io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0; + // io.MousePos : filled by WM_MOUSEMOVE event + // io.MouseDown : filled by WM_*BUTTON* events + // io.MouseWheel : filled by WM_MOUSEWHEEL events + + // Start the frame + ImGui::NewFrame(); } int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR, int) @@ -308,99 +258,91 @@ // Create the application's window hWnd = CreateWindow(L"ImGui Example", L"ImGui DirectX9 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, NULL, NULL, wc.hInstance, NULL); - INT64 ticks_per_second, time; if (!QueryPerformanceFrequency((LARGE_INTEGER *)&ticks_per_second)) return 1; if (!QueryPerformanceCounter((LARGE_INTEGER *)&time)) return 1; // Initialize Direct3D - if (InitD3D(hWnd) >= 0) + if (InitD3D(hWnd) < 0) + { + if (g_pVB) + g_pVB->Release(); + UnregisterClass(L"ImGui Example", wc.hInstance); + return 1; + } + + // Show the window + ShowWindow(hWnd, SW_SHOWDEFAULT); + UpdateWindow(hWnd); + + InitImGui(); + + // Enter the message loop + MSG msg; + ZeroMemory(&msg, sizeof(msg)); + while (msg.message != WM_QUIT) { - // Show the window - ShowWindow(hWnd, SW_SHOWDEFAULT); - UpdateWindow(hWnd); - - InitImGui(); - - // Enter the message loop - MSG msg; - ZeroMemory(&msg, sizeof(msg)); - while (msg.message != WM_QUIT) + if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) { - if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - continue; - } + TranslateMessage(&msg); + DispatchMessage(&msg); + continue; + } - // 1) ImGui start frame, setup time delta & inputs - ImGuiIO& io = ImGui::GetIO(); - INT64 current_time; - QueryPerformanceCounter((LARGE_INTEGER *)¤t_time); - io.DeltaTime = (float)(current_time - time) / ticks_per_second; - time = current_time; - BYTE keystate[256]; - GetKeyboardState(keystate); - for (int i = 0; i < 256; i++) - io.KeysDown[i] = (keystate[i] & 0x80) != 0; - io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0; - io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0; - // io.MousePos : filled by WM_MOUSEMOVE event - // io.MouseDown : filled by WM_*BUTTON* events - // io.MouseWheel : filled by WM_MOUSEWHEEL events - ImGui::NewFrame(); + UpdateImGui(); - // 2) ImGui usage - 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"); + // 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] = io.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); + // 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); - if (show_test_window) - { - // More example code in ShowTestWindow() - 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); - } - - if (show_another_window) - { - ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); - ImGui::Text("Hello"); - ImGui::End(); - } - - // 3) Render - g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); - g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false); - g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, false); - g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(204, 153, 153), 1.0f, 0); // Clear the backbuffer and the zbuffer - if (g_pd3dDevice->BeginScene() >= 0) - { - ImGui::Render(); - g_pd3dDevice->EndScene(); - } - g_pd3dDevice->Present(NULL, NULL, NULL, NULL); + // 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::Shutdown(); - } + // Show another simple window + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); + ImGui::Text("Hello"); + ImGui::End(); + } + + // Rendering + g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); + g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false); + g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, false); + g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(204, 153, 153), 1.0f, 0); + if (g_pd3dDevice->BeginScene() >= 0) + { + ImGui::Render(); + g_pd3dDevice->EndScene(); + } + g_pd3dDevice->Present(NULL, NULL, NULL, NULL); + } + + ImGui::Shutdown(); if (g_pVB) g_pVB->Release(); diff --git a/examples/opengl_example/Makefile.Linux b/examples/opengl_example/Makefile.Linux new file mode 100644 index 0000000..6fd0b97 --- /dev/null +++ b/examples/opengl_example/Makefile.Linux @@ -0,0 +1,18 @@ +# +# Quick and dirty makefile to build on Linux +# tested on Ubuntu 14.04.1 32bit +# + +SRC = main.cpp ../../imgui.cpp + +OBJ = $(SRC:.cpp=.o) + +CXXFLAGS = -I../../ `pkg-config --cflags glfw3` + +LIBS = `pkg-config --static --libs glfw3` -lGLEW + +all: $(OBJ) + $(CXX) $(OBJ) $(LIBS) + +clean: + $(RM) -f $(OBJ) diff --git a/examples/opengl_example/Makefile.Macosx b/examples/opengl_example/Makefile.Macosx new file mode 100644 index 0000000..44ffe15 --- /dev/null +++ b/examples/opengl_example/Makefile.Macosx @@ -0,0 +1,18 @@ +# This makefile currently only works for mac os +# You should install via homebrew: +# brew install glew +# brew install glfw3 +# + +CXXFLAGS=-framework OpenGL -framework Cocoa -framework IOKit +CXXFLAGS+=-I/usr/local/Cellar/glew/1.10.0/include -I/usr/local/Cellar/glfw3/3.0.4/include +CXXFLAGS+=-L/usr/local/Cellar/glew/1.10.0/lib -L/usr/local/Cellar/glfw3/3.0.4/lib +CXXFLAGS+=-lglew -lglfw3 +CXXFLAGS+=-I../../ +CXXFLAGS+= -D__APPLE__ + +main: main.cpp ../../imgui.cpp + $(CXX) $(CXXFLAGS) -o $@ $^ + +clean: + rm main diff --git a/examples/opengl_example/main.cpp b/examples/opengl_example/main.cpp index 909ffa9..609021e 100644 --- a/examples/opengl_example/main.cpp +++ b/examples/opengl_example/main.cpp @@ -2,116 +2,65 @@ #include #include #define STB_IMAGE_IMPLEMENTATION -#include "stb_image.h" // for .png loading +#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 +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen #endif static GLFWwindow* window; -static GLuint vbo; -static GLuint vao; -static GLuint vertexShader; -static GLuint fragmentShader; -static GLuint shaderProgram; static GLuint fontTex; -static GLint uniMVP; -static GLint uniClipRect; +// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structuer) +// We are using the fixed pipeline. +// A faster way would be to collate all vertices from all cmd_lists into a single vertex buffer static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) { - size_t total_vtx_count = 0; - for (int n = 0; n < cmd_lists_count; n++) - total_vtx_count += cmd_lists[n]->vtx_buffer.size(); - if (total_vtx_count == 0) + if (cmd_lists_count == 0) return; - int read_pos_clip_rect_buf = 0; // offset in 'clip_rect_buffer'. each PushClipRect command consume 1 of those. - - ImVector clip_rect_stack; - clip_rect_stack.push_back(ImVec4(-9999,-9999,+9999,+9999)); - - // Setup orthographic projection - const float L = 0.0f; - const float R = ImGui::GetIO().DisplaySize.x; - const float B = ImGui::GetIO().DisplaySize.y; - const float T = 0.0f; - const float mvp[4][4] = - { - { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, - { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, - { 0.0f, 0.0f, -1.0f, 0.0f }, - { -(R+L)/(R-L), -(T+B)/(T-B), 0.0f, 1.0f }, - }; - - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBindVertexArray(vao); - glBufferData(GL_ARRAY_BUFFER, total_vtx_count * sizeof(ImDrawVert), NULL, GL_STREAM_DRAW); - unsigned char* buffer_data = (unsigned char*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); - if (!buffer_data) - return; - int vtx_consumed = 0; - for (int n = 0; n < cmd_lists_count; n++) - { - const ImDrawList* cmd_list = cmd_lists[n]; - if (!cmd_list->vtx_buffer.empty()) - { - memcpy(buffer_data, &cmd_list->vtx_buffer[0], cmd_list->vtx_buffer.size() * sizeof(ImDrawVert)); - buffer_data += cmd_list->vtx_buffer.size() * sizeof(ImDrawVert); - vtx_consumed += cmd_list->vtx_buffer.size(); - } - } - glUnmapBuffer(GL_ARRAY_BUFFER); - - glUseProgram(shaderProgram); - glUniformMatrix4fv(uniMVP, 1, GL_FALSE, &mvp[0][0]); - - // Setup render state: alpha-blending enabled, no face culling, no depth testing + // 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); - vtx_consumed = 0; // offset in vertex buffer. each command consume ImDrawCmd::vtx_count of those - bool clip_rect_dirty = true; + // 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]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; - const ImDrawCmd* pcmd = &cmd_list->commands.front(); - const ImDrawCmd* pcmd_end = &cmd_list->commands.back(); - int clip_rect_buf_consumed = 0; // offset in cmd_list->clip_rect_buffer. each PushClipRect command consume 1 of those. - while (pcmd <= pcmd_end) + 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++) { - const ImDrawCmd& cmd = *pcmd++; - switch (cmd.cmd_type) - { - case ImDrawCmdType_DrawTriangleList: - if (clip_rect_dirty) - { - glUniform4fv(uniClipRect, 1, (float*)&clip_rect_stack.back()); - clip_rect_dirty = false; - } - glDrawArrays(GL_TRIANGLES, vtx_consumed, cmd.vtx_count); - vtx_consumed += cmd.vtx_count; - break; - - case ImDrawCmdType_PushClipRect: - clip_rect_stack.push_back(cmd_list->clip_rect_buffer[clip_rect_buf_consumed++]); - clip_rect_dirty = true; - break; - - case ImDrawCmdType_PopClipRect: - clip_rect_stack.pop_back(); - clip_rect_dirty = true; - break; - } + 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); } static const char* ImImpl_GetClipboardTextFn() @@ -124,55 +73,33 @@ if (!text_end) text_end = text + strlen(text); - char* buf = (char*)malloc(text_end - text + 1); - memcpy(buf, text, text_end-text); - buf[text_end-text] = '\0'; - glfwSetClipboardString(window, buf); - free(buf); + 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); + } } -// Shader sources -// FIXME-OPT: clip at vertex level -const GLchar* vertexSource = - "#version 150 core\n" - "uniform mat4 MVP;" - "in vec2 i_pos;" - "in vec2 i_uv;" - "in vec4 i_col;" - "out vec4 col;" - "out vec2 pixel_pos;" - "out vec2 uv;" - "void main() {" - " col = i_col;" - " pixel_pos = i_pos;" - " uv = i_uv;" - " gl_Position = MVP * vec4(i_pos.x, i_pos.y, 0.0f, 1.0f);" - "}"; -const GLchar* fragmentSource = - "#version 150 core\n" - "uniform sampler2D Tex;" - "uniform vec4 ClipRect;" - "in vec4 col;" - "in vec2 pixel_pos;" - "in vec2 uv;" - "out vec4 o_col;" - "void main() {" - " o_col = texture(Tex, uv) * col;" - //" if (pixel_pos.x < ClipRect.x || pixel_pos.y < ClipRect.y || pixel_pos.x > ClipRect.z || pixel_pos.y > ClipRect.w) discard;" // Clipping: using discard - //" if (step(ClipRect.x,pixel_pos.x) * step(ClipRect.y,pixel_pos.y) * step(pixel_pos.x,ClipRect.z) * step(pixel_pos.y,ClipRect.w) < 1.0f) discard;" // Clipping: using discard and step - " o_col.w *= (step(ClipRect.x,pixel_pos.x) * step(ClipRect.y,pixel_pos.y) * step(pixel_pos.x,ClipRect.z) * step(pixel_pos.y,ClipRect.w));" // Clipping: branch-less, set alpha 0.0f - "}"; - +// GLFW callbacks to get events static void glfw_error_callback(int error, const char* description) { - fputs(description, stderr); + fputs(description, stderr); } -static float mouse_wheel = 0.0f; static void glfw_scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { - mouse_wheel = (float)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) @@ -197,79 +124,17 @@ { glfwSetErrorCallback(glfw_error_callback); - if (!glfwInit()) - exit(1); + if (!glfwInit()) + exit(1); - //glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - //glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); - //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); - glfwWindowHint(GLFW_REFRESH_RATE, 60); glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); - window = glfwCreateWindow(1280, 720, "ImGui OpenGL example", nullptr, nullptr); + 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); - glewExperimental = GL_TRUE; glewInit(); - - GLenum err = GL_NO_ERROR; - GLint status = GL_TRUE; - err = glGetError(); IM_ASSERT(err == GL_NO_ERROR); - - // Create and compile the vertex shader - vertexShader = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(vertexShader, 1, &vertexSource, NULL); - glCompileShader(vertexShader); - glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status); - if (status != GL_TRUE) - { - char buffer[512]; - glGetShaderInfoLog(vertexShader, 1024, NULL, buffer); - printf("%s", buffer); - IM_ASSERT(status == GL_TRUE); - } - - // Create and compile the fragment shader - fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(fragmentShader, 1, &fragmentSource, NULL); - glCompileShader(fragmentShader); - glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status); - IM_ASSERT(status == GL_TRUE); - - // Link the vertex and fragment shader into a shader program - shaderProgram = glCreateProgram(); - glAttachShader(shaderProgram, vertexShader); - glAttachShader(shaderProgram, fragmentShader); - glBindFragDataLocation(shaderProgram, 0, "o_col"); - glLinkProgram(shaderProgram); - glGetProgramiv(shaderProgram, GL_LINK_STATUS, &status); - IM_ASSERT(status == GL_TRUE); - - uniMVP = glGetUniformLocation(shaderProgram, "MVP"); - uniClipRect = glGetUniformLocation(shaderProgram, "ClipRect"); - - // Create Vertex Buffer Objects & Vertex Array Objects - glGenBuffers(1, &vbo); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - GLint posAttrib = glGetAttribLocation(shaderProgram, "i_pos"); - glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), 0); - glEnableVertexAttribArray(posAttrib); - - GLint uvAttrib = glGetAttribLocation(shaderProgram, "i_uv"); - glEnableVertexAttribArray(uvAttrib); - glVertexAttribPointer(uvAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (void*)(2*sizeof(float))); - - GLint colAttrib = glGetAttribLocation(shaderProgram, "i_col"); - glVertexAttribPointer(colAttrib, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (void*)(4*sizeof(float))); - glEnableVertexAttribArray(colAttrib); - err = glGetError(); IM_ASSERT(err == GL_NO_ERROR); } void InitImGui() @@ -278,9 +143,10 @@ glfwGetWindowSize(window, &w, &h); ImGuiIO& io = ImGui::GetIO(); - io.DisplaySize = ImVec2((float)w, (float)h); - io.DeltaTime = 1.0f/60.0f; - io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; + io.DisplaySize = ImVec2((float)w, (float)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.5f; // 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; @@ -307,7 +173,6 @@ 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); - const void* png_data; unsigned int png_size; ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); @@ -317,44 +182,43 @@ stbi_image_free(tex_data); } -void Shutdown() +void UpdateImGui() { - ImGui::Shutdown(); + ImGuiIO& io = ImGui::GetIO(); - glDeleteProgram(shaderProgram); - glDeleteShader(fragmentShader); - glDeleteShader(vertexShader); - glDeleteBuffers(1, &vbo); - glDeleteVertexArrays(1, &vao); + // Setup timestep + static double time = 0.0f; + const double current_time = glfwGetTime(); + io.DeltaTime = (float)(current_time - time); + time = current_time; - glfwTerminate(); + // 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, (float)mouse_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(); - double time = glfwGetTime(); while (!glfwWindowShouldClose(window)) { ImGuiIO& io = ImGui::GetIO(); + io.MouseWheel = 0; glfwPollEvents(); + UpdateImGui(); - // 1) ImGui start frame, setup time delta & inputs - const double current_time = glfwGetTime(); - io.DeltaTime = (float)(current_time - time); - time = current_time; - double mouse_x, mouse_y; - glfwGetCursorPos(window, &mouse_x, &mouse_y); - io.MousePos = ImVec2((float)mouse_x, (float)mouse_y); - io.MouseDown[0] = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) != 0; - io.MouseDown[1] = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT) != 0; - io.MouseWheel = (mouse_wheel != 0) ? mouse_wheel > 0.0f ? 1 : - 1 : 0; - mouse_wheel = 0.0f; - ImGui::NewFrame(); - - // 2) ImGui usage + // 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; @@ -368,19 +232,21 @@ 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] = io.DeltaTime * 1000.0f; + 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) { - // More example code in ShowTestWindow() - 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::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); } + // Show another simple window if (show_another_window) { ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); @@ -388,15 +254,15 @@ ImGui::End(); } - // 3) Render - glClearColor(0.8f, 0.6f, 0.6f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); + // 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); } - Shutdown(); - + ImGui::Shutdown(); + glfwTerminate(); return 0; } diff --git a/imconfig.h b/imconfig.h index 0a340ee..98b16f1 100644 --- a/imconfig.h +++ b/imconfig.h @@ -4,15 +4,22 @@ #pragma once -//----- Define your own ImVector<> type if you don't want to use the provided implementation defined in imgui.h +//---- Define your own ImVector<> type if you don't want to use the provided implementation defined in imgui.h //#include //#define ImVector std::vector //#define ImVector MyVector -//----- Define assertion handler. Default to calling assert(). -// #define IM_ASSERT(_EXPR) MyAssert(_EXPR) +//---- Define assertion handler. Defaults to calling assert(). +//#define IM_ASSERT(_EXPR) MyAssert(_EXPR) -//----- Define implicit cast operators to convert back<>forth from your math types and ImVec2/ImVec4. +//---- Don't implement default clipboard handlers for Windows (so as not to link with OpenClipboard(), etc.) +//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS + +//---- If you are loading a custom font, ImGui expect to find a pure white pixel at (0,0) +// Change it's UV coordinate here if you can't have a white pixel at (0,0) +//#define IMGUI_FONT_TEX_UV_FOR_WHITE ImVec2(0.f/256.f,0.f/256.f) + +//---- Define implicit cast operators to convert back<>forth from your math types and ImVec2/ImVec4. /* #define IM_VEC2_CLASS_EXTRA \ ImVec2(const MyVec2& f) { x = f.x; y = f.y; } \ @@ -23,12 +30,12 @@ operator MyVec4() const { return MyVec4(x,y,z,w); } */ -//----- Freely implement extra functions within the ImGui:: namespace. -//----- e.g. you can create variants of the ImGui::Value() helper for your low-level math types. +//---- Freely implement extra functions within the ImGui:: namespace. +//---- e.g. you can create variants of the ImGui::Value() helper for your low-level math types. /* namespace ImGui { - void Value(const char* prefix, cosnt MyVec2& v, const char* float_format = NULL); - void Value(const char* prefix, cosnt MyVec4& v, const char* float_format = NULL); + void Value(const char* prefix, const MyVec2& v, const char* float_format = NULL); + void Value(const char* prefix, const MyVec4& v, const char* float_format = NULL); }; */ diff --git a/imgui.cpp b/imgui.cpp index 1242b02..888a43b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -101,7 +101,7 @@ - if you want to use a different font than the default - create bitmap font data using BMFont. allocate ImGui::GetIO().Font and use ->LoadFromFile()/LoadFromMemory(), set ImGui::GetIO().FontHeight - - load your texture yourself. texture *MUST* have white pixel at UV coordinate 'IMDRAW_TEX_UV_FOR_WHITE' (you can #define it in imconfig.h), this is used by solid objects. + - load your texture yourself. texture *MUST* have white pixel at UV coordinate 'IMGUI_FONT_TEX_UV_FOR_WHITE' (you can #define it in imconfig.h), this is used by solid objects. - tip: the construct 'if (IMGUI_ONCE_UPON_A_FRAME)' will evaluate to true only once a frame, you can use it to add custom UI in the middle of a deep nested inner loop in your code. - tip: you can call Render() multiple times (e.g for VR renders), up to you to communicate the extra state to your RenderDrawListFn function. @@ -110,18 +110,20 @@ ISSUES AND TODO-LIST - misc: merge ImVec4 / ImGuiAabb, they are essentially duplicate containers - - main: make IsHovered() more consistent for various type of widgets, widgets with multiple components, etc. also effectively IsHovered() region sometimes differs from hot region, e.g tree nodes - window: autofit is losing its purpose when user relies on any dynamic layout (window width multiplier, column). maybe just discard autofit? - window: support horizontal scroll - window: fix resize grip scaling along with Rounding style setting + - window/style: add global alpha modifier (not just "fill_alpha") - widgets: switching from "widget-label" to "label-widget" would make it more convenient to integrate widgets in trees - widgets: clip text? hover clipped text shows it in a tooltip or in-place overlay + - main: make IsHovered() more consistent for various type of widgets, widgets with multiple components, etc. also effectively IsHovered() region sometimes differs from hot region, e.g tree nodes + - main: make IsHovered() info stored in a stack? so that 'if TreeNode() { Text; TreePop; } if IsHovered' return the hover state of the TreeNode? - scrollbar: use relative mouse movement when first-clicking inside of scroll grab box. - input number: optional range min/max - input number: holding [-]/[+] buttons should increase the step non-linearly - input number: rename Input*() to Input(), Slider*() to Slider() ? - layout: clean up the InputFloat3/SliderFloat3/ColorEdit4 horrible layout code. item width should include frame padding, then we can have a generic horizontal layout helper. - - add input2/4 helper (once above layout helpers are in they'll be smaller) + - add input4 helper (once above layout helpers are in they'll be smaller) - columns: declare column set (each column: fixed size, %, fill, distribute default size among fills) - columns: columns header to act as button (~sort op) and allow resize/reorder - columns: user specify columns size @@ -151,13 +153,12 @@ - optimisation: turn some the various stack vectors into statically-sized arrays - optimisation: better clipping for multi-component widgets - optimisation: specialize for height based clipping first (assume widgets never go up + height tests before width tests?) - - optimisation/portability: provide ImVector style implementation - - optimisation/portability: remove dependency on */ #include "imgui.h" -#include +#include // toupper #include // sqrt +#include // intptr_t #include // vsnprintf #include // memset @@ -188,7 +189,7 @@ static void ItemSize(const ImGuiAabb& aabb, ImVec2* adjust_start_offset = NULL); static void PushColumnClipRect(int column_index = -1); static bool IsClipped(const ImGuiAabb& aabb); -static bool ClipAdvance(const ImGuiAabb& aabb, bool skip_columns = false); +static bool ClipAdvance(const ImGuiAabb& aabb); static bool IsMouseHoveringBox(const ImGuiAabb& box); static bool IsKeyPressedMap(ImGuiKey key, bool repeat = true); @@ -200,6 +201,13 @@ }; // namespace ImGui //----------------------------------------------------------------------------- +// Platform dependant default implementations +//----------------------------------------------------------------------------- + +static const char* GetClipboardTextFn_DefaultImpl(); +static void SetClipboardTextFn_DefaultImpl(const char* text, const char* text_end); + +//----------------------------------------------------------------------------- // User facing structures //----------------------------------------------------------------------------- @@ -230,6 +238,7 @@ Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.80f, 0.40f); Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.80f, 0.50f, 0.50f, 0.40f); Colors[ImGuiCol_ComboBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.99f); + Colors[ImGuiCol_CheckHovered] = ImVec4(0.60f, 0.40f, 0.40f, 0.45f); Colors[ImGuiCol_CheckActive] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f); Colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.80f, 0.50f, 0.50f, 1.00f); @@ -265,10 +274,15 @@ LogFilename = "imgui_log.txt"; Font = NULL; FontAllowScaling = false; + PixelCenterOffset = 0.5f; MousePos = ImVec2(-1,-1); MousePosPrev = ImVec2(-1,-1); MouseDoubleClickTime = 0.30f; MouseDoubleClickMaxDist = 6.0f; + + // Platform dependant default implementations + GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; + SetClipboardTextFn = SetClipboardTextFn_DefaultImpl; } // Pass in translated ASCII characters for text input. @@ -276,7 +290,7 @@ // - on Windows you can get those using ToAscii+keyboard state, or via the VM_CHAR message void ImGuiIO::AddInputCharacter(char c) { - const int n = strlen(InputCharacters); + const size_t n = strlen(InputCharacters); if (n < sizeof(InputCharacters) / sizeof(InputCharacters[0])) { InputCharacters[n] = c; @@ -288,12 +302,17 @@ // Helpers //----------------------------------------------------------------------------- -#undef ARRAYSIZE -#define ARRAYSIZE(_ARR) (sizeof(_ARR)/sizeof(*_ARR)) +#define IM_ARRAYSIZE(_ARR) ((int)(sizeof(_ARR)/sizeof(*_ARR))) #undef PI const float PI = 3.14159265358979323846f; +#ifdef INT_MAX +#define IM_INT_MAX INT_MAX +#else +#define IM_INT_MAX 2147483647 +#endif + // Math bits // We are keeping those static in the .cpp file so as not to leak them outside, in the case the user has implicit cast operators between ImVec2 and its own types. static inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x*rhs, lhs.y*rhs); } @@ -333,7 +352,7 @@ if (!needle_end) needle_end = needle + strlen(needle); - const char un0 = toupper(*needle); + const char un0 = (char)toupper(*needle); while (*haystack) { if (toupper(*haystack) == un0) @@ -378,7 +397,7 @@ int w = vsnprintf(buf, buf_size, fmt, args); va_end(args); buf[buf_size-1] = 0; - if (w == -1) w = buf_size; + if (w == -1) w = (int)buf_size; return w; } @@ -386,7 +405,7 @@ { int w = vsnprintf(buf, buf_size, fmt, args); buf[buf_size-1] = 0; - if (w == -1) w = buf_size; + if (w == -1) w = (int)buf_size; return w; } @@ -416,7 +435,7 @@ } const float chroma = r - (g < b ? g : b); - out_h = abs(K + (g - b) / (6.f * chroma + 1e-20f)); + out_h = fabsf(K + (g - b) / (6.f * chroma + 1e-20f)); out_s = chroma / (r + 1e-20f); out_v = r; } @@ -493,6 +512,7 @@ float PrevLineHeight; float LogLineHeight; int TreeDepth; + ImGuiAabb LastItemAabb; bool LastItemHovered; ImVector ChildWindows; ImVector AllowKeyboardFocus; @@ -515,6 +535,7 @@ CurrentLineHeight = PrevLineHeight = 0.0f; LogLineHeight = -1.0f; TreeDepth = 0; + LastItemAabb = ImGuiAabb(0.0f,0.0f,0.0f,0.0f); LastItemHovered = false; StateStorage = NULL; OpenNextNode = -1; @@ -537,7 +558,7 @@ { char Text[1024]; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so own buffer. char InitialText[1024]; // backup of end-user buffer at focusing time, to ESC key can do a revert. Also used for arithmetic operations (but could use a pre-parsed float there). - int MaxLength; // end-user buffer size <= 1024 (or increase above) + size_t BufSize; // end-user buffer size, <= 1024 (or increase above) float Width; // widget width float ScrollX; STB_TexteditState StbState; @@ -551,7 +572,7 @@ void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking bool CursorIsVisible() const { return CursorAnim <= 0.0f || fmodf(CursorAnim, 1.20f) <= 0.80f; } // Blinking bool HasSelection() const { return StbState.select_start != StbState.select_end; } - void SelectAll() { StbState.select_start = 0; StbState.select_end = strlen(Text); StbState.cursor = StbState.select_end; StbState.has_preferred_x = false; } + void SelectAll() { StbState.select_start = 0; StbState.select_end = (int)strlen(Text); StbState.cursor = StbState.select_end; StbState.has_preferred_x = false; } void OnKeyboardPressed(int key); void UpdateScrollOffset(); @@ -604,6 +625,7 @@ ImGuiStorage ColorEditModeStorage; // for user selection ImGuiID ActiveComboID; char Tooltip[1024]; + char* PrivateClipboard; // if no custom clipboard handler is defined // Logging bool LogEnabled; @@ -627,6 +649,7 @@ SliderAsInputTextId = 0; ActiveComboID = 0; memset(Tooltip, 0, sizeof(Tooltip)); + PrivateClipboard = NULL; LogEnabled = false; LogFile = NULL; LogAutoExpandMaxDepth = 2; @@ -690,6 +713,7 @@ static ImGuiWindow* GetCurrentWindow() { + IM_ASSERT(GImGui.CurrentWindow != NULL); // ImGui::NewFrame() hasn't been called yet? GImGui.CurrentWindow->Accessed = true; return GImGui.CurrentWindow; } @@ -712,7 +736,7 @@ { ImVector::iterator first = data.begin(); ImVector::iterator last = data.end(); - int count = last - first; + int count = (int)(last - first); while (count > 0) { int count2 = count / 2; @@ -789,7 +813,7 @@ width = ImMax(window->Pos.x + ImGui::GetWindowContentRegionMax().x - window->DC.CursorPos.x - (label_size.x + GImGui.Style.ItemSpacing.x*4), 10.0f); } ImGui::PushItemWidth(width); - ImGui::InputText(label, InputBuf, ARRAYSIZE(InputBuf)); + ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf)); ImGui::PopItemWidth(); Build(); } @@ -865,18 +889,20 @@ //----------------------------------------------------------------------------- -void ImGuiTextBuffer::Append(const char* fmt, ...) +void ImGuiTextBuffer::append(const char* fmt, ...) { va_list args; va_start(args, fmt); int len = vsnprintf(NULL, 0, fmt, args); va_end(args); + if (len <= 0) + return; const size_t write_off = Buf.size(); if (write_off + len >= Buf.capacity()) Buf.reserve(Buf.capacity() * 2); - Buf.resize(write_off + len); + Buf.resize(write_off + (size_t)len); va_start(args, fmt); ImFormatStringV(&Buf[write_off] - 1, len+1, fmt, args); @@ -909,8 +935,8 @@ AutoFitFrames = 3; FocusIdxCounter = -1; - FocusIdxRequestCurrent = INT_MAX; - FocusIdxRequestNext = INT_MAX; + FocusIdxRequestCurrent = IM_INT_MAX; + FocusIdxRequestNext = IM_INT_MAX; DrawList = new ImDrawList(); } @@ -951,7 +977,7 @@ return false; // Process input at this point: TAB, Shift-TAB switch focus - if (FocusIdxRequestNext == INT_MAX && is_active && ImGui::IsKeyPressedMap(ImGuiKey_Tab)) + if (FocusIdxRequestNext == IM_INT_MAX && is_active && ImGui::IsKeyPressedMap(ImGuiKey_Tab)) { // Modulo on index will be applied at the end of frame once we've got the total counter of items. FocusIdxRequestNext = FocusIdxCounter + (g.IO.KeyShift ? -1 : +1); @@ -971,7 +997,11 @@ ImGuiState& g = GImGui; if (!DrawList->commands.empty() && !DrawList->vtx_buffer.empty()) + { + if (DrawList->commands.back().vtx_count == 0) + DrawList->commands.pop_back(); g.RenderDrawLists.push_back(DrawList); + } for (size_t i = 0; i < DC.ChildWindows.size(); i++) { ImGuiWindow* child = DC.ChildWindows[i]; @@ -1025,7 +1055,7 @@ if (fseek(f, 0, SEEK_SET)) return; char* f_data = new char[f_size+1]; - f_size = fread(f_data, 1, f_size, f); // Text conversion alter read size so let's not be fussy about return value + f_size = (long)fread(f_data, 1, f_size, f); // Text conversion alter read size so let's not be fussy about return value fclose(f); if (f_size == 0) { @@ -1045,7 +1075,7 @@ if (line_start[0] == '[' && line_end > line_start && line_end[-1] == ']') { char name[64]; - ImFormatString(name, ARRAYSIZE(name), "%.*s", line_end-line_start-2, line_start+1); + ImFormatString(name, IM_ARRAYSIZE(name), "%.*s", line_end-line_start-2, line_start+1); settings = FindWindowSettings(name); } else if (settings) @@ -1160,7 +1190,7 @@ else g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev; g.IO.MousePosPrev = g.IO.MousePos; - for (int i = 0; i < ARRAYSIZE(g.IO.MouseDown); i++) + for (size_t i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) { g.IO.MouseDownTime[i] = g.IO.MouseDown[i] ? (g.IO.MouseDownTime[i] < 0.0f ? 0.0f : g.IO.MouseDownTime[i] + g.IO.DeltaTime) : -1.0f; g.IO.MouseClicked[i] = (g.IO.MouseDownTime[i] == 0.0f); @@ -1180,7 +1210,7 @@ } } } - for (int i = 0; i < ARRAYSIZE(g.IO.KeysDown); i++) + for (size_t i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++) g.IO.KeysDownTime[i] = g.IO.KeysDown[i] ? (g.IO.KeysDownTime[i] < 0.0f ? 0.0f : g.IO.KeysDownTime[i] + g.IO.DeltaTime) : -1.0f; // Clear reference to active widget if the widget isn't alive anymore @@ -1280,6 +1310,12 @@ g.IO.Font = NULL; } + if (g.PrivateClipboard) + { + free(g.PrivateClipboard); + g.PrivateClipboard = NULL; + } + g.Initialized = false; } @@ -1298,7 +1334,6 @@ static void PushClipRect(const ImVec4& clip_rect, bool clipped = true) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); ImVec4 cr = clip_rect; @@ -1315,7 +1350,6 @@ static void PopClipRect() { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); window->ClipRectStack.pop_back(); window->DrawList->PopClipRect(); @@ -1446,9 +1480,9 @@ else { if (log_new_line || !is_first_line) - g.LogClipboard.Append("\n%*s%.*s", tree_depth*4, "", char_count, text_remaining); + g.LogClipboard.append("\n%*s%.*s", tree_depth*4, "", char_count, text_remaining); else - g.LogClipboard.Append(" %.*s", char_count, text_remaining); + g.LogClipboard.append(" %.*s", char_count, text_remaining); } } @@ -1491,7 +1525,6 @@ static void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding); @@ -1504,7 +1537,6 @@ static void RenderCollapseTriangle(ImVec2 p_min, bool open, float scale = 1.0f, bool shadow = false) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); const float h = window->FontSize() * 1.00f; @@ -1533,7 +1565,6 @@ static ImVec2 CalcTextSize(const char* text, const char* text_end, const bool hide_text_after_hash) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); const char* text_display_end; @@ -1551,7 +1582,7 @@ ImGuiState& g = GImGui; for (int i = (int)g.Windows.size()-1; i >= 0; i--) { - ImGuiWindow* window = g.Windows[i]; + ImGuiWindow* window = g.Windows[(size_t)i]; if (!window->Visible) continue; if (excluding_childs && (window->Flags & ImGuiWindowFlags_ChildWindow) != 0) @@ -1583,6 +1614,11 @@ return box_for_touch.Contains(g.IO.MousePos); } +bool IsMouseHoveringBox(const ImVec2& box_min, const ImVec2& box_max) +{ + return IsMouseHoveringBox(ImGuiAabb(box_min, box_max)); +} + static bool IsKeyPressedMap(ImGuiKey key, bool repeat) { ImGuiState& g = GImGui; @@ -1593,7 +1629,7 @@ bool IsKeyPressed(int key_index, bool repeat) { ImGuiState& g = GImGui; - IM_ASSERT(key_index >= 0 && key_index < ARRAYSIZE(g.IO.KeysDown)); + IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysDown)); const float t = g.IO.KeysDownTime[key_index]; if (t == 0.0f) return true; @@ -1611,7 +1647,7 @@ bool IsMouseClicked(int button, bool repeat) { ImGuiState& g = GImGui; - IM_ASSERT(button >= 0 && button < ARRAYSIZE(g.IO.MouseDown)); + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); const float t = g.IO.MouseDownTime[button]; if (t == 0.0f) return true; @@ -1626,6 +1662,13 @@ return false; } +bool IsMouseDoubleClicked(int button) +{ + ImGuiState& g = GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseDoubleClicked[button]; +} + ImVec2 GetMousePos() { return GImGui.IO.MousePos; @@ -1637,12 +1680,24 @@ return window->DC.LastItemHovered; } +ImVec2 GetItemBoxMin() +{ + ImGuiWindow* window = GetCurrentWindow(); + return window->DC.LastItemAabb.Min; +} + +ImVec2 GetItemBoxMax() +{ + ImGuiWindow* window = GetCurrentWindow(); + return window->DC.LastItemAabb.Max; +} + void SetTooltip(const char* fmt, ...) { ImGuiState& g = GImGui; va_list args; va_start(args, fmt); - ImFormatStringV(g.Tooltip, ARRAYSIZE(g.Tooltip), fmt, args); + ImFormatStringV(g.Tooltip, IM_ARRAYSIZE(g.Tooltip), fmt, args); va_end(args); } @@ -1676,7 +1731,7 @@ ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); - ImU32 flags = ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_ChildWindow; + ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_ChildWindow; const ImVec2 content_max = window->Pos + ImGui::GetWindowContentRegionMax(); const ImVec2 cursor_pos = window->Pos + ImGui::GetCursorPos(); @@ -1695,7 +1750,7 @@ flags |= extra_flags; char title[256]; - ImFormatString(title, ARRAYSIZE(title), "%s.%s", window->Name, str_id); + ImFormatString(title, IM_ARRAYSIZE(title), "%s.%s", window->Name, str_id); const float alpha = (flags & ImGuiWindowFlags_ComboBox) ? 1.0f : 0.0f; ImGui::Begin(title, NULL, size, alpha, flags); @@ -1784,10 +1839,14 @@ parent_window->DC.ChildWindows.push_back(window); window->Pos = window->PosFloat = parent_window->DC.CursorPos; window->SizeFull = size; - if (!(flags & ImGuiWindowFlags_ComboBox)) - ImGui::PushClipRect(parent_window->ClipRectStack.back()); } + // Outer clipping rectangle + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_ComboBox)) + ImGui::PushClipRect(g.CurrentWindowStack[g.CurrentWindowStack.size()-2]->ClipRectStack.back()); + else + ImGui::PushClipRect(ImVec4(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y)); + // ID stack window->IDStack.resize(0); ImGui::PushID(window); @@ -1828,9 +1887,9 @@ window->ItemWidthDefault = (float)(int)(window->Size.x > 0.0f ? window->Size.x * 0.65f : 250.0f); // Prepare for focus requests - if (window->FocusIdxRequestNext == INT_MAX || window->FocusIdxCounter == -1) + if (window->FocusIdxRequestNext == IM_INT_MAX || window->FocusIdxCounter == -1) { - window->FocusIdxRequestCurrent = INT_MAX; + window->FocusIdxRequestCurrent = IM_INT_MAX; } else { @@ -1838,7 +1897,7 @@ window->FocusIdxRequestCurrent = (window->FocusIdxRequestNext + mod) % mod; } window->FocusIdxCounter = -1; - window->FocusIdxRequestNext = INT_MAX; + window->FocusIdxRequestNext = IM_INT_MAX; ImGuiAabb title_bar_aabb = window->TitleBarAabb(); @@ -2021,16 +2080,22 @@ // Title bar if (!(window->Flags & ImGuiWindowFlags_NoTitleBar)) { - ImGui::PushClipRect(ImVec4(window->Pos.x-0.5f, window->Pos.y-0.5f, window->Pos.x+window->Size.x-1.5f, window->Pos.y+window->Size.y-1.5f), false); RenderCollapseTriangle(window->Pos + style.FramePadding, !window->Collapsed, 1.0f, true); RenderText(window->Pos + style.FramePadding + ImVec2(window->FontSize() + style.ItemInnerSpacing.x, 0), name); if (open) ImGui::CloseWindowButton(open); - ImGui::PopClipRect(); } } + else + { + // Outer clipping rectangle + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_ComboBox)) + ImGui::PushClipRect(g.CurrentWindowStack[g.CurrentWindowStack.size()-2]->ClipRectStack.back()); + else + ImGui::PushClipRect(ImVec4(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y)); + } - // Clip rectangle + // Innter clipping rectangle // We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame const ImGuiAabb title_bar_aabb = window->TitleBarAabb(); ImVec4 clip_rect(title_bar_aabb.Min.x+0.5f, title_bar_aabb.Max.y+0.5f, window->Aabb().Max.x-1.5f, window->Aabb().Max.y-1.5f); @@ -2054,10 +2119,8 @@ ImGuiWindow* window = g.CurrentWindow; ImGui::Columns(1, "#CloseColumns"); - ImGui::PopClipRect(); - if (window->Flags & ImGuiWindowFlags_ChildWindow) - if (!(window->Flags & ImGuiWindowFlags_ComboBox)) - ImGui::PopClipRect(); + ImGui::PopClipRect(); // inner window clip rectangle + ImGui::PopClipRect(); // outer window clip rectangle // Select window for move/focus when we're done with all our widgets ImGuiAabb bb(window->Pos, window->Pos+window->Size); @@ -2079,7 +2142,7 @@ } if (g.LogClipboard.size() > 1) { - g.LogClipboard.Append("\n"); + g.LogClipboard.append("\n"); if (g.IO.SetClipboardTextFn) g.IO.SetClipboardTextFn(g.LogClipboard.begin(), g.LogClipboard.end()); g.LogClipboard.clear(); @@ -2119,6 +2182,12 @@ window->DC.ItemWidth.pop_back(); } +float GetItemWidth() +{ + ImGuiWindow* window = GetCurrentWindow(); + return window->DC.ItemWidth.back(); +} + void PushAllowKeyboardFocus(bool allow_keyboard_focus) { ImGuiWindow* window = GetCurrentWindow(); @@ -2170,6 +2239,7 @@ case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered"; case ImGuiCol_ScrollbarGrabActive: return "ScrollbarGrabActive"; case ImGuiCol_ComboBg: return "ComboBg"; + case ImGuiCol_CheckHovered: return "CheckHovered"; case ImGuiCol_CheckActive: return "CheckActive"; case ImGuiCol_SliderGrab: return "SliderGrab"; case ImGuiCol_SliderGrabActive: return "SliderGrabActive"; @@ -2284,27 +2354,30 @@ void SetScrollPosHere() { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); window->NextScrollY = (window->DC.CursorPos.y + window->ScrollY) - (window->Pos.y + window->SizeFull.y * 0.5f) - (window->TitleBarHeight() + window->WindowPadding().y); } void SetTreeStateStorage(ImGuiStorage* tree) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); window->DC.StateStorage = tree ? tree : &window->StateStorage; } +ImGuiStorage* GetTreeStateStorage() +{ + ImGuiWindow* window = GetCurrentWindow(); + return window->DC.StateStorage; +} + void TextV(const char* fmt, va_list args) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->Collapsed) return; static char buf[1024]; - const char* text_end = buf + ImFormatStringV(buf, ARRAYSIZE(buf), fmt, args); + const char* text_end = buf + ImFormatStringV(buf, IM_ARRAYSIZE(buf), fmt, args); TextUnformatted(buf, text_end); } @@ -2358,10 +2431,6 @@ pos.y += lines_skipped * line_height; } } - else - { - printf(""); - } // lines to render? if (line < text_end) @@ -2443,7 +2512,7 @@ va_list args; va_start(args, fmt); const char* text_begin = &buf[0]; - const char* text_end = text_begin + ImFormatStringV(buf, ARRAYSIZE(buf), fmt, args); + const char* text_end = text_begin + ImFormatStringV(buf, IM_ARRAYSIZE(buf), fmt, args); va_end(args); const ImVec2 text_size = CalcTextSize(label); @@ -2569,7 +2638,6 @@ static bool CloseWindowButton(bool* open) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); const ImGuiID id = window->GetID("##CLOSE"); @@ -2605,7 +2673,8 @@ return; g.LogEnabled = true; g.LogFile = stdout; - g.LogAutoExpandMaxDepth = max_depth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; } void LogToFile(int max_depth, const char* filename) @@ -2613,10 +2682,12 @@ ImGuiState& g = GImGui; if (g.LogEnabled) return; - IM_ASSERT(filename); + if (!filename) + filename = g.IO.LogFilename; g.LogEnabled = true; g.LogFile = fopen(filename, "at"); - g.LogAutoExpandMaxDepth = max_depth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; } void LogToClipboard(int max_depth) @@ -2626,7 +2697,8 @@ return; g.LogEnabled = true; g.LogFile = NULL; - g.LogAutoExpandMaxDepth = max_depth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; } void LogButtons() @@ -2748,12 +2820,12 @@ va_list args; va_start(args, fmt); const char* text_begin = buf; - const char* text_end = text_begin + ImFormatStringV(buf, ARRAYSIZE(buf), fmt, args); + const char* text_end = text_begin + ImFormatStringV(buf, IM_ARRAYSIZE(buf), fmt, args); va_end(args); const float line_height = window->FontSize(); const ImVec2 text_size = CalcTextSize(text_begin, text_end); - const ImGuiAabb bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(line_height + (text_size.x ? (g.Style.FramePadding.x*2) : 0.0f),0) + text_size); // Empty text doesn't add padding + const ImGuiAabb bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(line_height + (text_size.x > 0.0f ? (g.Style.FramePadding.x*2) : 0.0f),0) + text_size); // Empty text doesn't add padding ItemSize(bb); if (ClipAdvance(bb)) @@ -2767,14 +2839,10 @@ bool TreeNode(const char* str_id, const char* fmt, ...) { - ImGuiState& g = GImGui; - ImGuiWindow* window = GetCurrentWindow(); - ImGuiStorage* tree = window->DC.StateStorage; - static char buf[1024]; va_list args; va_start(args, fmt); - ImFormatStringV(buf, ARRAYSIZE(buf), fmt, args); + ImFormatStringV(buf, IM_ARRAYSIZE(buf), fmt, args); va_end(args); if (!str_id || !str_id[0]) @@ -2792,14 +2860,10 @@ bool TreeNode(const void* ptr_id, const char* fmt, ...) { - ImGuiState& g = GImGui; - ImGuiWindow* window = GetCurrentWindow(); - ImGuiStorage* tree = window->DC.StateStorage; - static char buf[1024]; va_list args; va_start(args, fmt); - ImFormatStringV(buf, ARRAYSIZE(buf), fmt, args); + ImFormatStringV(buf, IM_ARRAYSIZE(buf), fmt, args); va_end(args); if (!ptr_id) @@ -2959,8 +3023,8 @@ if (v_min * v_max < 0.0f) { // Different sign - const float linear_dist_min_to_0 = powf(abs(0.0f - v_min), 1.0f/power); - const float linear_dist_max_to_0 = powf(abs(v_max - 0.0f), 1.0f/power); + const float linear_dist_min_to_0 = powf(fabsf(0.0f - v_min), 1.0f/power); + const float linear_dist_max_to_0 = powf(fabsf(v_max - 0.0f), 1.0f/power); linear_zero_pos = linear_dist_min_to_0 / (linear_dist_min_to_0+linear_dist_max_to_0); } else @@ -2992,12 +3056,12 @@ if (start_text_input || (g.ActiveId == id && id == g.SliderAsInputTextId)) { char text_buf[64]; - ImFormatString(text_buf, ARRAYSIZE(text_buf), "%.*f", decimal_precision, *v); + ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "%.*f", decimal_precision, *v); g.ActiveId = g.SliderAsInputTextId; g.HoveredId = 0; window->FocusItemUnregister(); // Our replacement slider will override the focus ID (that we needed to declare previously to allow for a TAB focus to happen before we got selected) - value_changed = ImGui::InputText(label, text_buf, ARRAYSIZE(text_buf), ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_AlignCenter); + value_changed = ImGui::InputText(label, text_buf, IM_ARRAYSIZE(text_buf), ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_AlignCenter); if (g.SliderAsInputTextId == 0) { // First frame @@ -3030,7 +3094,7 @@ if (!is_unbound) { const float normalized_pos = ImClamp((g.IO.MousePos.x - slider_effective_x1) / slider_effective_w, 0.0f, 1.0f); - + // Linear slider //float new_value = ImLerp(v_min, v_max, normalized_pos); @@ -3046,9 +3110,11 @@ else { // Positive: rescale to the positive range before powering - float a = normalized_pos; - if (abs(linear_zero_pos - 1.0f) > 1.e-6) - a = (a - linear_zero_pos) / (1.0f - linear_zero_pos); + float a; + if (fabsf(linear_zero_pos - 1.0f) > 1.e-6) + a = (normalized_pos - linear_zero_pos) / (1.0f - linear_zero_pos); + else + a = normalized_pos; a = powf(a, power); new_value = ImLerp(ImMax(v_min,0.0f), v_max, a); } @@ -3105,7 +3171,7 @@ } char value_buf[64]; - ImFormatString(value_buf, ARRAYSIZE(value_buf), display_format, *v); + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); RenderText(ImVec2(slider_bb.GetCenter().x-CalcTextSize(value_buf).x*0.5f, frame_bb.Min.y + style.FramePadding.y), value_buf); RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, slider_bb.Min.y), label); @@ -3131,6 +3197,38 @@ return changed; } +bool SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* display_format, float power) +{ + ImGuiState& g = GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->Collapsed) + return false; + + const ImGuiStyle& style = g.Style; + + bool value_changed = false; + ImGui::PushID(label); + + const int components = 2; + const float w_full = window->DC.ItemWidth.back(); + const float w_item_one = ImMax(1.0f, (float)(int)((w_full - (style.FramePadding.x*2.0f+style.ItemInnerSpacing.x)*(components-1)) / (float)components)); + const float w_item_last = ImMax(1.0f, (float)(int)(w_full - (w_item_one+style.FramePadding.x*2.0f+style.ItemInnerSpacing.x)*(components-1))); + + ImGui::PushItemWidth(w_item_one); + value_changed |= ImGui::SliderFloat("##X", &v[0], v_min, v_max, display_format, power); + ImGui::SameLine(0, 0); + ImGui::PopItemWidth(); + ImGui::PushItemWidth(w_item_last); + value_changed |= ImGui::SliderFloat("##Y", &v[1], v_min, v_max, display_format, power); + ImGui::SameLine(0, 0); + ImGui::PopItemWidth(); + + ImGui::TextUnformatted(label, FindTextDisplayEnd(label)); + + ImGui::PopID(); + return value_changed; +} + bool SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* display_format, float power) { ImGuiState& g = GImGui; @@ -3139,10 +3237,8 @@ return false; const ImGuiStyle& style = g.Style; - const ImVec2 text_size = CalcTextSize(label); bool value_changed = false; - ImGui::PushID(label); const int components = 3; @@ -3165,7 +3261,6 @@ ImGui::TextUnformatted(label, FindTextDisplayEnd(label)); ImGui::PopID(); - return value_changed; } @@ -3184,7 +3279,6 @@ return; const ImGuiStyle& style = g.Style; - const ImGuiID id = window->GetID(label); const ImVec2 text_size = CalcTextSize(label); if (graph_size.x == 0) @@ -3302,13 +3396,12 @@ const ImGuiAabb text_bb(window->DC.CursorPos + ImVec2(0,style.FramePadding.y), window->DC.CursorPos + ImVec2(0,style.FramePadding.y) + text_size); ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight())); + const ImGuiAabb total_bb(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max)); - if (ClipAdvance(check_bb)) + if (ClipAdvance(total_bb)) return; - RenderFrame(check_bb.Min, check_bb.Max, window->Color(ImGuiCol_FrameBg)); - - const bool hovered = (g.HoveredWindow == window) && (g.HoveredId == 0) && IsMouseHoveringBox(check_bb); + const bool hovered = (g.HoveredWindow == window) && (g.HoveredId == 0) && IsMouseHoveringBox(total_bb); const bool pressed = hovered && g.IO.MouseClicked[0]; if (hovered) g.HoveredId = id; @@ -3318,6 +3411,7 @@ g.ActiveId = 0; // Clear focus } + RenderFrame(check_bb.Min, check_bb.Max, window->Color(hovered ? ImGuiCol_CheckHovered : ImGuiCol_FrameBg)); if (*v) { window->DrawList->AddRectFilled(check_bb.Min+ImVec2(4,4), check_bb.Max-ImVec2(4,4), window->Color(ImGuiCol_CheckActive)); @@ -3356,8 +3450,9 @@ const ImGuiAabb text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + text_size); ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight())); + const ImGuiAabb total_bb(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max)); - if (ClipAdvance(check_bb)) + if (ClipAdvance(total_bb)) return false; ImVec2 center = check_bb.GetCenter(); @@ -3365,12 +3460,12 @@ center.y = (float)(int)center.y + 0.5f; const float radius = check_bb.GetHeight() * 0.5f; - const bool hovered = (g.HoveredWindow == window) && (g.HoveredId == 0) && IsMouseHoveringBox(check_bb); + const bool hovered = (g.HoveredWindow == window) && (g.HoveredId == 0) && IsMouseHoveringBox(total_bb); const bool pressed = hovered && g.IO.MouseClicked[0]; if (hovered) g.HoveredId = id; - window->DrawList->AddCircleFilled(center, radius, window->Color(ImGuiCol_FrameBg), 16); + window->DrawList->AddCircleFilled(center, radius, window->Color(hovered ? ImGuiCol_CheckHovered : ImGuiCol_FrameBg), 16); if (active) window->DrawList->AddCircleFilled(center, radius-2, window->Color(ImGuiCol_CheckActive), 16); @@ -3400,7 +3495,7 @@ // Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, ASCII, fixed-width font) int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return (int)strlen(obj->Text); } char STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return (char)obj->Text[idx]; } -float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { return obj->Font->CalcTextSize(obj->FontSize, 0, &obj->Text[char_idx], &obj->Text[char_idx]+1, NULL).x; } +float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { (void)line_start_idx; return obj->Font->CalcTextSize(obj->FontSize, 0, &obj->Text[char_idx], &obj->Text[char_idx]+1, NULL).x; } char STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : (char)key; } char STB_TEXTEDIT_NEWLINE = '\n'; void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx) @@ -3421,17 +3516,17 @@ #define STB_TEXTEDIT_IS_SPACE(c) (is_white(c) || is_separator(c)) void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int idx, int n) { char* dst = obj->Text+idx; const char* src = obj->Text+idx+n; while (char c = *src++) *dst++ = c; *dst = '\0'; } -bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int idx, const char* new_text, int new_text_size) +bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int idx, const char* new_text, int new_text_len) { - char* buf_end = obj->Text + obj->MaxLength; - int text_size = strlen(obj->Text); + char* buf_end = obj->Text + obj->BufSize; + const int text_len = (int)strlen(obj->Text); - if (new_text_size > buf_end - (obj->Text + text_size + 1)) + if (new_text_len > buf_end - (obj->Text + text_len + 1)) return false; - memmove(obj->Text + idx + new_text_size, obj->Text + idx, text_size - idx); - memcpy(obj->Text + idx, new_text, new_text_size); - obj->Text[text_size + new_text_size] = 0; + memmove(obj->Text + idx + new_text_len, obj->Text + idx, text_len - idx); + memcpy(obj->Text + idx, new_text, new_text_len); + obj->Text[text_len + new_text_len] = 0; return true; } @@ -3513,7 +3608,7 @@ const float clip_end = (text_end[0] != '\0' && text_end > text_start) ? symbol_w : 0.0f; // Draw text - ImGui::RenderText(pos+ImVec2(clip_begin,0), text_start+(clip_begin?1:0), text_end-(clip_end?1:0), false);//, &text_params_with_clipping); + ImGui::RenderText(pos+ImVec2(clip_begin,0), text_start+(clip_begin>0.0f?1:0), text_end-(clip_end>0.0f?1:0), false);//, &text_params_with_clipping); // Draw the clip symbol const char s[2] = {symbol_c,'\0'}; @@ -3540,35 +3635,34 @@ ImGui::PushID(label); const float button_sz = window->FontSize(); - if (step) + if (step > 0.0f) ImGui::PushItemWidth(ImMax(1.0f, window->DC.ItemWidth.back() - (button_sz+g.Style.FramePadding.x*2.0f+g.Style.ItemInnerSpacing.x)*2)); char buf[64]; if (decimal_precision < 0) - ImFormatString(buf, ARRAYSIZE(buf), "%f", *v); // Ideally we'd have a minimum decimal precision of 1 to visually denote that it is a float, while hiding non-significant digits? + ImFormatString(buf, IM_ARRAYSIZE(buf), "%f", *v); // Ideally we'd have a minimum decimal precision of 1 to visually denote that it is a float, while hiding non-significant digits? else - ImFormatString(buf, ARRAYSIZE(buf), "%.*f", decimal_precision, *v); + ImFormatString(buf, IM_ARRAYSIZE(buf), "%.*f", decimal_precision, *v); bool value_changed = false; - if (ImGui::InputText("", buf, ARRAYSIZE(buf), ImGuiInputTextFlags_CharsDecimal|ImGuiInputTextFlags_AlignCenter|ImGuiInputTextFlags_AutoSelectAll)) + if (ImGui::InputText("", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsDecimal|ImGuiInputTextFlags_AlignCenter|ImGuiInputTextFlags_AutoSelectAll)) { ApplyNumericalTextInput(buf, v); value_changed = true; } - if (step) - ImGui::PopItemWidth(); - if (step) + if (step > 0.0f) { + ImGui::PopItemWidth(); ImGui::SameLine(0, 0); if (ImGui::Button("-", ImVec2(button_sz,button_sz), true)) { - *v -= g.IO.KeyCtrl && step_fast ? step_fast : step; + *v -= g.IO.KeyCtrl && step_fast > 0.0f ? step_fast : step; value_changed = true; } ImGui::SameLine(0, (int)g.Style.ItemInnerSpacing.x); if (ImGui::Button("+", ImVec2(button_sz,button_sz), true)) { - *v += g.IO.KeyCtrl && step_fast ? step_fast : step; + *v += g.IO.KeyCtrl && step_fast > 0.0f ? step_fast : step; value_changed = true; } } @@ -3655,7 +3749,7 @@ bool cancel_edit = false; if (g.ActiveId == id) { - edit_state.MaxLength = buf_size < ARRAYSIZE(edit_state.Text) ? buf_size : ARRAYSIZE(edit_state.Text); + edit_state.BufSize = buf_size < IM_ARRAYSIZE(edit_state.Text) ? buf_size : IM_ARRAYSIZE(edit_state.Text); edit_state.Font = window->Font(); edit_state.FontSize = window->FontSize(); @@ -3720,7 +3814,7 @@ if (const char* clipboard = g.IO.GetClipboardTextFn()) { // Remove new-line from pasted buffer - int clipboard_len = strlen(clipboard); + size_t clipboard_len = strlen(clipboard); char* clipboard_filtered = (char*)malloc(clipboard_len+1); int clipboard_filtered_len = 0; for (int i = 0; clipboard[i]; i++) @@ -3738,7 +3832,7 @@ else if (g.IO.InputCharacters[0]) { // Text input - for (int n = 0; n < ARRAYSIZE(g.IO.InputCharacters) && g.IO.InputCharacters[n]; n++) + for (int n = 0; n < IM_ARRAYSIZE(g.IO.InputCharacters) && g.IO.InputCharacters[n]; n++) { const char c = g.IO.InputCharacters[n]; if (c) @@ -3815,6 +3909,38 @@ return value_changed; } +bool InputFloat2(const char* label, float v[2], int decimal_precision) +{ + ImGuiState& g = GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->Collapsed) + return false; + + const ImGuiStyle& style = g.Style; + + bool value_changed = false; + ImGui::PushID(label); + + const int components = 2; + const float w_full = window->DC.ItemWidth.back(); + const float w_item_one = ImMax(1.0f, (float)(int)((w_full - (style.FramePadding.x*2.0f+style.ItemInnerSpacing.x) * (components-1)) / (float)components)); + const float w_item_last = ImMax(1.0f, (float)(int)(w_full - (w_item_one+style.FramePadding.x*2.0f+style.ItemInnerSpacing.x) * (components-1))); + + ImGui::PushItemWidth(w_item_one); + value_changed |= ImGui::InputFloat("##X", &v[0], 0, 0, decimal_precision); + ImGui::SameLine(0, 0); + ImGui::PopItemWidth(); + ImGui::PushItemWidth(w_item_last); + value_changed |= ImGui::InputFloat("##Y", &v[1], 0, 0, decimal_precision); + ImGui::SameLine(0, 0); + ImGui::PopItemWidth(); + + ImGui::TextUnformatted(label, FindTextDisplayEnd(label)); + + ImGui::PopID(); + return value_changed; +} + bool InputFloat3(const char* label, float v[3], int decimal_precision) { ImGuiState& g = GImGui; @@ -3823,10 +3949,8 @@ return false; const ImGuiStyle& style = g.Style; - const ImVec2 text_size = CalcTextSize(label); bool value_changed = false; - ImGui::PushID(label); const int components = 3; @@ -3849,7 +3973,6 @@ ImGui::TextUnformatted(label, FindTextDisplayEnd(label)); ImGui::PopID(); - return value_changed; } @@ -3958,6 +4081,7 @@ ImGuiWindowFlags flags = ImGuiWindowFlags_ComboBox | ((window->Flags & ImGuiWindowFlags_ShowBorders) ? ImGuiWindowFlags_ShowBorders : 0); ImGui::BeginChild("#ComboBox", popup_aabb.GetSize(), false, flags); ImGuiWindow* child_window = GetCurrentWindow(); + ImGui::Spacing(); bool combo_item_active = false; combo_item_active |= (g.ActiveId == child_window->GetID("#SCROLLY")); @@ -4074,8 +4198,6 @@ const float w_full = window->DC.ItemWidth.back(); const float square_sz = (window->FontSize() + style.FramePadding.x * 2.0f); - const ImVec2 text_size = CalcTextSize(label); - ImGuiColorEditMode edit_mode = window->DC.ColorEditMode; if (edit_mode == ImGuiColorEditMode_UserSelect) edit_mode = g.ColorEditModeStorage.GetInt(id, 0) % 3; @@ -4142,7 +4264,7 @@ else sprintf(buf, "#%02X%02X%02X", ix, iy, iz); ImGui::PushItemWidth(w_slider_all - g.Style.ItemInnerSpacing.x); - value_changed |= ImGui::InputText("##Text", buf, ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal); + value_changed |= ImGui::InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal); ImGui::PopItemWidth(); char* p = buf; while (*p == '#' || *p == ' ' || *p == '\t') @@ -4197,15 +4319,12 @@ void ColorEditMode(ImGuiColorEditMode mode) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); - window->DC.ColorEditMode = mode; } void Separator() { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->Collapsed) return; @@ -4216,7 +4335,7 @@ const ImGuiAabb bb(ImVec2(window->Pos.x, window->DC.CursorPos.y), ImVec2(window->Pos.x + window->Size.x, window->DC.CursorPos.y)); ItemSize(ImVec2(0.0f, bb.GetSize().y)); // NB: we don't provide our width so that it doesn't get feed back into AutoFit - if (ClipAdvance(bb, true)) + if (ClipAdvance(bb)) { if (window->DC.ColumnsCount > 1) ImGui::PushColumnClipRect(); @@ -4231,7 +4350,6 @@ void Spacing() { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->Collapsed) return; @@ -4301,9 +4419,10 @@ return IsClipped(ImGuiAabb(window->DC.CursorPos, window->DC.CursorPos + item_size)); } -static bool ClipAdvance(const ImGuiAabb& bb, bool skip_columns) +static bool ClipAdvance(const ImGuiAabb& bb) { ImGuiWindow* window = GetCurrentWindow(); + window->DC.LastItemAabb = bb; if (ImGui::IsClipped(bb)) { window->DC.LastItemHovered = false; @@ -4424,7 +4543,8 @@ // Draw before resize so our items positioning are in sync with the line const ImU32 col = window->Color(held ? ImGuiCol_ColumnActive : hovered ? ImGuiCol_ColumnHovered : ImGuiCol_Column); - window->DrawList->AddLine(ImVec2(x, y1), ImVec2(x, y2), col); + const float xi = (float)(int)x; + window->DrawList->AddLine(ImVec2(xi, y1), ImVec2(xi, y2), col); if (held) { @@ -4538,58 +4658,62 @@ { commands.resize(0); vtx_buffer.resize(0); - clip_rect_buffer.resize(0); - vtx_write_ = NULL; - clip_rect_stack_.resize(0); + vtx_write = NULL; + clip_rect_stack.resize(0); } void ImDrawList::PushClipRect(const ImVec4& clip_rect) { - commands.push_back(ImDrawCmd(ImDrawCmdType_PushClipRect)); - clip_rect_buffer.push_back(clip_rect); - clip_rect_stack_.push_back(clip_rect); + if (!commands.empty() && commands.back().vtx_count == 0) + { + // Reuse empty command because high-level clipping may have discarded the other vertices already + commands.back().clip_rect = clip_rect; + } + else + { + ImDrawCmd draw_cmd; + draw_cmd.vtx_count = 0; + draw_cmd.clip_rect = clip_rect; + commands.push_back(draw_cmd); + } + clip_rect_stack.push_back(clip_rect); } void ImDrawList::PopClipRect() { - if (!commands.empty() && commands.back().cmd_type == ImDrawCmdType_PushClipRect) + clip_rect_stack.pop_back(); + const ImVec4 clip_rect = clip_rect_stack.empty() ? ImVec4(-9999.0f,-9999.0f, +9999.0f, +9999.0f) : clip_rect_stack.back(); + if (!commands.empty() && commands.back().vtx_count == 0) { - // Discard push/pop combo because high-level clipping may have discarded the other draw commands already - commands.pop_back(); - clip_rect_buffer.pop_back(); + // Reuse empty command because high-level clipping may have discarded the other vertices already + commands.back().clip_rect = clip_rect; } else { - commands.push_back(ImDrawCmd(ImDrawCmdType_PopClipRect)); + ImDrawCmd draw_cmd; + draw_cmd.vtx_count = 0; + draw_cmd.clip_rect = clip_rect; + commands.push_back(draw_cmd); } - clip_rect_stack_.pop_back(); } -void ImDrawList::AddCommand(ImDrawCmdType cmd_type, int vtx_count) +void ImDrawList::ReserveVertices(unsigned int vtx_count) { - // Maximum value that can fit in our u16 vtx_count member - const int VTX_COUNT_MAX = (1<<16); - - // Merge commands if we can, turning them into less draw calls - ImDrawCmd* prev = commands.empty() ? NULL : &commands.back(); - if (vtx_count > 0 && prev && prev->cmd_type == (ImU32)cmd_type && prev->vtx_count + vtx_count < VTX_COUNT_MAX) - prev->vtx_count += vtx_count; - else - commands.push_back(ImDrawCmd(cmd_type, vtx_count)); - if (vtx_count > 0) { + ImDrawCmd& draw_cmd = commands.back(); + draw_cmd.vtx_count += vtx_count; vtx_buffer.resize(vtx_buffer.size() + vtx_count); - vtx_write_ = &vtx_buffer[vtx_buffer.size() - vtx_count]; + vtx_write = &vtx_buffer[vtx_buffer.size() - vtx_count]; } } void ImDrawList::AddVtx(const ImVec2& pos, ImU32 col) { - vtx_write_->pos = pos; - vtx_write_->col = col; - vtx_write_->uv = IMDRAW_TEX_UV_FOR_WHITE; - vtx_write_++; + vtx_write->pos = pos; + vtx_write->col = col; + vtx_write->uv = IMGUI_FONT_TEX_UV_FOR_WHITE; + vtx_write++; } void ImDrawList::AddVtxLine(const ImVec2& a, const ImVec2& b, ImU32 col) @@ -4611,7 +4735,7 @@ if ((col >> 24) == 0) return; - AddCommand(ImDrawCmdType_DrawTriangleList, 6); + ReserveVertices(6); AddVtxLine(a, b, col); } @@ -4621,9 +4745,9 @@ static bool circle_vtx_builds = false; if (!circle_vtx_builds) { - for (int i = 0; i < ARRAYSIZE(circle_vtx); i++) + for (int i = 0; i < IM_ARRAYSIZE(circle_vtx); i++) { - const float a = ((float)i / (float)ARRAYSIZE(circle_vtx)) * 2*PI; + const float a = ((float)i / (float)IM_ARRAYSIZE(circle_vtx)) * 2*PI; circle_vtx[i].x = cos(a + PI); circle_vtx[i].y = sin(a + PI); } @@ -4632,19 +4756,19 @@ if (tris) { - AddCommand(ImDrawCmdType_DrawTriangleList, (a_max-a_min) * 3); + ReserveVertices((a_max-a_min) * 3); for (int a = a_min; a < a_max; a++) { - AddVtx(center + circle_vtx[a % ARRAYSIZE(circle_vtx)] * rad, col); - AddVtx(center + circle_vtx[(a+1) % ARRAYSIZE(circle_vtx)] * rad, col); + AddVtx(center + circle_vtx[a % IM_ARRAYSIZE(circle_vtx)] * rad, col); + AddVtx(center + circle_vtx[(a+1) % IM_ARRAYSIZE(circle_vtx)] * rad, col); AddVtx(center + third_point_offset, col); } } else { - AddCommand(ImDrawCmdType_DrawTriangleList, (a_max-a_min) * 6); + ReserveVertices((a_max-a_min) * 6); for (int a = a_min; a < a_max; a++) - AddVtxLine(center + circle_vtx[a % ARRAYSIZE(circle_vtx)] * rad, center + circle_vtx[(a+1) % ARRAYSIZE(circle_vtx)] * rad, col); + AddVtxLine(center + circle_vtx[a % IM_ARRAYSIZE(circle_vtx)] * rad, center + circle_vtx[(a+1) % IM_ARRAYSIZE(circle_vtx)] * rad, col); } } @@ -4653,14 +4777,14 @@ if ((col >> 24) == 0) return; - //const float r = ImMin(rounding, ImMin(abs(b.x-a.x), abs(b.y-a.y))*0.5f); + //const float r = ImMin(rounding, ImMin(fabsf(b.x-a.x), fabsf(b.y-a.y))*0.5f); float r = rounding; - r = ImMin(r, abs(b.x-a.x) * ( ((rounding_corners&(1|2))==(1|2)) || ((rounding_corners&(4|8))==(4|8)) ? 0.5f : 1.0f )); - r = ImMin(r, abs(b.y-a.y) * ( ((rounding_corners&(1|8))==(1|8)) || ((rounding_corners&(2|4))==(2|4)) ? 0.5f : 1.0f )); + r = ImMin(r, fabsf(b.x-a.x) * ( ((rounding_corners&(1|2))==(1|2)) || ((rounding_corners&(4|8))==(4|8)) ? 0.5f : 1.0f )); + r = ImMin(r, fabsf(b.y-a.y) * ( ((rounding_corners&(1|8))==(1|8)) || ((rounding_corners&(2|4))==(2|4)) ? 0.5f : 1.0f )); if (r == 0.0f || rounding_corners == 0) { - AddCommand(ImDrawCmdType_DrawTriangleList, 4*6); + ReserveVertices(4*6); AddVtxLine(ImVec2(a.x,a.y), ImVec2(b.x,a.y), col); AddVtxLine(ImVec2(b.x,a.y), ImVec2(b.x,b.y), col); AddVtxLine(ImVec2(b.x,b.y), ImVec2(a.x,b.y), col); @@ -4668,7 +4792,7 @@ } else { - AddCommand(ImDrawCmdType_DrawTriangleList, 4*6); + ReserveVertices(4*6); AddVtxLine(ImVec2(a.x + ((rounding_corners & 1)?r:0), a.y), ImVec2(b.x - ((rounding_corners & 2)?r:0), a.y), col); AddVtxLine(ImVec2(b.x, a.y + ((rounding_corners & 2)?r:0)), ImVec2(b.x, b.y - ((rounding_corners & 4)?r:0)), col); AddVtxLine(ImVec2(b.x - ((rounding_corners & 4)?r:0), b.y), ImVec2(a.x + ((rounding_corners & 8)?r:0), b.y), col); @@ -4686,15 +4810,15 @@ if ((col >> 24) == 0) return; - //const float r = ImMin(rounding, ImMin(abs(b.x-a.x), abs(b.y-a.y))*0.5f); + //const float r = ImMin(rounding, ImMin(fabsf(b.x-a.x), fabsf(b.y-a.y))*0.5f); float r = rounding; - r = ImMin(r, abs(b.x-a.x) * ( ((rounding_corners&(1|2))==(1|2)) || ((rounding_corners&(4|8))==(4|8)) ? 0.5f : 1.0f )); - r = ImMin(r, abs(b.y-a.y) * ( ((rounding_corners&(1|8))==(1|8)) || ((rounding_corners&(2|4))==(2|4)) ? 0.5f : 1.0f )); + r = ImMin(r, fabsf(b.x-a.x) * ( ((rounding_corners&(1|2))==(1|2)) || ((rounding_corners&(4|8))==(4|8)) ? 0.5f : 1.0f )); + r = ImMin(r, fabsf(b.y-a.y) * ( ((rounding_corners&(1|8))==(1|8)) || ((rounding_corners&(2|4))==(2|4)) ? 0.5f : 1.0f )); if (r == 0.0f || rounding_corners == 0) { // Use triangle so we can merge more draw calls together (at the cost of extra vertices) - AddCommand(ImDrawCmdType_DrawTriangleList, 6); + ReserveVertices(6); AddVtx(ImVec2(a.x,a.y), col); AddVtx(ImVec2(b.x,a.y), col); AddVtx(ImVec2(b.x,b.y), col); @@ -4704,7 +4828,7 @@ } else { - AddCommand(ImDrawCmdType_DrawTriangleList, 6+6*2); + ReserveVertices(6+6*2); AddVtx(ImVec2(a.x+r,a.y), col); AddVtx(ImVec2(b.x-r,a.y), col); AddVtx(ImVec2(b.x-r,b.y), col); @@ -4742,7 +4866,7 @@ if ((col >> 24) == 0) return; - AddCommand(ImDrawCmdType_DrawTriangleList, 3); + ReserveVertices(3); AddVtx(a, col); AddVtx(b, col); AddVtx(c, col); @@ -4753,7 +4877,7 @@ if ((col >> 24) == 0) return; - AddCommand(ImDrawCmdType_DrawTriangleList, num_segments*6); + ReserveVertices(num_segments*6); const float a_step = 2*PI/(float)num_segments; float a0 = 0.0f; for (int i = 0; i < num_segments; i++) @@ -4769,7 +4893,7 @@ if ((col >> 24) == 0) return; - AddCommand(ImDrawCmdType_DrawTriangleList, num_segments*3); + ReserveVertices(num_segments*3); const float a_step = 2*PI/(float)num_segments; float a0 = 0.0f; for (int i = 0; i < num_segments; i++) @@ -4790,17 +4914,19 @@ if (text_end == NULL) text_end = text_begin + strlen(text_begin); - int char_count = text_end - text_begin; - int vtx_count_max = char_count * 6; - int vtx_begin = vtx_buffer.size(); - AddCommand(ImDrawCmdType_DrawTriangleList, vtx_count_max); + // reserve vertices for worse case + const int char_count = (int)(text_end - text_begin); + const int vtx_count_max = char_count * 6; + const size_t vtx_begin = vtx_buffer.size(); + ReserveVertices(vtx_count_max); - font->RenderText(font_size, pos, col, clip_rect_stack_.back(), text_begin, text_end, vtx_write_); - vtx_buffer.resize(vtx_write_ - &vtx_buffer.front()); - int vtx_count = vtx_buffer.size() - vtx_begin; + font->RenderText(font_size, pos, col, clip_rect_stack.back(), text_begin, text_end, vtx_write); + // give unused vertices + vtx_buffer.resize(vtx_write - &vtx_buffer.front()); + const int vtx_count = (int)(vtx_buffer.size() - vtx_begin); commands.back().vtx_count -= (vtx_count_max - vtx_count); - vtx_write_ -= (vtx_count_max - vtx_count); + vtx_write -= (vtx_count_max - vtx_count); } //----------------------------------------------------------------------------- @@ -4849,7 +4975,7 @@ fclose(f); return false; } - if (fread(Data, 1, DataSize, f) != DataSize) + if ((int)fread(Data, 1, DataSize, f) != DataSize) { fclose(f); free(Data); @@ -4910,7 +5036,7 @@ void ImBitmapFont::BuildLookupTable() { ImU32 max_c = 0; - for (int i = 0; i != GlyphsCount; i++) + for (size_t i = 0; i != GlyphsCount; i++) if (max_c < Glyphs[i].Id) max_c = Glyphs[i].Id; @@ -4960,7 +5086,8 @@ if (const FntGlyph* glyph = FindGlyph((unsigned short)c)) { const float char_width = (glyph->XAdvance + Info->SpacingHoriz) * scale; - const float char_extend = (glyph->XOffset + glyph->Width * scale); + //const float char_extend = (glyph->XOffset + glyph->Width * scale); + if (line_width + char_width >= max_width) break; line_width += char_width; @@ -5002,10 +5129,10 @@ pos.x = (float)(int)pos.x + 0.5f; pos.y = (float)(int)pos.y + 0.5f; - ImVec2 text_size = ImVec2(0,0); - float line_width = 0.0f; const ImVec4 clip_rect = clip_rect_ref; + const float uv_offset = GImGui.IO.PixelCenterOffset; + float x = pos.x; float y = pos.y; for (const char* s = text_begin; s < text_end; s++) @@ -5021,7 +5148,7 @@ if (const FntGlyph* glyph = FindGlyph((unsigned short)c)) { const float char_width = (glyph->XAdvance + Info->SpacingHoriz) * scale; - const float char_extend = (glyph->XOffset + glyph->Width * scale); + //const float char_extend = (glyph->XOffset + glyph->Width * scale); if (c != ' ' && c != '\n') { @@ -5042,10 +5169,10 @@ continue; } - const float s1 = (0.0f + glyph->X) * tex_scale_x; - const float t1 = (0.0f + glyph->Y) * tex_scale_y; - const float s2 = (0.0f + glyph->X + glyph->Width) * tex_scale_x; - const float t2 = (0.0f + glyph->Y + glyph->Height) * tex_scale_y; + const float s1 = (uv_offset + glyph->X) * tex_scale_x; + const float t1 = (uv_offset + glyph->Y) * tex_scale_y; + const float s2 = (uv_offset + glyph->X + glyph->Width) * tex_scale_x; + const float t2 = (uv_offset + glyph->Y + glyph->Height) * tex_scale_y; out_vertices[0].pos = ImVec2(x1, y1); out_vertices[0].uv = ImVec2(s1, t1); @@ -5080,6 +5207,81 @@ } //----------------------------------------------------------------------------- +// PLATFORM DEPENDANT HELPERS +//----------------------------------------------------------------------------- + +#if defined(_MSC_VER) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS) + +#define WIN32_LEAN_AND_MEAN +#include + +// Win32 API clipboard implementation +static const char* GetClipboardTextFn_DefaultImpl() +{ + static char* buf_local = NULL; + if (buf_local) + { + free(buf_local); + buf_local = NULL; + } + if (!OpenClipboard(NULL)) + return NULL; + HANDLE buf_handle = GetClipboardData(CF_TEXT); + if (buf_handle == NULL) + return NULL; + if (char* buf_global = (char*)GlobalLock(buf_handle)) + buf_local = strdup(buf_global); + GlobalUnlock(buf_handle); + CloseClipboard(); + return buf_local; +} + +// Win32 API clipboard implementation +static void SetClipboardTextFn_DefaultImpl(const char* text, const char* text_end) +{ + if (!OpenClipboard(NULL)) + return; + if (!text_end) + text_end = text + strlen(text); + const int buf_length = (text_end - text) + 1; + HGLOBAL buf_handle = GlobalAlloc(GMEM_MOVEABLE, buf_length * sizeof(char)); + if (buf_handle == NULL) + return; + char* buf_global = (char *)GlobalLock(buf_handle); + memcpy(buf_global, text, text_end - text); + buf_global[text_end - text] = 0; + GlobalUnlock(buf_handle); + EmptyClipboard(); + SetClipboardData(CF_TEXT, buf_handle); + CloseClipboard(); +} + +#else + +// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers +static const char* GetClipboardTextFn_DefaultImpl() +{ + return GImGui.PrivateClipboard; +} + +// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers +static void SetClipboardTextFn_DefaultImpl(const char* text, const char* text_end) +{ + if (GImGui.PrivateClipboard) + { + free(GImGui.PrivateClipboard); + GImGui.PrivateClipboard = NULL; + } + if (!text_end) + text_end = text + strlen(text); + GImGui.PrivateClipboard = (char*)malloc(text_end - text + 1); + memcpy(GImGui.PrivateClipboard, text, text_end - text); + GImGui.PrivateClipboard[text_end - text] = 0; +} + +#endif + +//----------------------------------------------------------------------------- // HELP //----------------------------------------------------------------------------- @@ -5135,11 +5337,17 @@ ImGui::SameLine(); ImGui::RadioButton("HEX", &edit_mode, ImGuiColorEditMode_HEX); + static ImGuiTextFilter filter; + filter.Draw("Filter colors", 200); + ImGui::ColorEditMode(edit_mode); - for (size_t i = 0; i < ImGuiCol_COUNT; i++) + for (int i = 0; i < ImGuiCol_COUNT; i++) { + const char* name = GetStyleColorName(i); + if (!filter.PassFilter(name)) + continue; ImGui::PushID(i); - ImGui::ColorEdit4(GetStyleColorName(i), (float*)&style.Colors[i], true); + ImGui::ColorEdit4(name, (float*)&style.Colors[i], true); if (memcmp(&style.Colors[i], (ref ? &ref->Colors[i] : &def.Colors[i]), sizeof(ImVec4)) != 0) { ImGui::SameLine(); if (ImGui::Button("Revert")) style.Colors[i] = ref ? ref->Colors[i] : def.Colors[i]; @@ -5251,15 +5459,18 @@ const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK" }; static int item2 = -1; - ImGui::Combo("combo scroll", &item2, items, ARRAYSIZE(items)); + ImGui::Combo("combo scroll", &item2, items, IM_ARRAYSIZE(items)); static char str0[128] = "Hello, world!"; static int i0=123; static float f0=0.001f; - ImGui::InputText("string", str0, ARRAYSIZE(str0)); + ImGui::InputText("string", str0, IM_ARRAYSIZE(str0)); ImGui::InputInt("input int", &i0); ImGui::InputFloat("input float", &f0, 0.01f, 1.0f); + //static float vec2b[3] = { 0.10f, 0.20f }; + //ImGui::InputFloat2("input float2", vec2b); + static float vec3b[3] = { 0.10f, 0.20f, 0.30f }; ImGui::InputFloat3("input float3", vec3b); @@ -5279,6 +5490,9 @@ static float angle = 0.0f; ImGui::SliderAngle("angle", &angle); + //static float vec2a[3] = { 0.10f, 0.20f }; + //ImGui::SliderFloat2("slider float2", vec2a, 0.0f, 1.0f); + static float vec3a[3] = { 0.10f, 0.20f, 0.30f }; ImGui::SliderFloat3("slider float3", vec3a, 0.0f, 1.0f); @@ -5293,7 +5507,7 @@ if (ImGui::CollapsingHeader("Graphs widgets")) { static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; - ImGui::PlotLines("Frame Times", arr, ARRAYSIZE(arr)); + ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr)); static bool pause; static ImVector values; if (values.empty()) { values.resize(100); memset(&values.front(), 0, values.size()*sizeof(float)); } @@ -5311,10 +5525,10 @@ phase += 0.10f*values_offset; } } - ImGui::PlotLines("Frame Times", &values.front(), values.size(), values_offset, "avg 0.0", -1.0f, 1.0f, ImVec2(0,70)); + ImGui::PlotLines("Frame Times", &values.front(), (int)values.size(), values_offset, "avg 0.0", -1.0f, 1.0f, ImVec2(0,70)); ImGui::SameLine(); ImGui::Checkbox("pause", &pause); - ImGui::PlotHistogram("Histogram", arr, ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0,70)); + ImGui::PlotHistogram("Histogram", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0,70)); } if (ImGui::CollapsingHeader("Widgets on same line")) @@ -5404,7 +5618,7 @@ for (int i = 0; i < 100; i++) { char buf[32]; - ImFormatString(buf, ARRAYSIZE(buf), "%08x", i*5731); + ImFormatString(buf, IM_ARRAYSIZE(buf), "%08x", i*5731); ImGui::Button(buf); ImGui::NextColumn(); } @@ -5459,7 +5673,7 @@ static float foo = 1.0f; ImGui::InputFloat("red", &foo, 0.05f, 0, 3); ImGui::NextColumn(); static float bar = 1.0f; - ImGui::InputFloat("blue", &foo, 0.05f, 0, 3); ImGui::NextColumn(); + ImGui::InputFloat("blue", &bar, 0.05f, 0, 3); ImGui::NextColumn(); ImGui::Columns(1); ImGui::Separator(); @@ -5503,7 +5717,7 @@ static ImGuiTextFilter filter; filter.Draw(); const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" }; - for (size_t i = 0; i < ARRAYSIZE(lines); i++) + for (size_t i = 0; i < IM_ARRAYSIZE(lines); i++) if (filter.PassFilter(lines[i])) ImGui::BulletText("%s", lines[i]); } @@ -5519,7 +5733,7 @@ if (ImGui::Button("Add 1000 lines")) { for (size_t i = 0; i < 1000; i++) - log.Append("%i The quick brown fox jumps over the lazy dog\n", lines+i); + log.append("%i The quick brown fox jumps over the lazy dog\n", lines+i); lines += 1000; } ImGui::BeginChild("Log"); diff --git a/imgui.h b/imgui.h index 3c75057..b006330 100644 --- a/imgui.h +++ b/imgui.h @@ -29,8 +29,8 @@ typedef int ImGuiCol; // enum ImGuiCol_ typedef int ImGuiKey; // enum ImGuiKey_ typedef int ImGuiColorEditMode; // enum ImGuiColorEditMode_ -typedef ImU32 ImGuiWindowFlags; // enum ImGuiWindowFlags_ -typedef ImU32 ImGuiInputTextFlags; // enum ImGuiInputTextFlags_ +typedef int ImGuiWindowFlags; // enum ImGuiWindowFlags_ +typedef int ImGuiInputTextFlags; // enum ImGuiInputTextFlags_ typedef ImBitmapFont* ImFont; struct ImVec2 @@ -85,9 +85,9 @@ inline void clear() { if (_data) { _size = _capacity = 0; free(_data); _data = NULL; } } inline iterator begin() { return _data; } - inline const iterator begin() const { return _data; } + inline const_iterator begin() const { return _data; } inline iterator end() { return _data + _size; } - inline const iterator end() const { return _data + _size; } + inline const_iterator end() const { return _data + _size; } inline value_type& front() { return at(0); } inline const value_type& front() const { return at(0); } inline value_type& back() { IM_ASSERT(_size > 0); return at(_size-1); } @@ -143,8 +143,10 @@ void SetFontScale(float scale); void SetScrollPosHere(); void SetTreeStateStorage(ImGuiStorage* tree); + ImGuiStorage* GetTreeStateStorage(); void PushItemWidth(float item_width); void PopItemWidth(); + float GetItemWidth(); void PushAllowKeyboardFocus(bool v); void PopAllowKeyboardFocus(); void PushStyleColor(ImGuiCol idx, ImVec4 col); @@ -181,6 +183,7 @@ bool SmallButton(const char* label); bool CollapsingHeader(const char* label, const char* str_id = NULL, const bool display_frame = true, const bool default_open = false); bool SliderFloat(const char* label, float* v, float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); + bool SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); bool SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); bool SliderAngle(const char* label, float* v, float v_degrees_min = -360.0f, float v_degrees_max = +360.0f); // *v in radians bool SliderInt(const char* label, int* v, int v_min, int v_max, const char* display_format = "%.0f"); @@ -191,6 +194,7 @@ bool RadioButton(const char* label, bool active); bool RadioButton(const char* label, int* v, int v_button); bool InputFloat(const char* label, float* v, float step = 0.0f, float step_fast = 0.0f, int decimal_precision = -1); + bool InputFloat2(const char* label, float v[2], int decimal_precision = -1); bool InputFloat3(const char* label, float v[3], int decimal_precision = -1); bool InputInt(const char* label, int* v, int step = 1, int step_fast = 100); bool InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0); @@ -220,17 +224,21 @@ // Logging void LogButtons(); - void LogToTTY(int max_depth); - void LogToFile(int max_depth, const char* filename); - void LogToClipboard(int max_depth); + void LogToTTY(int max_depth = -1); + void LogToFile(int max_depth = -1, const char* filename = NULL); + void LogToClipboard(int max_depth = -1); // Utilities void SetTooltip(const char* fmt, ...); // set tooltip under mouse-cursor, typically use with ImGui::IsHovered(). (currently no contention handling, last call win) void SetNewWindowDefaultPos(ImVec2 pos); // set position of window that do bool IsHovered(); // was the last item active area hovered by mouse? + ImVec2 GetItemBoxMin(); // get bounding box of last item + ImVec2 GetItemBoxMax(); // get bounding box of last item bool IsClipped(ImVec2 item_size); // to perform coarse clipping on user's side (as an optimisation) bool IsKeyPressed(int key_index, bool repeat = true); // key_index into the keys_down[512] array, imgui doesn't know the semantic of each entry bool IsMouseClicked(int button, bool repeat = false); + bool IsMouseDoubleClicked(int button); + bool IsMouseHoveringBox(const ImVec2& box_min, const ImVec2& box_max); ImVec2 GetMousePos(); float GetTime(); int GetFrameCount(); @@ -302,6 +310,7 @@ ImGuiCol_ScrollbarGrabHovered, ImGuiCol_ScrollbarGrabActive, ImGuiCol_ComboBg, + ImGuiCol_CheckHovered, ImGuiCol_CheckActive, ImGuiCol_SliderGrab, ImGuiCol_SliderGrabActive, @@ -373,14 +382,21 @@ ImFont Font; // // Gets passed to text functions. Typedef ImFont to the type you want (ImBitmapFont* or your own font). float FontHeight; // // Default font height, must be the vertical distance between two lines of text, aka == CalcTextSize(" ").y bool FontAllowScaling; // = false // Set to allow scaling text with CTRL+Wheel. + float PixelCenterOffset; // = 0.5f // Set to 0.0f for DirectX <= 9, 0.5f for Direct3D >= 10 and OpenGL. - // Settings - Functions (fill once) - void (*RenderDrawListsFn)(ImDrawList** const draw_lists, int count); // Required - const char* (*GetClipboardTextFn)(); // Required for clipboard support - void (*SetClipboardTextFn)(const char* text, const char* text_end); // Required for clipboard support (nb- the string is *NOT* zero-terminated at 'text_end') + // Settings - Rendering function (REQUIRED) + // See example code if you are unsure of how to implement this. + void (*RenderDrawListsFn)(ImDrawList** const draw_lists, int count); + + // Settings - Clipboard Support + // Override to provide your clipboard handlers. + // On Windows architecture, defaults to use the native Win32 clipboard, otherwise default to use a ImGui private clipboard. + // NB- for SetClipboardTextFn, the string is *NOT* zero-terminated at 'text_end' + const char* (*GetClipboardTextFn)(); + void (*SetClipboardTextFn)(const char* text, const char* text_end); // Input - Fill before calling NewFrame() - ImVec2 MousePos; // Mouse position (set to -1,-1 if no mouse / on another screen, etc.) + ImVec2 MousePos; // Mouse position, in pixels (set to -1,-1 if no mouse / on another screen, etc.) bool MouseDown[2]; // Mouse buttons int MouseWheel; // Mouse wheel: -1,0,+1 bool KeyCtrl; // Keyboard modifier pressed: Control @@ -388,10 +404,13 @@ bool KeysDown[512]; // Keyboard keys that are pressed (in whatever order user naturally has access to keyboard data) char InputCharacters[16]; // List of characters input (translated by user from keypress+keyboard state). Fill using AddInputCharacter() helper. - // Output - Retrieve after calling NewFrame(), you can use them to discard inputs for the rest of your application + // Output - Retrieve after calling NewFrame(), you can use them to discard inputs or hide them from the rest of your application bool WantCaptureMouse; // ImGui is using your mouse input (= window is being hovered or widget is active). bool WantCaptureKeyboard; // imGui is using your keyboard input (= widget is active). + // Function + void AddInputCharacter(char c); // Helper to add a new character into InputCharacters[] + // [Internal] ImGui will maintain those fields for you ImVec2 MousePosPrev; ImVec2 MouseDelta; @@ -403,7 +422,6 @@ float KeysDownTime[512]; ImGuiIO(); - void AddInputCharacter(char c); // Helper to add a new character into InputCharacters[] }; //----------------------------------------------------------------------------- @@ -464,7 +482,7 @@ size_t size() const { return Buf.size()-1; } bool empty() { return Buf.empty(); } void clear() { Buf.clear(); Buf.push_back(0); } - void Append(const char* fmt, ...); + void append(const char* fmt, ...); }; // Helper: Key->value storage @@ -491,23 +509,14 @@ // Hold a series of drawing commands. The user provide a renderer for ImDrawList //----------------------------------------------------------------------------- -enum ImDrawCmdType -{ - ImDrawCmdType_DrawTriangleList, - ImDrawCmdType_PushClipRect, - ImDrawCmdType_PopClipRect, -}; - -// sizeof() == 4 struct ImDrawCmd { - ImDrawCmdType cmd_type : 16; - unsigned int vtx_count : 16; - ImDrawCmd(ImDrawCmdType _cmd_type = ImDrawCmdType_DrawTriangleList, unsigned int _vtx_count = 0) { cmd_type = _cmd_type; vtx_count = _vtx_count; } + unsigned int vtx_count; + ImVec4 clip_rect; }; -#ifndef IMDRAW_TEX_UV_FOR_WHITE -#define IMDRAW_TEX_UV_FOR_WHITE ImVec2(0,0) +#ifndef IMGUI_FONT_TEX_UV_FOR_WHITE +#define IMGUI_FONT_TEX_UV_FOR_WHITE ImVec2(0.f,0.f) #endif // sizeof() == 20 @@ -522,18 +531,20 @@ // User is responsible for providing a renderer for this in ImGuiIO::RenderDrawListFn struct ImDrawList { - ImVector commands; + // This is what you have to render + ImVector commands; // commands ImVector vtx_buffer; // each command consume ImDrawCmd::vtx_count of those - ImVector clip_rect_buffer; // each PushClipRect command consume 1 of those - ImVector clip_rect_stack_; // [internal] clip rect stack while building the command-list (so text command can perform clipping early on) - ImDrawVert* vtx_write_; // [internal] point within vtx_buffer after each add command. allow us to use less [] and .resize on the vector (often slow on windows/debug) + + // [Internal to ImGui] + ImVector clip_rect_stack; // [internal] clip rect stack while building the command-list (so text command can perform clipping early on) + ImDrawVert* vtx_write; // [internal] point within vtx_buffer after each add command (to avoid using the ImVector<> operators too much) ImDrawList() { Clear(); } void Clear(); void PushClipRect(const ImVec4& clip_rect); void PopClipRect(); - void AddCommand(ImDrawCmdType cmd_type, int vtx_count); + void ReserveVertices(unsigned int vtx_count); void AddVtx(const ImVec2& pos, ImU32 col); void AddVtxLine(const ImVec2& a, const ImVec2& b, ImU32 col); diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..94f4ef0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +## Visual Studio files +examples/directx9_example/Debug/* +examples/directx9_example/Release/* +examples/directx9_example/ipch/* +examples/opengl_example/Debug/* +examples/opengl_example/Release/* +examples/opengl_example/ipch/* +*.opensdf +*.sdf +*.suo +*.vcxproj.user + +## Ini files +imgui.ini diff --git a/README.md b/README.md index 65a9ad7..c3f7767 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@ ImGui ===== -ImGui is a bloat-free graphical user interface library for C/C++. It is portable, renderer agnostic and carries minimal amount of dependencies (only 3 files are needed). It is based on an "immediate" graphical user interface paradigm which allows you to build simple user interfaces with ease. +ImGui is a bloat-free graphical user interface library for C++. It outputs vertex buffers that you can render in your 3D-pipeline enabled application. It is portable, renderer agnostic and carries minimal amount of dependencies (only 3 files are needed). It is based on an "immediate" graphical user interface paradigm which allows you to build simple user interfaces with ease. -ImGui is designed to allow programmers to create "content creation" or "debug" tools (as opposed to tools for the average end-user). It favors simplicity and thus lacks certain features normally found in more high-level libraries, such as string localisation. +ImGui is designed to enable fast iteration and allow programmers to create "content creation" or "debug" tools (as opposed to tools for the average end-user). It favors simplicity and thus lacks certain features normally found in more high-level libraries, such as string localisation. -Usage example: +ImGui is particularly suited to integration in 3D applications, fullscreen applications, embedded applications, games, or any applications on consoles platforms where operating system features are non-standard. + +After ImGui is setup in your engine, you can use it like in this example: ![screenshot of sample code alongside its output with ImGui](/web/code_sample_01.png?raw=true) -ImGui output vertex buffer and simple command-list that you can render in application. Refer to the sample applications in the examples/ folder for instructions on how to integrate ImGui within your existing codebase. - +ImGui outputs vertex buffers and simple command-lists that you can render in your application. Because it doesn't know or touch graphics state directly, you can call ImGui commands anywhere in your code (e.g. in the middle of a running algorithm, or in the middle of your own rendering process). Refer to the sample applications in the examples/ folder for instructions on how to integrate ImGui with your existing codebase. Gallery ------- @@ -20,12 +21,22 @@ ![screenshot 3](/web/test_window_03.png?raw=true) ![screenshot 4](/web/test_window_04.png?raw=true) +References +---------- + +The Immediate Mode GUI paradigm may at first appear unusual to some users. This is mainly because "Retained Mode" GUIs have been so widespread and predominant. The following links can give you a better understanding about how Immediate Mode GUIs works. +- [Johannes 'johno' Norneby's article](http://www.johno.se/book/imgui.html). +- [A presentation by Rickard Gustafsson and Johannes Algelind](http://www.cse.chalmers.se/edu/year/2011/course/TDA361/Advanced%20Computer%20Graphics/IMGUI.pdf). +- [Jari Komppa's tutorial on building an ImGui library](http://iki.fi/sol/imgui/). +- [Casey Muratori's original video that popularized the concept](https://mollyrocket.com/861). + + Credits ------- Developed by [Omar Cornut](http://www.miracleworld.net). The library was developed with the support of [Media Molecule](http://www.mediamolecule.com) and first used internally on the game [Tearaway](http://tearaway.mediamolecule.com). -Embeds [proggy_clean font](http://www.proggyfonts.net/) by Tristan Grimmer (also MIT license). +Embeds [proggy_clean](http://www.proggyfonts.net/) font by Tristan Grimmer (also MIT license). Inspiration, feedback, and testing: Casey Muratori, Atman Binstock, Mikko Mononen, Emmanuel Briney, Stefan Kamoda, Matt Willis. Thanks! diff --git a/examples/directx9_example/main.cpp b/examples/directx9_example/main.cpp index fd25f01..3036eb7 100644 --- a/examples/directx9_example/main.cpp +++ b/examples/directx9_example/main.cpp @@ -12,16 +12,15 @@ static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; // Our rendering device static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL; // Buffer to hold vertices static LPDIRECT3DTEXTURE9 g_pTexture = NULL; // Our texture - struct CUSTOMVERTEX { D3DXVECTOR3 position; D3DCOLOR color; float tu, tv; }; - #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1) +// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structuer) static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) { size_t total_vtx_count = 0; @@ -30,31 +29,13 @@ if (total_vtx_count == 0) return; - ImVector clip_rect_stack; - clip_rect_stack.push_back(ImVec4(-9999,-9999,+9999,+9999)); - - // Setup orthographic projection - // Set up world matrix - D3DXMATRIXA16 mat; - D3DXMatrixIdentity(&mat); - g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat); - g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat); - D3DXMatrixOrthoOffCenterLH(&mat, 0.0f, ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y, 0.0f, -1.0f, +1.0f); - g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat); - - D3DSURFACE_DESC texture_desc; - g_pTexture->GetLevelDesc(0, &texture_desc); - - // Fill the vertex buffer + // Copy and convert all vertices into a single contiguous buffer CUSTOMVERTEX* vtx_dst; if (g_pVB->Lock(0, total_vtx_count, (void**)&vtx_dst, D3DLOCK_DISCARD) < 0) return; - for (int n = 0; n < cmd_lists_count; n++) { const ImDrawList* cmd_list = cmd_lists[n]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; const ImDrawVert* vtx_src = &cmd_list->vtx_buffer[0]; for (size_t i = 0; i < cmd_list->vtx_buffer.size(); i++) { @@ -73,11 +54,10 @@ g_pd3dDevice->SetStreamSource( 0, g_pVB, 0, sizeof( CUSTOMVERTEX ) ); g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX ); - // Setup render state: alpha-blending enabled, no face culling, no depth testing + // Setup render state: alpha-blending, no face culling, no depth testing g_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE ); g_pd3dDevice->SetRenderState( D3DRS_LIGHTING, false ); g_pd3dDevice->SetRenderState( D3DRS_ZENABLE, false ); - g_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, true ); g_pd3dDevice->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_ADD ); g_pd3dDevice->SetRenderState( D3DRS_ALPHATESTENABLE, false ); @@ -94,97 +74,31 @@ g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE ); g_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE ); - int vtx_consumed = 0; // offset in vertex buffer. each command consume ImDrawCmd::vtx_count of those - bool clip_rect_dirty = true; + // Setup orthographic projection matrix + D3DXMATRIXA16 mat; + D3DXMatrixIdentity(&mat); + g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat); + g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat); + D3DXMatrixOrthoOffCenterLH(&mat, 0.0f, ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y, 0.0f, -1.0f, +1.0f); + g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat); + // Render command lists + int vtx_offset = 0; for (int n = 0; n < cmd_lists_count; n++) { + // Render command list const ImDrawList* cmd_list = cmd_lists[n]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; - const ImDrawCmd* pcmd = &cmd_list->commands.front(); - const ImDrawCmd* pcmd_end = &cmd_list->commands.back(); - int clip_rect_buf_consumed = 0; // offset in cmd_list->clip_rect_buffer. each PushClipRect command consume 1 of those. - while (pcmd <= pcmd_end) + const ImDrawCmd* pcmd_end = cmd_list->commands.end(); + for (const ImDrawCmd* pcmd = cmd_list->commands.begin(); pcmd != pcmd_end; pcmd++) { - const ImDrawCmd& cmd = *pcmd++; - switch (cmd.cmd_type) - { - case ImDrawCmdType_DrawTriangleList: - if (clip_rect_dirty) - { - const ImVec4& clip_rect = clip_rect_stack.back(); - const RECT r = { (LONG)clip_rect.x, (LONG)clip_rect.y, (LONG)clip_rect.z, (LONG)clip_rect.w }; - g_pd3dDevice->SetScissorRect(&r); - clip_rect_dirty = false; - } - g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, vtx_consumed, cmd.vtx_count/3); - vtx_consumed += cmd.vtx_count; - break; - - case ImDrawCmdType_PushClipRect: - clip_rect_stack.push_back(cmd_list->clip_rect_buffer[clip_rect_buf_consumed++]); - clip_rect_dirty = true; - break; - - case ImDrawCmdType_PopClipRect: - clip_rect_stack.pop_back(); - clip_rect_dirty = true; - break; - } + const RECT r = { (LONG)pcmd->clip_rect.x, (LONG)pcmd->clip_rect.y, (LONG)pcmd->clip_rect.z, (LONG)pcmd->clip_rect.w }; + g_pd3dDevice->SetScissorRect(&r); + g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, vtx_offset, pcmd->vtx_count/3); + vtx_offset += pcmd->vtx_count; } } } -// Get text data in Win32 clipboard -static const char* ImImpl_GetClipboardTextFn() -{ - static char* buf_local = NULL; - if (buf_local) - { - free(buf_local); - buf_local = NULL; - } - - if (!OpenClipboard(NULL)) - return NULL; - - HANDLE buf_handle = GetClipboardData(CF_TEXT); - if (buf_handle == NULL) - return NULL; - - if (char* buf_global = (char*)GlobalLock(buf_handle)) - buf_local = strdup(buf_global); - GlobalUnlock(buf_handle); - CloseClipboard(); - - return buf_local; -} - -// Set text data in Win32 clipboard -static void ImImpl_SetClipboardTextFn(const char* text, const char* text_end) -{ - if (!OpenClipboard(NULL)) - return; - - if (!text_end) - text_end = text + strlen(text); - - const int buf_length = (text_end - text) + 1; - HGLOBAL buf_handle = GlobalAlloc(GMEM_MOVEABLE, buf_length * sizeof(char)); - if (buf_handle == NULL) - return; - - char* buf_global = (char *)GlobalLock(buf_handle); - memcpy(buf_global, text, text_end - text); - buf_global[text_end - text] = 0; - GlobalUnlock(buf_handle); - - EmptyClipboard(); - SetClipboardData(CF_TEXT, buf_handle); - CloseClipboard(); -} - HRESULT InitD3D(HWND hWnd) { if (NULL == (g_pD3D = Direct3DCreate9(D3D_SDK_VERSION))) @@ -203,17 +117,6 @@ if (g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &g_pd3dDevice) < 0) return E_FAIL; - // Create the vertex buffer. - if (g_pd3dDevice->CreateVertexBuffer(10000 * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL) < 0) - return E_FAIL; - - // Load font texture - const void* png_data; - unsigned int png_size; - ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); - if (D3DXCreateTextureFromFileInMemory(g_pd3dDevice, png_data, png_size, &g_pTexture) < 0) - return E_FAIL; - return S_OK; } @@ -247,9 +150,11 @@ io.MouseDown[1] = false; return true; case WM_MOUSEWHEEL: + // Mouse wheel: -1,0,+1 io.MouseWheel = GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? +1 : -1; return true; case WM_MOUSEMOVE: + // Mouse position, in pixels (set to -1,-1 if no mouse / on another screen, etc.) io.MousePos.x = (signed short)(lParam); io.MousePos.y = (signed short)(lParam >> 16); return true; @@ -274,9 +179,10 @@ GetClientRect(hWnd, &rect); ImGuiIO& io = ImGui::GetIO(); - io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); - io.DeltaTime = 1.0f/60.0f; - io.KeyMap[ImGuiKey_Tab] = VK_TAB; + io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top)); // 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 Direct3D Texels + io.KeyMap[ImGuiKey_Tab] = VK_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array that we will update during the application lifetime. io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT; io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT; io.KeyMap[ImGuiKey_UpArrow] = VK_UP; @@ -295,8 +201,52 @@ io.KeyMap[ImGuiKey_Z] = 'Z'; io.RenderDrawListsFn = ImImpl_RenderDrawLists; - io.SetClipboardTextFn = ImImpl_SetClipboardTextFn; - io.GetClipboardTextFn = ImImpl_GetClipboardTextFn; + + // Create the vertex buffer + if (g_pd3dDevice->CreateVertexBuffer(10000 * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL) < 0) + { + IM_ASSERT(0); + return; + } + + // Load font texture + const void* png_data; + unsigned int png_size; + ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); + if (D3DXCreateTextureFromFileInMemory(g_pd3dDevice, png_data, png_size, &g_pTexture) < 0) + { + IM_ASSERT(0); + return; + } +} + +INT64 ticks_per_second = 0; +INT64 time = 0; + +void UpdateImGui() +{ + ImGuiIO& io = ImGui::GetIO(); + + // Setup timestep + INT64 current_time; + QueryPerformanceCounter((LARGE_INTEGER *)¤t_time); + io.DeltaTime = (float)(current_time - time) / ticks_per_second; + time = current_time; + + // Setup inputs + // (we already got mouse position, buttons, wheel from the window message callback) + BYTE keystate[256]; + GetKeyboardState(keystate); + for (int i = 0; i < 256; i++) + io.KeysDown[i] = (keystate[i] & 0x80) != 0; + io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0; + io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0; + // io.MousePos : filled by WM_MOUSEMOVE event + // io.MouseDown : filled by WM_*BUTTON* events + // io.MouseWheel : filled by WM_MOUSEWHEEL events + + // Start the frame + ImGui::NewFrame(); } int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, LPWSTR, int) @@ -308,99 +258,91 @@ // Create the application's window hWnd = CreateWindow(L"ImGui Example", L"ImGui DirectX9 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, NULL, NULL, wc.hInstance, NULL); - INT64 ticks_per_second, time; if (!QueryPerformanceFrequency((LARGE_INTEGER *)&ticks_per_second)) return 1; if (!QueryPerformanceCounter((LARGE_INTEGER *)&time)) return 1; // Initialize Direct3D - if (InitD3D(hWnd) >= 0) + if (InitD3D(hWnd) < 0) + { + if (g_pVB) + g_pVB->Release(); + UnregisterClass(L"ImGui Example", wc.hInstance); + return 1; + } + + // Show the window + ShowWindow(hWnd, SW_SHOWDEFAULT); + UpdateWindow(hWnd); + + InitImGui(); + + // Enter the message loop + MSG msg; + ZeroMemory(&msg, sizeof(msg)); + while (msg.message != WM_QUIT) { - // Show the window - ShowWindow(hWnd, SW_SHOWDEFAULT); - UpdateWindow(hWnd); - - InitImGui(); - - // Enter the message loop - MSG msg; - ZeroMemory(&msg, sizeof(msg)); - while (msg.message != WM_QUIT) + if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) { - if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - continue; - } + TranslateMessage(&msg); + DispatchMessage(&msg); + continue; + } - // 1) ImGui start frame, setup time delta & inputs - ImGuiIO& io = ImGui::GetIO(); - INT64 current_time; - QueryPerformanceCounter((LARGE_INTEGER *)¤t_time); - io.DeltaTime = (float)(current_time - time) / ticks_per_second; - time = current_time; - BYTE keystate[256]; - GetKeyboardState(keystate); - for (int i = 0; i < 256; i++) - io.KeysDown[i] = (keystate[i] & 0x80) != 0; - io.KeyCtrl = (keystate[VK_CONTROL] & 0x80) != 0; - io.KeyShift = (keystate[VK_SHIFT] & 0x80) != 0; - // io.MousePos : filled by WM_MOUSEMOVE event - // io.MouseDown : filled by WM_*BUTTON* events - // io.MouseWheel : filled by WM_MOUSEWHEEL events - ImGui::NewFrame(); + UpdateImGui(); - // 2) ImGui usage - 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"); + // 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] = io.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); + // 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); - if (show_test_window) - { - // More example code in ShowTestWindow() - 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); - } - - if (show_another_window) - { - ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); - ImGui::Text("Hello"); - ImGui::End(); - } - - // 3) Render - g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); - g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false); - g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, false); - g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(204, 153, 153), 1.0f, 0); // Clear the backbuffer and the zbuffer - if (g_pd3dDevice->BeginScene() >= 0) - { - ImGui::Render(); - g_pd3dDevice->EndScene(); - } - g_pd3dDevice->Present(NULL, NULL, NULL, NULL); + // 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::Shutdown(); - } + // Show another simple window + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); + ImGui::Text("Hello"); + ImGui::End(); + } + + // Rendering + g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false); + g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false); + g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, false); + g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(204, 153, 153), 1.0f, 0); + if (g_pd3dDevice->BeginScene() >= 0) + { + ImGui::Render(); + g_pd3dDevice->EndScene(); + } + g_pd3dDevice->Present(NULL, NULL, NULL, NULL); + } + + ImGui::Shutdown(); if (g_pVB) g_pVB->Release(); diff --git a/examples/opengl_example/Makefile.Linux b/examples/opengl_example/Makefile.Linux new file mode 100644 index 0000000..6fd0b97 --- /dev/null +++ b/examples/opengl_example/Makefile.Linux @@ -0,0 +1,18 @@ +# +# Quick and dirty makefile to build on Linux +# tested on Ubuntu 14.04.1 32bit +# + +SRC = main.cpp ../../imgui.cpp + +OBJ = $(SRC:.cpp=.o) + +CXXFLAGS = -I../../ `pkg-config --cflags glfw3` + +LIBS = `pkg-config --static --libs glfw3` -lGLEW + +all: $(OBJ) + $(CXX) $(OBJ) $(LIBS) + +clean: + $(RM) -f $(OBJ) diff --git a/examples/opengl_example/Makefile.Macosx b/examples/opengl_example/Makefile.Macosx new file mode 100644 index 0000000..44ffe15 --- /dev/null +++ b/examples/opengl_example/Makefile.Macosx @@ -0,0 +1,18 @@ +# This makefile currently only works for mac os +# You should install via homebrew: +# brew install glew +# brew install glfw3 +# + +CXXFLAGS=-framework OpenGL -framework Cocoa -framework IOKit +CXXFLAGS+=-I/usr/local/Cellar/glew/1.10.0/include -I/usr/local/Cellar/glfw3/3.0.4/include +CXXFLAGS+=-L/usr/local/Cellar/glew/1.10.0/lib -L/usr/local/Cellar/glfw3/3.0.4/lib +CXXFLAGS+=-lglew -lglfw3 +CXXFLAGS+=-I../../ +CXXFLAGS+= -D__APPLE__ + +main: main.cpp ../../imgui.cpp + $(CXX) $(CXXFLAGS) -o $@ $^ + +clean: + rm main diff --git a/examples/opengl_example/main.cpp b/examples/opengl_example/main.cpp index 909ffa9..609021e 100644 --- a/examples/opengl_example/main.cpp +++ b/examples/opengl_example/main.cpp @@ -2,116 +2,65 @@ #include #include #define STB_IMAGE_IMPLEMENTATION -#include "stb_image.h" // for .png loading +#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 +#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen #endif static GLFWwindow* window; -static GLuint vbo; -static GLuint vao; -static GLuint vertexShader; -static GLuint fragmentShader; -static GLuint shaderProgram; static GLuint fontTex; -static GLint uniMVP; -static GLint uniClipRect; +// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structuer) +// We are using the fixed pipeline. +// A faster way would be to collate all vertices from all cmd_lists into a single vertex buffer static void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) { - size_t total_vtx_count = 0; - for (int n = 0; n < cmd_lists_count; n++) - total_vtx_count += cmd_lists[n]->vtx_buffer.size(); - if (total_vtx_count == 0) + if (cmd_lists_count == 0) return; - int read_pos_clip_rect_buf = 0; // offset in 'clip_rect_buffer'. each PushClipRect command consume 1 of those. - - ImVector clip_rect_stack; - clip_rect_stack.push_back(ImVec4(-9999,-9999,+9999,+9999)); - - // Setup orthographic projection - const float L = 0.0f; - const float R = ImGui::GetIO().DisplaySize.x; - const float B = ImGui::GetIO().DisplaySize.y; - const float T = 0.0f; - const float mvp[4][4] = - { - { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, - { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, - { 0.0f, 0.0f, -1.0f, 0.0f }, - { -(R+L)/(R-L), -(T+B)/(T-B), 0.0f, 1.0f }, - }; - - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBindVertexArray(vao); - glBufferData(GL_ARRAY_BUFFER, total_vtx_count * sizeof(ImDrawVert), NULL, GL_STREAM_DRAW); - unsigned char* buffer_data = (unsigned char*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); - if (!buffer_data) - return; - int vtx_consumed = 0; - for (int n = 0; n < cmd_lists_count; n++) - { - const ImDrawList* cmd_list = cmd_lists[n]; - if (!cmd_list->vtx_buffer.empty()) - { - memcpy(buffer_data, &cmd_list->vtx_buffer[0], cmd_list->vtx_buffer.size() * sizeof(ImDrawVert)); - buffer_data += cmd_list->vtx_buffer.size() * sizeof(ImDrawVert); - vtx_consumed += cmd_list->vtx_buffer.size(); - } - } - glUnmapBuffer(GL_ARRAY_BUFFER); - - glUseProgram(shaderProgram); - glUniformMatrix4fv(uniMVP, 1, GL_FALSE, &mvp[0][0]); - - // Setup render state: alpha-blending enabled, no face culling, no depth testing + // 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); - vtx_consumed = 0; // offset in vertex buffer. each command consume ImDrawCmd::vtx_count of those - bool clip_rect_dirty = true; + // 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]; - if (cmd_list->commands.empty() || cmd_list->vtx_buffer.empty()) - continue; - const ImDrawCmd* pcmd = &cmd_list->commands.front(); - const ImDrawCmd* pcmd_end = &cmd_list->commands.back(); - int clip_rect_buf_consumed = 0; // offset in cmd_list->clip_rect_buffer. each PushClipRect command consume 1 of those. - while (pcmd <= pcmd_end) + 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++) { - const ImDrawCmd& cmd = *pcmd++; - switch (cmd.cmd_type) - { - case ImDrawCmdType_DrawTriangleList: - if (clip_rect_dirty) - { - glUniform4fv(uniClipRect, 1, (float*)&clip_rect_stack.back()); - clip_rect_dirty = false; - } - glDrawArrays(GL_TRIANGLES, vtx_consumed, cmd.vtx_count); - vtx_consumed += cmd.vtx_count; - break; - - case ImDrawCmdType_PushClipRect: - clip_rect_stack.push_back(cmd_list->clip_rect_buffer[clip_rect_buf_consumed++]); - clip_rect_dirty = true; - break; - - case ImDrawCmdType_PopClipRect: - clip_rect_stack.pop_back(); - clip_rect_dirty = true; - break; - } + 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); } static const char* ImImpl_GetClipboardTextFn() @@ -124,55 +73,33 @@ if (!text_end) text_end = text + strlen(text); - char* buf = (char*)malloc(text_end - text + 1); - memcpy(buf, text, text_end-text); - buf[text_end-text] = '\0'; - glfwSetClipboardString(window, buf); - free(buf); + 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); + } } -// Shader sources -// FIXME-OPT: clip at vertex level -const GLchar* vertexSource = - "#version 150 core\n" - "uniform mat4 MVP;" - "in vec2 i_pos;" - "in vec2 i_uv;" - "in vec4 i_col;" - "out vec4 col;" - "out vec2 pixel_pos;" - "out vec2 uv;" - "void main() {" - " col = i_col;" - " pixel_pos = i_pos;" - " uv = i_uv;" - " gl_Position = MVP * vec4(i_pos.x, i_pos.y, 0.0f, 1.0f);" - "}"; -const GLchar* fragmentSource = - "#version 150 core\n" - "uniform sampler2D Tex;" - "uniform vec4 ClipRect;" - "in vec4 col;" - "in vec2 pixel_pos;" - "in vec2 uv;" - "out vec4 o_col;" - "void main() {" - " o_col = texture(Tex, uv) * col;" - //" if (pixel_pos.x < ClipRect.x || pixel_pos.y < ClipRect.y || pixel_pos.x > ClipRect.z || pixel_pos.y > ClipRect.w) discard;" // Clipping: using discard - //" if (step(ClipRect.x,pixel_pos.x) * step(ClipRect.y,pixel_pos.y) * step(pixel_pos.x,ClipRect.z) * step(pixel_pos.y,ClipRect.w) < 1.0f) discard;" // Clipping: using discard and step - " o_col.w *= (step(ClipRect.x,pixel_pos.x) * step(ClipRect.y,pixel_pos.y) * step(pixel_pos.x,ClipRect.z) * step(pixel_pos.y,ClipRect.w));" // Clipping: branch-less, set alpha 0.0f - "}"; - +// GLFW callbacks to get events static void glfw_error_callback(int error, const char* description) { - fputs(description, stderr); + fputs(description, stderr); } -static float mouse_wheel = 0.0f; static void glfw_scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { - mouse_wheel = (float)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) @@ -197,79 +124,17 @@ { glfwSetErrorCallback(glfw_error_callback); - if (!glfwInit()) - exit(1); + if (!glfwInit()) + exit(1); - //glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - //glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); - //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); - glfwWindowHint(GLFW_REFRESH_RATE, 60); glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); - window = glfwCreateWindow(1280, 720, "ImGui OpenGL example", nullptr, nullptr); + 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); - glewExperimental = GL_TRUE; glewInit(); - - GLenum err = GL_NO_ERROR; - GLint status = GL_TRUE; - err = glGetError(); IM_ASSERT(err == GL_NO_ERROR); - - // Create and compile the vertex shader - vertexShader = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(vertexShader, 1, &vertexSource, NULL); - glCompileShader(vertexShader); - glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status); - if (status != GL_TRUE) - { - char buffer[512]; - glGetShaderInfoLog(vertexShader, 1024, NULL, buffer); - printf("%s", buffer); - IM_ASSERT(status == GL_TRUE); - } - - // Create and compile the fragment shader - fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(fragmentShader, 1, &fragmentSource, NULL); - glCompileShader(fragmentShader); - glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status); - IM_ASSERT(status == GL_TRUE); - - // Link the vertex and fragment shader into a shader program - shaderProgram = glCreateProgram(); - glAttachShader(shaderProgram, vertexShader); - glAttachShader(shaderProgram, fragmentShader); - glBindFragDataLocation(shaderProgram, 0, "o_col"); - glLinkProgram(shaderProgram); - glGetProgramiv(shaderProgram, GL_LINK_STATUS, &status); - IM_ASSERT(status == GL_TRUE); - - uniMVP = glGetUniformLocation(shaderProgram, "MVP"); - uniClipRect = glGetUniformLocation(shaderProgram, "ClipRect"); - - // Create Vertex Buffer Objects & Vertex Array Objects - glGenBuffers(1, &vbo); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - - GLint posAttrib = glGetAttribLocation(shaderProgram, "i_pos"); - glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), 0); - glEnableVertexAttribArray(posAttrib); - - GLint uvAttrib = glGetAttribLocation(shaderProgram, "i_uv"); - glEnableVertexAttribArray(uvAttrib); - glVertexAttribPointer(uvAttrib, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (void*)(2*sizeof(float))); - - GLint colAttrib = glGetAttribLocation(shaderProgram, "i_col"); - glVertexAttribPointer(colAttrib, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (void*)(4*sizeof(float))); - glEnableVertexAttribArray(colAttrib); - err = glGetError(); IM_ASSERT(err == GL_NO_ERROR); } void InitImGui() @@ -278,9 +143,10 @@ glfwGetWindowSize(window, &w, &h); ImGuiIO& io = ImGui::GetIO(); - io.DisplaySize = ImVec2((float)w, (float)h); - io.DeltaTime = 1.0f/60.0f; - io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB; + io.DisplaySize = ImVec2((float)w, (float)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.5f; // 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; @@ -307,7 +173,6 @@ 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); - const void* png_data; unsigned int png_size; ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); @@ -317,44 +182,43 @@ stbi_image_free(tex_data); } -void Shutdown() +void UpdateImGui() { - ImGui::Shutdown(); + ImGuiIO& io = ImGui::GetIO(); - glDeleteProgram(shaderProgram); - glDeleteShader(fragmentShader); - glDeleteShader(vertexShader); - glDeleteBuffers(1, &vbo); - glDeleteVertexArrays(1, &vao); + // Setup timestep + static double time = 0.0f; + const double current_time = glfwGetTime(); + io.DeltaTime = (float)(current_time - time); + time = current_time; - glfwTerminate(); + // 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, (float)mouse_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(); - double time = glfwGetTime(); while (!glfwWindowShouldClose(window)) { ImGuiIO& io = ImGui::GetIO(); + io.MouseWheel = 0; glfwPollEvents(); + UpdateImGui(); - // 1) ImGui start frame, setup time delta & inputs - const double current_time = glfwGetTime(); - io.DeltaTime = (float)(current_time - time); - time = current_time; - double mouse_x, mouse_y; - glfwGetCursorPos(window, &mouse_x, &mouse_y); - io.MousePos = ImVec2((float)mouse_x, (float)mouse_y); - io.MouseDown[0] = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) != 0; - io.MouseDown[1] = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT) != 0; - io.MouseWheel = (mouse_wheel != 0) ? mouse_wheel > 0.0f ? 1 : - 1 : 0; - mouse_wheel = 0.0f; - ImGui::NewFrame(); - - // 2) ImGui usage + // 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; @@ -368,19 +232,21 @@ 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] = io.DeltaTime * 1000.0f; + 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) { - // More example code in ShowTestWindow() - 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::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); } + // Show another simple window if (show_another_window) { ImGui::Begin("Another Window", &show_another_window, ImVec2(200,100)); @@ -388,15 +254,15 @@ ImGui::End(); } - // 3) Render - glClearColor(0.8f, 0.6f, 0.6f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); + // 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); } - Shutdown(); - + ImGui::Shutdown(); + glfwTerminate(); return 0; } diff --git a/imconfig.h b/imconfig.h index 0a340ee..98b16f1 100644 --- a/imconfig.h +++ b/imconfig.h @@ -4,15 +4,22 @@ #pragma once -//----- Define your own ImVector<> type if you don't want to use the provided implementation defined in imgui.h +//---- Define your own ImVector<> type if you don't want to use the provided implementation defined in imgui.h //#include //#define ImVector std::vector //#define ImVector MyVector -//----- Define assertion handler. Default to calling assert(). -// #define IM_ASSERT(_EXPR) MyAssert(_EXPR) +//---- Define assertion handler. Defaults to calling assert(). +//#define IM_ASSERT(_EXPR) MyAssert(_EXPR) -//----- Define implicit cast operators to convert back<>forth from your math types and ImVec2/ImVec4. +//---- Don't implement default clipboard handlers for Windows (so as not to link with OpenClipboard(), etc.) +//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS + +//---- If you are loading a custom font, ImGui expect to find a pure white pixel at (0,0) +// Change it's UV coordinate here if you can't have a white pixel at (0,0) +//#define IMGUI_FONT_TEX_UV_FOR_WHITE ImVec2(0.f/256.f,0.f/256.f) + +//---- Define implicit cast operators to convert back<>forth from your math types and ImVec2/ImVec4. /* #define IM_VEC2_CLASS_EXTRA \ ImVec2(const MyVec2& f) { x = f.x; y = f.y; } \ @@ -23,12 +30,12 @@ operator MyVec4() const { return MyVec4(x,y,z,w); } */ -//----- Freely implement extra functions within the ImGui:: namespace. -//----- e.g. you can create variants of the ImGui::Value() helper for your low-level math types. +//---- Freely implement extra functions within the ImGui:: namespace. +//---- e.g. you can create variants of the ImGui::Value() helper for your low-level math types. /* namespace ImGui { - void Value(const char* prefix, cosnt MyVec2& v, const char* float_format = NULL); - void Value(const char* prefix, cosnt MyVec4& v, const char* float_format = NULL); + void Value(const char* prefix, const MyVec2& v, const char* float_format = NULL); + void Value(const char* prefix, const MyVec4& v, const char* float_format = NULL); }; */ diff --git a/imgui.cpp b/imgui.cpp index 1242b02..888a43b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -101,7 +101,7 @@ - if you want to use a different font than the default - create bitmap font data using BMFont. allocate ImGui::GetIO().Font and use ->LoadFromFile()/LoadFromMemory(), set ImGui::GetIO().FontHeight - - load your texture yourself. texture *MUST* have white pixel at UV coordinate 'IMDRAW_TEX_UV_FOR_WHITE' (you can #define it in imconfig.h), this is used by solid objects. + - load your texture yourself. texture *MUST* have white pixel at UV coordinate 'IMGUI_FONT_TEX_UV_FOR_WHITE' (you can #define it in imconfig.h), this is used by solid objects. - tip: the construct 'if (IMGUI_ONCE_UPON_A_FRAME)' will evaluate to true only once a frame, you can use it to add custom UI in the middle of a deep nested inner loop in your code. - tip: you can call Render() multiple times (e.g for VR renders), up to you to communicate the extra state to your RenderDrawListFn function. @@ -110,18 +110,20 @@ ISSUES AND TODO-LIST - misc: merge ImVec4 / ImGuiAabb, they are essentially duplicate containers - - main: make IsHovered() more consistent for various type of widgets, widgets with multiple components, etc. also effectively IsHovered() region sometimes differs from hot region, e.g tree nodes - window: autofit is losing its purpose when user relies on any dynamic layout (window width multiplier, column). maybe just discard autofit? - window: support horizontal scroll - window: fix resize grip scaling along with Rounding style setting + - window/style: add global alpha modifier (not just "fill_alpha") - widgets: switching from "widget-label" to "label-widget" would make it more convenient to integrate widgets in trees - widgets: clip text? hover clipped text shows it in a tooltip or in-place overlay + - main: make IsHovered() more consistent for various type of widgets, widgets with multiple components, etc. also effectively IsHovered() region sometimes differs from hot region, e.g tree nodes + - main: make IsHovered() info stored in a stack? so that 'if TreeNode() { Text; TreePop; } if IsHovered' return the hover state of the TreeNode? - scrollbar: use relative mouse movement when first-clicking inside of scroll grab box. - input number: optional range min/max - input number: holding [-]/[+] buttons should increase the step non-linearly - input number: rename Input*() to Input(), Slider*() to Slider() ? - layout: clean up the InputFloat3/SliderFloat3/ColorEdit4 horrible layout code. item width should include frame padding, then we can have a generic horizontal layout helper. - - add input2/4 helper (once above layout helpers are in they'll be smaller) + - add input4 helper (once above layout helpers are in they'll be smaller) - columns: declare column set (each column: fixed size, %, fill, distribute default size among fills) - columns: columns header to act as button (~sort op) and allow resize/reorder - columns: user specify columns size @@ -151,13 +153,12 @@ - optimisation: turn some the various stack vectors into statically-sized arrays - optimisation: better clipping for multi-component widgets - optimisation: specialize for height based clipping first (assume widgets never go up + height tests before width tests?) - - optimisation/portability: provide ImVector style implementation - - optimisation/portability: remove dependency on */ #include "imgui.h" -#include +#include // toupper #include // sqrt +#include // intptr_t #include // vsnprintf #include // memset @@ -188,7 +189,7 @@ static void ItemSize(const ImGuiAabb& aabb, ImVec2* adjust_start_offset = NULL); static void PushColumnClipRect(int column_index = -1); static bool IsClipped(const ImGuiAabb& aabb); -static bool ClipAdvance(const ImGuiAabb& aabb, bool skip_columns = false); +static bool ClipAdvance(const ImGuiAabb& aabb); static bool IsMouseHoveringBox(const ImGuiAabb& box); static bool IsKeyPressedMap(ImGuiKey key, bool repeat = true); @@ -200,6 +201,13 @@ }; // namespace ImGui //----------------------------------------------------------------------------- +// Platform dependant default implementations +//----------------------------------------------------------------------------- + +static const char* GetClipboardTextFn_DefaultImpl(); +static void SetClipboardTextFn_DefaultImpl(const char* text, const char* text_end); + +//----------------------------------------------------------------------------- // User facing structures //----------------------------------------------------------------------------- @@ -230,6 +238,7 @@ Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.80f, 0.40f); Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.80f, 0.50f, 0.50f, 0.40f); Colors[ImGuiCol_ComboBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.99f); + Colors[ImGuiCol_CheckHovered] = ImVec4(0.60f, 0.40f, 0.40f, 0.45f); Colors[ImGuiCol_CheckActive] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f); Colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.80f, 0.50f, 0.50f, 1.00f); @@ -265,10 +274,15 @@ LogFilename = "imgui_log.txt"; Font = NULL; FontAllowScaling = false; + PixelCenterOffset = 0.5f; MousePos = ImVec2(-1,-1); MousePosPrev = ImVec2(-1,-1); MouseDoubleClickTime = 0.30f; MouseDoubleClickMaxDist = 6.0f; + + // Platform dependant default implementations + GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; + SetClipboardTextFn = SetClipboardTextFn_DefaultImpl; } // Pass in translated ASCII characters for text input. @@ -276,7 +290,7 @@ // - on Windows you can get those using ToAscii+keyboard state, or via the VM_CHAR message void ImGuiIO::AddInputCharacter(char c) { - const int n = strlen(InputCharacters); + const size_t n = strlen(InputCharacters); if (n < sizeof(InputCharacters) / sizeof(InputCharacters[0])) { InputCharacters[n] = c; @@ -288,12 +302,17 @@ // Helpers //----------------------------------------------------------------------------- -#undef ARRAYSIZE -#define ARRAYSIZE(_ARR) (sizeof(_ARR)/sizeof(*_ARR)) +#define IM_ARRAYSIZE(_ARR) ((int)(sizeof(_ARR)/sizeof(*_ARR))) #undef PI const float PI = 3.14159265358979323846f; +#ifdef INT_MAX +#define IM_INT_MAX INT_MAX +#else +#define IM_INT_MAX 2147483647 +#endif + // Math bits // We are keeping those static in the .cpp file so as not to leak them outside, in the case the user has implicit cast operators between ImVec2 and its own types. static inline ImVec2 operator*(const ImVec2& lhs, const float rhs) { return ImVec2(lhs.x*rhs, lhs.y*rhs); } @@ -333,7 +352,7 @@ if (!needle_end) needle_end = needle + strlen(needle); - const char un0 = toupper(*needle); + const char un0 = (char)toupper(*needle); while (*haystack) { if (toupper(*haystack) == un0) @@ -378,7 +397,7 @@ int w = vsnprintf(buf, buf_size, fmt, args); va_end(args); buf[buf_size-1] = 0; - if (w == -1) w = buf_size; + if (w == -1) w = (int)buf_size; return w; } @@ -386,7 +405,7 @@ { int w = vsnprintf(buf, buf_size, fmt, args); buf[buf_size-1] = 0; - if (w == -1) w = buf_size; + if (w == -1) w = (int)buf_size; return w; } @@ -416,7 +435,7 @@ } const float chroma = r - (g < b ? g : b); - out_h = abs(K + (g - b) / (6.f * chroma + 1e-20f)); + out_h = fabsf(K + (g - b) / (6.f * chroma + 1e-20f)); out_s = chroma / (r + 1e-20f); out_v = r; } @@ -493,6 +512,7 @@ float PrevLineHeight; float LogLineHeight; int TreeDepth; + ImGuiAabb LastItemAabb; bool LastItemHovered; ImVector ChildWindows; ImVector AllowKeyboardFocus; @@ -515,6 +535,7 @@ CurrentLineHeight = PrevLineHeight = 0.0f; LogLineHeight = -1.0f; TreeDepth = 0; + LastItemAabb = ImGuiAabb(0.0f,0.0f,0.0f,0.0f); LastItemHovered = false; StateStorage = NULL; OpenNextNode = -1; @@ -537,7 +558,7 @@ { char Text[1024]; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so own buffer. char InitialText[1024]; // backup of end-user buffer at focusing time, to ESC key can do a revert. Also used for arithmetic operations (but could use a pre-parsed float there). - int MaxLength; // end-user buffer size <= 1024 (or increase above) + size_t BufSize; // end-user buffer size, <= 1024 (or increase above) float Width; // widget width float ScrollX; STB_TexteditState StbState; @@ -551,7 +572,7 @@ void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking bool CursorIsVisible() const { return CursorAnim <= 0.0f || fmodf(CursorAnim, 1.20f) <= 0.80f; } // Blinking bool HasSelection() const { return StbState.select_start != StbState.select_end; } - void SelectAll() { StbState.select_start = 0; StbState.select_end = strlen(Text); StbState.cursor = StbState.select_end; StbState.has_preferred_x = false; } + void SelectAll() { StbState.select_start = 0; StbState.select_end = (int)strlen(Text); StbState.cursor = StbState.select_end; StbState.has_preferred_x = false; } void OnKeyboardPressed(int key); void UpdateScrollOffset(); @@ -604,6 +625,7 @@ ImGuiStorage ColorEditModeStorage; // for user selection ImGuiID ActiveComboID; char Tooltip[1024]; + char* PrivateClipboard; // if no custom clipboard handler is defined // Logging bool LogEnabled; @@ -627,6 +649,7 @@ SliderAsInputTextId = 0; ActiveComboID = 0; memset(Tooltip, 0, sizeof(Tooltip)); + PrivateClipboard = NULL; LogEnabled = false; LogFile = NULL; LogAutoExpandMaxDepth = 2; @@ -690,6 +713,7 @@ static ImGuiWindow* GetCurrentWindow() { + IM_ASSERT(GImGui.CurrentWindow != NULL); // ImGui::NewFrame() hasn't been called yet? GImGui.CurrentWindow->Accessed = true; return GImGui.CurrentWindow; } @@ -712,7 +736,7 @@ { ImVector::iterator first = data.begin(); ImVector::iterator last = data.end(); - int count = last - first; + int count = (int)(last - first); while (count > 0) { int count2 = count / 2; @@ -789,7 +813,7 @@ width = ImMax(window->Pos.x + ImGui::GetWindowContentRegionMax().x - window->DC.CursorPos.x - (label_size.x + GImGui.Style.ItemSpacing.x*4), 10.0f); } ImGui::PushItemWidth(width); - ImGui::InputText(label, InputBuf, ARRAYSIZE(InputBuf)); + ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf)); ImGui::PopItemWidth(); Build(); } @@ -865,18 +889,20 @@ //----------------------------------------------------------------------------- -void ImGuiTextBuffer::Append(const char* fmt, ...) +void ImGuiTextBuffer::append(const char* fmt, ...) { va_list args; va_start(args, fmt); int len = vsnprintf(NULL, 0, fmt, args); va_end(args); + if (len <= 0) + return; const size_t write_off = Buf.size(); if (write_off + len >= Buf.capacity()) Buf.reserve(Buf.capacity() * 2); - Buf.resize(write_off + len); + Buf.resize(write_off + (size_t)len); va_start(args, fmt); ImFormatStringV(&Buf[write_off] - 1, len+1, fmt, args); @@ -909,8 +935,8 @@ AutoFitFrames = 3; FocusIdxCounter = -1; - FocusIdxRequestCurrent = INT_MAX; - FocusIdxRequestNext = INT_MAX; + FocusIdxRequestCurrent = IM_INT_MAX; + FocusIdxRequestNext = IM_INT_MAX; DrawList = new ImDrawList(); } @@ -951,7 +977,7 @@ return false; // Process input at this point: TAB, Shift-TAB switch focus - if (FocusIdxRequestNext == INT_MAX && is_active && ImGui::IsKeyPressedMap(ImGuiKey_Tab)) + if (FocusIdxRequestNext == IM_INT_MAX && is_active && ImGui::IsKeyPressedMap(ImGuiKey_Tab)) { // Modulo on index will be applied at the end of frame once we've got the total counter of items. FocusIdxRequestNext = FocusIdxCounter + (g.IO.KeyShift ? -1 : +1); @@ -971,7 +997,11 @@ ImGuiState& g = GImGui; if (!DrawList->commands.empty() && !DrawList->vtx_buffer.empty()) + { + if (DrawList->commands.back().vtx_count == 0) + DrawList->commands.pop_back(); g.RenderDrawLists.push_back(DrawList); + } for (size_t i = 0; i < DC.ChildWindows.size(); i++) { ImGuiWindow* child = DC.ChildWindows[i]; @@ -1025,7 +1055,7 @@ if (fseek(f, 0, SEEK_SET)) return; char* f_data = new char[f_size+1]; - f_size = fread(f_data, 1, f_size, f); // Text conversion alter read size so let's not be fussy about return value + f_size = (long)fread(f_data, 1, f_size, f); // Text conversion alter read size so let's not be fussy about return value fclose(f); if (f_size == 0) { @@ -1045,7 +1075,7 @@ if (line_start[0] == '[' && line_end > line_start && line_end[-1] == ']') { char name[64]; - ImFormatString(name, ARRAYSIZE(name), "%.*s", line_end-line_start-2, line_start+1); + ImFormatString(name, IM_ARRAYSIZE(name), "%.*s", line_end-line_start-2, line_start+1); settings = FindWindowSettings(name); } else if (settings) @@ -1160,7 +1190,7 @@ else g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev; g.IO.MousePosPrev = g.IO.MousePos; - for (int i = 0; i < ARRAYSIZE(g.IO.MouseDown); i++) + for (size_t i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) { g.IO.MouseDownTime[i] = g.IO.MouseDown[i] ? (g.IO.MouseDownTime[i] < 0.0f ? 0.0f : g.IO.MouseDownTime[i] + g.IO.DeltaTime) : -1.0f; g.IO.MouseClicked[i] = (g.IO.MouseDownTime[i] == 0.0f); @@ -1180,7 +1210,7 @@ } } } - for (int i = 0; i < ARRAYSIZE(g.IO.KeysDown); i++) + for (size_t i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++) g.IO.KeysDownTime[i] = g.IO.KeysDown[i] ? (g.IO.KeysDownTime[i] < 0.0f ? 0.0f : g.IO.KeysDownTime[i] + g.IO.DeltaTime) : -1.0f; // Clear reference to active widget if the widget isn't alive anymore @@ -1280,6 +1310,12 @@ g.IO.Font = NULL; } + if (g.PrivateClipboard) + { + free(g.PrivateClipboard); + g.PrivateClipboard = NULL; + } + g.Initialized = false; } @@ -1298,7 +1334,6 @@ static void PushClipRect(const ImVec4& clip_rect, bool clipped = true) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); ImVec4 cr = clip_rect; @@ -1315,7 +1350,6 @@ static void PopClipRect() { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); window->ClipRectStack.pop_back(); window->DrawList->PopClipRect(); @@ -1446,9 +1480,9 @@ else { if (log_new_line || !is_first_line) - g.LogClipboard.Append("\n%*s%.*s", tree_depth*4, "", char_count, text_remaining); + g.LogClipboard.append("\n%*s%.*s", tree_depth*4, "", char_count, text_remaining); else - g.LogClipboard.Append(" %.*s", char_count, text_remaining); + g.LogClipboard.append(" %.*s", char_count, text_remaining); } } @@ -1491,7 +1525,6 @@ static void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding); @@ -1504,7 +1537,6 @@ static void RenderCollapseTriangle(ImVec2 p_min, bool open, float scale = 1.0f, bool shadow = false) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); const float h = window->FontSize() * 1.00f; @@ -1533,7 +1565,6 @@ static ImVec2 CalcTextSize(const char* text, const char* text_end, const bool hide_text_after_hash) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); const char* text_display_end; @@ -1551,7 +1582,7 @@ ImGuiState& g = GImGui; for (int i = (int)g.Windows.size()-1; i >= 0; i--) { - ImGuiWindow* window = g.Windows[i]; + ImGuiWindow* window = g.Windows[(size_t)i]; if (!window->Visible) continue; if (excluding_childs && (window->Flags & ImGuiWindowFlags_ChildWindow) != 0) @@ -1583,6 +1614,11 @@ return box_for_touch.Contains(g.IO.MousePos); } +bool IsMouseHoveringBox(const ImVec2& box_min, const ImVec2& box_max) +{ + return IsMouseHoveringBox(ImGuiAabb(box_min, box_max)); +} + static bool IsKeyPressedMap(ImGuiKey key, bool repeat) { ImGuiState& g = GImGui; @@ -1593,7 +1629,7 @@ bool IsKeyPressed(int key_index, bool repeat) { ImGuiState& g = GImGui; - IM_ASSERT(key_index >= 0 && key_index < ARRAYSIZE(g.IO.KeysDown)); + IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysDown)); const float t = g.IO.KeysDownTime[key_index]; if (t == 0.0f) return true; @@ -1611,7 +1647,7 @@ bool IsMouseClicked(int button, bool repeat) { ImGuiState& g = GImGui; - IM_ASSERT(button >= 0 && button < ARRAYSIZE(g.IO.MouseDown)); + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); const float t = g.IO.MouseDownTime[button]; if (t == 0.0f) return true; @@ -1626,6 +1662,13 @@ return false; } +bool IsMouseDoubleClicked(int button) +{ + ImGuiState& g = GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseDoubleClicked[button]; +} + ImVec2 GetMousePos() { return GImGui.IO.MousePos; @@ -1637,12 +1680,24 @@ return window->DC.LastItemHovered; } +ImVec2 GetItemBoxMin() +{ + ImGuiWindow* window = GetCurrentWindow(); + return window->DC.LastItemAabb.Min; +} + +ImVec2 GetItemBoxMax() +{ + ImGuiWindow* window = GetCurrentWindow(); + return window->DC.LastItemAabb.Max; +} + void SetTooltip(const char* fmt, ...) { ImGuiState& g = GImGui; va_list args; va_start(args, fmt); - ImFormatStringV(g.Tooltip, ARRAYSIZE(g.Tooltip), fmt, args); + ImFormatStringV(g.Tooltip, IM_ARRAYSIZE(g.Tooltip), fmt, args); va_end(args); } @@ -1676,7 +1731,7 @@ ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); - ImU32 flags = ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_ChildWindow; + ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_ChildWindow; const ImVec2 content_max = window->Pos + ImGui::GetWindowContentRegionMax(); const ImVec2 cursor_pos = window->Pos + ImGui::GetCursorPos(); @@ -1695,7 +1750,7 @@ flags |= extra_flags; char title[256]; - ImFormatString(title, ARRAYSIZE(title), "%s.%s", window->Name, str_id); + ImFormatString(title, IM_ARRAYSIZE(title), "%s.%s", window->Name, str_id); const float alpha = (flags & ImGuiWindowFlags_ComboBox) ? 1.0f : 0.0f; ImGui::Begin(title, NULL, size, alpha, flags); @@ -1784,10 +1839,14 @@ parent_window->DC.ChildWindows.push_back(window); window->Pos = window->PosFloat = parent_window->DC.CursorPos; window->SizeFull = size; - if (!(flags & ImGuiWindowFlags_ComboBox)) - ImGui::PushClipRect(parent_window->ClipRectStack.back()); } + // Outer clipping rectangle + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_ComboBox)) + ImGui::PushClipRect(g.CurrentWindowStack[g.CurrentWindowStack.size()-2]->ClipRectStack.back()); + else + ImGui::PushClipRect(ImVec4(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y)); + // ID stack window->IDStack.resize(0); ImGui::PushID(window); @@ -1828,9 +1887,9 @@ window->ItemWidthDefault = (float)(int)(window->Size.x > 0.0f ? window->Size.x * 0.65f : 250.0f); // Prepare for focus requests - if (window->FocusIdxRequestNext == INT_MAX || window->FocusIdxCounter == -1) + if (window->FocusIdxRequestNext == IM_INT_MAX || window->FocusIdxCounter == -1) { - window->FocusIdxRequestCurrent = INT_MAX; + window->FocusIdxRequestCurrent = IM_INT_MAX; } else { @@ -1838,7 +1897,7 @@ window->FocusIdxRequestCurrent = (window->FocusIdxRequestNext + mod) % mod; } window->FocusIdxCounter = -1; - window->FocusIdxRequestNext = INT_MAX; + window->FocusIdxRequestNext = IM_INT_MAX; ImGuiAabb title_bar_aabb = window->TitleBarAabb(); @@ -2021,16 +2080,22 @@ // Title bar if (!(window->Flags & ImGuiWindowFlags_NoTitleBar)) { - ImGui::PushClipRect(ImVec4(window->Pos.x-0.5f, window->Pos.y-0.5f, window->Pos.x+window->Size.x-1.5f, window->Pos.y+window->Size.y-1.5f), false); RenderCollapseTriangle(window->Pos + style.FramePadding, !window->Collapsed, 1.0f, true); RenderText(window->Pos + style.FramePadding + ImVec2(window->FontSize() + style.ItemInnerSpacing.x, 0), name); if (open) ImGui::CloseWindowButton(open); - ImGui::PopClipRect(); } } + else + { + // Outer clipping rectangle + if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_ComboBox)) + ImGui::PushClipRect(g.CurrentWindowStack[g.CurrentWindowStack.size()-2]->ClipRectStack.back()); + else + ImGui::PushClipRect(ImVec4(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y)); + } - // Clip rectangle + // Innter clipping rectangle // We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame const ImGuiAabb title_bar_aabb = window->TitleBarAabb(); ImVec4 clip_rect(title_bar_aabb.Min.x+0.5f, title_bar_aabb.Max.y+0.5f, window->Aabb().Max.x-1.5f, window->Aabb().Max.y-1.5f); @@ -2054,10 +2119,8 @@ ImGuiWindow* window = g.CurrentWindow; ImGui::Columns(1, "#CloseColumns"); - ImGui::PopClipRect(); - if (window->Flags & ImGuiWindowFlags_ChildWindow) - if (!(window->Flags & ImGuiWindowFlags_ComboBox)) - ImGui::PopClipRect(); + ImGui::PopClipRect(); // inner window clip rectangle + ImGui::PopClipRect(); // outer window clip rectangle // Select window for move/focus when we're done with all our widgets ImGuiAabb bb(window->Pos, window->Pos+window->Size); @@ -2079,7 +2142,7 @@ } if (g.LogClipboard.size() > 1) { - g.LogClipboard.Append("\n"); + g.LogClipboard.append("\n"); if (g.IO.SetClipboardTextFn) g.IO.SetClipboardTextFn(g.LogClipboard.begin(), g.LogClipboard.end()); g.LogClipboard.clear(); @@ -2119,6 +2182,12 @@ window->DC.ItemWidth.pop_back(); } +float GetItemWidth() +{ + ImGuiWindow* window = GetCurrentWindow(); + return window->DC.ItemWidth.back(); +} + void PushAllowKeyboardFocus(bool allow_keyboard_focus) { ImGuiWindow* window = GetCurrentWindow(); @@ -2170,6 +2239,7 @@ case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered"; case ImGuiCol_ScrollbarGrabActive: return "ScrollbarGrabActive"; case ImGuiCol_ComboBg: return "ComboBg"; + case ImGuiCol_CheckHovered: return "CheckHovered"; case ImGuiCol_CheckActive: return "CheckActive"; case ImGuiCol_SliderGrab: return "SliderGrab"; case ImGuiCol_SliderGrabActive: return "SliderGrabActive"; @@ -2284,27 +2354,30 @@ void SetScrollPosHere() { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); window->NextScrollY = (window->DC.CursorPos.y + window->ScrollY) - (window->Pos.y + window->SizeFull.y * 0.5f) - (window->TitleBarHeight() + window->WindowPadding().y); } void SetTreeStateStorage(ImGuiStorage* tree) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); window->DC.StateStorage = tree ? tree : &window->StateStorage; } +ImGuiStorage* GetTreeStateStorage() +{ + ImGuiWindow* window = GetCurrentWindow(); + return window->DC.StateStorage; +} + void TextV(const char* fmt, va_list args) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->Collapsed) return; static char buf[1024]; - const char* text_end = buf + ImFormatStringV(buf, ARRAYSIZE(buf), fmt, args); + const char* text_end = buf + ImFormatStringV(buf, IM_ARRAYSIZE(buf), fmt, args); TextUnformatted(buf, text_end); } @@ -2358,10 +2431,6 @@ pos.y += lines_skipped * line_height; } } - else - { - printf(""); - } // lines to render? if (line < text_end) @@ -2443,7 +2512,7 @@ va_list args; va_start(args, fmt); const char* text_begin = &buf[0]; - const char* text_end = text_begin + ImFormatStringV(buf, ARRAYSIZE(buf), fmt, args); + const char* text_end = text_begin + ImFormatStringV(buf, IM_ARRAYSIZE(buf), fmt, args); va_end(args); const ImVec2 text_size = CalcTextSize(label); @@ -2569,7 +2638,6 @@ static bool CloseWindowButton(bool* open) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); const ImGuiID id = window->GetID("##CLOSE"); @@ -2605,7 +2673,8 @@ return; g.LogEnabled = true; g.LogFile = stdout; - g.LogAutoExpandMaxDepth = max_depth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; } void LogToFile(int max_depth, const char* filename) @@ -2613,10 +2682,12 @@ ImGuiState& g = GImGui; if (g.LogEnabled) return; - IM_ASSERT(filename); + if (!filename) + filename = g.IO.LogFilename; g.LogEnabled = true; g.LogFile = fopen(filename, "at"); - g.LogAutoExpandMaxDepth = max_depth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; } void LogToClipboard(int max_depth) @@ -2626,7 +2697,8 @@ return; g.LogEnabled = true; g.LogFile = NULL; - g.LogAutoExpandMaxDepth = max_depth; + if (max_depth >= 0) + g.LogAutoExpandMaxDepth = max_depth; } void LogButtons() @@ -2748,12 +2820,12 @@ va_list args; va_start(args, fmt); const char* text_begin = buf; - const char* text_end = text_begin + ImFormatStringV(buf, ARRAYSIZE(buf), fmt, args); + const char* text_end = text_begin + ImFormatStringV(buf, IM_ARRAYSIZE(buf), fmt, args); va_end(args); const float line_height = window->FontSize(); const ImVec2 text_size = CalcTextSize(text_begin, text_end); - const ImGuiAabb bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(line_height + (text_size.x ? (g.Style.FramePadding.x*2) : 0.0f),0) + text_size); // Empty text doesn't add padding + const ImGuiAabb bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(line_height + (text_size.x > 0.0f ? (g.Style.FramePadding.x*2) : 0.0f),0) + text_size); // Empty text doesn't add padding ItemSize(bb); if (ClipAdvance(bb)) @@ -2767,14 +2839,10 @@ bool TreeNode(const char* str_id, const char* fmt, ...) { - ImGuiState& g = GImGui; - ImGuiWindow* window = GetCurrentWindow(); - ImGuiStorage* tree = window->DC.StateStorage; - static char buf[1024]; va_list args; va_start(args, fmt); - ImFormatStringV(buf, ARRAYSIZE(buf), fmt, args); + ImFormatStringV(buf, IM_ARRAYSIZE(buf), fmt, args); va_end(args); if (!str_id || !str_id[0]) @@ -2792,14 +2860,10 @@ bool TreeNode(const void* ptr_id, const char* fmt, ...) { - ImGuiState& g = GImGui; - ImGuiWindow* window = GetCurrentWindow(); - ImGuiStorage* tree = window->DC.StateStorage; - static char buf[1024]; va_list args; va_start(args, fmt); - ImFormatStringV(buf, ARRAYSIZE(buf), fmt, args); + ImFormatStringV(buf, IM_ARRAYSIZE(buf), fmt, args); va_end(args); if (!ptr_id) @@ -2959,8 +3023,8 @@ if (v_min * v_max < 0.0f) { // Different sign - const float linear_dist_min_to_0 = powf(abs(0.0f - v_min), 1.0f/power); - const float linear_dist_max_to_0 = powf(abs(v_max - 0.0f), 1.0f/power); + const float linear_dist_min_to_0 = powf(fabsf(0.0f - v_min), 1.0f/power); + const float linear_dist_max_to_0 = powf(fabsf(v_max - 0.0f), 1.0f/power); linear_zero_pos = linear_dist_min_to_0 / (linear_dist_min_to_0+linear_dist_max_to_0); } else @@ -2992,12 +3056,12 @@ if (start_text_input || (g.ActiveId == id && id == g.SliderAsInputTextId)) { char text_buf[64]; - ImFormatString(text_buf, ARRAYSIZE(text_buf), "%.*f", decimal_precision, *v); + ImFormatString(text_buf, IM_ARRAYSIZE(text_buf), "%.*f", decimal_precision, *v); g.ActiveId = g.SliderAsInputTextId; g.HoveredId = 0; window->FocusItemUnregister(); // Our replacement slider will override the focus ID (that we needed to declare previously to allow for a TAB focus to happen before we got selected) - value_changed = ImGui::InputText(label, text_buf, ARRAYSIZE(text_buf), ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_AlignCenter); + value_changed = ImGui::InputText(label, text_buf, IM_ARRAYSIZE(text_buf), ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_AlignCenter); if (g.SliderAsInputTextId == 0) { // First frame @@ -3030,7 +3094,7 @@ if (!is_unbound) { const float normalized_pos = ImClamp((g.IO.MousePos.x - slider_effective_x1) / slider_effective_w, 0.0f, 1.0f); - + // Linear slider //float new_value = ImLerp(v_min, v_max, normalized_pos); @@ -3046,9 +3110,11 @@ else { // Positive: rescale to the positive range before powering - float a = normalized_pos; - if (abs(linear_zero_pos - 1.0f) > 1.e-6) - a = (a - linear_zero_pos) / (1.0f - linear_zero_pos); + float a; + if (fabsf(linear_zero_pos - 1.0f) > 1.e-6) + a = (normalized_pos - linear_zero_pos) / (1.0f - linear_zero_pos); + else + a = normalized_pos; a = powf(a, power); new_value = ImLerp(ImMax(v_min,0.0f), v_max, a); } @@ -3105,7 +3171,7 @@ } char value_buf[64]; - ImFormatString(value_buf, ARRAYSIZE(value_buf), display_format, *v); + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); RenderText(ImVec2(slider_bb.GetCenter().x-CalcTextSize(value_buf).x*0.5f, frame_bb.Min.y + style.FramePadding.y), value_buf); RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, slider_bb.Min.y), label); @@ -3131,6 +3197,38 @@ return changed; } +bool SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* display_format, float power) +{ + ImGuiState& g = GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->Collapsed) + return false; + + const ImGuiStyle& style = g.Style; + + bool value_changed = false; + ImGui::PushID(label); + + const int components = 2; + const float w_full = window->DC.ItemWidth.back(); + const float w_item_one = ImMax(1.0f, (float)(int)((w_full - (style.FramePadding.x*2.0f+style.ItemInnerSpacing.x)*(components-1)) / (float)components)); + const float w_item_last = ImMax(1.0f, (float)(int)(w_full - (w_item_one+style.FramePadding.x*2.0f+style.ItemInnerSpacing.x)*(components-1))); + + ImGui::PushItemWidth(w_item_one); + value_changed |= ImGui::SliderFloat("##X", &v[0], v_min, v_max, display_format, power); + ImGui::SameLine(0, 0); + ImGui::PopItemWidth(); + ImGui::PushItemWidth(w_item_last); + value_changed |= ImGui::SliderFloat("##Y", &v[1], v_min, v_max, display_format, power); + ImGui::SameLine(0, 0); + ImGui::PopItemWidth(); + + ImGui::TextUnformatted(label, FindTextDisplayEnd(label)); + + ImGui::PopID(); + return value_changed; +} + bool SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* display_format, float power) { ImGuiState& g = GImGui; @@ -3139,10 +3237,8 @@ return false; const ImGuiStyle& style = g.Style; - const ImVec2 text_size = CalcTextSize(label); bool value_changed = false; - ImGui::PushID(label); const int components = 3; @@ -3165,7 +3261,6 @@ ImGui::TextUnformatted(label, FindTextDisplayEnd(label)); ImGui::PopID(); - return value_changed; } @@ -3184,7 +3279,6 @@ return; const ImGuiStyle& style = g.Style; - const ImGuiID id = window->GetID(label); const ImVec2 text_size = CalcTextSize(label); if (graph_size.x == 0) @@ -3302,13 +3396,12 @@ const ImGuiAabb text_bb(window->DC.CursorPos + ImVec2(0,style.FramePadding.y), window->DC.CursorPos + ImVec2(0,style.FramePadding.y) + text_size); ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight())); + const ImGuiAabb total_bb(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max)); - if (ClipAdvance(check_bb)) + if (ClipAdvance(total_bb)) return; - RenderFrame(check_bb.Min, check_bb.Max, window->Color(ImGuiCol_FrameBg)); - - const bool hovered = (g.HoveredWindow == window) && (g.HoveredId == 0) && IsMouseHoveringBox(check_bb); + const bool hovered = (g.HoveredWindow == window) && (g.HoveredId == 0) && IsMouseHoveringBox(total_bb); const bool pressed = hovered && g.IO.MouseClicked[0]; if (hovered) g.HoveredId = id; @@ -3318,6 +3411,7 @@ g.ActiveId = 0; // Clear focus } + RenderFrame(check_bb.Min, check_bb.Max, window->Color(hovered ? ImGuiCol_CheckHovered : ImGuiCol_FrameBg)); if (*v) { window->DrawList->AddRectFilled(check_bb.Min+ImVec2(4,4), check_bb.Max-ImVec2(4,4), window->Color(ImGuiCol_CheckActive)); @@ -3356,8 +3450,9 @@ const ImGuiAabb text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + text_size); ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight())); + const ImGuiAabb total_bb(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max)); - if (ClipAdvance(check_bb)) + if (ClipAdvance(total_bb)) return false; ImVec2 center = check_bb.GetCenter(); @@ -3365,12 +3460,12 @@ center.y = (float)(int)center.y + 0.5f; const float radius = check_bb.GetHeight() * 0.5f; - const bool hovered = (g.HoveredWindow == window) && (g.HoveredId == 0) && IsMouseHoveringBox(check_bb); + const bool hovered = (g.HoveredWindow == window) && (g.HoveredId == 0) && IsMouseHoveringBox(total_bb); const bool pressed = hovered && g.IO.MouseClicked[0]; if (hovered) g.HoveredId = id; - window->DrawList->AddCircleFilled(center, radius, window->Color(ImGuiCol_FrameBg), 16); + window->DrawList->AddCircleFilled(center, radius, window->Color(hovered ? ImGuiCol_CheckHovered : ImGuiCol_FrameBg), 16); if (active) window->DrawList->AddCircleFilled(center, radius-2, window->Color(ImGuiCol_CheckActive), 16); @@ -3400,7 +3495,7 @@ // Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, ASCII, fixed-width font) int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return (int)strlen(obj->Text); } char STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return (char)obj->Text[idx]; } -float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { return obj->Font->CalcTextSize(obj->FontSize, 0, &obj->Text[char_idx], &obj->Text[char_idx]+1, NULL).x; } +float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { (void)line_start_idx; return obj->Font->CalcTextSize(obj->FontSize, 0, &obj->Text[char_idx], &obj->Text[char_idx]+1, NULL).x; } char STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : (char)key; } char STB_TEXTEDIT_NEWLINE = '\n'; void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx) @@ -3421,17 +3516,17 @@ #define STB_TEXTEDIT_IS_SPACE(c) (is_white(c) || is_separator(c)) void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int idx, int n) { char* dst = obj->Text+idx; const char* src = obj->Text+idx+n; while (char c = *src++) *dst++ = c; *dst = '\0'; } -bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int idx, const char* new_text, int new_text_size) +bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int idx, const char* new_text, int new_text_len) { - char* buf_end = obj->Text + obj->MaxLength; - int text_size = strlen(obj->Text); + char* buf_end = obj->Text + obj->BufSize; + const int text_len = (int)strlen(obj->Text); - if (new_text_size > buf_end - (obj->Text + text_size + 1)) + if (new_text_len > buf_end - (obj->Text + text_len + 1)) return false; - memmove(obj->Text + idx + new_text_size, obj->Text + idx, text_size - idx); - memcpy(obj->Text + idx, new_text, new_text_size); - obj->Text[text_size + new_text_size] = 0; + memmove(obj->Text + idx + new_text_len, obj->Text + idx, text_len - idx); + memcpy(obj->Text + idx, new_text, new_text_len); + obj->Text[text_len + new_text_len] = 0; return true; } @@ -3513,7 +3608,7 @@ const float clip_end = (text_end[0] != '\0' && text_end > text_start) ? symbol_w : 0.0f; // Draw text - ImGui::RenderText(pos+ImVec2(clip_begin,0), text_start+(clip_begin?1:0), text_end-(clip_end?1:0), false);//, &text_params_with_clipping); + ImGui::RenderText(pos+ImVec2(clip_begin,0), text_start+(clip_begin>0.0f?1:0), text_end-(clip_end>0.0f?1:0), false);//, &text_params_with_clipping); // Draw the clip symbol const char s[2] = {symbol_c,'\0'}; @@ -3540,35 +3635,34 @@ ImGui::PushID(label); const float button_sz = window->FontSize(); - if (step) + if (step > 0.0f) ImGui::PushItemWidth(ImMax(1.0f, window->DC.ItemWidth.back() - (button_sz+g.Style.FramePadding.x*2.0f+g.Style.ItemInnerSpacing.x)*2)); char buf[64]; if (decimal_precision < 0) - ImFormatString(buf, ARRAYSIZE(buf), "%f", *v); // Ideally we'd have a minimum decimal precision of 1 to visually denote that it is a float, while hiding non-significant digits? + ImFormatString(buf, IM_ARRAYSIZE(buf), "%f", *v); // Ideally we'd have a minimum decimal precision of 1 to visually denote that it is a float, while hiding non-significant digits? else - ImFormatString(buf, ARRAYSIZE(buf), "%.*f", decimal_precision, *v); + ImFormatString(buf, IM_ARRAYSIZE(buf), "%.*f", decimal_precision, *v); bool value_changed = false; - if (ImGui::InputText("", buf, ARRAYSIZE(buf), ImGuiInputTextFlags_CharsDecimal|ImGuiInputTextFlags_AlignCenter|ImGuiInputTextFlags_AutoSelectAll)) + if (ImGui::InputText("", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsDecimal|ImGuiInputTextFlags_AlignCenter|ImGuiInputTextFlags_AutoSelectAll)) { ApplyNumericalTextInput(buf, v); value_changed = true; } - if (step) - ImGui::PopItemWidth(); - if (step) + if (step > 0.0f) { + ImGui::PopItemWidth(); ImGui::SameLine(0, 0); if (ImGui::Button("-", ImVec2(button_sz,button_sz), true)) { - *v -= g.IO.KeyCtrl && step_fast ? step_fast : step; + *v -= g.IO.KeyCtrl && step_fast > 0.0f ? step_fast : step; value_changed = true; } ImGui::SameLine(0, (int)g.Style.ItemInnerSpacing.x); if (ImGui::Button("+", ImVec2(button_sz,button_sz), true)) { - *v += g.IO.KeyCtrl && step_fast ? step_fast : step; + *v += g.IO.KeyCtrl && step_fast > 0.0f ? step_fast : step; value_changed = true; } } @@ -3655,7 +3749,7 @@ bool cancel_edit = false; if (g.ActiveId == id) { - edit_state.MaxLength = buf_size < ARRAYSIZE(edit_state.Text) ? buf_size : ARRAYSIZE(edit_state.Text); + edit_state.BufSize = buf_size < IM_ARRAYSIZE(edit_state.Text) ? buf_size : IM_ARRAYSIZE(edit_state.Text); edit_state.Font = window->Font(); edit_state.FontSize = window->FontSize(); @@ -3720,7 +3814,7 @@ if (const char* clipboard = g.IO.GetClipboardTextFn()) { // Remove new-line from pasted buffer - int clipboard_len = strlen(clipboard); + size_t clipboard_len = strlen(clipboard); char* clipboard_filtered = (char*)malloc(clipboard_len+1); int clipboard_filtered_len = 0; for (int i = 0; clipboard[i]; i++) @@ -3738,7 +3832,7 @@ else if (g.IO.InputCharacters[0]) { // Text input - for (int n = 0; n < ARRAYSIZE(g.IO.InputCharacters) && g.IO.InputCharacters[n]; n++) + for (int n = 0; n < IM_ARRAYSIZE(g.IO.InputCharacters) && g.IO.InputCharacters[n]; n++) { const char c = g.IO.InputCharacters[n]; if (c) @@ -3815,6 +3909,38 @@ return value_changed; } +bool InputFloat2(const char* label, float v[2], int decimal_precision) +{ + ImGuiState& g = GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->Collapsed) + return false; + + const ImGuiStyle& style = g.Style; + + bool value_changed = false; + ImGui::PushID(label); + + const int components = 2; + const float w_full = window->DC.ItemWidth.back(); + const float w_item_one = ImMax(1.0f, (float)(int)((w_full - (style.FramePadding.x*2.0f+style.ItemInnerSpacing.x) * (components-1)) / (float)components)); + const float w_item_last = ImMax(1.0f, (float)(int)(w_full - (w_item_one+style.FramePadding.x*2.0f+style.ItemInnerSpacing.x) * (components-1))); + + ImGui::PushItemWidth(w_item_one); + value_changed |= ImGui::InputFloat("##X", &v[0], 0, 0, decimal_precision); + ImGui::SameLine(0, 0); + ImGui::PopItemWidth(); + ImGui::PushItemWidth(w_item_last); + value_changed |= ImGui::InputFloat("##Y", &v[1], 0, 0, decimal_precision); + ImGui::SameLine(0, 0); + ImGui::PopItemWidth(); + + ImGui::TextUnformatted(label, FindTextDisplayEnd(label)); + + ImGui::PopID(); + return value_changed; +} + bool InputFloat3(const char* label, float v[3], int decimal_precision) { ImGuiState& g = GImGui; @@ -3823,10 +3949,8 @@ return false; const ImGuiStyle& style = g.Style; - const ImVec2 text_size = CalcTextSize(label); bool value_changed = false; - ImGui::PushID(label); const int components = 3; @@ -3849,7 +3973,6 @@ ImGui::TextUnformatted(label, FindTextDisplayEnd(label)); ImGui::PopID(); - return value_changed; } @@ -3958,6 +4081,7 @@ ImGuiWindowFlags flags = ImGuiWindowFlags_ComboBox | ((window->Flags & ImGuiWindowFlags_ShowBorders) ? ImGuiWindowFlags_ShowBorders : 0); ImGui::BeginChild("#ComboBox", popup_aabb.GetSize(), false, flags); ImGuiWindow* child_window = GetCurrentWindow(); + ImGui::Spacing(); bool combo_item_active = false; combo_item_active |= (g.ActiveId == child_window->GetID("#SCROLLY")); @@ -4074,8 +4198,6 @@ const float w_full = window->DC.ItemWidth.back(); const float square_sz = (window->FontSize() + style.FramePadding.x * 2.0f); - const ImVec2 text_size = CalcTextSize(label); - ImGuiColorEditMode edit_mode = window->DC.ColorEditMode; if (edit_mode == ImGuiColorEditMode_UserSelect) edit_mode = g.ColorEditModeStorage.GetInt(id, 0) % 3; @@ -4142,7 +4264,7 @@ else sprintf(buf, "#%02X%02X%02X", ix, iy, iz); ImGui::PushItemWidth(w_slider_all - g.Style.ItemInnerSpacing.x); - value_changed |= ImGui::InputText("##Text", buf, ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal); + value_changed |= ImGui::InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal); ImGui::PopItemWidth(); char* p = buf; while (*p == '#' || *p == ' ' || *p == '\t') @@ -4197,15 +4319,12 @@ void ColorEditMode(ImGuiColorEditMode mode) { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); - window->DC.ColorEditMode = mode; } void Separator() { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->Collapsed) return; @@ -4216,7 +4335,7 @@ const ImGuiAabb bb(ImVec2(window->Pos.x, window->DC.CursorPos.y), ImVec2(window->Pos.x + window->Size.x, window->DC.CursorPos.y)); ItemSize(ImVec2(0.0f, bb.GetSize().y)); // NB: we don't provide our width so that it doesn't get feed back into AutoFit - if (ClipAdvance(bb, true)) + if (ClipAdvance(bb)) { if (window->DC.ColumnsCount > 1) ImGui::PushColumnClipRect(); @@ -4231,7 +4350,6 @@ void Spacing() { - ImGuiState& g = GImGui; ImGuiWindow* window = GetCurrentWindow(); if (window->Collapsed) return; @@ -4301,9 +4419,10 @@ return IsClipped(ImGuiAabb(window->DC.CursorPos, window->DC.CursorPos + item_size)); } -static bool ClipAdvance(const ImGuiAabb& bb, bool skip_columns) +static bool ClipAdvance(const ImGuiAabb& bb) { ImGuiWindow* window = GetCurrentWindow(); + window->DC.LastItemAabb = bb; if (ImGui::IsClipped(bb)) { window->DC.LastItemHovered = false; @@ -4424,7 +4543,8 @@ // Draw before resize so our items positioning are in sync with the line const ImU32 col = window->Color(held ? ImGuiCol_ColumnActive : hovered ? ImGuiCol_ColumnHovered : ImGuiCol_Column); - window->DrawList->AddLine(ImVec2(x, y1), ImVec2(x, y2), col); + const float xi = (float)(int)x; + window->DrawList->AddLine(ImVec2(xi, y1), ImVec2(xi, y2), col); if (held) { @@ -4538,58 +4658,62 @@ { commands.resize(0); vtx_buffer.resize(0); - clip_rect_buffer.resize(0); - vtx_write_ = NULL; - clip_rect_stack_.resize(0); + vtx_write = NULL; + clip_rect_stack.resize(0); } void ImDrawList::PushClipRect(const ImVec4& clip_rect) { - commands.push_back(ImDrawCmd(ImDrawCmdType_PushClipRect)); - clip_rect_buffer.push_back(clip_rect); - clip_rect_stack_.push_back(clip_rect); + if (!commands.empty() && commands.back().vtx_count == 0) + { + // Reuse empty command because high-level clipping may have discarded the other vertices already + commands.back().clip_rect = clip_rect; + } + else + { + ImDrawCmd draw_cmd; + draw_cmd.vtx_count = 0; + draw_cmd.clip_rect = clip_rect; + commands.push_back(draw_cmd); + } + clip_rect_stack.push_back(clip_rect); } void ImDrawList::PopClipRect() { - if (!commands.empty() && commands.back().cmd_type == ImDrawCmdType_PushClipRect) + clip_rect_stack.pop_back(); + const ImVec4 clip_rect = clip_rect_stack.empty() ? ImVec4(-9999.0f,-9999.0f, +9999.0f, +9999.0f) : clip_rect_stack.back(); + if (!commands.empty() && commands.back().vtx_count == 0) { - // Discard push/pop combo because high-level clipping may have discarded the other draw commands already - commands.pop_back(); - clip_rect_buffer.pop_back(); + // Reuse empty command because high-level clipping may have discarded the other vertices already + commands.back().clip_rect = clip_rect; } else { - commands.push_back(ImDrawCmd(ImDrawCmdType_PopClipRect)); + ImDrawCmd draw_cmd; + draw_cmd.vtx_count = 0; + draw_cmd.clip_rect = clip_rect; + commands.push_back(draw_cmd); } - clip_rect_stack_.pop_back(); } -void ImDrawList::AddCommand(ImDrawCmdType cmd_type, int vtx_count) +void ImDrawList::ReserveVertices(unsigned int vtx_count) { - // Maximum value that can fit in our u16 vtx_count member - const int VTX_COUNT_MAX = (1<<16); - - // Merge commands if we can, turning them into less draw calls - ImDrawCmd* prev = commands.empty() ? NULL : &commands.back(); - if (vtx_count > 0 && prev && prev->cmd_type == (ImU32)cmd_type && prev->vtx_count + vtx_count < VTX_COUNT_MAX) - prev->vtx_count += vtx_count; - else - commands.push_back(ImDrawCmd(cmd_type, vtx_count)); - if (vtx_count > 0) { + ImDrawCmd& draw_cmd = commands.back(); + draw_cmd.vtx_count += vtx_count; vtx_buffer.resize(vtx_buffer.size() + vtx_count); - vtx_write_ = &vtx_buffer[vtx_buffer.size() - vtx_count]; + vtx_write = &vtx_buffer[vtx_buffer.size() - vtx_count]; } } void ImDrawList::AddVtx(const ImVec2& pos, ImU32 col) { - vtx_write_->pos = pos; - vtx_write_->col = col; - vtx_write_->uv = IMDRAW_TEX_UV_FOR_WHITE; - vtx_write_++; + vtx_write->pos = pos; + vtx_write->col = col; + vtx_write->uv = IMGUI_FONT_TEX_UV_FOR_WHITE; + vtx_write++; } void ImDrawList::AddVtxLine(const ImVec2& a, const ImVec2& b, ImU32 col) @@ -4611,7 +4735,7 @@ if ((col >> 24) == 0) return; - AddCommand(ImDrawCmdType_DrawTriangleList, 6); + ReserveVertices(6); AddVtxLine(a, b, col); } @@ -4621,9 +4745,9 @@ static bool circle_vtx_builds = false; if (!circle_vtx_builds) { - for (int i = 0; i < ARRAYSIZE(circle_vtx); i++) + for (int i = 0; i < IM_ARRAYSIZE(circle_vtx); i++) { - const float a = ((float)i / (float)ARRAYSIZE(circle_vtx)) * 2*PI; + const float a = ((float)i / (float)IM_ARRAYSIZE(circle_vtx)) * 2*PI; circle_vtx[i].x = cos(a + PI); circle_vtx[i].y = sin(a + PI); } @@ -4632,19 +4756,19 @@ if (tris) { - AddCommand(ImDrawCmdType_DrawTriangleList, (a_max-a_min) * 3); + ReserveVertices((a_max-a_min) * 3); for (int a = a_min; a < a_max; a++) { - AddVtx(center + circle_vtx[a % ARRAYSIZE(circle_vtx)] * rad, col); - AddVtx(center + circle_vtx[(a+1) % ARRAYSIZE(circle_vtx)] * rad, col); + AddVtx(center + circle_vtx[a % IM_ARRAYSIZE(circle_vtx)] * rad, col); + AddVtx(center + circle_vtx[(a+1) % IM_ARRAYSIZE(circle_vtx)] * rad, col); AddVtx(center + third_point_offset, col); } } else { - AddCommand(ImDrawCmdType_DrawTriangleList, (a_max-a_min) * 6); + ReserveVertices((a_max-a_min) * 6); for (int a = a_min; a < a_max; a++) - AddVtxLine(center + circle_vtx[a % ARRAYSIZE(circle_vtx)] * rad, center + circle_vtx[(a+1) % ARRAYSIZE(circle_vtx)] * rad, col); + AddVtxLine(center + circle_vtx[a % IM_ARRAYSIZE(circle_vtx)] * rad, center + circle_vtx[(a+1) % IM_ARRAYSIZE(circle_vtx)] * rad, col); } } @@ -4653,14 +4777,14 @@ if ((col >> 24) == 0) return; - //const float r = ImMin(rounding, ImMin(abs(b.x-a.x), abs(b.y-a.y))*0.5f); + //const float r = ImMin(rounding, ImMin(fabsf(b.x-a.x), fabsf(b.y-a.y))*0.5f); float r = rounding; - r = ImMin(r, abs(b.x-a.x) * ( ((rounding_corners&(1|2))==(1|2)) || ((rounding_corners&(4|8))==(4|8)) ? 0.5f : 1.0f )); - r = ImMin(r, abs(b.y-a.y) * ( ((rounding_corners&(1|8))==(1|8)) || ((rounding_corners&(2|4))==(2|4)) ? 0.5f : 1.0f )); + r = ImMin(r, fabsf(b.x-a.x) * ( ((rounding_corners&(1|2))==(1|2)) || ((rounding_corners&(4|8))==(4|8)) ? 0.5f : 1.0f )); + r = ImMin(r, fabsf(b.y-a.y) * ( ((rounding_corners&(1|8))==(1|8)) || ((rounding_corners&(2|4))==(2|4)) ? 0.5f : 1.0f )); if (r == 0.0f || rounding_corners == 0) { - AddCommand(ImDrawCmdType_DrawTriangleList, 4*6); + ReserveVertices(4*6); AddVtxLine(ImVec2(a.x,a.y), ImVec2(b.x,a.y), col); AddVtxLine(ImVec2(b.x,a.y), ImVec2(b.x,b.y), col); AddVtxLine(ImVec2(b.x,b.y), ImVec2(a.x,b.y), col); @@ -4668,7 +4792,7 @@ } else { - AddCommand(ImDrawCmdType_DrawTriangleList, 4*6); + ReserveVertices(4*6); AddVtxLine(ImVec2(a.x + ((rounding_corners & 1)?r:0), a.y), ImVec2(b.x - ((rounding_corners & 2)?r:0), a.y), col); AddVtxLine(ImVec2(b.x, a.y + ((rounding_corners & 2)?r:0)), ImVec2(b.x, b.y - ((rounding_corners & 4)?r:0)), col); AddVtxLine(ImVec2(b.x - ((rounding_corners & 4)?r:0), b.y), ImVec2(a.x + ((rounding_corners & 8)?r:0), b.y), col); @@ -4686,15 +4810,15 @@ if ((col >> 24) == 0) return; - //const float r = ImMin(rounding, ImMin(abs(b.x-a.x), abs(b.y-a.y))*0.5f); + //const float r = ImMin(rounding, ImMin(fabsf(b.x-a.x), fabsf(b.y-a.y))*0.5f); float r = rounding; - r = ImMin(r, abs(b.x-a.x) * ( ((rounding_corners&(1|2))==(1|2)) || ((rounding_corners&(4|8))==(4|8)) ? 0.5f : 1.0f )); - r = ImMin(r, abs(b.y-a.y) * ( ((rounding_corners&(1|8))==(1|8)) || ((rounding_corners&(2|4))==(2|4)) ? 0.5f : 1.0f )); + r = ImMin(r, fabsf(b.x-a.x) * ( ((rounding_corners&(1|2))==(1|2)) || ((rounding_corners&(4|8))==(4|8)) ? 0.5f : 1.0f )); + r = ImMin(r, fabsf(b.y-a.y) * ( ((rounding_corners&(1|8))==(1|8)) || ((rounding_corners&(2|4))==(2|4)) ? 0.5f : 1.0f )); if (r == 0.0f || rounding_corners == 0) { // Use triangle so we can merge more draw calls together (at the cost of extra vertices) - AddCommand(ImDrawCmdType_DrawTriangleList, 6); + ReserveVertices(6); AddVtx(ImVec2(a.x,a.y), col); AddVtx(ImVec2(b.x,a.y), col); AddVtx(ImVec2(b.x,b.y), col); @@ -4704,7 +4828,7 @@ } else { - AddCommand(ImDrawCmdType_DrawTriangleList, 6+6*2); + ReserveVertices(6+6*2); AddVtx(ImVec2(a.x+r,a.y), col); AddVtx(ImVec2(b.x-r,a.y), col); AddVtx(ImVec2(b.x-r,b.y), col); @@ -4742,7 +4866,7 @@ if ((col >> 24) == 0) return; - AddCommand(ImDrawCmdType_DrawTriangleList, 3); + ReserveVertices(3); AddVtx(a, col); AddVtx(b, col); AddVtx(c, col); @@ -4753,7 +4877,7 @@ if ((col >> 24) == 0) return; - AddCommand(ImDrawCmdType_DrawTriangleList, num_segments*6); + ReserveVertices(num_segments*6); const float a_step = 2*PI/(float)num_segments; float a0 = 0.0f; for (int i = 0; i < num_segments; i++) @@ -4769,7 +4893,7 @@ if ((col >> 24) == 0) return; - AddCommand(ImDrawCmdType_DrawTriangleList, num_segments*3); + ReserveVertices(num_segments*3); const float a_step = 2*PI/(float)num_segments; float a0 = 0.0f; for (int i = 0; i < num_segments; i++) @@ -4790,17 +4914,19 @@ if (text_end == NULL) text_end = text_begin + strlen(text_begin); - int char_count = text_end - text_begin; - int vtx_count_max = char_count * 6; - int vtx_begin = vtx_buffer.size(); - AddCommand(ImDrawCmdType_DrawTriangleList, vtx_count_max); + // reserve vertices for worse case + const int char_count = (int)(text_end - text_begin); + const int vtx_count_max = char_count * 6; + const size_t vtx_begin = vtx_buffer.size(); + ReserveVertices(vtx_count_max); - font->RenderText(font_size, pos, col, clip_rect_stack_.back(), text_begin, text_end, vtx_write_); - vtx_buffer.resize(vtx_write_ - &vtx_buffer.front()); - int vtx_count = vtx_buffer.size() - vtx_begin; + font->RenderText(font_size, pos, col, clip_rect_stack.back(), text_begin, text_end, vtx_write); + // give unused vertices + vtx_buffer.resize(vtx_write - &vtx_buffer.front()); + const int vtx_count = (int)(vtx_buffer.size() - vtx_begin); commands.back().vtx_count -= (vtx_count_max - vtx_count); - vtx_write_ -= (vtx_count_max - vtx_count); + vtx_write -= (vtx_count_max - vtx_count); } //----------------------------------------------------------------------------- @@ -4849,7 +4975,7 @@ fclose(f); return false; } - if (fread(Data, 1, DataSize, f) != DataSize) + if ((int)fread(Data, 1, DataSize, f) != DataSize) { fclose(f); free(Data); @@ -4910,7 +5036,7 @@ void ImBitmapFont::BuildLookupTable() { ImU32 max_c = 0; - for (int i = 0; i != GlyphsCount; i++) + for (size_t i = 0; i != GlyphsCount; i++) if (max_c < Glyphs[i].Id) max_c = Glyphs[i].Id; @@ -4960,7 +5086,8 @@ if (const FntGlyph* glyph = FindGlyph((unsigned short)c)) { const float char_width = (glyph->XAdvance + Info->SpacingHoriz) * scale; - const float char_extend = (glyph->XOffset + glyph->Width * scale); + //const float char_extend = (glyph->XOffset + glyph->Width * scale); + if (line_width + char_width >= max_width) break; line_width += char_width; @@ -5002,10 +5129,10 @@ pos.x = (float)(int)pos.x + 0.5f; pos.y = (float)(int)pos.y + 0.5f; - ImVec2 text_size = ImVec2(0,0); - float line_width = 0.0f; const ImVec4 clip_rect = clip_rect_ref; + const float uv_offset = GImGui.IO.PixelCenterOffset; + float x = pos.x; float y = pos.y; for (const char* s = text_begin; s < text_end; s++) @@ -5021,7 +5148,7 @@ if (const FntGlyph* glyph = FindGlyph((unsigned short)c)) { const float char_width = (glyph->XAdvance + Info->SpacingHoriz) * scale; - const float char_extend = (glyph->XOffset + glyph->Width * scale); + //const float char_extend = (glyph->XOffset + glyph->Width * scale); if (c != ' ' && c != '\n') { @@ -5042,10 +5169,10 @@ continue; } - const float s1 = (0.0f + glyph->X) * tex_scale_x; - const float t1 = (0.0f + glyph->Y) * tex_scale_y; - const float s2 = (0.0f + glyph->X + glyph->Width) * tex_scale_x; - const float t2 = (0.0f + glyph->Y + glyph->Height) * tex_scale_y; + const float s1 = (uv_offset + glyph->X) * tex_scale_x; + const float t1 = (uv_offset + glyph->Y) * tex_scale_y; + const float s2 = (uv_offset + glyph->X + glyph->Width) * tex_scale_x; + const float t2 = (uv_offset + glyph->Y + glyph->Height) * tex_scale_y; out_vertices[0].pos = ImVec2(x1, y1); out_vertices[0].uv = ImVec2(s1, t1); @@ -5080,6 +5207,81 @@ } //----------------------------------------------------------------------------- +// PLATFORM DEPENDANT HELPERS +//----------------------------------------------------------------------------- + +#if defined(_MSC_VER) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS) + +#define WIN32_LEAN_AND_MEAN +#include + +// Win32 API clipboard implementation +static const char* GetClipboardTextFn_DefaultImpl() +{ + static char* buf_local = NULL; + if (buf_local) + { + free(buf_local); + buf_local = NULL; + } + if (!OpenClipboard(NULL)) + return NULL; + HANDLE buf_handle = GetClipboardData(CF_TEXT); + if (buf_handle == NULL) + return NULL; + if (char* buf_global = (char*)GlobalLock(buf_handle)) + buf_local = strdup(buf_global); + GlobalUnlock(buf_handle); + CloseClipboard(); + return buf_local; +} + +// Win32 API clipboard implementation +static void SetClipboardTextFn_DefaultImpl(const char* text, const char* text_end) +{ + if (!OpenClipboard(NULL)) + return; + if (!text_end) + text_end = text + strlen(text); + const int buf_length = (text_end - text) + 1; + HGLOBAL buf_handle = GlobalAlloc(GMEM_MOVEABLE, buf_length * sizeof(char)); + if (buf_handle == NULL) + return; + char* buf_global = (char *)GlobalLock(buf_handle); + memcpy(buf_global, text, text_end - text); + buf_global[text_end - text] = 0; + GlobalUnlock(buf_handle); + EmptyClipboard(); + SetClipboardData(CF_TEXT, buf_handle); + CloseClipboard(); +} + +#else + +// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers +static const char* GetClipboardTextFn_DefaultImpl() +{ + return GImGui.PrivateClipboard; +} + +// Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers +static void SetClipboardTextFn_DefaultImpl(const char* text, const char* text_end) +{ + if (GImGui.PrivateClipboard) + { + free(GImGui.PrivateClipboard); + GImGui.PrivateClipboard = NULL; + } + if (!text_end) + text_end = text + strlen(text); + GImGui.PrivateClipboard = (char*)malloc(text_end - text + 1); + memcpy(GImGui.PrivateClipboard, text, text_end - text); + GImGui.PrivateClipboard[text_end - text] = 0; +} + +#endif + +//----------------------------------------------------------------------------- // HELP //----------------------------------------------------------------------------- @@ -5135,11 +5337,17 @@ ImGui::SameLine(); ImGui::RadioButton("HEX", &edit_mode, ImGuiColorEditMode_HEX); + static ImGuiTextFilter filter; + filter.Draw("Filter colors", 200); + ImGui::ColorEditMode(edit_mode); - for (size_t i = 0; i < ImGuiCol_COUNT; i++) + for (int i = 0; i < ImGuiCol_COUNT; i++) { + const char* name = GetStyleColorName(i); + if (!filter.PassFilter(name)) + continue; ImGui::PushID(i); - ImGui::ColorEdit4(GetStyleColorName(i), (float*)&style.Colors[i], true); + ImGui::ColorEdit4(name, (float*)&style.Colors[i], true); if (memcmp(&style.Colors[i], (ref ? &ref->Colors[i] : &def.Colors[i]), sizeof(ImVec4)) != 0) { ImGui::SameLine(); if (ImGui::Button("Revert")) style.Colors[i] = ref ? ref->Colors[i] : def.Colors[i]; @@ -5251,15 +5459,18 @@ const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK" }; static int item2 = -1; - ImGui::Combo("combo scroll", &item2, items, ARRAYSIZE(items)); + ImGui::Combo("combo scroll", &item2, items, IM_ARRAYSIZE(items)); static char str0[128] = "Hello, world!"; static int i0=123; static float f0=0.001f; - ImGui::InputText("string", str0, ARRAYSIZE(str0)); + ImGui::InputText("string", str0, IM_ARRAYSIZE(str0)); ImGui::InputInt("input int", &i0); ImGui::InputFloat("input float", &f0, 0.01f, 1.0f); + //static float vec2b[3] = { 0.10f, 0.20f }; + //ImGui::InputFloat2("input float2", vec2b); + static float vec3b[3] = { 0.10f, 0.20f, 0.30f }; ImGui::InputFloat3("input float3", vec3b); @@ -5279,6 +5490,9 @@ static float angle = 0.0f; ImGui::SliderAngle("angle", &angle); + //static float vec2a[3] = { 0.10f, 0.20f }; + //ImGui::SliderFloat2("slider float2", vec2a, 0.0f, 1.0f); + static float vec3a[3] = { 0.10f, 0.20f, 0.30f }; ImGui::SliderFloat3("slider float3", vec3a, 0.0f, 1.0f); @@ -5293,7 +5507,7 @@ if (ImGui::CollapsingHeader("Graphs widgets")) { static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; - ImGui::PlotLines("Frame Times", arr, ARRAYSIZE(arr)); + ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr)); static bool pause; static ImVector values; if (values.empty()) { values.resize(100); memset(&values.front(), 0, values.size()*sizeof(float)); } @@ -5311,10 +5525,10 @@ phase += 0.10f*values_offset; } } - ImGui::PlotLines("Frame Times", &values.front(), values.size(), values_offset, "avg 0.0", -1.0f, 1.0f, ImVec2(0,70)); + ImGui::PlotLines("Frame Times", &values.front(), (int)values.size(), values_offset, "avg 0.0", -1.0f, 1.0f, ImVec2(0,70)); ImGui::SameLine(); ImGui::Checkbox("pause", &pause); - ImGui::PlotHistogram("Histogram", arr, ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0,70)); + ImGui::PlotHistogram("Histogram", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0,70)); } if (ImGui::CollapsingHeader("Widgets on same line")) @@ -5404,7 +5618,7 @@ for (int i = 0; i < 100; i++) { char buf[32]; - ImFormatString(buf, ARRAYSIZE(buf), "%08x", i*5731); + ImFormatString(buf, IM_ARRAYSIZE(buf), "%08x", i*5731); ImGui::Button(buf); ImGui::NextColumn(); } @@ -5459,7 +5673,7 @@ static float foo = 1.0f; ImGui::InputFloat("red", &foo, 0.05f, 0, 3); ImGui::NextColumn(); static float bar = 1.0f; - ImGui::InputFloat("blue", &foo, 0.05f, 0, 3); ImGui::NextColumn(); + ImGui::InputFloat("blue", &bar, 0.05f, 0, 3); ImGui::NextColumn(); ImGui::Columns(1); ImGui::Separator(); @@ -5503,7 +5717,7 @@ static ImGuiTextFilter filter; filter.Draw(); const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" }; - for (size_t i = 0; i < ARRAYSIZE(lines); i++) + for (size_t i = 0; i < IM_ARRAYSIZE(lines); i++) if (filter.PassFilter(lines[i])) ImGui::BulletText("%s", lines[i]); } @@ -5519,7 +5733,7 @@ if (ImGui::Button("Add 1000 lines")) { for (size_t i = 0; i < 1000; i++) - log.Append("%i The quick brown fox jumps over the lazy dog\n", lines+i); + log.append("%i The quick brown fox jumps over the lazy dog\n", lines+i); lines += 1000; } ImGui::BeginChild("Log"); diff --git a/imgui.h b/imgui.h index 3c75057..b006330 100644 --- a/imgui.h +++ b/imgui.h @@ -29,8 +29,8 @@ typedef int ImGuiCol; // enum ImGuiCol_ typedef int ImGuiKey; // enum ImGuiKey_ typedef int ImGuiColorEditMode; // enum ImGuiColorEditMode_ -typedef ImU32 ImGuiWindowFlags; // enum ImGuiWindowFlags_ -typedef ImU32 ImGuiInputTextFlags; // enum ImGuiInputTextFlags_ +typedef int ImGuiWindowFlags; // enum ImGuiWindowFlags_ +typedef int ImGuiInputTextFlags; // enum ImGuiInputTextFlags_ typedef ImBitmapFont* ImFont; struct ImVec2 @@ -85,9 +85,9 @@ inline void clear() { if (_data) { _size = _capacity = 0; free(_data); _data = NULL; } } inline iterator begin() { return _data; } - inline const iterator begin() const { return _data; } + inline const_iterator begin() const { return _data; } inline iterator end() { return _data + _size; } - inline const iterator end() const { return _data + _size; } + inline const_iterator end() const { return _data + _size; } inline value_type& front() { return at(0); } inline const value_type& front() const { return at(0); } inline value_type& back() { IM_ASSERT(_size > 0); return at(_size-1); } @@ -143,8 +143,10 @@ void SetFontScale(float scale); void SetScrollPosHere(); void SetTreeStateStorage(ImGuiStorage* tree); + ImGuiStorage* GetTreeStateStorage(); void PushItemWidth(float item_width); void PopItemWidth(); + float GetItemWidth(); void PushAllowKeyboardFocus(bool v); void PopAllowKeyboardFocus(); void PushStyleColor(ImGuiCol idx, ImVec4 col); @@ -181,6 +183,7 @@ bool SmallButton(const char* label); bool CollapsingHeader(const char* label, const char* str_id = NULL, const bool display_frame = true, const bool default_open = false); bool SliderFloat(const char* label, float* v, float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); + bool SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); bool SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* display_format = "%.3f", float power = 1.0f); bool SliderAngle(const char* label, float* v, float v_degrees_min = -360.0f, float v_degrees_max = +360.0f); // *v in radians bool SliderInt(const char* label, int* v, int v_min, int v_max, const char* display_format = "%.0f"); @@ -191,6 +194,7 @@ bool RadioButton(const char* label, bool active); bool RadioButton(const char* label, int* v, int v_button); bool InputFloat(const char* label, float* v, float step = 0.0f, float step_fast = 0.0f, int decimal_precision = -1); + bool InputFloat2(const char* label, float v[2], int decimal_precision = -1); bool InputFloat3(const char* label, float v[3], int decimal_precision = -1); bool InputInt(const char* label, int* v, int step = 1, int step_fast = 100); bool InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0); @@ -220,17 +224,21 @@ // Logging void LogButtons(); - void LogToTTY(int max_depth); - void LogToFile(int max_depth, const char* filename); - void LogToClipboard(int max_depth); + void LogToTTY(int max_depth = -1); + void LogToFile(int max_depth = -1, const char* filename = NULL); + void LogToClipboard(int max_depth = -1); // Utilities void SetTooltip(const char* fmt, ...); // set tooltip under mouse-cursor, typically use with ImGui::IsHovered(). (currently no contention handling, last call win) void SetNewWindowDefaultPos(ImVec2 pos); // set position of window that do bool IsHovered(); // was the last item active area hovered by mouse? + ImVec2 GetItemBoxMin(); // get bounding box of last item + ImVec2 GetItemBoxMax(); // get bounding box of last item bool IsClipped(ImVec2 item_size); // to perform coarse clipping on user's side (as an optimisation) bool IsKeyPressed(int key_index, bool repeat = true); // key_index into the keys_down[512] array, imgui doesn't know the semantic of each entry bool IsMouseClicked(int button, bool repeat = false); + bool IsMouseDoubleClicked(int button); + bool IsMouseHoveringBox(const ImVec2& box_min, const ImVec2& box_max); ImVec2 GetMousePos(); float GetTime(); int GetFrameCount(); @@ -302,6 +310,7 @@ ImGuiCol_ScrollbarGrabHovered, ImGuiCol_ScrollbarGrabActive, ImGuiCol_ComboBg, + ImGuiCol_CheckHovered, ImGuiCol_CheckActive, ImGuiCol_SliderGrab, ImGuiCol_SliderGrabActive, @@ -373,14 +382,21 @@ ImFont Font; // // Gets passed to text functions. Typedef ImFont to the type you want (ImBitmapFont* or your own font). float FontHeight; // // Default font height, must be the vertical distance between two lines of text, aka == CalcTextSize(" ").y bool FontAllowScaling; // = false // Set to allow scaling text with CTRL+Wheel. + float PixelCenterOffset; // = 0.5f // Set to 0.0f for DirectX <= 9, 0.5f for Direct3D >= 10 and OpenGL. - // Settings - Functions (fill once) - void (*RenderDrawListsFn)(ImDrawList** const draw_lists, int count); // Required - const char* (*GetClipboardTextFn)(); // Required for clipboard support - void (*SetClipboardTextFn)(const char* text, const char* text_end); // Required for clipboard support (nb- the string is *NOT* zero-terminated at 'text_end') + // Settings - Rendering function (REQUIRED) + // See example code if you are unsure of how to implement this. + void (*RenderDrawListsFn)(ImDrawList** const draw_lists, int count); + + // Settings - Clipboard Support + // Override to provide your clipboard handlers. + // On Windows architecture, defaults to use the native Win32 clipboard, otherwise default to use a ImGui private clipboard. + // NB- for SetClipboardTextFn, the string is *NOT* zero-terminated at 'text_end' + const char* (*GetClipboardTextFn)(); + void (*SetClipboardTextFn)(const char* text, const char* text_end); // Input - Fill before calling NewFrame() - ImVec2 MousePos; // Mouse position (set to -1,-1 if no mouse / on another screen, etc.) + ImVec2 MousePos; // Mouse position, in pixels (set to -1,-1 if no mouse / on another screen, etc.) bool MouseDown[2]; // Mouse buttons int MouseWheel; // Mouse wheel: -1,0,+1 bool KeyCtrl; // Keyboard modifier pressed: Control @@ -388,10 +404,13 @@ bool KeysDown[512]; // Keyboard keys that are pressed (in whatever order user naturally has access to keyboard data) char InputCharacters[16]; // List of characters input (translated by user from keypress+keyboard state). Fill using AddInputCharacter() helper. - // Output - Retrieve after calling NewFrame(), you can use them to discard inputs for the rest of your application + // Output - Retrieve after calling NewFrame(), you can use them to discard inputs or hide them from the rest of your application bool WantCaptureMouse; // ImGui is using your mouse input (= window is being hovered or widget is active). bool WantCaptureKeyboard; // imGui is using your keyboard input (= widget is active). + // Function + void AddInputCharacter(char c); // Helper to add a new character into InputCharacters[] + // [Internal] ImGui will maintain those fields for you ImVec2 MousePosPrev; ImVec2 MouseDelta; @@ -403,7 +422,6 @@ float KeysDownTime[512]; ImGuiIO(); - void AddInputCharacter(char c); // Helper to add a new character into InputCharacters[] }; //----------------------------------------------------------------------------- @@ -464,7 +482,7 @@ size_t size() const { return Buf.size()-1; } bool empty() { return Buf.empty(); } void clear() { Buf.clear(); Buf.push_back(0); } - void Append(const char* fmt, ...); + void append(const char* fmt, ...); }; // Helper: Key->value storage @@ -491,23 +509,14 @@ // Hold a series of drawing commands. The user provide a renderer for ImDrawList //----------------------------------------------------------------------------- -enum ImDrawCmdType -{ - ImDrawCmdType_DrawTriangleList, - ImDrawCmdType_PushClipRect, - ImDrawCmdType_PopClipRect, -}; - -// sizeof() == 4 struct ImDrawCmd { - ImDrawCmdType cmd_type : 16; - unsigned int vtx_count : 16; - ImDrawCmd(ImDrawCmdType _cmd_type = ImDrawCmdType_DrawTriangleList, unsigned int _vtx_count = 0) { cmd_type = _cmd_type; vtx_count = _vtx_count; } + unsigned int vtx_count; + ImVec4 clip_rect; }; -#ifndef IMDRAW_TEX_UV_FOR_WHITE -#define IMDRAW_TEX_UV_FOR_WHITE ImVec2(0,0) +#ifndef IMGUI_FONT_TEX_UV_FOR_WHITE +#define IMGUI_FONT_TEX_UV_FOR_WHITE ImVec2(0.f,0.f) #endif // sizeof() == 20 @@ -522,18 +531,20 @@ // User is responsible for providing a renderer for this in ImGuiIO::RenderDrawListFn struct ImDrawList { - ImVector commands; + // This is what you have to render + ImVector commands; // commands ImVector vtx_buffer; // each command consume ImDrawCmd::vtx_count of those - ImVector clip_rect_buffer; // each PushClipRect command consume 1 of those - ImVector clip_rect_stack_; // [internal] clip rect stack while building the command-list (so text command can perform clipping early on) - ImDrawVert* vtx_write_; // [internal] point within vtx_buffer after each add command. allow us to use less [] and .resize on the vector (often slow on windows/debug) + + // [Internal to ImGui] + ImVector clip_rect_stack; // [internal] clip rect stack while building the command-list (so text command can perform clipping early on) + ImDrawVert* vtx_write; // [internal] point within vtx_buffer after each add command (to avoid using the ImVector<> operators too much) ImDrawList() { Clear(); } void Clear(); void PushClipRect(const ImVec4& clip_rect); void PopClipRect(); - void AddCommand(ImDrawCmdType cmd_type, int vtx_count); + void ReserveVertices(unsigned int vtx_count); void AddVtx(const ImVec2& pos, ImU32 col); void AddVtxLine(const ImVec2& a, const ImVec2& b, ImU32 col); diff --git a/web/code_sample_01.png b/web/code_sample_01.png index 6ff370c..6178f8d 100644 --- a/web/code_sample_01.png +++ b/web/code_sample_01.png Binary files differ